diff options
author | stelar7 <dudedbz@gmail.com> | 2021-05-15 17:02:40 +0200 |
---|---|---|
committer | Linus Groh <mail@linusgroh.de> | 2021-05-18 08:45:53 +0100 |
commit | 24c5b0e81c6902907d708f862e54d40f5248973e (patch) | |
tree | 9db626adda893f052e3d35417f5a5b45b55b597c /Userland/Libraries | |
parent | 9f42ccd6399d604a49a36cdf91a4221b85f0d138 (diff) | |
download | serenity-24c5b0e81c6902907d708f862e54d40f5248973e.zip |
LibGfx: Add support for DDS images
Diffstat (limited to 'Userland/Libraries')
-rw-r--r-- | Userland/Libraries/LibGfx/Bitmap.cpp | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/Bitmap.h | 21 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/DDSLoader.cpp | 1054 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/DDSLoader.h | 260 | ||||
-rw-r--r-- | Userland/Libraries/LibGfx/ImageDecoder.cpp | 5 |
6 files changed, 1332 insertions, 10 deletions
diff --git a/Userland/Libraries/LibGfx/Bitmap.cpp b/Userland/Libraries/LibGfx/Bitmap.cpp index 0d9cb01d19..bc4d586513 100644 --- a/Userland/Libraries/LibGfx/Bitmap.cpp +++ b/Userland/Libraries/LibGfx/Bitmap.cpp @@ -13,6 +13,7 @@ #include <AK/String.h> #include <LibGfx/BMPLoader.h> #include <LibGfx/Bitmap.h> +#include <LibGfx/DDSLoader.h> #include <LibGfx/GIFLoader.h> #include <LibGfx/ICOLoader.h> #include <LibGfx/JPGLoader.h> diff --git a/Userland/Libraries/LibGfx/Bitmap.h b/Userland/Libraries/LibGfx/Bitmap.h index 9a6cfee8a3..461645a97b 100644 --- a/Userland/Libraries/LibGfx/Bitmap.h +++ b/Userland/Libraries/LibGfx/Bitmap.h @@ -13,16 +13,17 @@ #include <LibGfx/Forward.h> #include <LibGfx/Rect.h> -#define ENUMERATE_IMAGE_FORMATS \ - __ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \ - __ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \ - __ENUMERATE_IMAGE_FORMAT(png, ".png") \ - __ENUMERATE_IMAGE_FORMAT(ppm, ".ppm") \ - __ENUMERATE_IMAGE_FORMAT(gif, ".gif") \ - __ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \ - __ENUMERATE_IMAGE_FORMAT(ico, ".ico") \ - __ENUMERATE_IMAGE_FORMAT(jpg, ".jpg") \ - __ENUMERATE_IMAGE_FORMAT(jpg, ".jpeg") +#define ENUMERATE_IMAGE_FORMATS \ + __ENUMERATE_IMAGE_FORMAT(pbm, ".pbm") \ + __ENUMERATE_IMAGE_FORMAT(pgm, ".pgm") \ + __ENUMERATE_IMAGE_FORMAT(png, ".png") \ + __ENUMERATE_IMAGE_FORMAT(ppm, ".ppm") \ + __ENUMERATE_IMAGE_FORMAT(gif, ".gif") \ + __ENUMERATE_IMAGE_FORMAT(bmp, ".bmp") \ + __ENUMERATE_IMAGE_FORMAT(ico, ".ico") \ + __ENUMERATE_IMAGE_FORMAT(jpg, ".jpg") \ + __ENUMERATE_IMAGE_FORMAT(jpg, ".jpeg") \ + __ENUMERATE_IMAGE_FORMAT(dds, ".dds") namespace Gfx { diff --git a/Userland/Libraries/LibGfx/CMakeLists.txt b/Userland/Libraries/LibGfx/CMakeLists.txt index 17dd58938b..ad84121ece 100644 --- a/Userland/Libraries/LibGfx/CMakeLists.txt +++ b/Userland/Libraries/LibGfx/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES ClassicStylePainter.cpp ClassicWindowTheme.cpp Color.cpp + DDSLoader.cpp DisjointRectSet.cpp Emoji.cpp FontDatabase.cpp diff --git a/Userland/Libraries/LibGfx/DDSLoader.cpp b/Userland/Libraries/LibGfx/DDSLoader.cpp new file mode 100644 index 0000000000..d83721ef9a --- /dev/null +++ b/Userland/Libraries/LibGfx/DDSLoader.cpp @@ -0,0 +1,1054 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <AK/Debug.h> +#include <AK/Endian.h> +#include <AK/LexicalPath.h> +#include <AK/MappedFile.h> +#include <AK/MemoryStream.h> +#include <AK/StringBuilder.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> + +#ifdef __serenity__ +# include <serenity.h> +#endif + +namespace Gfx { + +struct DDSLoadingContext { + enum State { + NotDecoded = 0, + Error, + BitmapDecoded, + }; + + State state { State::NotDecoded }; + + const u8* 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 bool is_planar(DXGIFormat format) +{ + switch (format) { + case DXGI_FORMAT_NV12: + case DXGI_FORMAT_420_OPAQUE: + case DXGI_FORMAT_P208: + case DXGI_FORMAT_P010: + case DXGI_FORMAT_P016: + return true; + default: + return false; + } +} + +static bool is_packed(DXGIFormat format) +{ + switch (format) { + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_YUY2: + case DXGI_FORMAT_Y210: + case DXGI_FORMAT_Y216: + return true; + default: + return false; + } +} + +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 bool is_block_compressed(DXGIFormat format) +{ + switch (format) { + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return true; + + default: + return false; + } +} + +static size_t block_size(DXGIFormat format) +{ + switch (format) { + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return 16; + + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + case DXGI_FORMAT_Y210: + case DXGI_FORMAT_Y216: + return 8; + + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_YUY2: + case DXGI_FORMAT_P010: + case DXGI_FORMAT_P016: + return 4; + + case DXGI_FORMAT_NV12: + case DXGI_FORMAT_420_OPAQUE: + case DXGI_FORMAT_P208: + return 2; + + default: + return 0; + } +} + +static size_t bits_per_pixel(DXGIFormat format) +{ + switch (format) { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: + return 128; + + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: + return 96; + + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + case DXGI_FORMAT_R32G8X24_TYPELESS: + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + case DXGI_FORMAT_Y416: + case DXGI_FORMAT_Y210: + case DXGI_FORMAT_Y216: + return 64; + + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_TYPELESS: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + case DXGI_FORMAT_AYUV: + case DXGI_FORMAT_Y410: + case DXGI_FORMAT_YUY2: + return 32; + + case DXGI_FORMAT_P010: + case DXGI_FORMAT_P016: + case DXGI_FORMAT_V408: + return 24; + + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + case DXGI_FORMAT_A8P8: + case DXGI_FORMAT_B4G4R4A4_UNORM: + case DXGI_FORMAT_P208: + case DXGI_FORMAT_V208: + return 16; + + case DXGI_FORMAT_NV12: + case DXGI_FORMAT_420_OPAQUE: + case DXGI_FORMAT_NV11: + return 12; + + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + case DXGI_FORMAT_AI44: + case DXGI_FORMAT_IA44: + case DXGI_FORMAT_P8: + return 8; + + case DXGI_FORMAT_R1_UNORM: + return 1; + + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + return 4; + + default: + return 0; + } +} + +static void decode_dx5_alpha_block(InputMemoryStream& stream, DDSLoadingContext& context, u64 bitmap_x, u64 bitmap_y) +{ + LittleEndian<u8> color0 {}, color1 {}; + LittleEndian<u8> code0 {}, code1 {}, code2 {}, code3 {}, code4 {}, code5 {}; + + stream >> color0; + stream >> color1; + stream >> code0; + stream >> code1; + stream >> code2; + stream >> code3; + stream >> code4; + stream >> code5; + + 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); + } + } +} + +static void decode_dx3_alpha_block(InputMemoryStream& stream, DDSLoadingContext& context, u64 bitmap_x, u64 bitmap_y) +{ + LittleEndian<u8> a0 {}, a1 {}, a2 {}, a3 {}, a4 {}, a5 {}, a6 {}, a7 {}; + + stream >> a0; + stream >> a1; + stream >> a2; + stream >> a3; + stream >> a4; + stream >> a5; + stream >> a6; + stream >> a7; + + 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); + } + } + } +} + +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 void decode_color_block(InputMemoryStream& stream, DDSLoadingContext& context, bool dxt1, u64 bitmap_x, u64 bitmap_y) +{ + LittleEndian<u8> c0_low {}, c0_high {}, c1_low {}, c1_high {}; + LittleEndian<u8> codes_0 {}, codes_1 {}, codes_2 {}, codes_3 {}; + + stream >> c0_low; + stream >> c0_high; + stream >> c1_low; + stream >> c1_high; + stream >> codes_0; + stream >> codes_1; + stream >> codes_2; + stream >> codes_3; + + 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++; + } + } +} + +static void decode_dxt(InputMemoryStream& stream, DDSLoadingContext& context, DXGIFormat format, u64 width, u64 y) +{ + if (format == DXGI_FORMAT_BC1_UNORM) { + for (size_t x = 0; x < width; x += 4) { + decode_color_block(stream, context, true, x, y); + } + } + + if (format == DXGI_FORMAT_BC2_UNORM) { + for (size_t x = 0; x < width; x += 4) { + decode_dx3_alpha_block(stream, context, x, y); + decode_color_block(stream, context, false, x, y); + } + } + + if (format == DXGI_FORMAT_BC3_UNORM) { + for (size_t x = 0; x < width; x += 4) { + decode_dx5_alpha_block(stream, context, x, y); + decode_color_block(stream, context, false, x, y); + } + } +} +static void decode_bitmap(InputMemoryStream& 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) { + decode_dxt(stream, context, format, width, y); + } + } + + // FIXME: Support more encodings (ATI, YUV, RAW, etc...). +} + +static size_t get_minimum_bytes_for_mipmap(DXGIFormat format, u64 width, u64 height) +{ + u64 row_bytes {}; + u64 row_count {}; + + if (is_block_compressed(format)) { + u64 width_in_blocks {}; + u64 height_in_blocks {}; + + if (width > 0) { + width_in_blocks = max(static_cast<u64>(1), (width + 3u) / 4u); + } + + if (height > 0) { + height_in_blocks = max(static_cast<u64>(1), (height + 3u) / 4u); + } + + row_bytes = width_in_blocks * block_size(format); + row_count = height_in_blocks; + return row_bytes * row_count; + } else if (is_packed(format)) { + row_bytes = ((width + 1u) >> 1) * block_size(format); + row_count = height; + return row_bytes * row_count; + } else if (format == DXGI_FORMAT_NV11) { + row_bytes = ((width + 3u) >> 2) * 4u; + row_count = height * 2u; + return row_bytes * row_count; + } else if (is_planar(format)) { + row_bytes = ((width + 1u) >> 1) * block_size(format); + row_count = height + ((height + 1u) >> 1); + return (row_bytes * row_count) + (((row_bytes * row_count) + 1) >> 1); + } else { + u32 bpp = bits_per_pixel(format); + + row_bytes = (width * bpp + 7u) / 8u; + row_count = height; + return row_bytes * row_count; + } +} + +static bool decode_dds(DDSLoadingContext& context) +{ + InputMemoryStream stream({ context.data, context.data_size }); + + // All valid DDS files are at least 128 bytes long. + if (stream.remaining() < 128) { + dbgln_if(DDS_DEBUG, "File is too short for DDS"); + context.state = DDSLoadingContext::State::Error; + return false; + } + + u32 magic; + stream >> magic; + + if (magic != create_four_cc('D', 'D', 'S', ' ')) { + dbgln_if(DDS_DEBUG, "Missing magic number"); + context.state = DDSLoadingContext::State::Error; + return false; + } + + stream >> context.header.size; + stream >> context.header.flags; + stream >> context.header.height; + stream >> context.header.width; + stream >> context.header.pitch; + stream >> context.header.depth; + stream >> context.header.mip_map_count; + // The bytes in context.header.reserved are unused, so we just skip over them (11 * 4 bytes). + stream.discard_or_error(44); + stream >> context.header.pixel_format.size; + stream >> context.header.pixel_format.flags; + stream >> context.header.pixel_format.four_cc; + stream >> context.header.pixel_format.rgb_bit_count; + stream >> context.header.pixel_format.r_bit_mask; + stream >> context.header.pixel_format.g_bit_mask; + stream >> context.header.pixel_format.b_bit_mask; + stream >> context.header.pixel_format.a_bit_mask; + stream >> context.header.caps1; + stream >> context.header.caps2; + stream >> context.header.caps3; + stream >> context.header.caps4; + stream >> context.header.reserved2; + + if (context.header.size != 124) { + dbgln_if(DDS_DEBUG, "Header size is malformed"); + context.state = DDSLoadingContext::State::Error; + return false; + } + if (context.header.pixel_format.size != 32) { + dbgln_if(DDS_DEBUG, "Pixel format size is malformed"); + context.state = DDSLoadingContext::State::Error; + return false; + } + + 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 (stream.bytes().size() < 148) { + dbgln_if(DDS_DEBUG, "DX10 header is too short"); + context.state = DDSLoadingContext::State::Error; + return false; + } + + u32 format {}; + stream >> format; + context.header10.format = static_cast<DXGIFormat>(format); + stream >> context.header10.resource_dimension; + stream >> context.header10.misc_flag; + stream >> context.header10.array_size; + stream >> context.header10.misc_flag2; + } + } + + 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 false; + } + + for (size_t mipmap_level = 0; mipmap_level < max(context.header.mip_map_count, 1u); mipmap_level++) { + u64 width = get_width(context.header, mipmap_level); + u64 height = get_height(context.header, mipmap_level); + + u64 needed_bytes = get_minimum_bytes_for_mipmap(format, width, height); + dbgln_if(DDS_DEBUG, "There are {} bytes remaining, we need {} for mipmap level {} of the image", stream.remaining(), needed_bytes, mipmap_level); + VERIFY(stream.remaining() >= needed_bytes); + + context.bitmap = Bitmap::create_purgeable(BitmapFormat::BGRA8888, { width, height }); + + decode_bitmap(stream, context, format, width, height); + + // We support parsing mipmaps, but we only care about the largest one :^) (Atleast for now) + break; + } + + context.state = DDSLoadingContext::State::BitmapDecoded; + + return true; +} + +void DDSLoadingContext::dump_debug() +{ + StringBuilder builder; + + builder.append("\nDDS:\n"); + builder.appendff("\tHeader Size: {}\n", header.size); + + builder.append("\tFlags:"); + if ((header.flags & DDSFlags::DDSD_CAPS) == DDSFlags::DDSD_CAPS) + builder.append(" DDSD_CAPS"); + if ((header.flags & DDSFlags::DDSD_HEIGHT) == DDSFlags::DDSD_HEIGHT) + builder.append(" DDSD_HEIGHT"); + if ((header.flags & DDSFlags::DDSD_WIDTH) == DDSFlags::DDSD_WIDTH) + builder.append(" DDSD_WIDTH"); + if ((header.flags & DDSFlags::DDSD_PITCH) == DDSFlags::DDSD_PITCH) + builder.append(" DDSD_PITCH"); + if ((header.flags & DDSFlags::DDSD_PIXELFORMAT) == DDSFlags::DDSD_PIXELFORMAT) + builder.append(" DDSD_PIXELFORMAT"); + if ((header.flags & DDSFlags::DDSD_MIPMAPCOUNT) == DDSFlags::DDSD_MIPMAPCOUNT) + builder.append(" DDSD_MIPMAPCOUNT"); + if ((header.flags & DDSFlags::DDSD_LINEARSIZE) == DDSFlags::DDSD_LINEARSIZE) + builder.append(" DDSD_LINEARSIZE"); + if ((header.flags & DDSFlags::DDSD_DEPTH) == DDSFlags::DDSD_DEPTH) + builder.append(" DDSD_DEPTH"); + builder.append("\n"); + + 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:"); + if ((header.caps1 & Caps1Flags::DDSCAPS_COMPLEX) == Caps1Flags::DDSCAPS_COMPLEX) + builder.append(" DDSCAPS_COMPLEX"); + if ((header.caps1 & Caps1Flags::DDSCAPS_MIPMAP) == Caps1Flags::DDSCAPS_MIPMAP) + builder.append(" DDSCAPS_MIPMAP"); + if ((header.caps1 & Caps1Flags::DDSCAPS_TEXTURE) == Caps1Flags::DDSCAPS_TEXTURE) + builder.append(" DDSCAPS_TEXTURE"); + builder.append("\n"); + + builder.append("\tCaps2:"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP) == Caps2Flags::DDSCAPS2_CUBEMAP) + builder.append(" DDSCAPS2_CUBEMAP"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEX) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEX) + builder.append(" DDSCAPS2_CUBEMAP_POSITIVEX"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEX) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEX) + builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEX"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEY) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEY) + builder.append(" DDSCAPS2_CUBEMAP_POSITIVEY"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEY) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEY) + builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEY"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEZ) == Caps2Flags::DDSCAPS2_CUBEMAP_POSITIVEZ) + builder.append(" DDSCAPS2_CUBEMAP_POSITIVEZ"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEZ) == Caps2Flags::DDSCAPS2_CUBEMAP_NEGATIVEZ) + builder.append(" DDSCAPS2_CUBEMAP_NEGATIVEZ"); + if ((header.caps2 & Caps2Flags::DDSCAPS2_VOLUME) == Caps2Flags::DDSCAPS2_VOLUME) + builder.append(" DDSCAPS2_VOLUME"); + builder.append("\n"); + + builder.append("Pixel Format:\n"); + builder.appendff("\tStruct Size: {}\n", header.pixel_format.size); + + builder.append("\tFlags:"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_ALPHAPIXELS) == PixelFormatFlags::DDPF_ALPHAPIXELS) + builder.append(" DDPF_ALPHAPIXELS"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_ALPHA) == PixelFormatFlags::DDPF_ALPHA) + builder.append(" DDPF_ALPHA"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_FOURCC) == PixelFormatFlags::DDPF_FOURCC) + builder.append(" DDPF_FOURCC"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_PALETTEINDEXED8) == PixelFormatFlags::DDPF_PALETTEINDEXED8) + builder.append(" DDPF_PALETTEINDEXED8"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_RGB) == PixelFormatFlags::DDPF_RGB) + builder.append(" DDPF_RGB"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_YUV) == PixelFormatFlags::DDPF_YUV) + builder.append(" DDPF_YUV"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_LUMINANCE) == PixelFormatFlags::DDPF_LUMINANCE) + builder.append(" DDPF_LUMINANCE"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_BUMPDUDV) == PixelFormatFlags::DDPF_BUMPDUDV) + builder.append(" DDPF_BUMPDUDV"); + if ((header.pixel_format.flags & PixelFormatFlags::DDPF_NORMAL) == PixelFormatFlags::DDPF_NORMAL) + builder.append(" DDPF_NORMAL"); + builder.append("\n"); + + builder.append("\tFour CC: "); + 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"); + 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"); + builder.appendff("\tFormat: {}\n", static_cast<u32>(header10.format)); + + builder.append("\tResource Dimension:"); + if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_UNKNOWN) == ResourceDimensions::DDS_DIMENSION_UNKNOWN) + builder.append(" DDS_DIMENSION_UNKNOWN"); + if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_BUFFER) == ResourceDimensions::DDS_DIMENSION_BUFFER) + builder.append(" DDS_DIMENSION_BUFFER"); + if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE1D) == ResourceDimensions::DDS_DIMENSION_TEXTURE1D) + builder.append(" DDS_DIMENSION_TEXTURE1D"); + if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE2D) == ResourceDimensions::DDS_DIMENSION_TEXTURE2D) + builder.append(" DDS_DIMENSION_TEXTURE2D"); + if ((header10.resource_dimension & ResourceDimensions::DDS_DIMENSION_TEXTURE3D) == ResourceDimensions::DDS_DIMENSION_TEXTURE3D) + builder.append(" DDS_DIMENSION_TEXTURE3D"); + builder.append("\n"); + + builder.appendff("\tArray Size: {}\n", header10.array_size); + + builder.append("\tMisc Flags:"); + if ((header10.misc_flag & MiscFlags::DDS_RESOURCE_MISC_TEXTURECUBE) == MiscFlags::DDS_RESOURCE_MISC_TEXTURECUBE) + builder.append(" DDS_RESOURCE_MISC_TEXTURECUBE"); + builder.append("\n"); + + builder.append("\tMisc Flags 2:"); + if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_UNKNOWN) == Misc2Flags::DDS_ALPHA_MODE_UNKNOWN) + builder.append(" DDS_ALPHA_MODE_UNKNOWN"); + if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_STRAIGHT) == Misc2Flags::DDS_ALPHA_MODE_STRAIGHT) + builder.append(" DDS_ALPHA_MODE_STRAIGHT"); + if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_PREMULTIPLIED) == Misc2Flags::DDS_ALPHA_MODE_PREMULTIPLIED) + builder.append(" DDS_ALPHA_MODE_PREMULTIPLIED"); + if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_OPAQUE) == Misc2Flags::DDS_ALPHA_MODE_OPAQUE) + builder.append(" DDS_ALPHA_MODE_OPAQUE"); + if ((header10.misc_flag2 & Misc2Flags::DDS_ALPHA_MODE_CUSTOM) == Misc2Flags::DDS_ALPHA_MODE_CUSTOM) + builder.append(" DDS_ALPHA_MODE_CUSTOM"); + builder.append("\n"); + + dbgln("{}", builder.to_string()); +} + +static RefPtr<Gfx::Bitmap> load_dds_impl(const u8* data, size_t length) +{ + DDSLoadingContext context; + context.data = data; + context.data_size = length; + + if (!decode_dds(context)) + return nullptr; + + return context.bitmap; +} + +RefPtr<Gfx::Bitmap> load_dds(String const& path) +{ + auto file_or_error = MappedFile::map(path); + if (file_or_error.is_error()) + return nullptr; + auto bitmap = load_dds_impl((const u8*)file_or_error.value()->data(), file_or_error.value()->size()); + if (bitmap) + bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded DDS: {}", bitmap->size(), LexicalPath::canonicalized_path(path))); + return bitmap; +} + +RefPtr<Gfx::Bitmap> load_dds_from_memory(const u8* data, size_t length) +{ + auto bitmap = load_dds_impl(data, length); + if (bitmap) + bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded DDS: <memory>", bitmap->size())); + return bitmap; +} + +DDSImageDecoderPlugin::DDSImageDecoderPlugin(const u8* data, size_t size) +{ + m_context = make<DDSLoadingContext>(); + m_context->data = data; + m_context->data_size = size; +} + +DDSImageDecoderPlugin::~DDSImageDecoderPlugin() +{ +} + +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 {}; +} + +RefPtr<Gfx::Bitmap> DDSImageDecoderPlugin::bitmap() +{ + if (m_context->state == DDSLoadingContext::State::Error) + return nullptr; + + if (m_context->state < DDSLoadingContext::State::BitmapDecoded) { + bool success = decode_dds(*m_context); + if (!success) + return nullptr; + } + + VERIFY(m_context->bitmap); + return m_context->bitmap; +} + +void DDSImageDecoderPlugin::set_volatile() +{ + if (m_context->bitmap) + m_context->bitmap->set_volatile(); +} + +bool DDSImageDecoderPlugin::set_nonvolatile() +{ + if (!m_context->bitmap) + return false; + return m_context->bitmap->set_nonvolatile(); +} + +bool DDSImageDecoderPlugin::sniff() +{ + // The header is always atleast 128 bytes, so if the file is smaller, it cant 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::is_animated() +{ + return false; +} + +size_t DDSImageDecoderPlugin::loop_count() +{ + return 0; +} + +size_t DDSImageDecoderPlugin::frame_count() +{ + return 1; +} + +ImageFrameDescriptor DDSImageDecoderPlugin::frame([[maybe_unused]] size_t i) +{ + // We have "frames", but they are all the same image, so lets just use the largest version. + return { bitmap(), 0 }; +} +} diff --git a/Userland/Libraries/LibGfx/DDSLoader.h b/Userland/Libraries/LibGfx/DDSLoader.h new file mode 100644 index 0000000000..8b5eab464b --- /dev/null +++ b/Userland/Libraries/LibGfx/DDSLoader.h @@ -0,0 +1,260 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include <LibGfx/Bitmap.h> +#include <LibGfx/ImageDecoder.h> + +namespace Gfx { + +enum MiscFlags : u32 { + DDS_RESOURCE_MISC_TEXTURECUBE = 0x4, +}; + +enum Misc2Flags : u32 { + DDS_ALPHA_MODE_UNKNOWN = 0x0, + DDS_ALPHA_MODE_STRAIGHT = 0x1, + DDS_ALPHA_MODE_PREMULTIPLIED = 0x2, + DDS_ALPHA_MODE_OPAQUE = 0x3, + DDS_ALPHA_MODE_CUSTOM = 0x4, +}; + +enum Caps1Flags : u32 { + DDSCAPS_COMPLEX = 0x8, + DDSCAPS_TEXTURE = 0x1000, + DDSCAPS_MIPMAP = 0x400000, +}; + +enum Caps2Flags : u32 { + DDSCAPS2_CUBEMAP = 0x200, + DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, + DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, + DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, + DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, + DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, + DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, + DDSCAPS2_VOLUME = 0x200000, +}; + +enum ResourceDimensions : u32 { + DDS_DIMENSION_UNKNOWN, + DDS_DIMENSION_BUFFER, + DDS_DIMENSION_TEXTURE1D = 2, + DDS_DIMENSION_TEXTURE2D = 3, + DDS_DIMENSION_TEXTURE3D = 4, +}; + +enum DXGIFormat : u32 { + DXGI_FORMAT_UNKNOWN = 0, + DXGI_FORMAT_R32G32B32A32_TYPELESS, + DXGI_FORMAT_R32G32B32A32_FLOAT, + DXGI_FORMAT_R32G32B32A32_UINT, + DXGI_FORMAT_R32G32B32A32_SINT, + DXGI_FORMAT_R32G32B32_TYPELESS, + DXGI_FORMAT_R32G32B32_FLOAT, + DXGI_FORMAT_R32G32B32_UINT, + DXGI_FORMAT_R32G32B32_SINT, + DXGI_FORMAT_R16G16B16A16_TYPELESS, + DXGI_FORMAT_R16G16B16A16_FLOAT, + DXGI_FORMAT_R16G16B16A16_UNORM, + DXGI_FORMAT_R16G16B16A16_UINT, + DXGI_FORMAT_R16G16B16A16_SNORM, + DXGI_FORMAT_R16G16B16A16_SINT, + DXGI_FORMAT_R32G32_TYPELESS, + DXGI_FORMAT_R32G32_FLOAT, + DXGI_FORMAT_R32G32_UINT, + DXGI_FORMAT_R32G32_SINT, + DXGI_FORMAT_R32G8X24_TYPELESS, + DXGI_FORMAT_D32_FLOAT_S8X24_UINT, + DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS, + DXGI_FORMAT_X32_TYPELESS_G8X24_UINT, + DXGI_FORMAT_R10G10B10A2_TYPELESS, + DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_FORMAT_R10G10B10A2_UINT, + DXGI_FORMAT_R11G11B10_FLOAT, + DXGI_FORMAT_R8G8B8A8_TYPELESS, + DXGI_FORMAT_R8G8B8A8_UNORM, + DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, + DXGI_FORMAT_R8G8B8A8_UINT, + DXGI_FORMAT_R8G8B8A8_SNORM, + DXGI_FORMAT_R8G8B8A8_SINT, + DXGI_FORMAT_R16G16_TYPELESS, + DXGI_FORMAT_R16G16_FLOAT, + DXGI_FORMAT_R16G16_UNORM, + DXGI_FORMAT_R16G16_UINT, + DXGI_FORMAT_R16G16_SNORM, + DXGI_FORMAT_R16G16_SINT, + DXGI_FORMAT_R32_TYPELESS, + DXGI_FORMAT_D32_FLOAT, + DXGI_FORMAT_R32_FLOAT, + DXGI_FORMAT_R32_UINT, + DXGI_FORMAT_R32_SINT, + DXGI_FORMAT_R24G8_TYPELESS, + DXGI_FORMAT_D24_UNORM_S8_UINT, + DXGI_FORMAT_R24_UNORM_X8_TYPELESS, + DXGI_FORMAT_X24_TYPELESS_G8_UINT, + DXGI_FORMAT_R8G8_TYPELESS, + DXGI_FORMAT_R8G8_UNORM, + DXGI_FORMAT_R8G8_UINT, + DXGI_FORMAT_R8G8_SNORM, + DXGI_FORMAT_R8G8_SINT, + DXGI_FORMAT_R16_TYPELESS, + DXGI_FORMAT_R16_FLOAT, + DXGI_FORMAT_D16_UNORM, + DXGI_FORMAT_R16_UNORM, + DXGI_FORMAT_R16_UINT, + DXGI_FORMAT_R16_SNORM, + DXGI_FORMAT_R16_SINT, + DXGI_FORMAT_R8_TYPELESS, + DXGI_FORMAT_R8_UNORM, + DXGI_FORMAT_R8_UINT, + DXGI_FORMAT_R8_SNORM, + DXGI_FORMAT_R8_SINT, + DXGI_FORMAT_A8_UNORM, + DXGI_FORMAT_R1_UNORM, + DXGI_FORMAT_R9G9B9E5_SHAREDEXP, + DXGI_FORMAT_R8G8_B8G8_UNORM, + DXGI_FORMAT_G8R8_G8B8_UNORM, + DXGI_FORMAT_BC1_TYPELESS, + DXGI_FORMAT_BC1_UNORM, + DXGI_FORMAT_BC1_UNORM_SRGB, + DXGI_FORMAT_BC2_TYPELESS, + DXGI_FORMAT_BC2_UNORM, + DXGI_FORMAT_BC2_UNORM_SRGB, + DXGI_FORMAT_BC3_TYPELESS, + DXGI_FORMAT_BC3_UNORM, + DXGI_FORMAT_BC3_UNORM_SRGB, + DXGI_FORMAT_BC4_TYPELESS, + DXGI_FORMAT_BC4_UNORM, + DXGI_FORMAT_BC4_SNORM, + DXGI_FORMAT_BC5_TYPELESS, + DXGI_FORMAT_BC5_UNORM, + DXGI_FORMAT_BC5_SNORM, + DXGI_FORMAT_B5G6R5_UNORM, + DXGI_FORMAT_B5G5R5A1_UNORM, + DXGI_FORMAT_B8G8R8A8_UNORM, + DXGI_FORMAT_B8G8R8X8_UNORM, + DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, + DXGI_FORMAT_B8G8R8A8_TYPELESS, + DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, + DXGI_FORMAT_B8G8R8X8_TYPELESS, + DXGI_FORMAT_B8G8R8X8_UNORM_SRGB, + DXGI_FORMAT_BC6H_TYPELESS, + DXGI_FORMAT_BC6H_UF16, + DXGI_FORMAT_BC6H_SF16, + DXGI_FORMAT_BC7_TYPELESS, + DXGI_FORMAT_BC7_UNORM, + DXGI_FORMAT_BC7_UNORM_SRGB, + DXGI_FORMAT_AYUV, + DXGI_FORMAT_Y410, + DXGI_FORMAT_Y416, + DXGI_FORMAT_NV12, + DXGI_FORMAT_P010, + DXGI_FORMAT_P016, + DXGI_FORMAT_420_OPAQUE, + DXGI_FORMAT_YUY2, + DXGI_FORMAT_Y210, + DXGI_FORMAT_Y216, + DXGI_FORMAT_NV11, + DXGI_FORMAT_AI44, + DXGI_FORMAT_IA44, + DXGI_FORMAT_P8, + DXGI_FORMAT_A8P8, + DXGI_FORMAT_B4G4R4A4_UNORM, + DXGI_FORMAT_P208, + DXGI_FORMAT_V208, + DXGI_FORMAT_V408, + DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE, + DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE, + DXGI_FORMAT_FORCE_UINT +}; + +enum DDSFlags : u32 { + DDSD_CAPS = 0x1, + DDSD_HEIGHT = 0x2, + DDSD_WIDTH = 0x4, + DDSD_PITCH = 0x8, + DDSD_PIXELFORMAT = 0x1000, + DDSD_MIPMAPCOUNT = 0x20000, + DDSD_LINEARSIZE = 0x80000, + DDSD_DEPTH = 0x800000, +}; + +enum PixelFormatFlags : u32 { + DDPF_ALPHAPIXELS = 0x1, + DDPF_ALPHA = 0x2, + DDPF_FOURCC = 0x4, + DDPF_PALETTEINDEXED8 = 0x20, + DDPF_RGB = 0x40, + DDPF_YUV = 0x200, + DDPF_LUMINANCE = 0x20000, + DDPF_BUMPDUDV = 0x80000, + DDPF_NORMAL = 0x80000000, +}; + +struct DDSPixelFormat { + u32 size {}; + u32 flags {}; + u32 four_cc {}; + u32 rgb_bit_count {}; + u32 r_bit_mask {}; + u32 g_bit_mask {}; + u32 b_bit_mask {}; + u32 a_bit_mask {}; +}; + +struct DDSHeader { + u32 size {}; + u32 flags {}; + u32 height {}; + u32 width {}; + u32 pitch {}; + u32 depth {}; + u32 mip_map_count {}; + u32 reserved[11]; + DDSPixelFormat pixel_format; + u32 caps1 {}; + u32 caps2 {}; + u32 caps3 {}; + u32 caps4 {}; + u32 reserved2 {}; +}; + +struct DDSHeaderDXT10 { + DXGIFormat format {}; + u32 resource_dimension {}; + u32 misc_flag {}; + u32 array_size {}; + u32 misc_flag2 {}; +}; + +RefPtr<Gfx::Bitmap> load_dds(String const& path); +RefPtr<Gfx::Bitmap> load_dds_from_memory(const u8*, size_t); + +struct DDSLoadingContext; + +class DDSImageDecoderPlugin final : public ImageDecoderPlugin { +public: + virtual ~DDSImageDecoderPlugin() override; + DDSImageDecoderPlugin(const u8*, size_t); + + virtual IntSize size() override; + virtual RefPtr<Gfx::Bitmap> bitmap() override; + virtual void set_volatile() override; + [[nodiscard]] virtual bool set_nonvolatile() override; + virtual bool sniff() override; + virtual bool is_animated() override; + virtual size_t loop_count() override; + virtual size_t frame_count() override; + virtual ImageFrameDescriptor frame(size_t i) override; + +private: + OwnPtr<DDSLoadingContext> m_context; + void dump_debug(); +}; + +} diff --git a/Userland/Libraries/LibGfx/ImageDecoder.cpp b/Userland/Libraries/LibGfx/ImageDecoder.cpp index 30e21a07c2..3e0690189c 100644 --- a/Userland/Libraries/LibGfx/ImageDecoder.cpp +++ b/Userland/Libraries/LibGfx/ImageDecoder.cpp @@ -5,6 +5,7 @@ */ #include <LibGfx/BMPLoader.h> +#include <LibGfx/DDSLoader.h> #include <LibGfx/GIFLoader.h> #include <LibGfx/ICOLoader.h> #include <LibGfx/ImageDecoder.h> @@ -50,6 +51,10 @@ ImageDecoder::ImageDecoder(const u8* data, size_t size) if (m_plugin->sniff()) return; + m_plugin = make<DDSImageDecoderPlugin>(data, size); + if (m_plugin->sniff()) + return; + m_plugin = nullptr; } |