1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-19 21:25:07 +00:00
serenity/Userland/Libraries/LibGfx/DDSLoader.cpp
MacDue 6cf8eeb7a4 LibGfx: Return bool not ErrorOr<bool> from ImageDecoderPlugin::sniff()
Nobody made use of the ErrorOr return value and it just added more
chance of confusion, since it was not clear if failing to sniff an
image should return an error or false. The answer was false, if you
returned Error you'd crash the ImageDecoder.
2023-02-26 19:43:17 +01:00

710 lines
27 KiB
C++

/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/Debug.h>
#include <AK/DeprecatedString.h>
#include <AK/Endian.h>
#include <AK/Error.h>
#include <AK/MemoryStream.h>
#include <AK/StringBuilder.h>
#include <AK/Try.h>
#include <AK/Vector.h>
#include <LibGfx/DDSLoader.h>
#include <fcntl.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
namespace Gfx {
struct DDSLoadingContext {
enum State {
NotDecoded = 0,
Error,
BitmapDecoded,
};
State state { State::NotDecoded };
u8 const* data { nullptr };
size_t data_size { 0 };
DDSHeader header;
DDSHeaderDXT10 header10;
RefPtr<Gfx::Bitmap> bitmap;
void dump_debug();
};
static constexpr u32 create_four_cc(char c0, char c1, char c2, char c3)
{
return c0 | c1 << 8 | c2 << 16 | c3 << 24;
}
static u64 get_width(DDSHeader header, size_t mipmap_level)
{
if (mipmap_level >= header.mip_map_count) {
return header.width;
}
return header.width >> mipmap_level;
}
static u64 get_height(DDSHeader header, size_t mipmap_level)
{
if (mipmap_level >= header.mip_map_count) {
return header.height;
}
return header.height >> mipmap_level;
}
static constexpr bool has_bitmask(DDSPixelFormat format, u32 r, u32 g, u32 b, u32 a)
{
return format.r_bit_mask == r && format.g_bit_mask == g && format.b_bit_mask == b && format.a_bit_mask == a;
}
static DXGIFormat get_format(DDSPixelFormat format)
{
if ((format.flags & PixelFormatFlags::DDPF_RGB) == PixelFormatFlags::DDPF_RGB) {
switch (format.rgb_bit_count) {
case 32: {
if (has_bitmask(format, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000))
return DXGI_FORMAT_R8G8B8A8_UNORM;
if (has_bitmask(format, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000))
return DXGI_FORMAT_B8G8R8A8_UNORM;
if (has_bitmask(format, 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000))
return DXGI_FORMAT_B8G8R8X8_UNORM;
if (has_bitmask(format, 0x3FF00000, 0x000FFC00, 0x000003FF, 0xC0000000))
return DXGI_FORMAT_R10G10B10A2_UNORM;
if (has_bitmask(format, 0x0000FFFF, 0xFFFF0000, 0x00000000, 0x00000000))
return DXGI_FORMAT_R16G16_UNORM;
if (has_bitmask(format, 0xFFFFFFFF, 0x00000000, 0x00000000, 0x00000000))
return DXGI_FORMAT_R32_FLOAT;
break;
}
case 24:
break;
case 16: {
if (has_bitmask(format, 0x7C00, 0x03E0, 0x001F, 0x8000))
return DXGI_FORMAT_B5G5R5A1_UNORM;
if (has_bitmask(format, 0xF800, 0x07E0, 0x001F, 0x0000))
return DXGI_FORMAT_B5G6R5_UNORM;
if (has_bitmask(format, 0xF800, 0x07E0, 0x001F, 0x0000))
return DXGI_FORMAT_B5G6R5_UNORM;
if (has_bitmask(format, 0x0F00, 0x00F0, 0x000F, 0xF000))
return DXGI_FORMAT_B4G4R4A4_UNORM;
if (has_bitmask(format, 0x00FF, 0x0000, 0x0000, 0xFF00))
return DXGI_FORMAT_R8G8_UNORM;
if (has_bitmask(format, 0xFFFF, 0x0000, 0x0000, 0x0000))
return DXGI_FORMAT_R16_UNORM;
break;
}
case 8: {
if (has_bitmask(format, 0xFF, 0x00, 0x00, 0x00))
return DXGI_FORMAT_R8_UNORM;
break;
}
}
} else if ((format.flags & PixelFormatFlags::DDPF_LUMINANCE) == PixelFormatFlags::DDPF_LUMINANCE) {
switch (format.rgb_bit_count) {
case 16: {
if (has_bitmask(format, 0xFFFF, 0x0000, 0x0000, 0x0000))
return DXGI_FORMAT_R16_UNORM;
if (has_bitmask(format, 0x00FF, 0x0000, 0x0000, 0xFF00))
return DXGI_FORMAT_R8G8_UNORM;
break;
}
case 8: {
if (has_bitmask(format, 0xFF, 0x00, 0x00, 0x00))
return DXGI_FORMAT_R8_UNORM;
// Some writers mistakenly write this as 8 bpp.
if (has_bitmask(format, 0x00FF, 0x0000, 0x0000, 0xFF00))
return DXGI_FORMAT_R8G8_UNORM;
break;
}
}
} else if ((format.flags & PixelFormatFlags::DDPF_ALPHA) == PixelFormatFlags::DDPF_ALPHA) {
if (format.rgb_bit_count == 8)
return DXGI_FORMAT_A8_UNORM;
} else if ((format.flags & PixelFormatFlags::DDPF_BUMPDUDV) == PixelFormatFlags::DDPF_BUMPDUDV) {
switch (format.rgb_bit_count) {
case 32: {
if (has_bitmask(format, 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000))
return DXGI_FORMAT_R8G8B8A8_SNORM;
if (has_bitmask(format, 0x0000FFFF, 0xFFFF0000, 0x00000000, 0x00000000))
return DXGI_FORMAT_R16G16_SNORM;
break;
}
case 16: {
if (has_bitmask(format, 0x00FF, 0xFF00, 0x0000, 0x0000))
return DXGI_FORMAT_R8G8_SNORM;
break;
}
}
} else if ((format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC) {
if (format.four_cc == create_four_cc('D', 'X', 'T', '1'))
return DXGI_FORMAT_BC1_UNORM;
if (format.four_cc == create_four_cc('D', 'X', 'T', '2'))
return DXGI_FORMAT_BC2_UNORM;
if (format.four_cc == create_four_cc('D', 'X', 'T', '3'))
return DXGI_FORMAT_BC2_UNORM;
if (format.four_cc == create_four_cc('D', 'X', 'T', '4'))
return DXGI_FORMAT_BC3_UNORM;
if (format.four_cc == create_four_cc('D', 'X', 'T', '5'))
return DXGI_FORMAT_BC3_UNORM;
if (format.four_cc == create_four_cc('A', 'T', 'I', '1'))
return DXGI_FORMAT_BC4_UNORM;
if (format.four_cc == create_four_cc('B', 'C', '4', 'U'))
return DXGI_FORMAT_BC4_UNORM;
if (format.four_cc == create_four_cc('B', 'C', '4', 'S'))
return DXGI_FORMAT_BC4_SNORM;
if (format.four_cc == create_four_cc('A', 'T', 'I', '2'))
return DXGI_FORMAT_BC5_UNORM;
if (format.four_cc == create_four_cc('B', 'C', '5', 'U'))
return DXGI_FORMAT_BC5_UNORM;
if (format.four_cc == create_four_cc('B', 'C', '5', 'S'))
return DXGI_FORMAT_BC5_SNORM;
if (format.four_cc == create_four_cc('R', 'G', 'B', 'G'))
return DXGI_FORMAT_R8G8_B8G8_UNORM;
if (format.four_cc == create_four_cc('G', 'R', 'G', 'B'))
return DXGI_FORMAT_G8R8_G8B8_UNORM;
if (format.four_cc == create_four_cc('Y', 'U', 'Y', '2'))
return DXGI_FORMAT_YUY2;
switch (format.four_cc) {
case 36:
return DXGI_FORMAT_R16G16B16A16_UNORM;
case 110:
return DXGI_FORMAT_R16G16B16A16_SNORM;
case 111:
return DXGI_FORMAT_R16_FLOAT;
case 112:
return DXGI_FORMAT_R16G16_FLOAT;
case 113:
return DXGI_FORMAT_R16G16B16A16_FLOAT;
case 114:
return DXGI_FORMAT_R32_FLOAT;
case 115:
return DXGI_FORMAT_R32G32_FLOAT;
case 116:
return DXGI_FORMAT_R32G32B32A32_FLOAT;
}
}
return DXGI_FORMAT_UNKNOWN;
}
static ErrorOr<void> decode_dx5_alpha_block(Stream& stream, DDSLoadingContext& context, u64 bitmap_x, u64 bitmap_y)
{
auto color0 = TRY(stream.read_value<LittleEndian<u8>>());
auto color1 = TRY(stream.read_value<LittleEndian<u8>>());
auto code0 = TRY(stream.read_value<LittleEndian<u8>>());
auto code1 = TRY(stream.read_value<LittleEndian<u8>>());
auto code2 = TRY(stream.read_value<LittleEndian<u8>>());
auto code3 = TRY(stream.read_value<LittleEndian<u8>>());
auto code4 = TRY(stream.read_value<LittleEndian<u8>>());
auto code5 = TRY(stream.read_value<LittleEndian<u8>>());
u32 codes[6] = { 0 };
codes[0] = code0 + 256 * (code1 + 256);
codes[1] = code1 + 256 * (code2 + 256);
codes[2] = code2 + 256 * (code3 + 256);
codes[3] = code3 + 256 * (code4 + 256);
codes[4] = code4 + 256 * code5;
codes[5] = code5;
u32 color[8] = { 0 };
if (color0 > 128) {
color[0] = color0;
}
if (color1 > 128) {
color[1] = color1;
}
if (color0 > color1) {
color[2] = (6 * color[0] + 1 * color[1]) / 7;
color[3] = (5 * color[0] + 2 * color[1]) / 7;
color[4] = (4 * color[0] + 3 * color[1]) / 7;
color[5] = (3 * color[0] + 4 * color[1]) / 7;
color[6] = (2 * color[0] + 5 * color[1]) / 7;
color[7] = (1 * color[0] + 6 * color[1]) / 7;
} else {
color[2] = (4 * color[0] + 1 * color[1]) / 5;
color[3] = (3 * color[0] + 2 * color[1]) / 5;
color[4] = (2 * color[0] + 3 * color[1]) / 5;
color[5] = (1 * color[0] + 4 * color[1]) / 5;
color[6] = 0;
color[7] = 255;
}
for (size_t y = 0; y < 4; y++) {
for (size_t x = 0; x < 4; x++) {
u8 index = 3 * (4 * y + x);
u8 bit_location = floor(index / 8.0);
u8 adjusted_index = index - (bit_location * 8);
u8 code = (codes[bit_location] >> adjusted_index) & 7;
u8 alpha = color[code];
Color color = Color(0, 0, 0, alpha);
context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color);
}
}
return {};
}
static ErrorOr<void> decode_dx3_alpha_block(Stream& stream, DDSLoadingContext& context, u64 bitmap_x, u64 bitmap_y)
{
auto a0 = TRY(stream.read_value<LittleEndian<u8>>());
auto a1 = TRY(stream.read_value<LittleEndian<u8>>());
auto a2 = TRY(stream.read_value<LittleEndian<u8>>());
auto a3 = TRY(stream.read_value<LittleEndian<u8>>());
auto a4 = TRY(stream.read_value<LittleEndian<u8>>());
auto a5 = TRY(stream.read_value<LittleEndian<u8>>());
auto a6 = TRY(stream.read_value<LittleEndian<u8>>());
auto a7 = TRY(stream.read_value<LittleEndian<u8>>());
u64 alpha_0 = a0 + 256u * (a1 + 256u * (a2 + 256u * (a3 + 256u)));
u64 alpha_1 = a4 + 256u * (a5 + 256u * (a6 + 256u * a7));
for (size_t y = 0; y < 4; y++) {
for (size_t x = 0; x < 4; x++) {
u8 code = 4 * (4 * y + x);
if (code >= 32) {
code = code - 32;
u8 alpha = ((alpha_1 >> code) & 0x0F) * 17;
Color color = Color(0, 0, 0, alpha);
context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color);
} else {
u8 alpha = ((alpha_0 >> code) & 0x0F) * 17;
Color color = Color(0, 0, 0, alpha);
context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color);
}
}
}
return {};
}
static void unpack_rbg_565(u32 rgb, u8* output)
{
u8 r = (rgb >> 11) & 0x1F;
u8 g = (rgb >> 5) & 0x3F;
u8 b = rgb & 0x1F;
output[0] = (r << 3) | (r >> 2);
output[1] = (g << 2) | (g >> 4);
output[2] = (b << 3) | (b >> 2);
output[3] = 255;
}
static ErrorOr<void> decode_color_block(Stream& stream, DDSLoadingContext& context, bool dxt1, u64 bitmap_x, u64 bitmap_y)
{
auto c0_low = TRY(stream.read_value<LittleEndian<u8>>());
auto c0_high = TRY(stream.read_value<LittleEndian<u8>>());
auto c1_low = TRY(stream.read_value<LittleEndian<u8>>());
auto c1_high = TRY(stream.read_value<LittleEndian<u8>>());
auto codes_0 = TRY(stream.read_value<LittleEndian<u8>>());
auto codes_1 = TRY(stream.read_value<LittleEndian<u8>>());
auto codes_2 = TRY(stream.read_value<LittleEndian<u8>>());
auto codes_3 = TRY(stream.read_value<LittleEndian<u8>>());
u64 code = codes_0 + 256 * (codes_1 + 256 * (codes_2 + 256 * codes_3));
u32 color_0 = c0_low + (c0_high * 256);
u32 color_1 = c1_low + (c1_high * 256);
u8 rgba[4][4];
unpack_rbg_565(color_0, rgba[0]);
unpack_rbg_565(color_1, rgba[1]);
if (color_0 > color_1) {
for (size_t i = 0; i < 3; i++) {
rgba[2][i] = (2 * rgba[0][i] + rgba[1][i]) / 3;
rgba[3][i] = (rgba[0][i] + 2 * rgba[1][i]) / 3;
}
rgba[2][3] = 255;
rgba[3][3] = 255;
} else {
for (size_t i = 0; i < 3; i++) {
rgba[2][i] = (rgba[0][i] + rgba[1][i]) / 2;
rgba[3][i] = 0;
}
rgba[2][3] = 255;
rgba[3][3] = dxt1 ? 0 : 255;
}
size_t i = 0;
for (size_t y = 0; y < 4; y++) {
for (size_t x = 0; x < 4; x++) {
u8 code_byte = (code >> (i * 2)) & 3;
u8 r = rgba[code_byte][0];
u8 g = rgba[code_byte][1];
u8 b = rgba[code_byte][2];
u8 a = dxt1 ? rgba[code_byte][3] : context.bitmap->get_pixel(bitmap_x + x, bitmap_y + y).alpha();
Color color = Color(r, g, b, a);
context.bitmap->set_pixel(bitmap_x + x, bitmap_y + y, color);
i++;
}
}
return {};
}
static ErrorOr<void> decode_dxt(Stream& stream, DDSLoadingContext& context, DXGIFormat format, u64 width, u64 y)
{
if (format == DXGI_FORMAT_BC1_UNORM) {
for (size_t x = 0; x < width; x += 4) {
TRY(decode_color_block(stream, context, true, x, y));
}
}
if (format == DXGI_FORMAT_BC2_UNORM) {
for (size_t x = 0; x < width; x += 4) {
TRY(decode_dx3_alpha_block(stream, context, x, y));
TRY(decode_color_block(stream, context, false, x, y));
}
}
if (format == DXGI_FORMAT_BC3_UNORM) {
for (size_t x = 0; x < width; x += 4) {
TRY(decode_dx5_alpha_block(stream, context, x, y));
TRY(decode_color_block(stream, context, false, x, y));
}
}
return {};
}
static ErrorOr<void> decode_bitmap(Stream& stream, DDSLoadingContext& context, DXGIFormat format, u64 width, u64 height)
{
Vector<u32> dxt_formats = { DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC3_UNORM };
if (dxt_formats.contains_slow(format)) {
for (u64 y = 0; y < height; y += 4) {
TRY(decode_dxt(stream, context, format, width, y));
}
}
// FIXME: Support more encodings (ATI, YUV, RAW, etc...).
return {};
}
static ErrorOr<void> decode_dds(DDSLoadingContext& context)
{
// All valid DDS files are at least 128 bytes long.
if (context.data_size < 128) {
dbgln_if(DDS_DEBUG, "File is too short for DDS");
context.state = DDSLoadingContext::State::Error;
return Error::from_string_literal("File is too short for DDS");
}
FixedMemoryStream stream { ReadonlyBytes { context.data, context.data_size } };
auto magic = TRY(stream.read_value<u32>());
if (magic != create_four_cc('D', 'D', 'S', ' ')) {
dbgln_if(DDS_DEBUG, "Missing magic number");
context.state = DDSLoadingContext::State::Error;
return Error::from_string_literal("Missing magic number");
}
context.header = TRY(stream.read_value<DDSHeader>());
if (context.header.size != 124) {
dbgln_if(DDS_DEBUG, "Header size is malformed");
context.state = DDSLoadingContext::State::Error;
return Error::from_string_literal("Header size is malformed");
}
if (context.header.pixel_format.size != 32) {
dbgln_if(DDS_DEBUG, "Pixel format size is malformed");
context.state = DDSLoadingContext::State::Error;
return Error::from_string_literal("Pixel format size is malformed");
}
if ((context.header.pixel_format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC) {
if (context.header.pixel_format.four_cc == create_four_cc('D', 'X', '1', '0')) {
if (context.data_size < 148) {
dbgln_if(DDS_DEBUG, "DX10 header is too short");
context.state = DDSLoadingContext::State::Error;
return Error::from_string_literal("DX10 header is too short");
}
context.header10 = TRY(stream.read_value<DDSHeaderDXT10>());
}
}
if constexpr (DDS_DEBUG) {
context.dump_debug();
}
DXGIFormat format = get_format(context.header.pixel_format);
Vector<u32> supported_formats = { DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC3_UNORM };
if (!supported_formats.contains_slow(format)) {
dbgln_if(DDS_DEBUG, "Format of type {} is not supported at the moment", static_cast<u32>(format));
context.state = DDSLoadingContext::State::Error;
return Error::from_string_literal("Format type is not supported at the moment");
}
// We support parsing mipmaps, but we only care about the largest one :^) (At least for now)
if (size_t mipmap_level = 0; mipmap_level < max(context.header.mip_map_count, 1u)) {
u64 width = get_width(context.header, mipmap_level);
u64 height = get_height(context.header, mipmap_level);
context.bitmap = TRY(Bitmap::create(BitmapFormat::BGRA8888, { width, height }));
TRY(decode_bitmap(stream, context, format, width, height));
}
context.state = DDSLoadingContext::State::BitmapDecoded;
return {};
}
void DDSLoadingContext::dump_debug()
{
StringBuilder builder;
builder.append("\nDDS:\n"sv);
builder.appendff("\tHeader Size: {}\n", header.size);
builder.append("\tFlags:"sv);
if ((header.flags & DDSFlags::DDSD_CAPS) == DDSFlags::DDSD_CAPS)
builder.append(" DDSD_CAPS"sv);
if ((header.flags & DDSFlags::DDSD_HEIGHT) == DDSFlags::DDSD_HEIGHT)
builder.append(" DDSD_HEIGHT"sv);
if ((header.flags & DDSFlags::DDSD_WIDTH) == DDSFlags::DDSD_WIDTH)
builder.append(" DDSD_WIDTH"sv);
if ((header.flags & DDSFlags::DDSD_PITCH) == DDSFlags::DDSD_PITCH)
builder.append(" DDSD_PITCH"sv);
if ((header.flags & DDSFlags::DDSD_PIXELFORMAT) == DDSFlags::DDSD_PIXELFORMAT)
builder.append(" DDSD_PIXELFORMAT"sv);
if ((header.flags & DDSFlags::DDSD_MIPMAPCOUNT) == DDSFlags::DDSD_MIPMAPCOUNT)
builder.append(" DDSD_MIPMAPCOUNT"sv);
if ((header.flags & DDSFlags::DDSD_LINEARSIZE) == DDSFlags::DDSD_LINEARSIZE)
builder.append(" DDSD_LINEARSIZE"sv);
if ((header.flags & DDSFlags::DDSD_DEPTH) == DDSFlags::DDSD_DEPTH)
builder.append(" DDSD_DEPTH"sv);
builder.append("\n"sv);
builder.appendff("\tHeight: {}\n", header.height);
builder.appendff("\tWidth: {}\n", header.width);
builder.appendff("\tPitch: {}\n", header.pitch);
builder.appendff("\tDepth: {}\n", header.depth);
builder.appendff("\tMipmap Count: {}\n", header.mip_map_count);
builder.append("\tCaps:"sv);
if ((header.caps1 & Caps1Flags::DDSCAPS_COMPLEX) == Caps1Flags::DDSCAPS_COMPLEX)
builder.append(" DDSCAPS_COMPLEX"sv);
if ((header.caps1 & Caps1Flags::DDSCAPS_MIPMAP) == Caps1Flags::DDSCAPS_MIPMAP)
builder.append(" DDSCAPS_MIPMAP"sv);
if ((header.caps1 & Caps1Flags::DDSCAPS_TEXTURE) == Caps1Flags::DDSCAPS_TEXTURE)
builder.append(" DDSCAPS_TEXTURE"sv);
builder.append("\n"sv);
builder.append("\tCaps2:"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP) == Caps2Flags::DDSCAPS2_CUBEMAP)
builder.append(" DDSCAPS2_CUBEMAP"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEX) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEX)
builder.append(" DDSCAPS2_CUBEMAP_POSITIVEX"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEX) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEX)
builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEX"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEY) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEY)
builder.append(" DDSCAPS2_CUBEMAP_POSITIVEY"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEY) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEY)
builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEY"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEZ) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEZ)
builder.append(" DDSCAPS2_CUBEMAP_POSITIVEZ"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEZ) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEZ)
builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEZ"sv);
if ((header.caps2 & Caps2Flags::DDSCAPS2_VOLUME) == Caps2Flags::DDSCAPS2_VOLUME)
builder.append(" DDSCAPS2_VOLUME"sv);
builder.append("\n"sv);
builder.append("Pixel Format:\n"sv);
builder.appendff("\tStruct Size: {}\n", header.pixel_format.size);
builder.append("\tFlags:"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_ALPHAPIXELS) == PixelFormatFlags::DDPF_ALPHAPIXELS)
builder.append(" DDPF_ALPHAPIXELS"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_ALPHA) == PixelFormatFlags::DDPF_ALPHA)
builder.append(" DDPF_ALPHA"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC)
builder.append(" DDPF_FOURCC"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_PALETTEINDEXED8) == PixelFormatFlags::DDPF_PALETTEINDEXED8)
builder.append(" DDPF_PALETTEINDEXED8"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_RGB) == PixelFormatFlags::DDPF_RGB)
builder.append(" DDPF_RGB"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_YUV) == PixelFormatFlags::DDPF_YUV)
builder.append(" DDPF_YUV"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_LUMINANCE) == PixelFormatFlags::DDPF_LUMINANCE)
builder.append(" DDPF_LUMINANCE"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_BUMPDUDV) == PixelFormatFlags::DDPF_BUMPDUDV)
builder.append(" DDPF_BUMPDUDV"sv);
if ((header.pixel_format.flags & PixelFormatFlags::DDPF_NORMAL) == PixelFormatFlags::DDPF_NORMAL)
builder.append(" DDPF_NORMAL"sv);
builder.append("\n"sv);
builder.append("\tFour CC: "sv);
builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 0)) & 0xFF);
builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 1)) & 0xFF);
builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 2)) & 0xFF);
builder.appendff("{:c}", (header.pixel_format.four_cc >> (8 * 3)) & 0xFF);
builder.append("\n"sv);
builder.appendff("\tRGB Bit Count: {}\n", header.pixel_format.rgb_bit_count);
builder.appendff("\tR Bit Mask: {}\n", header.pixel_format.r_bit_mask);
builder.appendff("\tG Bit Mask: {}\n", header.pixel_format.g_bit_mask);
builder.appendff("\tB Bit Mask: {}\n", header.pixel_format.b_bit_mask);
builder.appendff("\tA Bit Mask: {}\n", header.pixel_format.a_bit_mask);
builder.append("DDS10:\n"sv);
builder.appendff("\tFormat: {}\n", static_cast<u32>(header10.format));
builder.append("\tResource Dimension:"sv);
if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_UNKNOWN) == ResourceDimensions::DDS_DIMENSION_UNKNOWN)
builder.append(" DDS_DIMENSION_UNKNOWN"sv);
if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_BUFFER) == ResourceDimensions::DDS_DIMENSION_BUFFER)
builder.append(" DDS_DIMENSION_BUFFER"sv);
if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE1D) == ResourceDimensions::DDS_DIMENSION_TEXTURE1D)
builder.append(" DDS_DIMENSION_TEXTURE1D"sv);
if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE2D) == ResourceDimensions::DDS_DIMENSION_TEXTURE2D)
builder.append(" DDS_DIMENSION_TEXTURE2D"sv);
if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE3D) == ResourceDimensions::DDS_DIMENSION_TEXTURE3D)
builder.append(" DDS_DIMENSION_TEXTURE3D"sv);
builder.append("\n"sv);
builder.appendff("\tArray Size: {}\n", header10.array_size);
builder.append("\tMisc Flags:"sv);
if ((header10.misc_flag & MiscFlags::DDS_RESOURCE_MISC_TEXTURECUBE) == MiscFlags::DDS_RESOURCE_MISC_TEXTURECUBE)
builder.append(" DDS_RESOURCE_MISC_TEXTURECUBE"sv);
builder.append("\n"sv);
builder.append("\tMisc Flags 2:"sv);
if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_UNKNOWN) == Misc2Flags::DDS_ALPHA_MODE_UNKNOWN)
builder.append(" DDS_ALPHA_MODE_UNKNOWN"sv);
if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_STRAIGHT) == Misc2Flags::DDS_ALPHA_MODE_STRAIGHT)
builder.append(" DDS_ALPHA_MODE_STRAIGHT"sv);
if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_PREMULTIPLIED) == Misc2Flags::DDS_ALPHA_MODE_PREMULTIPLIED)
builder.append(" DDS_ALPHA_MODE_PREMULTIPLIED"sv);
if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_OPAQUE) == Misc2Flags::DDS_ALPHA_MODE_OPAQUE)
builder.append(" DDS_ALPHA_MODE_OPAQUE"sv);
if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_CUSTOM) == Misc2Flags::DDS_ALPHA_MODE_CUSTOM)
builder.append(" DDS_ALPHA_MODE_CUSTOM"sv);
builder.append("\n"sv);
dbgln("{}", builder.to_deprecated_string());
}
DDSImageDecoderPlugin::DDSImageDecoderPlugin(u8 const* data, size_t size)
{
m_context = make<DDSLoadingContext>();
m_context->data = data;
m_context->data_size = size;
}
DDSImageDecoderPlugin::~DDSImageDecoderPlugin() = default;
IntSize DDSImageDecoderPlugin::size()
{
if (m_context->state == DDSLoadingContext::State::Error)
return {};
if (m_context->state == DDSLoadingContext::State::BitmapDecoded)
return { m_context->header.width, m_context->header.height };
return {};
}
void DDSImageDecoderPlugin::set_volatile()
{
if (m_context->bitmap)
m_context->bitmap->set_volatile();
}
bool DDSImageDecoderPlugin::set_nonvolatile(bool& was_purged)
{
if (!m_context->bitmap)
return false;
return m_context->bitmap->set_nonvolatile(was_purged);
}
bool DDSImageDecoderPlugin::initialize()
{
// The header is always at least 128 bytes, so if the file is smaller, it can't be a DDS.
return m_context->data_size > 128
&& m_context->data[0] == 0x44
&& m_context->data[1] == 0x44
&& m_context->data[2] == 0x53
&& m_context->data[3] == 0x20;
}
bool DDSImageDecoderPlugin::sniff(ReadonlyBytes data)
{
// The header is always at least 128 bytes, so if the file is smaller, it can't be a DDS.
return data.size() > 128
&& data.data()[0] == 0x44
&& data.data()[1] == 0x44
&& data.data()[2] == 0x53
&& data.data()[3] == 0x20;
}
ErrorOr<NonnullOwnPtr<ImageDecoderPlugin>> DDSImageDecoderPlugin::create(ReadonlyBytes data)
{
return adopt_nonnull_own_or_enomem(new (nothrow) DDSImageDecoderPlugin(data.data(), data.size()));
}
bool DDSImageDecoderPlugin::is_animated()
{
return false;
}
size_t DDSImageDecoderPlugin::loop_count()
{
return 0;
}
size_t DDSImageDecoderPlugin::frame_count()
{
return 1;
}
ErrorOr<ImageFrameDescriptor> DDSImageDecoderPlugin::frame(size_t index)
{
if (index > 0)
return Error::from_string_literal("DDSImageDecoderPlugin: Invalid frame index");
if (m_context->state == DDSLoadingContext::State::Error)
return Error::from_string_literal("DDSImageDecoderPlugin: Decoding failed");
if (m_context->state < DDSLoadingContext::State::BitmapDecoded) {
TRY(decode_dds(*m_context));
}
VERIFY(m_context->bitmap);
return ImageFrameDescriptor { m_context->bitmap, 0 };
}
ErrorOr<Optional<ReadonlyBytes>> DDSImageDecoderPlugin::icc_data()
{
return OptionalNone {};
}
}