diff --git a/.kde-ci.yml b/.kde-ci.yml index 021ac72..baca634 100644 --- a/.kde-ci.yml +++ b/.kde-ci.yml @@ -7,4 +7,4 @@ Dependencies: Options: test-before-installing: True require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ] - cmake-options: "-DKIMAGEFORMATS_JXR=ON" + cmake-options: "-DKIMAGEFORMATS_DDS=ON -DKIMAGEFORMATS_JXR=ON" diff --git a/CMakeLists.txt b/CMakeLists.txt index ebdc0d6..a92c0a6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,9 @@ set_package_properties(libavif PROPERTIES PURPOSE "Required for the QImage plugin for AVIF images" ) +# DDS plugin disabled by default due to security issues +option(KIMAGEFORMATS_DDS "Enable plugin for DDS format" OFF) + option(KIMAGEFORMATS_HEIF "Enable plugin for HEIF format" OFF) if(KIMAGEFORMATS_HEIF) pkg_check_modules(LibHeif IMPORTED_TARGET libheif>=1.10.0) @@ -82,6 +85,7 @@ set_package_properties(LibRaw PROPERTIES PURPOSE "Required for the QImage plugin for RAW images" ) +# JXR plugin disabled by default due to security issues option(KIMAGEFORMATS_JXR "Enable plugin for JPEG XR format" OFF) if(KIMAGEFORMATS_JXR) find_package(LibJXR) diff --git a/README.md b/README.md index 552e72d..8c7ab64 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The following image formats have read-only support: The following image formats have read and write support: - AV1 Image File Format (avif) +- DirectDraw Surface (dds) - Encapsulated PostScript (eps) - High Efficiency Image File Format (heif) - JPEG XL (jxl) @@ -51,6 +52,8 @@ better to submit the plugin directly to the Qt Project. ## Duplicated Plugins +### The TGA plugin + The TGA plugin supports more formats than Qt's own TGA plugin; specifically, the one provided here supports indexed, greyscale and RLE images (types 1-3 and 9-11), while Qt's plugin only supports type 2 @@ -61,6 +64,11 @@ licensing. If anyone were willing to write fresh code to improve Qt's TGA plugin, it would allow the TGA plugin in this framework to be removed. +### The DDS plugin + +The DDS plugin is a fork from Qt 5.6. It will be activated once the +security issues are resolved. + ## License This framework is licensed under the @@ -114,6 +122,7 @@ plugin ('n/a' means no limit, i.e. the limit depends on the format encoding). - ANI: n/a - AVIF: 32,768 x 32,768 pixels, in any case no larger than 256 megapixels +- DDS: n/a - EXR: 300,000 x 300,000 pixels - HDR: n/a (large image) - HEIF: n/a @@ -162,6 +171,11 @@ without using the ICC profile. JXR, PSD and SCT plugins natively support 4-channel CMYK images when compiled with Qt 6.8+. +### The DDS plugin + +**This plugin is disabled by default. It can be enabled with the +`KIMAGEFORMATS_DDS` build option in the cmake file.** + ### The HEIF plugin **This plugin is disabled by default. It can be enabled with the diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index d186d15..45043cd 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -76,6 +76,11 @@ kimageformats_read_tests( tga ) +if(KIMAGEFORMATS_DDS) + kimageformats_read_tests(dds) + kimageformats_write_tests(dds) +endif() + if (KF6Archive_FOUND) kimageformats_read_tests( kra diff --git a/autotests/read/dds/indexed.dds b/autotests/read/dds/indexed.dds new file mode 100644 index 0000000..0c5dd97 Binary files /dev/null and b/autotests/read/dds/indexed.dds differ diff --git a/autotests/read/dds/indexed.png b/autotests/read/dds/indexed.png new file mode 100644 index 0000000..6ff4b00 Binary files /dev/null and b/autotests/read/dds/indexed.png differ diff --git a/autotests/read/dds/rgba.dds b/autotests/read/dds/rgba.dds new file mode 100644 index 0000000..921ee25 Binary files /dev/null and b/autotests/read/dds/rgba.dds differ diff --git a/autotests/read/dds/rgba.png b/autotests/read/dds/rgba.png new file mode 100644 index 0000000..3cc7ea6 Binary files /dev/null and b/autotests/read/dds/rgba.png differ diff --git a/autotests/read/dds/rgba102.dds b/autotests/read/dds/rgba102.dds new file mode 100644 index 0000000..9ab2c2b Binary files /dev/null and b/autotests/read/dds/rgba102.dds differ diff --git a/autotests/read/dds/rgba102.png b/autotests/read/dds/rgba102.png new file mode 100644 index 0000000..491d237 Binary files /dev/null and b/autotests/read/dds/rgba102.png differ diff --git a/autotests/read/dds/rgba4.dds b/autotests/read/dds/rgba4.dds new file mode 100644 index 0000000..524c133 Binary files /dev/null and b/autotests/read/dds/rgba4.dds differ diff --git a/autotests/read/dds/rgba4.png b/autotests/read/dds/rgba4.png new file mode 100644 index 0000000..cafbfe0 Binary files /dev/null and b/autotests/read/dds/rgba4.png differ diff --git a/autotests/read/dds/rgba5551.dds b/autotests/read/dds/rgba5551.dds new file mode 100644 index 0000000..9ecca47 Binary files /dev/null and b/autotests/read/dds/rgba5551.dds differ diff --git a/autotests/read/dds/rgba5551.png b/autotests/read/dds/rgba5551.png new file mode 100644 index 0000000..619793c Binary files /dev/null and b/autotests/read/dds/rgba5551.png differ diff --git a/autotests/read/dds/rgba_abgr.dds b/autotests/read/dds/rgba_abgr.dds new file mode 100644 index 0000000..98d4a7f Binary files /dev/null and b/autotests/read/dds/rgba_abgr.dds differ diff --git a/autotests/read/dds/rgba_abgr.png b/autotests/read/dds/rgba_abgr.png new file mode 100644 index 0000000..ac7c02e Binary files /dev/null and b/autotests/read/dds/rgba_abgr.png differ diff --git a/autotests/read/dds/rgba_bc1.dds b/autotests/read/dds/rgba_bc1.dds new file mode 100644 index 0000000..8327ca4 Binary files /dev/null and b/autotests/read/dds/rgba_bc1.dds differ diff --git a/autotests/read/dds/rgba_bc1.png b/autotests/read/dds/rgba_bc1.png new file mode 100644 index 0000000..bd32155 Binary files /dev/null and b/autotests/read/dds/rgba_bc1.png differ diff --git a/autotests/read/dds/rgba_bc2.dds b/autotests/read/dds/rgba_bc2.dds new file mode 100644 index 0000000..c88d321 Binary files /dev/null and b/autotests/read/dds/rgba_bc2.dds differ diff --git a/autotests/read/dds/rgba_bc2.png b/autotests/read/dds/rgba_bc2.png new file mode 100644 index 0000000..86302fa Binary files /dev/null and b/autotests/read/dds/rgba_bc2.png differ diff --git a/autotests/read/dds/rgba_bc3.dds b/autotests/read/dds/rgba_bc3.dds new file mode 100644 index 0000000..ae55f5e Binary files /dev/null and b/autotests/read/dds/rgba_bc3.dds differ diff --git a/autotests/read/dds/rgba_bc3.png b/autotests/read/dds/rgba_bc3.png new file mode 100644 index 0000000..9b34f21 Binary files /dev/null and b/autotests/read/dds/rgba_bc3.png differ diff --git a/autotests/read/dds/rgba_f16.dds b/autotests/read/dds/rgba_f16.dds new file mode 100644 index 0000000..f00c955 Binary files /dev/null and b/autotests/read/dds/rgba_f16.dds differ diff --git a/autotests/read/dds/rgba_f16.png b/autotests/read/dds/rgba_f16.png new file mode 100644 index 0000000..5773bb4 Binary files /dev/null and b/autotests/read/dds/rgba_f16.png differ diff --git a/autotests/read/dds/rgba_f32.dds b/autotests/read/dds/rgba_f32.dds new file mode 100644 index 0000000..b71e320 Binary files /dev/null and b/autotests/read/dds/rgba_f32.dds differ diff --git a/autotests/read/dds/rgba_f32.png b/autotests/read/dds/rgba_f32.png new file mode 100644 index 0000000..c6b1502 Binary files /dev/null and b/autotests/read/dds/rgba_f32.png differ diff --git a/autotests/write/basic/rgb.dds b/autotests/write/basic/rgb.dds new file mode 100644 index 0000000..dd346b5 Binary files /dev/null and b/autotests/write/basic/rgb.dds differ diff --git a/autotests/write/basic/rgba.dds b/autotests/write/basic/rgba.dds new file mode 100644 index 0000000..5c01962 Binary files /dev/null and b/autotests/write/basic/rgba.dds differ diff --git a/autotests/write/format/dds/Format_A2BGR30_Premultiplied.dds b/autotests/write/format/dds/Format_A2BGR30_Premultiplied.dds new file mode 100644 index 0000000..4d89528 Binary files /dev/null and b/autotests/write/format/dds/Format_A2BGR30_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_A2RGB30_Premultiplied.dds b/autotests/write/format/dds/Format_A2RGB30_Premultiplied.dds new file mode 100644 index 0000000..4d89528 Binary files /dev/null and b/autotests/write/format/dds/Format_A2RGB30_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_ARGB32.dds b/autotests/write/format/dds/Format_ARGB32.dds new file mode 100644 index 0000000..e997898 Binary files /dev/null and b/autotests/write/format/dds/Format_ARGB32.dds differ diff --git a/autotests/write/format/dds/Format_ARGB32_Premultiplied.dds b/autotests/write/format/dds/Format_ARGB32_Premultiplied.dds new file mode 100644 index 0000000..f3965b8 Binary files /dev/null and b/autotests/write/format/dds/Format_ARGB32_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_ARGB4444_Premultiplied.dds b/autotests/write/format/dds/Format_ARGB4444_Premultiplied.dds new file mode 100644 index 0000000..8cff7ad Binary files /dev/null and b/autotests/write/format/dds/Format_ARGB4444_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_ARGB6666_Premultiplied.dds b/autotests/write/format/dds/Format_ARGB6666_Premultiplied.dds new file mode 100644 index 0000000..b0d8a5e Binary files /dev/null and b/autotests/write/format/dds/Format_ARGB6666_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_ARGB8555_Premultiplied.dds b/autotests/write/format/dds/Format_ARGB8555_Premultiplied.dds new file mode 100644 index 0000000..54546d7 Binary files /dev/null and b/autotests/write/format/dds/Format_ARGB8555_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_ARGB8565_Premultiplied.dds b/autotests/write/format/dds/Format_ARGB8565_Premultiplied.dds new file mode 100644 index 0000000..9e3f45c Binary files /dev/null and b/autotests/write/format/dds/Format_ARGB8565_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_BGR30.dds b/autotests/write/format/dds/Format_BGR30.dds new file mode 100644 index 0000000..091736a Binary files /dev/null and b/autotests/write/format/dds/Format_BGR30.dds differ diff --git a/autotests/write/format/dds/Format_BGR888.dds b/autotests/write/format/dds/Format_BGR888.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_BGR888.dds differ diff --git a/autotests/write/format/dds/Format_CMYK8888.dds b/autotests/write/format/dds/Format_CMYK8888.dds new file mode 100644 index 0000000..63f9cdf Binary files /dev/null and b/autotests/write/format/dds/Format_CMYK8888.dds differ diff --git a/autotests/write/format/dds/Format_Grayscale16.dds b/autotests/write/format/dds/Format_Grayscale16.dds new file mode 100644 index 0000000..0f4aab4 Binary files /dev/null and b/autotests/write/format/dds/Format_Grayscale16.dds differ diff --git a/autotests/write/format/dds/Format_Grayscale8.dds b/autotests/write/format/dds/Format_Grayscale8.dds new file mode 100644 index 0000000..0f4aab4 Binary files /dev/null and b/autotests/write/format/dds/Format_Grayscale8.dds differ diff --git a/autotests/write/format/dds/Format_Indexed8.dds b/autotests/write/format/dds/Format_Indexed8.dds new file mode 100644 index 0000000..52352ae Binary files /dev/null and b/autotests/write/format/dds/Format_Indexed8.dds differ diff --git a/autotests/write/format/dds/Format_Mono.dds b/autotests/write/format/dds/Format_Mono.dds new file mode 100644 index 0000000..20080ee Binary files /dev/null and b/autotests/write/format/dds/Format_Mono.dds differ diff --git a/autotests/write/format/dds/Format_MonoLSB.dds b/autotests/write/format/dds/Format_MonoLSB.dds new file mode 100644 index 0000000..20080ee Binary files /dev/null and b/autotests/write/format/dds/Format_MonoLSB.dds differ diff --git a/autotests/write/format/dds/Format_RGB16.dds b/autotests/write/format/dds/Format_RGB16.dds new file mode 100644 index 0000000..9bd4162 Binary files /dev/null and b/autotests/write/format/dds/Format_RGB16.dds differ diff --git a/autotests/write/format/dds/Format_RGB30.dds b/autotests/write/format/dds/Format_RGB30.dds new file mode 100644 index 0000000..091736a Binary files /dev/null and b/autotests/write/format/dds/Format_RGB30.dds differ diff --git a/autotests/write/format/dds/Format_RGB32.dds b/autotests/write/format/dds/Format_RGB32.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_RGB32.dds differ diff --git a/autotests/write/format/dds/Format_RGB444.dds b/autotests/write/format/dds/Format_RGB444.dds new file mode 100644 index 0000000..395c35d Binary files /dev/null and b/autotests/write/format/dds/Format_RGB444.dds differ diff --git a/autotests/write/format/dds/Format_RGB555.dds b/autotests/write/format/dds/Format_RGB555.dds new file mode 100644 index 0000000..d5a5988 Binary files /dev/null and b/autotests/write/format/dds/Format_RGB555.dds differ diff --git a/autotests/write/format/dds/Format_RGB666.dds b/autotests/write/format/dds/Format_RGB666.dds new file mode 100644 index 0000000..199c114 Binary files /dev/null and b/autotests/write/format/dds/Format_RGB666.dds differ diff --git a/autotests/write/format/dds/Format_RGB888.dds b/autotests/write/format/dds/Format_RGB888.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_RGB888.dds differ diff --git a/autotests/write/format/dds/Format_RGBA16FPx4.dds b/autotests/write/format/dds/Format_RGBA16FPx4.dds new file mode 100644 index 0000000..cd2e759 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA16FPx4.dds differ diff --git a/autotests/write/format/dds/Format_RGBA16FPx4_Premultiplied.dds b/autotests/write/format/dds/Format_RGBA16FPx4_Premultiplied.dds new file mode 100644 index 0000000..3b98133 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA16FPx4_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_RGBA32FPx4.dds b/autotests/write/format/dds/Format_RGBA32FPx4.dds new file mode 100644 index 0000000..cd2e759 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA32FPx4.dds differ diff --git a/autotests/write/format/dds/Format_RGBA32FPx4_Premultiplied.dds b/autotests/write/format/dds/Format_RGBA32FPx4_Premultiplied.dds new file mode 100644 index 0000000..cd2e759 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA32FPx4_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_RGBA64.dds b/autotests/write/format/dds/Format_RGBA64.dds new file mode 100644 index 0000000..e997898 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA64.dds differ diff --git a/autotests/write/format/dds/Format_RGBA64_Premultiplied.dds b/autotests/write/format/dds/Format_RGBA64_Premultiplied.dds new file mode 100644 index 0000000..cd2e759 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA64_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_RGBA8888.dds b/autotests/write/format/dds/Format_RGBA8888.dds new file mode 100644 index 0000000..e997898 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA8888.dds differ diff --git a/autotests/write/format/dds/Format_RGBA8888_Premultiplied.dds b/autotests/write/format/dds/Format_RGBA8888_Premultiplied.dds new file mode 100644 index 0000000..f3965b8 Binary files /dev/null and b/autotests/write/format/dds/Format_RGBA8888_Premultiplied.dds differ diff --git a/autotests/write/format/dds/Format_RGBX16FPx4.dds b/autotests/write/format/dds/Format_RGBX16FPx4.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_RGBX16FPx4.dds differ diff --git a/autotests/write/format/dds/Format_RGBX32FPx4.dds b/autotests/write/format/dds/Format_RGBX32FPx4.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_RGBX32FPx4.dds differ diff --git a/autotests/write/format/dds/Format_RGBX64.dds b/autotests/write/format/dds/Format_RGBX64.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_RGBX64.dds differ diff --git a/autotests/write/format/dds/Format_RGBX8888.dds b/autotests/write/format/dds/Format_RGBX8888.dds new file mode 100644 index 0000000..31a3e0d Binary files /dev/null and b/autotests/write/format/dds/Format_RGBX8888.dds differ diff --git a/src/imageformats/CMakeLists.txt b/src/imageformats/CMakeLists.txt index 43a6a47..e280cb9 100644 --- a/src/imageformats/CMakeLists.txt +++ b/src/imageformats/CMakeLists.txt @@ -31,6 +31,12 @@ endif() ################################## +if(KIMAGEFORMATS_DDS) + kimageformats_add_plugin(kimg_dds SOURCES dds.cpp) +endif() + +################################## + if (BUILD_EPS_PLUGIN) if (TARGET Qt6::PrintSupport) kimageformats_add_plugin(kimg_eps SOURCES eps.cpp) diff --git a/src/imageformats/dds.cpp b/src/imageformats/dds.cpp new file mode 100644 index 0000000..9cdffa5 --- /dev/null +++ b/src/imageformats/dds.cpp @@ -0,0 +1,1933 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2015 The Qt Company Ltd + SPDX-FileCopyrightText: 2013 Ivan Komissarov + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only +*/ + +// Forked from Qt 5.6 branch + +#include "dds_p.h" +#include "util_p.h" + +#include +#include + +#include + +enum Format { + FormatUnknown = 0, + + FormatR8G8B8 = 20, + FormatA8R8G8B8 = 21, + FormatX8R8G8B8 = 22, + FormatR5G6B5 = 23, + FormatX1R5G5B5 = 24, + FormatA1R5G5B5 = 25, + FormatA4R4G4B4 = 26, + FormatR3G3B2 = 27, + FormatA8 = 28, + FormatA8R3G3B2 = 29, + FormatX4R4G4B4 = 30, + FormatA2B10G10R10 = 31, + FormatA8B8G8R8 = 32, + FormatX8B8G8R8 = 33, + FormatG16R16 = 34, + FormatA2R10G10B10 = 35, + FormatA16B16G16R16 = 36, + + FormatA8P8 = 40, + FormatP8 = 41, + + FormatL8 = 50, + FormatA8L8 = 51, + FormatA4L4 = 52, + + FormatV8U8 = 60, + FormatL6V5U5 = 61, + FormatX8L8V8U8 = 62, + FormatQ8W8V8U8 = 63, + FormatV16U16 = 64, + FormatA2W10V10U10 = 67, + + FormatUYVY = 0x59565955, // "UYVY" + FormatR8G8B8G8 = 0x47424752, // "RGBG" + FormatYUY2 = 0x32595559, // "YUY2" + FormatG8R8G8B8 = 0x42475247, // "GRGB" + FormatDXT1 = 0x31545844, // "DXT1" + FormatDXT2 = 0x32545844, // "DXT2" + FormatDXT3 = 0x33545844, // "DXT3" + FormatDXT4 = 0x34545844, // "DXT4" + FormatDXT5 = 0x35545844, // "DXT5" + FormatRXGB = 0x42475852, // "RXGB" + FormatATI2 = 0x32495441, // "ATI2" + + FormatD16Lockable = 70, + FormatD32 = 71, + FormatD15S1 = 73, + FormatD24S8 = 75, + FormatD24X8 = 77, + FormatD24X4S4 = 79, + FormatD16 = 80, + + FormatD32FLockable = 82, + FormatD24FS8 = 83, + + FormatD32Lockable = 84, + FormatS8Lockable = 85, + + FormatL16 = 81, + + FormatVertexData =100, + FormatIndex16 =101, + FormatIndex32 =102, + + FormatQ16W16V16U16 = 110, + + FormatMulti2ARGB8 = 0x3154454d, // "MET1" + + FormatR16F = 111, + FormatG16R16F = 112, + FormatA16B16G16R16F = 113, + + FormatR32F = 114, + FormatG32R32F = 115, + FormatA32B32G32R32F = 116, + + FormatCxV8U8 = 117, + + FormatA1 = 118, + FormatA2B10G10R10_XR_BIAS = 119, + FormatBinaryBuffer = 199, + + FormatP4, + FormatA4P4, + + FormatLast = 0x7fffffff +}; + +enum Colors { + Red = 0, + Green, + Blue, + Alpha, + ColorCount +}; + +enum DXTVersions { + One = 1, + Two = 2, + Three = 3, + Four = 4, + Five = 5, + RXGB = 6 +}; + +// All magic numbers are little-endian as long as dds format has little +// endian byte order +static const quint32 ddsMagic = 0x20534444; // "DDS " +static const quint32 dx10Magic = 0x30315844; // "DX10" + +static const qint64 headerSize = 128; +static const quint32 ddsSize = 124; // headerSize without magic +static const quint32 pixelFormatSize = 32; + +struct FaceOffset +{ + int x, y; +}; + +static const FaceOffset faceOffsets[6] = { {2, 1}, {0, 1}, {1, 0}, {1, 2}, {1, 1}, {3, 1} }; + +static int faceFlags[6] = { + DDSHeader::Caps2CubeMapPositiveX, + DDSHeader::Caps2CubeMapNegativeX, + DDSHeader::Caps2CubeMapPositiveY, + DDSHeader::Caps2CubeMapNegativeY, + DDSHeader::Caps2CubeMapPositiveZ, + DDSHeader::Caps2CubeMapNegativeZ +}; + +struct FormatInfo +{ + Format format; + quint32 flags; + quint32 bitCount; + quint32 rBitMask; + quint32 gBitMask; + quint32 bBitMask; + quint32 aBitMask; +}; + +static const FormatInfo formatInfos[] = { + { FormatA8R8G8B8, DDSPixelFormat::FlagRGBA, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }, + { FormatX8R8G8B8, DDSPixelFormat::FlagRGB, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }, + { FormatA2B10G10R10, DDSPixelFormat::FlagRGBA, 32, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000 }, + { FormatA8B8G8R8, DDSPixelFormat::FlagRGBA, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }, + { FormatX8B8G8R8, DDSPixelFormat::FlagRGB, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000 }, + { FormatG16R16, DDSPixelFormat::FlagRGBA, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }, + { FormatG16R16, DDSPixelFormat::FlagRGB, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }, + { FormatA2R10G10B10, DDSPixelFormat::FlagRGBA, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000 }, + + { FormatR8G8B8, DDSPixelFormat::FlagRGB, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }, + + { FormatR5G6B5, DDSPixelFormat::FlagRGB, 16, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000 }, + { FormatX1R5G5B5, DDSPixelFormat::FlagRGB, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00000000 }, + { FormatA1R5G5B5, DDSPixelFormat::FlagRGBA, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000 }, + { FormatA4R4G4B4, DDSPixelFormat::FlagRGBA, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000 }, + { FormatA8R3G3B2, DDSPixelFormat::FlagRGBA, 16, 0x000000e0, 0x0000001c, 0x00000003, 0x0000ff00 }, + { FormatX4R4G4B4, DDSPixelFormat::FlagRGB, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x00000000 }, + { FormatA8L8, DDSPixelFormat::FlagLA, 16, 0x000000ff, 0x00000000, 0x00000000, 0x0000ff00 }, + { FormatL16, DDSPixelFormat::FlagLuminance, 16, 0x0000ffff, 0x00000000, 0x00000000, 0x00000000 }, + + { FormatR3G3B2, DDSPixelFormat::FlagRGB, 8, 0x000000e0, 0x0000001c, 0x00000003, 0x00000000 }, + { FormatA8, DDSPixelFormat::FlagAlpha, 8, 0x00000000, 0x00000000, 0x00000000, 0x000000ff }, + { FormatL8, DDSPixelFormat::FlagLuminance, 8, 0x000000ff, 0x00000000, 0x00000000, 0x00000000 }, + { FormatA4L4, DDSPixelFormat::FlagLA, 8, 0x0000000f, 0x00000000, 0x00000000, 0x000000f0 }, + + { FormatV8U8, DDSPixelFormat::FlagNormal, 16, 0x000000ff, 0x0000ff00, 0x00000000, 0x00000000 }, + { FormatL6V5U5, 0, 16, 0x0000001f, 0x000003e0, 0x0000fc00, 0x00000000 }, + { FormatX8L8V8U8, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000 }, + { FormatQ8W8V8U8, DDSPixelFormat::FlagNormal, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }, + { FormatV16U16, DDSPixelFormat::FlagNormal, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }, + { FormatA2W10V10U10, DDSPixelFormat::FlagNormal, 32, 0x3ff00000, 0x000ffc00, 0x000003ff, 0xc0000000 } +}; +static const size_t formatInfosSize = sizeof(formatInfos)/sizeof(FormatInfo); + +static const Format knownFourCCs[] = { + FormatA16B16G16R16, + FormatV8U8, + FormatUYVY, + FormatR8G8B8G8, + FormatYUY2, + FormatG8R8G8B8, + FormatDXT1, + FormatDXT2, + FormatDXT3, + FormatDXT4, + FormatDXT5, + FormatRXGB, + FormatATI2, + FormatQ16W16V16U16, + FormatR16F, + FormatG16R16F, + FormatA16B16G16R16F, + FormatR32F, + FormatG32R32F, + FormatA32B32G32R32F, + FormatCxV8U8 +}; +static const size_t knownFourCCsSize = sizeof(knownFourCCs)/sizeof(Format); + +struct FormatName +{ + Format format; + const char *const name; +}; +static const FormatName formatNames[] = { + { FormatUnknown, "unknown" }, + + { FormatR8G8B8, "R8G8B8" }, + { FormatA8R8G8B8, "A8R8G8B8" }, + { FormatX8R8G8B8, "X8R8G8B8" }, + { FormatR5G6B5, "R5G6B5" }, + { FormatX1R5G5B5, "X1R5G5B5" }, + { FormatA1R5G5B5, "A1R5G5B5" }, + { FormatA4R4G4B4, "A4R4G4B4" }, + { FormatR3G3B2, "R3G3B2" }, + { FormatA8, "A8" }, + { FormatA8R3G3B2, "A8R3G3B2" }, + { FormatX4R4G4B4, "X4R4G4B4" }, + { FormatA2B10G10R10, "A2B10G10R10" }, + { FormatA8B8G8R8, "A8B8G8R8" }, + { FormatX8B8G8R8, "X8B8G8R8" }, + { FormatG16R16, "G16R16" }, + { FormatA2R10G10B10, "A2R10G10B10" }, + { FormatA16B16G16R16, "A16B16G16R16" }, + + { FormatA8P8, "A8P8" }, + { FormatP8, "P8" }, + + { FormatL8, "L8" }, + { FormatA8L8, "A8L8" }, + { FormatA4L4, "A4L4" }, + + { FormatV8U8, "V8U8" }, + { FormatL6V5U5, "L6V5U5" }, + { FormatX8L8V8U8, "X8L8V8U8" }, + { FormatQ8W8V8U8, "Q8W8V8U8" }, + { FormatV16U16, "V16U16" }, + { FormatA2W10V10U10, "A2W10V10U10" }, + + { FormatUYVY, "UYVY" }, + { FormatR8G8B8G8, "R8G8_B8G8" }, + { FormatYUY2, "YUY2" }, + { FormatG8R8G8B8, "G8R8_G8B8" }, + { FormatDXT1, "DXT1" }, + { FormatDXT2, "DXT2" }, + { FormatDXT3, "DXT3" }, + { FormatDXT4, "DXT4" }, + { FormatDXT5, "DXT5" }, + { FormatRXGB, "RXGB" }, + { FormatATI2, "ATI2" }, + + { FormatD16Lockable, "D16Lockable" }, + { FormatD32, "D32" }, + { FormatD15S1, "D15S1" }, + { FormatD24S8, "D24S8" }, + { FormatD24X8, "D24X8" }, + { FormatD24X4S4, "D24X4S4" }, + { FormatD16, "D16" }, + + { FormatD32FLockable, "D32FLockable" }, + { FormatD24FS8, "D24FS8" }, + + { FormatD32Lockable, "D32Lockable" }, + { FormatS8Lockable, "S8Lockable" }, + + { FormatL16, "L16" }, + + { FormatVertexData, "VertexData" }, + { FormatIndex32, "Index32" }, + { FormatIndex32, "Index32" }, + + { FormatQ16W16V16U16, "Q16W16V16U16" }, + + { FormatMulti2ARGB8, "Multi2ARGB8" }, + + { FormatR16F, "R16F" }, + { FormatG16R16F, "G16R16F" }, + { FormatA16B16G16R16F, "A16B16G16R16F" }, + + { FormatR32F, "R32F" }, + { FormatG32R32F, "G32R32F" }, + { FormatA32B32G32R32F, "A32B32G32R32F" }, + + { FormatCxV8U8, "CxV8U8" }, + + { FormatA1, "A1" }, + { FormatA2B10G10R10_XR_BIAS, "A2B10G10R10_XR_BIAS" }, + { FormatBinaryBuffer, "BinaryBuffer" }, + + { FormatP4, "P4" }, + { FormatA4P4, "A4P4" } +}; +static const size_t formatNamesSize = sizeof(formatNames)/sizeof(FormatName); + +QDataStream &operator>>(QDataStream &s, DDSPixelFormat &pixelFormat) +{ + s >> pixelFormat.size; + s >> pixelFormat.flags; + s >> pixelFormat.fourCC; + s >> pixelFormat.rgbBitCount; + s >> pixelFormat.rBitMask; + s >> pixelFormat.gBitMask; + s >> pixelFormat.bBitMask; + s >> pixelFormat.aBitMask; + return s; +} + +QDataStream &operator<<(QDataStream &s, const DDSPixelFormat &pixelFormat) +{ + s << pixelFormat.size; + s << pixelFormat.flags; + s << pixelFormat.fourCC; + s << pixelFormat.rgbBitCount; + s << pixelFormat.rBitMask; + s << pixelFormat.gBitMask; + s << pixelFormat.bBitMask; + s << pixelFormat.aBitMask; + return s; +} + +QDataStream &operator>>(QDataStream &s, DDSHeader &header) +{ + s >> header.magic; + s >> header.size; + s >> header.flags; + s >> header.height; + s >> header.width; + s >> header.pitchOrLinearSize; + s >> header.depth; + s >> header.mipMapCount; + for (int i = 0; i < DDSHeader::ReservedCount; i++) + s >> header.reserved1[i]; + s >> header.pixelFormat; + s >> header.caps; + s >> header.caps2; + s >> header.caps3; + s >> header.caps4; + s >> header.reserved2; + return s; +} + +QDataStream &operator<<(QDataStream &s, const DDSHeader &header) +{ + s << header.magic; + s << header.size; + s << header.flags; + s << header.height; + s << header.width; + s << header.pitchOrLinearSize; + s << header.depth; + s << header.mipMapCount; + for (int i = 0; i < DDSHeader::ReservedCount; i++) + s << header.reserved1[i]; + s << header.pixelFormat; + s << header.caps; + s << header.caps2; + s << header.caps3; + s << header.caps4; + s << header.reserved2; + return s; +} + +QDataStream &operator>>(QDataStream &s, DDSHeaderDX10 &header) +{ + s >> header.dxgiFormat; + s >> header.resourceDimension; + s >> header.miscFlag; + s >> header.arraySize; + s >> header.reserved; + return s; +} + +QDataStream &operator<<(QDataStream &s, const DDSHeaderDX10 &header) +{ + s << header.dxgiFormat; + s << header.resourceDimension; + s << header.miscFlag; + s << header.arraySize; + s << header.reserved; + return s; +} + +static inline int maskToShift(quint32 mask) +{ + if (mask == 0) + return 0; + + int result = 0; + while (!((mask >> result) & 1)) + result++; + return result; +} + +static inline int maskLength(quint32 mask) +{ + int result = 0; + while (mask) { + if (mask & 1) + result++; + mask >>= 1; + } + return result; +} + +static inline quint32 readValue(QDataStream &s, quint32 size) +{ + Q_ASSERT(size == 8 || size == 16 || size == 24 || size == 32); + + quint32 value = 0; + quint8 tmp; + for (unsigned bit = 0; bit < size; bit += 8) { + s >> tmp; + value += (quint32(tmp) << bit); + } + return value; +} + +static inline bool hasAlpha(const DDSHeader &dds) +{ + return (dds.pixelFormat.flags & (DDSPixelFormat::FlagAlphaPixels | DDSPixelFormat::FlagAlpha)) != 0; +} + +static inline bool isCubeMap(const DDSHeader &dds) +{ + return (dds.caps2 & DDSHeader::Caps2CubeMap) != 0; +} + +static inline QRgb yuv2rgb(quint8 Y, quint8 U, quint8 V) +{ + return qRgb(quint8(Y + 1.13983 * (V - 128)), + quint8(Y - 0.39465 * (U - 128) - 0.58060 * (V - 128)), + quint8(Y + 2.03211 * (U - 128))); +} + +static Format getFormat(const DDSHeader &dds) +{ + const DDSPixelFormat &format = dds.pixelFormat; + if (format.flags & DDSPixelFormat::FlagPaletteIndexed4) { + return FormatP4; + } else if (format.flags & DDSPixelFormat::FlagPaletteIndexed8) { + return FormatP8; + } else if (format.flags & DDSPixelFormat::FlagFourCC) { + for (size_t i = 0; i < knownFourCCsSize; ++i) { + if (dds.pixelFormat.fourCC == knownFourCCs[i]) + return knownFourCCs[i]; + } + } else { + for (size_t i = 0; i < formatInfosSize; ++i) { + const FormatInfo &info = formatInfos[i]; + if ((format.flags & info.flags) == info.flags && + format.rgbBitCount == info.bitCount && + format.rBitMask == info.rBitMask && + format.gBitMask == info.gBitMask && + format.bBitMask == info.bBitMask && + format.aBitMask == info.aBitMask) { + return info.format; + } + } + } + + return FormatUnknown; +} + +static inline quint8 getNormalZ(quint8 nx, quint8 ny) +{ + const double fx = nx / 127.5 - 1.0; + const double fy = ny / 127.5 - 1.0; + const double fxfy = 1.0 - fx * fx - fy * fy; + return fxfy > 0 ? 255 * std::sqrt(fxfy) : 0; +} + +static inline void decodeColor(quint16 color, quint8 &red, quint8 &green, quint8 &blue) +{ + red = ((color >> 11) & 0x1f) << 3; + green = ((color >> 5) & 0x3f) << 2; + blue = (color & 0x1f) << 3; +} + +static inline quint8 calcC2(quint8 c0, quint8 c1) +{ + return 2.0 * c0 / 3.0 + c1 / 3.0; +} + +static inline quint8 calcC2a(quint8 c0, quint8 c1) +{ + return c0 / 2.0 + c1 / 2.0; +} + +static inline quint8 calcC3(quint8 c0, quint8 c1) +{ + return c0 / 3.0 + 2.0 * c1 / 3.0; +} + +static void DXTFillColors(QRgb *result, quint16 c0, quint16 c1, quint32 table, bool dxt1a = false) +{ + quint8 r[4]; + quint8 g[4]; + quint8 b[4]; + quint8 a[4]; + + a[0] = a[1] = a[2] = a[3] = 255; + + decodeColor(c0, r[0], g[0], b[0]); + decodeColor(c1, r[1], g[1], b[1]); + if (!dxt1a) { + r[2] = calcC2(r[0], r[1]); + g[2] = calcC2(g[0], g[1]); + b[2] = calcC2(b[0], b[1]); + r[3] = calcC3(r[0], r[1]); + g[3] = calcC3(g[0], g[1]); + b[3] = calcC3(b[0], b[1]); + } else { + r[2] = calcC2a(r[0], r[1]); + g[2] = calcC2a(g[0], g[1]); + b[2] = calcC2a(b[0], b[1]); + r[3] = g[3] = b[3] = a[3] = 0; + } + + for (int k = 0; k < 4; k++) + for (int l = 0; l < 4; l++) { + unsigned index = table & 0x0003; + table >>= 2; + + result[k * 4 + l] = qRgba(r[index], g[index], b[index], a[index]); + } +} + +template +inline void setAlphaDXT32Helper(QRgb *rgbArr, quint64 alphas) +{ + Q_STATIC_ASSERT(version == Two || version == Three); + for (int i = 0; i < 16; i++) { + quint8 alpha = 16 * (alphas & 0x0f); + QRgb rgb = rgbArr[i]; + if (version == Two) // DXT2 + rgbArr[i] = qRgba(qRed(rgb) * alpha / 0xff, qGreen(rgb) * alpha / 0xff, qBlue(rgb) * alpha / 0xff, alpha); + else if (version == Three) // DXT3 + rgbArr[i] = qRgba(qRed(rgb), qGreen(rgb), qBlue(rgb), alpha); + alphas = alphas >> 4; + } +} + +template +inline void setAlphaDXT45Helper(QRgb *rgbArr, quint64 alphas) +{ + Q_STATIC_ASSERT(version == Four || version == Five); + quint8 a[8]; + a[0] = alphas & 0xff; + a[1] = (alphas >> 8) & 0xff; + if (a[0] > a[1]) { + a[2] = (6*a[0] + 1*a[1]) / 7; + a[3] = (5*a[0] + 2*a[1]) / 7; + a[4] = (4*a[0] + 3*a[1]) / 7; + a[5] = (3*a[0] + 4*a[1]) / 7; + a[6] = (2*a[0] + 5*a[1]) / 7; + a[7] = (1*a[0] + 6*a[1]) / 7; + } else { + a[2] = (4*a[0] + 1*a[1]) / 5; + a[3] = (3*a[0] + 2*a[1]) / 5; + a[4] = (2*a[0] + 3*a[1]) / 5; + a[5] = (1*a[0] + 4*a[1]) / 5; + a[6] = 0; + a[7] = 255; + } + alphas >>= 16; + for (int i = 0; i < 16; i++) { + quint8 index = alphas & 0x07; + quint8 alpha = a[index]; + QRgb rgb = rgbArr[i]; + if (version == Four) // DXT4 + rgbArr[i] = qRgba(qRed(rgb) * alpha / 0xff, qGreen(rgb) * alpha / 0xff, qBlue(rgb) * alpha / 0xff, alpha); + else if (version == Five) // DXT5 + rgbArr[i] = qRgba(qRed(rgb), qGreen(rgb), qBlue(rgb), alpha); + alphas = alphas >> 3; + } +} + +template +inline void setAlphaDXT(QRgb *rgbArr, quint64 alphas) +{ + Q_UNUSED(rgbArr); + Q_UNUSED(alphas); +} + +template <> +inline void setAlphaDXT(QRgb *rgbArr, quint64 alphas) +{ + setAlphaDXT32Helper(rgbArr, alphas); +} + +template <> +inline void setAlphaDXT(QRgb *rgbArr, quint64 alphas) +{ + setAlphaDXT32Helper(rgbArr, alphas); +} + +template <> +inline void setAlphaDXT(QRgb *rgbArr, quint64 alphas) +{ + setAlphaDXT45Helper(rgbArr, alphas); +} + +template <> +inline void setAlphaDXT(QRgb *rgbArr, quint64 alphas) +{ + setAlphaDXT45Helper(rgbArr, alphas); +} + +template <> +inline void setAlphaDXT(QRgb *rgbArr, quint64 alphas) +{ + setAlphaDXT45Helper(rgbArr, alphas); +} + +static inline QRgb invertRXGBColors(QRgb pixel) +{ + return qRgb(qAlpha(pixel), qGreen(pixel), qBlue(pixel)); +} + +template +static QImage readDXT(QDataStream &s, quint32 width, quint32 height) +{ + QImage::Format format = (version == Two || version == Four) ? + QImage::Format_ARGB32_Premultiplied : QImage::Format_ARGB32; + + QImage image = imageAlloc(width, height, format); + if (image.isNull()) { + return image; + } + + for (quint32 i = 0; i < height; i += 4) { + for (quint32 j = 0; j < width; j += 4) { + quint64 alpha = 0; + quint16 c0, c1; + quint32 table; + if (version != One) + s >> alpha; + s >> c0; + s >> c1; + s >> table; + if (s.status() != QDataStream::Ok) + return QImage(); + + QRgb arr[16]; + + DXTFillColors(arr, c0, c1, table, version == One && c0 <= c1); + setAlphaDXT(arr, alpha); + + const quint32 kMax = qMin(4, height - i); + const quint32 lMax = qMin(4, width - j); + for (quint32 k = 0; k < kMax; k++) { + QRgb *line = reinterpret_cast(image.scanLine(i + k)); + for (quint32 l = 0; l < lMax; l++) { + QRgb pixel = arr[k * 4 + l]; + if (version == RXGB) + pixel = invertRXGBColors(pixel); + + line[j + l] = pixel; + } + } + } + } + return image; +} + +static inline QImage readDXT1(QDataStream &s, quint32 width, quint32 height) +{ + return readDXT(s, width, height); +} + +static inline QImage readDXT2(QDataStream &s, quint32 width, quint32 height) +{ + return readDXT(s, width, height); +} + +static inline QImage readDXT3(QDataStream &s, quint32 width, quint32 height) +{ + return readDXT(s, width, height); +} + +static inline QImage readDXT4(QDataStream &s, quint32 width, quint32 height) +{ + return readDXT(s, width, height); +} + +static inline QImage readDXT5(QDataStream &s, quint32 width, quint32 height) +{ + return readDXT(s, width, height); +} + +static inline QImage readRXGB(QDataStream &s, quint32 width, quint32 height) +{ + return readDXT(s, width, height); +} + +static QImage readATI2(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 i = 0; i < height; i += 4) { + for (quint32 j = 0; j < width; j += 4) { + quint64 alpha1; + quint64 alpha2; + s >> alpha1; + s >> alpha2; + if (s.status() != QDataStream::Ok) + return QImage(); + + QRgb arr[16]; + memset(arr, 0, sizeof(QRgb) * 16); + setAlphaDXT(arr, alpha1); + for (int k = 0; k < 16; ++k) { + quint8 a = qAlpha(arr[k]); + arr[k] = qRgba(0, 0, a, 0); + } + setAlphaDXT(arr, alpha2); + + const quint32 kMax = qMin(4, height - i); + const quint32 lMax = qMin(4, width - j); + for (quint32 k = 0; k < kMax; k++) { + QRgb *line = reinterpret_cast(image.scanLine(i + k)); + for (quint32 l = 0; l < lMax; l++) { + QRgb pixel = arr[k * 4 + l]; + const quint8 nx = qAlpha(pixel); + const quint8 ny = qBlue(pixel); + const quint8 nz = getNormalZ(nx, ny); + line[j + l] = qRgb(nx, ny, nz); + } + } + } + } + return image; +} + +static QImage readUnsignedImage(QDataStream &s, const DDSHeader &dds, quint32 width, quint32 height, bool hasAlpha) +{ + quint32 flags = dds.pixelFormat.flags; + + quint32 masks[ColorCount]; + quint8 shifts[ColorCount]; + quint8 bits[ColorCount]; + masks[Red] = dds.pixelFormat.rBitMask; + masks[Green] = dds.pixelFormat.gBitMask; + masks[Blue] = dds.pixelFormat.bBitMask; + masks[Alpha] = hasAlpha ? dds.pixelFormat.aBitMask : 0; + for (int i = 0; i < ColorCount; ++i) { + shifts[i] = maskToShift(masks[i]); + bits[i] = maskLength(masks[i]); + + // move mask to the left + if (bits[i] <= 8) + masks[i] = (masks[i] >> shifts[i]) << (8 - bits[i]); + } + + const QImage::Format format = hasAlpha ? QImage::Format_ARGB32 : QImage::Format_RGB32; + + QImage image = imageAlloc(width, height, format); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + for (quint32 x = 0; x < width; x++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + + quint32 value = readValue(s, dds.pixelFormat.rgbBitCount); + quint8 colors[ColorCount]; + + for (int c = 0; c < ColorCount; ++c) { + if (bits[c] > 8) { + // truncate unneseccary bits + colors[c] = (value & masks[c]) >> shifts[c] >> (bits[c] - 8); + } else { + // move color to the left + quint8 color = value >> shifts[c] << (8 - bits[c]) & masks[c]; + if (masks[c]) + colors[c] = color * 0xff / masks[c]; + else + colors[c] = 0; + } + } + + if (flags & DDSPixelFormat::FlagLuminance) + line[x] = qRgba(colors[Red], colors[Red], colors[Red], colors[Alpha]); + else if (flags & DDSPixelFormat::FlagYUV) + line[x] = yuv2rgb(colors[Red], colors[Green], colors[Blue]); + else + line[x] = qRgba(colors[Red], colors[Green], colors[Blue], colors[Alpha]); + + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static double readFloat16(QDataStream &s) +{ + quint16 value; + s >> value; + + double sign = (value & 0x8000) == 0x8000 ? -1.0 : 1.0; + qint8 exp = (value & 0x7C00) >> 10; + quint16 fraction = value & 0x3FF; + + if (exp == 0) + return sign * std::pow(2.0, -14.0) * fraction / 1024.0; + else + return sign * std::pow(2.0, exp - 15) * (1 + fraction / 1024.0); +} + +static inline float readFloat32(QDataStream &s) +{ + Q_ASSERT(sizeof(float) == 4); + float value; + // TODO: find better way to avoid setting precision each time + QDataStream::FloatingPointPrecision precision = s.floatingPointPrecision(); + s.setFloatingPointPrecision(QDataStream::SinglePrecision); + s >> value; + s.setFloatingPointPrecision(precision); + return value; +} + +static QImage readR16F(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 r = readFloat16(s) * 255; + line[x] = qRgba(r, 0, 0, 0); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readRG16F(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 r = readFloat16(s) * 255; + quint8 g = readFloat16(s) * 255; + line[x] = qRgba(r, g, 0, 0); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readARGB16F(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 colors[ColorCount]; + for (int c = 0; c < ColorCount; ++c) + colors[c] = readFloat16(s) * 255; + + line[x] = qRgba(colors[Red], colors[Green], colors[Blue], colors[Alpha]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readR32F(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 r = readFloat32(s) * 255; + line[x] = qRgba(r, 0, 0, 0); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readRG32F(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 r = readFloat32(s) * 255; + quint8 g = readFloat32(s) * 255; + line[x] = qRgba(r, g, 0, 0); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readARGB32F(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 colors[ColorCount]; + for (int c = 0; c < ColorCount; ++c) + colors[c] = readFloat32(s) * 255; + line[x] = qRgba(colors[Red], colors[Green], colors[Blue], colors[Alpha]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readQ16W16V16U16(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + quint8 colors[ColorCount]; + qint16 tmp; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + for (int i = 0; i < ColorCount; i++) { + s >> tmp; + colors[i] = (tmp + 0x7FFF) >> 8; + } + line[x] = qRgba(colors[Red], colors[Green], colors[Blue], colors[Alpha]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readCxV8U8(QDataStream &s, const quint32 width, const quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + qint8 v, u; + s >> v >> u; + + const quint8 vn = v + 128; + const quint8 un = u + 128; + const quint8 c = getNormalZ(vn, un); + + line[x] = qRgb(vn, un, c); + + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readPalette8Image(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_Indexed8); + if (image.isNull()) { + return image; + } + + for (int i = 0; i < 256; ++i) { + quint8 r, g, b, a; + s >> r >> g >> b >> a; + image.setColor(i, qRgba(r, g, b, a)); + } + + for (quint32 y = 0; y < height; y++) { + for (quint32 x = 0; x < width; x++) { + quint8 index; + s >> index; + image.setPixel(x, y, index); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readPalette4Image(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_Indexed8); + if (image.isNull()) { + return image; + } + + for (int i = 0; i < 16; ++i) { + quint8 r, g, b, a; + s >> r >> g >> b >> a; + image.setColor(i, qRgba(r, g, b, a)); + } + + for (quint32 y = 0; y < height; y++) { + quint8 index; + for (quint32 x = 0; x < width - 1; ) { + s >> index; + image.setPixel(x++, y, (index & 0x0f) >> 0); + image.setPixel(x++, y, (index & 0xf0) >> 4); + if (s.status() != QDataStream::Ok) + return QImage(); + } + if (width % 2 == 1) { + s >> index; + image.setPixel(width - 1, y, (index & 0x0f) >> 0); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readARGB16(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + quint8 colors[ColorCount]; + for (int i = 0; i < ColorCount; ++i) { + quint16 color; + s >> color; + colors[i] = quint8(color >> 8); + } + line[x] = qRgba(colors[Red], colors[Green], colors[Blue], colors[Alpha]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readV8U8(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + qint8 v, u; + s >> v >> u; + line[x] = qRgb(v + 128, u + 128, 255); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readL6V5U5(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + quint16 tmp; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + s >> tmp; + quint8 r = qint8((tmp & 0x001f) >> 0) * 0xff/0x1f + 128; + quint8 g = qint8((tmp & 0x03e0) >> 5) * 0xff/0x1f + 128; + quint8 b = quint8((tmp & 0xfc00) >> 10) * 0xff/0x3f; + line[x] = qRgba(r, g, 0xff, b); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + return image; +} + +static QImage readX8L8V8U8(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + quint8 a, l; + qint8 v, u; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + s >> v >> u >> a >> l; + line[x] = qRgba(v + 128, u + 128, 255, a); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readQ8W8V8U8(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + quint8 colors[ColorCount]; + qint8 tmp; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + for (int i = 0; i < ColorCount; i++) { + s >> tmp; + colors[i] = tmp + 128; + } + line[x] = qRgba(colors[Red], colors[Green], colors[Blue], colors[Alpha]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readV16U16(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + qint16 v, u; + s >> v >> u; + v = (v + 0x8000) >> 8; + u = (u + 0x8000) >> 8; + line[x] = qRgb(v, u, 255); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readA2W10V10U10(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_ARGB32); + if (image.isNull()) { + return image; + } + + quint32 tmp; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + s >> tmp; + quint8 r = qint8((tmp & 0x3ff00000) >> 20 >> 2) + 128; + quint8 g = qint8((tmp & 0x000ffc00) >> 10 >> 2) + 128; + quint8 b = qint8((tmp & 0x000003ff) >> 0 >> 2) + 128; + quint8 a = 0xff * ((tmp & 0xc0000000) >> 30) / 3; + // dunno why we should swap b and r here + std::swap(b, r); + line[x] = qRgba(r, g, b, a); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readUYVY(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + quint8 uyvy[4]; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width - 1; ) { + s >> uyvy[0] >> uyvy[1] >> uyvy[2] >> uyvy[3]; + line[x++] = yuv2rgb(uyvy[1], uyvy[0], uyvy[2]); + line[x++] = yuv2rgb(uyvy[3], uyvy[0], uyvy[2]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + if (width % 2 == 1) { + s >> uyvy[0] >> uyvy[1] >> uyvy[2] >> uyvy[3]; + line[width - 1] = yuv2rgb(uyvy[1], uyvy[0], uyvy[2]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readR8G8B8G8(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + quint8 rgbg[4]; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width - 1; ) { + s >> rgbg[1] >> rgbg[0] >> rgbg[3] >> rgbg[2]; + line[x++] = qRgb(rgbg[0], rgbg[1], rgbg[2]); + line[x++] = qRgb(rgbg[0], rgbg[3], rgbg[2]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + if (width % 2 == 1) { + s >> rgbg[1] >> rgbg[0] >> rgbg[3] >> rgbg[2]; + line[width - 1] = qRgb(rgbg[0], rgbg[1], rgbg[2]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readYUY2(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + quint8 yuyv[4]; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width - 1; ) { + s >> yuyv[0] >> yuyv[1] >> yuyv[2] >> yuyv[3]; + line[x++] = yuv2rgb(yuyv[0], yuyv[1], yuyv[3]); + line[x++] = yuv2rgb(yuyv[2], yuyv[1], yuyv[3]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + if (width % 2 == 1) { + s >> yuyv[0] >> yuyv[1] >> yuyv[2] >> yuyv[3]; + line[width - 1] = yuv2rgb(yuyv[2], yuyv[1], yuyv[3]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readG8R8G8B8(QDataStream &s, quint32 width, quint32 height) +{ + QImage image = imageAlloc(width, height, QImage::Format_RGB32); + if (image.isNull()) { + return image; + } + + quint8 grgb[4]; + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width - 1; ) { + s >> grgb[1] >> grgb[0] >> grgb[3] >> grgb[2]; + line[x++] = qRgb(grgb[1], grgb[0], grgb[3]); + line[x++] = qRgb(grgb[1], grgb[2], grgb[3]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + if (width % 2 == 1) { + s >> grgb[1] >> grgb[0] >> grgb[3] >> grgb[2]; + line[width - 1] = qRgb(grgb[1], grgb[0], grgb[3]); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + + return image; +} + +static QImage readA2R10G10B10(QDataStream &s, const DDSHeader &dds, quint32 width, quint32 height) +{ + QImage image = readUnsignedImage(s, dds, width, height, true); + if (image.isNull()) { + return image; + } + + for (quint32 y = 0; y < height; y++) { + QRgb *line = reinterpret_cast(image.scanLine(y)); + for (quint32 x = 0; x < width; x++) { + QRgb pixel = image.pixel(x, y); + line[x] = qRgba(qBlue(pixel), qGreen(pixel), qRed(pixel), qAlpha(pixel)); + if (s.status() != QDataStream::Ok) + return QImage(); + } + } + return image; +} + +static QImage readLayer(QDataStream &s, const DDSHeader &dds, const int format, quint32 width, quint32 height) +{ + if (width * height == 0) + return QImage(); + + switch (format) { + case FormatR8G8B8: + case FormatX8R8G8B8: + case FormatR5G6B5: + case FormatR3G3B2: + case FormatX1R5G5B5: + case FormatX4R4G4B4: + case FormatX8B8G8R8: + case FormatG16R16: + case FormatL8: + case FormatL16: + return readUnsignedImage(s, dds, width, height, false); + case FormatA8R8G8B8: + case FormatA1R5G5B5: + case FormatA4R4G4B4: + case FormatA8: + case FormatA8R3G3B2: + case FormatA8B8G8R8: + case FormatA8L8: + case FormatA4L4: + return readUnsignedImage(s, dds, width, height, true); + case FormatA2R10G10B10: + case FormatA2B10G10R10: + return readA2R10G10B10(s, dds, width, height); + case FormatP8: + case FormatA8P8: + return readPalette8Image(s, width, height); + case FormatP4: + case FormatA4P4: + return readPalette4Image(s, width, height); + case FormatA16B16G16R16: + return readARGB16(s, width, height); + case FormatV8U8: + return readV8U8(s, width, height); + case FormatL6V5U5: + return readL6V5U5(s, width, height); + case FormatX8L8V8U8: + return readX8L8V8U8(s, width, height); + case FormatQ8W8V8U8: + return readQ8W8V8U8(s, width, height); + case FormatV16U16: + return readV16U16(s, width, height); + case FormatA2W10V10U10: + return readA2W10V10U10(s, width, height); + case FormatUYVY: + return readUYVY(s, width, height); + case FormatR8G8B8G8: + return readR8G8B8G8(s, width, height); + case FormatYUY2: + return readYUY2(s, width, height); + case FormatG8R8G8B8: + return readG8R8G8B8(s, width, height); + case FormatDXT1: + return readDXT1(s, width, height); + case FormatDXT2: + return readDXT2(s, width, height); + case FormatDXT3: + return readDXT3(s, width, height); + case FormatDXT4: + return readDXT4(s, width, height); + case FormatDXT5: + return readDXT5(s, width, height); + case FormatRXGB: + return readRXGB(s, width, height); + case FormatATI2: + return readATI2(s, width, height); + case FormatR16F: + return readR16F(s, width, height); + case FormatG16R16F: + return readRG16F(s, width, height); + case FormatA16B16G16R16F: + return readARGB16F(s, width, height); + case FormatR32F: + return readR32F(s, width, height); + case FormatG32R32F: + return readRG32F(s, width, height); + case FormatA32B32G32R32F: + return readARGB32F(s, width, height); + case FormatD16Lockable: + case FormatD32: + case FormatD15S1: + case FormatD24S8: + case FormatD24X8: + case FormatD24X4S4: + case FormatD16: + case FormatD32FLockable: + case FormatD24FS8: + case FormatD32Lockable: + case FormatS8Lockable: + case FormatVertexData: + case FormatIndex16: + case FormatIndex32: + break; + case FormatQ16W16V16U16: + return readQ16W16V16U16(s, width, height); + case FormatMulti2ARGB8: + break; + case FormatCxV8U8: + return readCxV8U8(s, width, height); + case FormatA1: + case FormatA2B10G10R10_XR_BIAS: + case FormatBinaryBuffer: + case FormatLast: + break; + } + + return QImage(); +} + +static inline QImage readTexture(QDataStream &s, const DDSHeader &dds, const int format, const int mipmapLevel) +{ + quint32 width = dds.width / (1 << mipmapLevel); + quint32 height = dds.height / (1 << mipmapLevel); + return readLayer(s, dds, format, width, height); +} + +static qint64 mipmapSize(const DDSHeader &dds, const int format, const int level) +{ + quint32 w = dds.width/(1 << level); + quint32 h = dds.height/(1 << level); + + switch (format) { + case FormatR8G8B8: + case FormatX8R8G8B8: + case FormatR5G6B5: + case FormatX1R5G5B5: + case FormatX4R4G4B4: + case FormatX8B8G8R8: + case FormatG16R16: + case FormatL8: + case FormatL16: + return w * h * dds.pixelFormat.rgbBitCount / 8; + case FormatA8R8G8B8: + case FormatA1R5G5B5: + case FormatA4R4G4B4: + case FormatA8: + case FormatA8R3G3B2: + case FormatA2B10G10R10: + case FormatA8B8G8R8: + case FormatA2R10G10B10: + case FormatA8L8: + case FormatA4L4: + return w * h * dds.pixelFormat.rgbBitCount / 8; + case FormatP8: + return 256 + w * h * 8; + case FormatA16B16G16R16: + return w * h * 4 * 2; + case FormatA8P8: + break; + case FormatV8U8: + case FormatL6V5U5: + return w * h * 2; + case FormatX8L8V8U8: + case FormatQ8W8V8U8: + case FormatV16U16: + case FormatA2W10V10U10: + return w * h * 4; + case FormatUYVY: + case FormatR8G8B8G8: + case FormatYUY2: + case FormatG8R8G8B8: + return w * h * 2; + case FormatDXT1: + return ((w + 3)/4) * ((h + 3)/4) * 8; + case FormatDXT2: + case FormatDXT3: + case FormatDXT4: + case FormatDXT5: + return ((w + 3)/4) * ((h + 3)/4) * 16; + case FormatD16Lockable: + case FormatD32: + case FormatD15S1: + case FormatD24S8: + case FormatD24X8: + case FormatD24X4S4: + case FormatD16: + case FormatD32FLockable: + case FormatD24FS8: + case FormatD32Lockable: + case FormatS8Lockable: + case FormatVertexData: + case FormatIndex16: + case FormatIndex32: + break; + case FormatQ16W16V16U16: + return w * h * 4 * 2; + case FormatMulti2ARGB8: + break; + case FormatR16F: + return w * h * 1 * 2; + case FormatG16R16F: + return w * h * 2 * 2; + case FormatA16B16G16R16F: + return w * h * 4 * 2; + case FormatR32F: + return w * h * 1 * 4; + case FormatG32R32F: + return w * h * 2 * 4; + case FormatA32B32G32R32F: + return w * h * 4 * 4; + case FormatCxV8U8: + return w * h * 2; + case FormatA1: + case FormatA2B10G10R10_XR_BIAS: + case FormatBinaryBuffer: + case FormatLast: + break; + } + + return 0; +} + +static qint64 mipmapOffset(const DDSHeader &dds, const int format, const int level) +{ + qint64 result = 0; + for (int i = 0; i < level; ++i) + result += mipmapSize(dds, format, i); + return result; +} + +static QImage readCubeMap(QDataStream &s, const DDSHeader &dds, const int fmt) +{ + QImage::Format format = hasAlpha(dds) ? QImage::Format_ARGB32 : QImage::Format_RGB32; + QImage image = imageAlloc(4 * dds.width, 3 * dds.height, format); + if (image.isNull()) { + return image; + } + + image.fill(0); + + for (int i = 0; i < 6; i++) { + if (!(dds.caps2 & faceFlags[i])) + continue; // Skip face. + + const QImage face = readLayer(s, dds, fmt, dds.width, dds.height); + if (face.isNull()) { + return {}; + } + + // Compute face offsets. + int offset_x = faceOffsets[i].x * dds.width; + int offset_y = faceOffsets[i].y * dds.height; + + // Copy face on the image. + for (quint32 y = 0; y < dds.height; y++) { + const QRgb *src = reinterpret_cast(face.scanLine(y)); + QRgb *dst = reinterpret_cast(image.scanLine(y + offset_y)) + offset_x; + memcpy(dst, src, sizeof(QRgb) * dds.width); + } + } + + return image; +} + +static QByteArray formatName(int format) +{ + for (size_t i = 0; i < formatNamesSize; ++i) { + if (formatNames[i].format == format) + return formatNames[i].name; + } + + return formatNames[0].name; +} + +static int formatByName(const QByteArray &name) +{ + const QByteArray loweredName = name.toLower(); + for (size_t i = 0; i < formatNamesSize; ++i) { + if (QByteArray(formatNames[i].name).toLower() == loweredName) + return formatNames[i].format; + } + + return FormatUnknown; +} + +QDDSHandler::QDDSHandler() : + m_header(), + m_format(FormatA8R8G8B8), + m_header10(), + m_currentImage(0), + m_scanState(ScanNotScanned) +{ +} + +bool QDDSHandler::canRead() const +{ + if (m_scanState == ScanNotScanned && !canRead(device())) + return false; + + if (m_scanState != ScanError) { + setFormat(QByteArrayLiteral("dds")); + return true; + } + + return false; +} + +bool QDDSHandler::read(QImage *outImage) +{ + if (!ensureScanned() || device()->isSequential()) + return false; + + qint64 pos = headerSize + mipmapOffset(m_header, m_format, m_currentImage); + if (!device()->seek(pos)) + return false; + QDataStream s(device()); + s.setByteOrder(QDataStream::LittleEndian); + + QImage image = isCubeMap(m_header) ? + readCubeMap(s, m_header, m_format) : + readTexture(s, m_header, m_format, m_currentImage); + if (image.isNull()) { + return false; + } + + bool ok = s.status() == QDataStream::Ok && !image.isNull(); + if (ok) + *outImage = image; + return ok; +} + +bool QDDSHandler::write(const QImage &outImage) +{ + if (m_format != FormatA8R8G8B8) { + qWarning() << "Format" << formatName(m_format) << "is not supported"; + return false; + } + + const QImage image = outImage.convertToFormat(QImage::Format_ARGB32); + if (image.isNull()) { + return false; + } + + QDataStream s(device()); + s.setByteOrder(QDataStream::LittleEndian); + + DDSHeader dds; + // Filling header + dds.magic = ddsMagic; + dds.size = 124; + dds.flags = DDSHeader::FlagCaps | DDSHeader::FlagHeight | + DDSHeader::FlagWidth | DDSHeader::FlagPixelFormat; + dds.height = image.height(); + dds.width = image.width(); + dds.pitchOrLinearSize = 128; + dds.depth = 0; + dds.mipMapCount = 0; + for (int i = 0; i < DDSHeader::ReservedCount; i++) + dds.reserved1[i] = 0; + dds.caps = DDSHeader::CapsTexture; + dds.caps2 = 0; + dds.caps3 = 0; + dds.caps4 = 0; + dds.reserved2 = 0; + + // Filling pixelformat + dds.pixelFormat.size = 32; + dds.pixelFormat.flags = DDSPixelFormat::FlagAlphaPixels | DDSPixelFormat::FlagRGB; + dds.pixelFormat.fourCC = 0; + dds.pixelFormat.rgbBitCount = 32; + dds.pixelFormat.aBitMask = 0xff000000; + dds.pixelFormat.rBitMask = 0x00ff0000; + dds.pixelFormat.gBitMask = 0x0000ff00; + dds.pixelFormat.bBitMask = 0x000000ff; + + s << dds; + for (int height = 0; height < image.height(); height++) { + for (int width = 0; width < image.width(); width++) { + QRgb pixel = image.pixel(width, height); + quint32 color; + quint8 alpha = qAlpha(pixel); + quint8 red = qRed(pixel); + quint8 green = qGreen(pixel); + quint8 blue = qBlue(pixel); + color = (alpha << 24) + (red << 16) + (green << 8) + blue; + s << color; + } + } + + return true; +} + +QVariant QDDSHandler::option(QImageIOHandler::ImageOption option) const +{ + if (!supportsOption(option) || !ensureScanned()) + return QVariant(); + + switch (option) { + case QImageIOHandler::Size: + return QSize(m_header.width, m_header.height); + case QImageIOHandler::SubType: + return formatName(m_format); + case QImageIOHandler::SupportedSubTypes: + return QVariant::fromValue(QList() << formatName(FormatA8R8G8B8)); + default: + break; + } + + return QVariant(); +} + +void QDDSHandler::setOption(QImageIOHandler::ImageOption option, const QVariant &value) +{ + if (option == QImageIOHandler::SubType) { + const QByteArray subType = value.toByteArray(); + m_format = formatByName(subType.toUpper()); + if (m_format == FormatUnknown) + qWarning() << "unknown format" << subType; + } +} + +bool QDDSHandler::supportsOption(QImageIOHandler::ImageOption option) const +{ + return (option == QImageIOHandler::Size) + || (option == QImageIOHandler::SubType) + || (option == QImageIOHandler::SupportedSubTypes); +} + +int QDDSHandler::imageCount() const +{ + if (!ensureScanned()) + return 0; + + return qMax(1, m_header.mipMapCount); +} + +bool QDDSHandler::jumpToImage(int imageNumber) +{ + if (imageNumber >= imageCount()) + return false; + + m_currentImage = imageNumber; + return true; +} + +bool QDDSHandler::canRead(QIODevice *device) +{ + if (!device) { + qWarning() << "DDSHandler::canRead() called with no device"; + return false; + } + + if (device->isSequential()) + return false; + + return device->peek(4) == QByteArrayLiteral("DDS "); +} + +bool QDDSHandler::ensureScanned() const +{ + if (m_scanState != ScanNotScanned) + return m_scanState == ScanSuccess; + + m_scanState = ScanError; + + QDDSHandler *that = const_cast(this); + that->m_format = FormatUnknown; + + if (device()->isSequential()) { + qWarning() << "Sequential devices are not supported"; + return false; + } + + qint64 oldPos = device()->pos(); + device()->seek(0); + + QDataStream s(device()); + s.setByteOrder(QDataStream::LittleEndian); + s >> that->m_header; + if (m_header.pixelFormat.fourCC == dx10Magic) + s >> that->m_header10; + + device()->seek(oldPos); + + if (s.status() != QDataStream::Ok) + return false; + + if (!verifyHeader(m_header)) + return false; + + that->m_format = getFormat(m_header); + if (that->m_format == FormatUnknown) + return false; + + m_scanState = ScanSuccess; + return true; +} + +bool QDDSHandler::verifyHeader(const DDSHeader &dds) const +{ + quint32 flags = dds.flags; + quint32 requiredFlags = DDSHeader::FlagCaps | DDSHeader::FlagHeight + | DDSHeader::FlagWidth | DDSHeader::FlagPixelFormat; + if ((flags & requiredFlags) != requiredFlags) { + qWarning() << "Wrong dds.flags - not all required flags present. " + "Actual flags :" << flags; + return false; + } + + if (dds.size != ddsSize) { + qWarning() << "Wrong dds.size: actual =" << dds.size + << "expected =" << ddsSize; + return false; + } + + if (dds.pixelFormat.size != pixelFormatSize) { + qWarning() << "Wrong dds.pixelFormat.size: actual =" << dds.pixelFormat.size + << "expected =" << pixelFormatSize; + return false; + } + + if (dds.width > INT_MAX || dds.height > INT_MAX) { + qWarning() << "Can't read image with w/h bigger than INT_MAX"; + return false; + } + + return true; +} + +QImageIOPlugin::Capabilities QDDSPlugin::capabilities(QIODevice *device, const QByteArray &format) const +{ + if (format == QByteArrayLiteral("dds")) + return Capabilities(CanRead | CanWrite); + if (!format.isEmpty()) + return {}; + if (!device || !device->isOpen()) + return {}; + + Capabilities cap; + if (device->isReadable() && QDDSHandler::canRead(device)) + cap |= CanRead; + if (device->isWritable()) + cap |= CanWrite; + return cap; +} + +QImageIOHandler *QDDSPlugin::create(QIODevice *device, const QByteArray &format) const +{ + QImageIOHandler *handler = new QDDSHandler; + handler->setDevice(device); + handler->setFormat(format); + return handler; +} diff --git a/src/imageformats/dds.json b/src/imageformats/dds.json new file mode 100644 index 0000000..bc30795 --- /dev/null +++ b/src/imageformats/dds.json @@ -0,0 +1,4 @@ +{ + "Keys": [ "dds" ], + "MimeTypes": [ "image/x-dds" ] +} diff --git a/src/imageformats/dds_p.h b/src/imageformats/dds_p.h new file mode 100644 index 0000000..0cac03d --- /dev/null +++ b/src/imageformats/dds_p.h @@ -0,0 +1,143 @@ +/* + This file is part of the KDE project + SPDX-FileCopyrightText: 2015 The Qt Company Ltd + SPDX-FileCopyrightText: 2013 Ivan Komissarov + + SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only +*/ + +#ifndef QDDSHANDLER_H +#define QDDSHANDLER_H + +#include + +struct DDSPixelFormat +{ + enum DDSPixelFormatFlags { + FlagAlphaPixels = 0x00000001, + FlagAlpha = 0x00000002, + FlagFourCC = 0x00000004, + FlagPaletteIndexed4 = 0x00000008, + FlagPaletteIndexed8 = 0x00000020, + FlagRGB = 0x00000040, + FlagYUV = 0x00000200, + FlagLuminance = 0x00020000, + FlagNormal = 0x00080000, + FlagRGBA = FlagAlphaPixels | FlagRGB, + FlagLA = FlagAlphaPixels | FlagLuminance + }; + + quint32 size; + quint32 flags; + quint32 fourCC; + quint32 rgbBitCount; + quint32 rBitMask; + quint32 gBitMask; + quint32 bBitMask; + quint32 aBitMask; +}; + +struct DDSHeader +{ + enum DDSFlags { + FlagCaps = 0x000001, + FlagHeight = 0x000002, + FlagWidth = 0x000004, + FlagPitch = 0x000008, + FlagPixelFormat = 0x001000, + FlagMipmapCount = 0x020000, + FlagLinearSize = 0x080000, + FlagDepth = 0x800000 + }; + + enum DDSCapsFlags { + CapsComplex = 0x000008, + CapsTexture = 0x001000, + CapsMipmap = 0x400000 + }; + + enum DDSCaps2Flags { + Caps2CubeMap = 0x0200, + Caps2CubeMapPositiveX = 0x0400, + Caps2CubeMapNegativeX = 0x0800, + Caps2CubeMapPositiveY = 0x1000, + Caps2CubeMapNegativeY = 0x2000, + Caps2CubeMapPositiveZ = 0x4000, + Caps2CubeMapNegativeZ = 0x8000, + Caps2Volume = 0x200000 + }; + + enum { ReservedCount = 11 }; + + quint32 magic; + quint32 size; + quint32 flags; + quint32 height; + quint32 width; + quint32 pitchOrLinearSize; + quint32 depth; + quint32 mipMapCount; + quint32 reserved1[ReservedCount]; + DDSPixelFormat pixelFormat; + quint32 caps; + quint32 caps2; + quint32 caps3; + quint32 caps4; + quint32 reserved2; +}; + +struct DDSHeaderDX10 +{ + quint32 dxgiFormat; + quint32 resourceDimension; + quint32 miscFlag; + quint32 arraySize; + quint32 reserved; +}; + +class QDDSHandler : public QImageIOHandler +{ +public: + QDDSHandler(); + + bool canRead() const override; + bool read(QImage *image) override; + bool write(const QImage &image) override; + + QVariant option(QImageIOHandler::ImageOption option) const override; + void setOption(ImageOption option, const QVariant &value) override; + bool supportsOption(QImageIOHandler::ImageOption option) const override; + + int imageCount() const override; + bool jumpToImage(int imageNumber) override; + + static bool canRead(QIODevice *device); + +private: + bool ensureScanned() const; + bool verifyHeader(const DDSHeader &dds) const; + +private: + enum ScanState { + ScanError = -1, + ScanNotScanned = 0, + ScanSuccess = 1, + }; + + DDSHeader m_header; + int m_format; + DDSHeaderDX10 m_header10; + int m_currentImage; + mutable ScanState m_scanState; +}; + +class QDDSPlugin : public QImageIOPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QImageIOHandlerFactoryInterface" FILE "dds.json") +public: + Capabilities capabilities(QIODevice *device, const QByteArray &format) const; + QImageIOHandler *create(QIODevice *device, const QByteArray &format = QByteArray()) const; +}; + +#endif // QDDSHANDLER_H