123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053 |
- /*
- * 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::try_create(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;
- return load_dds_from_memory((u8 const*)file_or_error.value()->data(), file_or_error.value()->size(), LexicalPath::canonicalized_path(path));
- }
- RefPtr<Gfx::Bitmap> load_dds_from_memory(u8 const* data, size_t length, String const& mmap_name)
- {
- auto bitmap = load_dds_impl(data, length);
- if (bitmap)
- bitmap->set_mmap_name(String::formatted("Gfx::Bitmap [{}] - Decoded DDS: {}", bitmap->size(), mmap_name));
- 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(bool& was_purged)
- {
- if (!m_context->bitmap)
- return false;
- return m_context->bitmap->set_nonvolatile(was_purged);
- }
- 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(size_t i)
- {
- if (i > 0)
- return {};
- return { bitmap(), 0 };
- }
- }
|