Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
ModernImageDecoder.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdlib>
5#include <cstring>
6#include <limits>
7#include <memory>
8#include <new>
9#include <stdexcept>
10
11namespace
12{
13 // stb's temporary working storage is bounded independently of the final
14 // pixel budget. The allocator is per-thread; no global decoder flags change.
15 constexpr std::size_t WorkingByteBudget = 256U * 1024U * 1024U;
16 thread_local std::size_t imageWorkingBytes{};
17 struct alignas(std::max_align_t) ImageAllocation { std::size_t bytes; };
18
19 void* imageAllocate(std::size_t bytes) noexcept
20 {
21 if (bytes > WorkingByteBudget - imageWorkingBytes) return nullptr;
22 auto* allocation = static_cast<ImageAllocation*>(
23 std::malloc(sizeof(ImageAllocation) + bytes));
24 if (!allocation) return nullptr;
25 allocation->bytes = bytes;
27 return allocation + 1;
28 }
29 void imageFree(void* pointer) noexcept
30 {
31 if (!pointer) return;
32 auto* allocation = static_cast<ImageAllocation*>(pointer) - 1;
34 std::free(allocation);
35 }
36 void* imageReallocate(void* pointer, std::size_t bytes) noexcept
37 {
38 if (!pointer) return imageAllocate(bytes);
39 auto* allocation = static_cast<ImageAllocation*>(pointer) - 1;
40 auto* replacement = imageAllocate(bytes);
41 if (!replacement) return nullptr;
42 std::memcpy(replacement, pointer, std::min(bytes, allocation->bytes));
44 return replacement;
45 }
46}
47
48#define STB_IMAGE_IMPLEMENTATION
49#define STBI_ONLY_PNG
50#define STBI_ONLY_JPEG
51#define STBI_NO_STDIO
52#define STBI_MAX_DIMENSIONS 8192
53#define STBI_MALLOC(bytes) imageAllocate(bytes)
54#define STBI_REALLOC(pointer, bytes) imageReallocate(pointer, bytes)
55#define STBI_FREE(pointer) imageFree(pointer)
56#include <stb_image.h>
57
58namespace monopoly::data
59{
60 std::expected<std::shared_ptr<const ModernTextureImage>, MeshRuntimeError>
62 ModernImageDecodeLimits limits) try
63 {
64 const auto failure = [](const char* detail)
65 {
68 error.detail = detail;
69 return std::unexpected(std::move(error));
70 };
71 if (bytes.empty() || bytes.size() > limits.maximumEncodedBytes ||
72 bytes.size() > static_cast<std::size_t>(std::numeric_limits<int>::max()))
73 return failure("modern encoded image exceeds its byte budget or is empty");
74 const auto* data = reinterpret_cast<const stbi_uc*>(bytes.data());
75 constexpr stbi_uc PngSignature[]{137, 80, 78, 71, 13, 10, 26, 10};
77 (bytes.size() < sizeof(PngSignature) ||
78 std::memcmp(data, PngSignature, sizeof(PngSignature)) != 0)) ||
80 (bytes.size() < 3 || data[0] != 0xFF || data[1] != 0xD8 || data[2] != 0xFF)))
81 return failure("modern image content does not match its PNG/JPEG MIME type");
82 int width{}, height{}, channels{};
83 if (!stbi_info_from_memory(data, static_cast<int>(bytes.size()), &width, &height, &channels))
84 return failure("modern image header cannot be decoded");
85 if (width <= 0 || height <= 0 ||
86 static_cast<std::uint32_t>(width) > limits.maximumDimension ||
87 static_cast<std::uint32_t>(height) > limits.maximumDimension ||
88 static_cast<std::size_t>(width) > limits.maximumDecodedBytes / 4U /
89 static_cast<std::size_t>(height))
90 return failure("modern decoded image exceeds its dimension or RGBA byte budget");
91 const auto decodedBytes = static_cast<std::size_t>(width) * height * 4U;
92 std::unique_ptr<stbi_uc, decltype(&stbi_image_free)> pixels(
93 stbi_load_from_memory(data, static_cast<int>(bytes.size()), &width, &height, &channels, 4),
95 if (!pixels) return failure("modern PNG/JPEG decode failed or exhausted working storage");
96 // Header and decoder dimensions must agree before copying the allocation.
97 if (width <= 0 || height <= 0 ||
98 static_cast<std::size_t>(width) * height * 4U != decodedBytes)
99 return failure("modern image dimensions changed during decode");
100 auto result = std::make_shared<ModernTextureImage>();
101 result->width = static_cast<std::uint32_t>(width);
102 result->height = static_cast<std::uint32_t>(height);
103 result->rgba.assign(pixels.get(), pixels.get() + decodedBytes);
104 return std::shared_ptr<const ModernTextureImage>(std::move(result));
105 }
106 catch (const std::bad_alloc&)
107 {
108 MeshRuntimeError error;
110 error.detail = "modern image pixel allocation failed";
111 return std::unexpected(std::move(error));
112 }
113 catch (const std::length_error&)
114 {
115 MeshRuntimeError error;
117 error.detail = "modern image pixel allocation exceeds container limits";
118 return std::unexpected(std::move(error));
119 }
120}
std::vector< std::uint8_t > data
std::vector< std::byte > bytes
std::uint32_t height
std::uint32_t width
unsigned index
std::string error
std::expected< std::shared_ptr< const ModernTextureImage >, MeshRuntimeError > decodeModernImage(std::span< const std::byte > bytes, ModernImageEncoding encoding, ModernImageDecodeLimits limits)