14 const std::byte*
bytes)
noexcept
17 static_cast<std::uint16_t
>(
18 std::to_integer<std::uint8_t>(
bytes[0])) |
19 static_cast<std::uint16_t
>(
20 std::to_integer<std::uint8_t>(
bytes[1]) << 8U);
25 const std::byte*
bytes)
noexcept
28 static_cast<std::uint32_t
>(
29 std::to_integer<std::uint8_t>(
bytes[0])) |
30 (
static_cast<std::uint32_t
>(
31 std::to_integer<std::uint8_t>(
bytes[1])) << 8U) |
32 (
static_cast<std::uint32_t
>(
33 std::to_integer<std::uint8_t>(
bytes[2])) << 16U) |
34 (
static_cast<std::uint32_t
>(
35 std::to_integer<std::uint8_t>(
bytes[3])) << 24U);
42 std::filesystem::path
path = {})
44 return { code, std::move(
path), std::move(detail) };
49 std::uint16_t bitsPerPixel)
noexcept
70 std::uint64_t& result)
noexcept
72 if (
left > std::numeric_limits<std::uint64_t>::max() -
right)
85 std::uint64_t& result)
noexcept
88 right > std::numeric_limits<std::uint64_t>::max() /
left)
112 return "UnsupportedDibHeader";
117 return "UnsupportedCompression";
119 return "PixelDataOutOfRange";
121 return "DeclaredSizeOutOfRange";
124 return "InvalidBitmapErrorCode";
129 std::span<const std::byte>
bytes)
135 "UAP requires a complete 16-byte NEWBITMAPHEADER"));
148 "UAP dimensions must be non-zero"));
152 "UAP palette must contain 1..256 entries and bound nAlpha"));
160 "UAP palette exceeds immutable payload bounds"));
169 "UAP top-down DWORD-padded raster is truncated"));
174 "UAP alpha-channel flag requires at least one alpha palette entry"));
185 const std::uint64_t
count =
186 static_cast<std::uint64_t
>(
m.width) *
m.height;
189 "decoded UAP exceeds pixel allocation budget"));
198 result.pixels.resize(
static_cast<std::size_t
>(
count) * 4U);
200 for (std::uint32_t
y = 0;
y <
m.height; ++
y)
202 const auto*
row =
bytes.data() +
m.pixelDataOffset +
203 static_cast<std::size_t
>(
y) *
m.rowStride;
204 for (std::uint32_t
x = 0;
x <
m.width; ++
x)
206 const auto index = std::to_integer<std::uint8_t>(
row[
x]);
207 if (
index >=
m.colourCount)
209 "UAP raster references an absent palette entry"));
216 std::uint32_t
alpha = 255;
222 "UAP alpha palette value exceeds 8-bit ArtLib range"));
231 if (
alpha == 0)
return 0;
235 return static_cast<std::uint8_t
>(std::min(
value, 255U));
239 (
static_cast<std::size_t
>(
y) *
m.width +
x) * 4U;
243 result.pixels[
offset + 3] =
static_cast<std::uint8_t
>(
alpha);
251 std::span<const std::byte>
bytes)
258 return std::unexpected(
error(
260 "BMP requires a 14-byte file header and 40-byte DIB header"));
263 if (std::to_integer<std::uint8_t>(
bytes[0]) !=
'B' ||
264 std::to_integer<std::uint8_t>(
bytes[1]) !=
'M')
266 return std::unexpected(
error(
268 "legacy asset is not a Windows BM bitmap"));
287 return std::unexpected(
error(
289 "BMP DIB header must expose at least BITMAPINFOHEADER"));
295 return std::unexpected(
error(
297 "BMP width must be positive and height must be non-zero"));
302 return std::unexpected(
error(
304 "Windows BMP plane count must equal one"));
309 return std::unexpected(
error(
311 "BI_RGB BMP bit depth must be 1, 4, 8, 16, 24 or 32"));
314 constexpr std::uint32_t
BiRgb = 0;
318 return std::unexpected(
error(
320 "legacy texture inspection supports only BI_RGB BMP data"));
323 const std::uint64_t
dibEnd =
330 return std::unexpected(
error(
332 "BMP pixel data offset is outside the file"));
336 ?
static_cast<std::uint64_t
>(-
342 std::uint64_t rowStride{};
364 return std::unexpected(
error(
366 "BMP raster size overflows portable 64-bit bounds"));
371 return std::unexpected(
error(
373 "BMP pixel array is truncated for its dimensions and bit depth"));
387 return std::unexpected(
error(
389 "BMP declared image size does not contain a complete raster"));
398 static_cast<std::uint64_t
>(
402 return std::unexpected(
error(
404 "BMP declared file size does not contain its declared raster"));
417 if (
m.bitsPerPixel != 8 &&
m.bitsPerPixel != 24)
419 "RGBA8 decoding supports only BI_RGB 8/24-bit BMP"));
420 const auto height =
static_cast<std::uint32_t
>(
m.topDown() ? -
m.height :
m.height);
421 const auto width =
static_cast<std::uint32_t
>(
m.width);
425 "decoded BMP exceeds pixel allocation budget"));
428 if (
m.bitsPerPixel == 8)
434 "BMP palette is truncated or overlaps raster"));
436 const auto stride = (
static_cast<std::uint64_t
>(
width) *
m.bitsPerPixel + 31U) / 32U * 4U;
438 result.
pixels.resize(
static_cast<std::size_t
>(
count) * 4U);
439 for (std::uint32_t
y = 0;
y <
height; ++
y)
443 for (std::uint32_t
x = 0;
x <
width; ++
x)
445 const std::byte* color{};
446 if (
m.bitsPerPixel == 8)
448 const auto index = std::to_integer<std::uint8_t>(
row[
x]);
451 "BMP raster references an absent palette entry"));
454 else color =
row + 3ULL *
x;
455 const auto offset = (
static_cast<std::size_t
>(
y) *
width +
x) * 4U;
456 result.pixels[
offset] = std::to_integer<std::uint8_t>(color[2]);
457 result.pixels[
offset + 1] = std::to_integer<std::uint8_t>(color[1]);
458 result.pixels[
offset + 2] = std::to_integer<std::uint8_t>(color[0]);
459 result.pixels[
offset + 3] = 255;
466 std::expected<LegacyBitmapMetadata, BitmapError>
469 std::ifstream
input(
path, std::ios::binary | std::ios::ate);
473 return std::unexpected(
error(
475 "unable to open legacy BMP",
479 const auto end =
input.tellg();
481 if (end < 0 ||
static_cast<std::uint64_t
>(end) >
482 std::numeric_limits<std::size_t>::max())
484 return std::unexpected(
error(
486 "unable to determine a portable BMP size",
490 std::vector<std::byte>
bytes(
static_cast<std::size_t
>(end));
491 input.seekg(0, std::ios::beg);
496 reinterpret_cast<char*
>(
bytes.data()),
497 static_cast<std::streamsize
>(
bytes.size()));
502 return std::unexpected(
error(
504 "unable to read complete legacy BMP",
514 return std::unexpected(std::move(
failure));
std::atomic< float > left
std::atomic< float > right
LegacyBitmapMetadata metadata
std::vector< std::byte > bytes
std::string_view bitmapErrorCodeName(BitmapErrorCode code) noexcept
std::expected< LegacyBitmapMetadata, BitmapError > inspectLegacyBitmapFile(const std::filesystem::path &path)
std::expected< LegacyUapMetadata, BitmapError > inspectLegacyUap(std::span< const std::byte > bytes)
std::expected< LegacyBitmapRGBA8, BitmapError > decodeLegacyUapRGBA8(std::span< const std::byte > bytes, std::size_t maxPixels)
std::expected< LegacyBitmapRGBA8, BitmapError > decodeLegacyBitmapRGBA8(std::span< const std::byte > bytes, std::size_t maxPixels)
std::expected< LegacyBitmapMetadata, BitmapError > inspectLegacyBitmap(std::span< const std::byte > bytes)
std::vector< std::uint8_t > pixels