mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-07-18 03:54:18 -04:00
Frameworks have a convention of naming uninstalled headers in src/ with a _p at the end of the name, to make it clear they are not part of the API. None of the headers in KImageFormats are installed, so it is not really necessary to follow this convention, but we follow it anyway for the benefit of both humans and tools (like kapidox).
1039 lines
28 KiB
C++
1039 lines
28 KiB
C++
/* This file is part of the KDE project
|
|
Copyright (C) 2003 Ignacio Castaño <castano@ludicon.com>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the Lesser GNU General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2 of the License, or (at your option) any later version.
|
|
|
|
Almost all this code is based on nVidia's DDS-loading example
|
|
and the DevIl's source code by Denton Woods.
|
|
*/
|
|
|
|
/* this code supports:
|
|
* reading:
|
|
* rgb and dxt dds files
|
|
* cubemap dds files
|
|
* volume dds files -- TODO
|
|
* writing:
|
|
* rgb dds files only -- TODO
|
|
*/
|
|
|
|
#include "dds_p.h"
|
|
|
|
#include <QtCore/QStringList>
|
|
#include <QImage>
|
|
#include <QtCore/QDataStream>
|
|
// #include <QDebug>
|
|
|
|
#include <math.h> // sqrtf
|
|
|
|
#ifndef __USE_ISOC99
|
|
#define sqrtf(x) ((float)sqrt(x))
|
|
#endif
|
|
|
|
typedef quint32 uint;
|
|
typedef quint16 ushort;
|
|
typedef quint8 uchar;
|
|
|
|
#if !defined(MAKEFOURCC)
|
|
# define MAKEFOURCC(ch0, ch1, ch2, ch3) \
|
|
(uint(uchar(ch0)) | (uint(uchar(ch1)) << 8) | \
|
|
(uint(uchar(ch2)) << 16) | (uint(uchar(ch3)) << 24 ))
|
|
#endif
|
|
|
|
#define HORIZONTAL 1
|
|
#define VERTICAL 2
|
|
#define CUBE_LAYOUT HORIZONTAL
|
|
|
|
struct Color8888 {
|
|
uchar r, g, b, a;
|
|
};
|
|
|
|
union Color565 {
|
|
struct {
|
|
ushort b : 5;
|
|
ushort g : 6;
|
|
ushort r : 5;
|
|
} c;
|
|
ushort u;
|
|
};
|
|
|
|
union Color1555 {
|
|
struct {
|
|
ushort b : 5;
|
|
ushort g : 5;
|
|
ushort r : 5;
|
|
ushort a : 1;
|
|
} c;
|
|
ushort u;
|
|
};
|
|
|
|
union Color4444 {
|
|
struct {
|
|
ushort b : 4;
|
|
ushort g : 4;
|
|
ushort r : 4;
|
|
ushort a : 4;
|
|
} c;
|
|
ushort u;
|
|
};
|
|
|
|
static const uint FOURCC_DDS = MAKEFOURCC('D', 'D', 'S', ' ');
|
|
static const uint FOURCC_DXT1 = MAKEFOURCC('D', 'X', 'T', '1');
|
|
static const uint FOURCC_DXT2 = MAKEFOURCC('D', 'X', 'T', '2');
|
|
static const uint FOURCC_DXT3 = MAKEFOURCC('D', 'X', 'T', '3');
|
|
static const uint FOURCC_DXT4 = MAKEFOURCC('D', 'X', 'T', '4');
|
|
static const uint FOURCC_DXT5 = MAKEFOURCC('D', 'X', 'T', '5');
|
|
static const uint FOURCC_RXGB = MAKEFOURCC('R', 'X', 'G', 'B');
|
|
static const uint FOURCC_ATI2 = MAKEFOURCC('A', 'T', 'I', '2');
|
|
|
|
static const uint DDSD_CAPS = 0x00000001l;
|
|
static const uint DDSD_PIXELFORMAT = 0x00001000l;
|
|
static const uint DDSD_WIDTH = 0x00000004l;
|
|
static const uint DDSD_HEIGHT = 0x00000002l;
|
|
static const uint DDSD_PITCH = 0x00000008l;
|
|
|
|
static const uint DDSCAPS_TEXTURE = 0x00001000l;
|
|
static const uint DDSCAPS2_VOLUME = 0x00200000l;
|
|
static const uint DDSCAPS2_CUBEMAP = 0x00000200l;
|
|
|
|
static const uint DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400l;
|
|
static const uint DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800l;
|
|
static const uint DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000l;
|
|
static const uint DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000l;
|
|
static const uint DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000l;
|
|
static const uint DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000l;
|
|
|
|
static const uint DDPF_RGB = 0x00000040l;
|
|
static const uint DDPF_FOURCC = 0x00000004l;
|
|
static const uint DDPF_ALPHAPIXELS = 0x00000001l;
|
|
|
|
enum DDSType {
|
|
DDS_A8R8G8B8 = 0,
|
|
DDS_A1R5G5B5 = 1,
|
|
DDS_A4R4G4B4 = 2,
|
|
DDS_R8G8B8 = 3,
|
|
DDS_R5G6B5 = 4,
|
|
DDS_DXT1 = 5,
|
|
DDS_DXT2 = 6,
|
|
DDS_DXT3 = 7,
|
|
DDS_DXT4 = 8,
|
|
DDS_DXT5 = 9,
|
|
DDS_RXGB = 10,
|
|
DDS_ATI2 = 11,
|
|
DDS_UNKNOWN
|
|
};
|
|
|
|
struct DDSPixelFormat {
|
|
uint size;
|
|
uint flags;
|
|
uint fourcc;
|
|
uint bitcount;
|
|
uint rmask;
|
|
uint gmask;
|
|
uint bmask;
|
|
uint amask;
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, DDSPixelFormat &pf)
|
|
{
|
|
s >> pf.size;
|
|
s >> pf.flags;
|
|
s >> pf.fourcc;
|
|
s >> pf.bitcount;
|
|
s >> pf.rmask;
|
|
s >> pf.gmask;
|
|
s >> pf.bmask;
|
|
s >> pf.amask;
|
|
return s;
|
|
}
|
|
|
|
struct DDSCaps {
|
|
uint caps1;
|
|
uint caps2;
|
|
uint caps3;
|
|
uint caps4;
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, DDSCaps &caps)
|
|
{
|
|
s >> caps.caps1;
|
|
s >> caps.caps2;
|
|
s >> caps.caps3;
|
|
s >> caps.caps4;
|
|
return s;
|
|
}
|
|
|
|
struct DDSHeader {
|
|
uint size;
|
|
uint flags;
|
|
uint height;
|
|
uint width;
|
|
uint pitch;
|
|
uint depth;
|
|
uint mipmapcount;
|
|
uint reserved[11];
|
|
DDSPixelFormat pf;
|
|
DDSCaps caps;
|
|
uint notused;
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, DDSHeader &header)
|
|
{
|
|
s >> header.size;
|
|
s >> header.flags;
|
|
s >> header.height;
|
|
s >> header.width;
|
|
s >> header.pitch;
|
|
s >> header.depth;
|
|
s >> header.mipmapcount;
|
|
for (int i = 0; i < 11; i++) {
|
|
s >> header.reserved[i];
|
|
}
|
|
s >> header.pf;
|
|
s >> header.caps;
|
|
s >> header.notused;
|
|
return s;
|
|
}
|
|
|
|
static bool IsValid(const DDSHeader &header)
|
|
{
|
|
if (header.size != 124) {
|
|
return false;
|
|
}
|
|
const uint required = (DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT);
|
|
if ((header.flags & required) != required) {
|
|
return false;
|
|
}
|
|
if (header.pf.size != 32) {
|
|
return false;
|
|
}
|
|
if (!(header.caps.caps1 & DDSCAPS_TEXTURE)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Get supported type. We currently support 10 different types.
|
|
static DDSType GetType(const DDSHeader &header)
|
|
{
|
|
if (header.pf.flags & DDPF_RGB) {
|
|
if (header.pf.flags & DDPF_ALPHAPIXELS) {
|
|
switch (header.pf.bitcount) {
|
|
case 16:
|
|
return (header.pf.amask == 0x8000) ? DDS_A1R5G5B5 : DDS_A4R4G4B4;
|
|
case 32:
|
|
return DDS_A8R8G8B8;
|
|
}
|
|
} else {
|
|
switch (header.pf.bitcount) {
|
|
case 16:
|
|
return DDS_R5G6B5;
|
|
case 24:
|
|
return DDS_R8G8B8;
|
|
}
|
|
}
|
|
} else if (header.pf.flags & DDPF_FOURCC) {
|
|
switch (header.pf.fourcc) {
|
|
case FOURCC_DXT1:
|
|
return DDS_DXT1;
|
|
case FOURCC_DXT2:
|
|
return DDS_DXT2;
|
|
case FOURCC_DXT3:
|
|
return DDS_DXT3;
|
|
case FOURCC_DXT4:
|
|
return DDS_DXT4;
|
|
case FOURCC_DXT5:
|
|
return DDS_DXT5;
|
|
case FOURCC_RXGB:
|
|
return DDS_RXGB;
|
|
case FOURCC_ATI2:
|
|
return DDS_ATI2;
|
|
}
|
|
}
|
|
return DDS_UNKNOWN;
|
|
}
|
|
|
|
static bool HasAlpha(const DDSHeader &header)
|
|
{
|
|
return header.pf.flags & DDPF_ALPHAPIXELS;
|
|
}
|
|
|
|
static bool IsCubeMap(const DDSHeader &header)
|
|
{
|
|
return header.caps.caps2 & DDSCAPS2_CUBEMAP;
|
|
}
|
|
|
|
static bool IsSupported(const DDSHeader &header)
|
|
{
|
|
if (header.caps.caps2 & DDSCAPS2_VOLUME) {
|
|
return false;
|
|
}
|
|
if (GetType(header) == DDS_UNKNOWN) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool LoadA8R8G8B8(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
for (uint y = 0; y < h; y++) {
|
|
QRgb *scanline = (QRgb *) img.scanLine(y);
|
|
for (uint x = 0; x < w; x++) {
|
|
uchar r, g, b, a;
|
|
s >> b >> g >> r >> a;
|
|
scanline[x] = qRgba(r, g, b, a);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LoadR8G8B8(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
for (uint y = 0; y < h; y++) {
|
|
QRgb *scanline = (QRgb *) img.scanLine(y);
|
|
for (uint x = 0; x < w; x++) {
|
|
uchar r, g, b;
|
|
s >> b >> g >> r;
|
|
scanline[x] = qRgb(r, g, b);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LoadA1R5G5B5(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
for (uint y = 0; y < h; y++) {
|
|
QRgb *scanline = (QRgb *) img.scanLine(y);
|
|
for (uint x = 0; x < w; x++) {
|
|
Color1555 color;
|
|
s >> color.u;
|
|
uchar a = (color.c.a != 0) ? 0xFF : 0;
|
|
uchar r = (color.c.r << 3) | (color.c.r >> 2);
|
|
uchar g = (color.c.g << 3) | (color.c.g >> 2);
|
|
uchar b = (color.c.b << 3) | (color.c.b >> 2);
|
|
scanline[x] = qRgba(r, g, b, a);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LoadA4R4G4B4(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
for (uint y = 0; y < h; y++) {
|
|
QRgb *scanline = (QRgb *) img.scanLine(y);
|
|
for (uint x = 0; x < w; x++) {
|
|
Color4444 color;
|
|
s >> color.u;
|
|
uchar a = (color.c.a << 4) | color.c.a;
|
|
uchar r = (color.c.r << 4) | color.c.r;
|
|
uchar g = (color.c.g << 4) | color.c.g;
|
|
uchar b = (color.c.b << 4) | color.c.b;
|
|
scanline[x] = qRgba(r, g, b, a);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LoadR5G6B5(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
for (uint y = 0; y < h; y++) {
|
|
QRgb *scanline = (QRgb *) img.scanLine(y);
|
|
for (uint x = 0; x < w; x++) {
|
|
Color565 color;
|
|
s >> color.u;
|
|
uchar r = (color.c.r << 3) | (color.c.r >> 2);
|
|
uchar g = (color.c.g << 2) | (color.c.g >> 4);
|
|
uchar b = (color.c.b << 3) | (color.c.b >> 2);
|
|
scanline[x] = qRgb(r, g, b);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static QDataStream &operator>> (QDataStream &s, Color565 &c)
|
|
{
|
|
return s >> c.u;
|
|
}
|
|
|
|
struct BlockDXT {
|
|
Color565 col0;
|
|
Color565 col1;
|
|
uchar row[4];
|
|
|
|
void GetColors(Color8888 color_array[4])
|
|
{
|
|
color_array[0].r = (col0.c.r << 3) | (col0.c.r >> 2);
|
|
color_array[0].g = (col0.c.g << 2) | (col0.c.g >> 4);
|
|
color_array[0].b = (col0.c.b << 3) | (col0.c.b >> 2);
|
|
color_array[0].a = 0xFF;
|
|
|
|
color_array[1].r = (col1.c.r << 3) | (col1.c.r >> 2);
|
|
color_array[1].g = (col1.c.g << 2) | (col1.c.g >> 4);
|
|
color_array[1].b = (col1.c.b << 3) | (col1.c.b >> 2);
|
|
color_array[1].a = 0xFF;
|
|
|
|
if (col0.u > col1.u) {
|
|
// Four-color block: derive the other two colors.
|
|
color_array[2].r = (2 * color_array[0].r + color_array[1].r) / 3;
|
|
color_array[2].g = (2 * color_array[0].g + color_array[1].g) / 3;
|
|
color_array[2].b = (2 * color_array[0].b + color_array[1].b) / 3;
|
|
color_array[2].a = 0xFF;
|
|
|
|
color_array[3].r = (2 * color_array[1].r + color_array[0].r) / 3;
|
|
color_array[3].g = (2 * color_array[1].g + color_array[0].g) / 3;
|
|
color_array[3].b = (2 * color_array[1].b + color_array[0].b) / 3;
|
|
color_array[3].a = 0xFF;
|
|
} else {
|
|
// Three-color block: derive the other color.
|
|
color_array[2].r = (color_array[0].r + color_array[1].r) / 2;
|
|
color_array[2].g = (color_array[0].g + color_array[1].g) / 2;
|
|
color_array[2].b = (color_array[0].b + color_array[1].b) / 2;
|
|
color_array[2].a = 0xFF;
|
|
|
|
// Set all components to 0 to match DXT specs.
|
|
color_array[3].r = 0x00; // color_array[2].r;
|
|
color_array[3].g = 0x00; // color_array[2].g;
|
|
color_array[3].b = 0x00; // color_array[2].b;
|
|
color_array[3].a = 0x00;
|
|
}
|
|
}
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, BlockDXT &c)
|
|
{
|
|
return s >> c.col0 >> c.col1 >> c.row[0] >> c.row[1] >> c.row[2] >> c.row[3];
|
|
}
|
|
|
|
struct BlockDXTAlphaExplicit {
|
|
ushort row[4];
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, BlockDXTAlphaExplicit &c)
|
|
{
|
|
return s >> c.row[0] >> c.row[1] >> c.row[2] >> c.row[3];
|
|
}
|
|
|
|
struct BlockDXTAlphaLinear {
|
|
uchar alpha0;
|
|
uchar alpha1;
|
|
uchar bits[6];
|
|
|
|
void GetAlphas(uchar alpha_array[8])
|
|
{
|
|
alpha_array[0] = alpha0;
|
|
alpha_array[1] = alpha1;
|
|
|
|
// 8-alpha or 6-alpha block?
|
|
if (alpha_array[0] > alpha_array[1]) {
|
|
// 8-alpha block: derive the other 6 alphas.
|
|
// 000 = alpha_0, 001 = alpha_1, others are interpolated
|
|
|
|
alpha_array[2] = (6 * alpha0 + alpha1) / 7; // bit code 010
|
|
alpha_array[3] = (5 * alpha0 + 2 * alpha1) / 7; // Bit code 011
|
|
alpha_array[4] = (4 * alpha0 + 3 * alpha1) / 7; // Bit code 100
|
|
alpha_array[5] = (3 * alpha0 + 4 * alpha1) / 7; // Bit code 101
|
|
alpha_array[6] = (2 * alpha0 + 5 * alpha1) / 7; // Bit code 110
|
|
alpha_array[7] = (alpha0 + 6 * alpha1) / 7; // Bit code 111
|
|
} else {
|
|
// 6-alpha block: derive the other alphas.
|
|
// 000 = alpha_0, 001 = alpha_1, others are interpolated
|
|
|
|
alpha_array[2] = (4 * alpha0 + alpha1) / 5; // Bit code 010
|
|
alpha_array[3] = (3 * alpha0 + 2 * alpha1) / 5; // Bit code 011
|
|
alpha_array[4] = (2 * alpha0 + 3 * alpha1) / 5; // Bit code 100
|
|
alpha_array[5] = (alpha0 + 4 * alpha1) / 5; // Bit code 101
|
|
alpha_array[6] = 0x00; // Bit code 110
|
|
alpha_array[7] = 0xFF; // Bit code 111
|
|
}
|
|
}
|
|
|
|
void GetBits(uchar bit_array[16])
|
|
{
|
|
// Split 24 packed bits into 8 bytes, 3 bits at a time.
|
|
uint b = bits[0] | bits[1] << 8 | bits[2] << 16;
|
|
bit_array[0] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[1] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[2] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[3] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[4] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[5] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[6] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[7] = uchar(b & 0x07);
|
|
|
|
b = bits[3] | bits[4] << 8 | bits[5] << 16;
|
|
bit_array[8] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[9] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[10] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[11] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[12] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[13] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[14] = uchar(b & 0x07); b >>= 3;
|
|
bit_array[15] = uchar(b & 0x07);
|
|
}
|
|
};
|
|
|
|
static QDataStream &operator>> (QDataStream &s, BlockDXTAlphaLinear &c)
|
|
{
|
|
s >> c.alpha0 >> c.alpha1;
|
|
return s >> c.bits[0] >> c.bits[1] >> c.bits[2] >> c.bits[3] >> c.bits[4] >> c.bits[5];
|
|
}
|
|
|
|
static bool LoadDXT1(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
BlockDXT block;
|
|
QRgb *scanline[4];
|
|
|
|
for (uint y = 0; y < h; y += 4) {
|
|
for (uint j = 0; j < 4; j++) {
|
|
scanline[j] = (QRgb *) img.scanLine(y + j);
|
|
}
|
|
for (uint x = 0; x < w; x += 4) {
|
|
|
|
// Read 64bit color block.
|
|
s >> block;
|
|
|
|
// Decode color block.
|
|
Color8888 color_array[4];
|
|
block.GetColors(color_array);
|
|
|
|
// bit masks = 00000011, 00001100, 00110000, 11000000
|
|
const uint masks[4] = { 3, 3 << 2, 3 << 4, 3 << 6 };
|
|
const int shift[4] = { 0, 2, 4, 6 };
|
|
|
|
// Write color block.
|
|
for (uint j = 0; j < 4; j++) {
|
|
for (uint i = 0; i < 4; i++) {
|
|
if (img.valid(x + i, y + j)) {
|
|
uint idx = (block.row[j] & masks[i]) >> shift[i];
|
|
scanline[j][x + i] = qRgba(color_array[idx].r, color_array[idx].g, color_array[idx].b, color_array[idx].a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool LoadDXT3(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
BlockDXT block;
|
|
BlockDXTAlphaExplicit alpha;
|
|
QRgb *scanline[4];
|
|
|
|
for (uint y = 0; y < h; y += 4) {
|
|
for (uint j = 0; j < 4; j++) {
|
|
scanline[j] = (QRgb *) img.scanLine(y + j);
|
|
}
|
|
for (uint x = 0; x < w; x += 4) {
|
|
|
|
// Read 128bit color block.
|
|
s >> alpha;
|
|
s >> block;
|
|
|
|
// Decode color block.
|
|
Color8888 color_array[4];
|
|
block.GetColors(color_array);
|
|
|
|
// bit masks = 00000011, 00001100, 00110000, 11000000
|
|
const uint masks[4] = { 3, 3 << 2, 3 << 4, 3 << 6 };
|
|
const int shift[4] = { 0, 2, 4, 6 };
|
|
|
|
// Write color block.
|
|
for (uint j = 0; j < 4; j++) {
|
|
ushort a = alpha.row[j];
|
|
for (uint i = 0; i < 4; i++) {
|
|
if (img.valid(x + i, y + j)) {
|
|
uint idx = (block.row[j] & masks[i]) >> shift[i];
|
|
color_array[idx].a = a & 0x0f;
|
|
color_array[idx].a = color_array[idx].a | (color_array[idx].a << 4);
|
|
scanline[j][x + i] = qRgba(color_array[idx].r, color_array[idx].g, color_array[idx].b, color_array[idx].a);
|
|
}
|
|
a >>= 4;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool LoadDXT2(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
if (!LoadDXT3(s, header, img)) {
|
|
return false;
|
|
}
|
|
//UndoPremultiplyAlpha(img);
|
|
return true;
|
|
}
|
|
|
|
static bool LoadDXT5(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
BlockDXT block;
|
|
BlockDXTAlphaLinear alpha;
|
|
QRgb *scanline[4];
|
|
|
|
for (uint y = 0; y < h; y += 4) {
|
|
for (uint j = 0; j < 4; j++) {
|
|
scanline[j] = (QRgb *) img.scanLine(y + j);
|
|
}
|
|
for (uint x = 0; x < w; x += 4) {
|
|
|
|
// Read 128bit color block.
|
|
s >> alpha;
|
|
s >> block;
|
|
|
|
// Decode color block.
|
|
Color8888 color_array[4];
|
|
block.GetColors(color_array);
|
|
|
|
uchar alpha_array[8];
|
|
alpha.GetAlphas(alpha_array);
|
|
|
|
uchar bit_array[16];
|
|
alpha.GetBits(bit_array);
|
|
|
|
// bit masks = 00000011, 00001100, 00110000, 11000000
|
|
const uint masks[4] = { 3, 3 << 2, 3 << 4, 3 << 6 };
|
|
const int shift[4] = { 0, 2, 4, 6 };
|
|
|
|
// Write color block.
|
|
for (uint j = 0; j < 4; j++) {
|
|
for (uint i = 0; i < 4; i++) {
|
|
if (img.valid(x + i, y + j)) {
|
|
uint idx = (block.row[j] & masks[i]) >> shift[i];
|
|
color_array[idx].a = alpha_array[bit_array[j * 4 + i]];
|
|
scanline[j][x + i] = qRgba(color_array[idx].r, color_array[idx].g, color_array[idx].b, color_array[idx].a);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
static bool LoadDXT4(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
if (!LoadDXT5(s, header, img)) {
|
|
return false;
|
|
}
|
|
//UndoPremultiplyAlpha(img);
|
|
return true;
|
|
}
|
|
|
|
static bool LoadRXGB(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
BlockDXT block;
|
|
BlockDXTAlphaLinear alpha;
|
|
QRgb *scanline[4];
|
|
|
|
for (uint y = 0; y < h; y += 4) {
|
|
for (uint j = 0; j < 4; j++) {
|
|
scanline[j] = (QRgb *) img.scanLine(y + j);
|
|
}
|
|
for (uint x = 0; x < w; x += 4) {
|
|
|
|
// Read 128bit color block.
|
|
s >> alpha;
|
|
s >> block;
|
|
|
|
// Decode color block.
|
|
Color8888 color_array[4];
|
|
block.GetColors(color_array);
|
|
|
|
uchar alpha_array[8];
|
|
alpha.GetAlphas(alpha_array);
|
|
|
|
uchar bit_array[16];
|
|
alpha.GetBits(bit_array);
|
|
|
|
// bit masks = 00000011, 00001100, 00110000, 11000000
|
|
const uint masks[4] = { 3, 3 << 2, 3 << 4, 3 << 6 };
|
|
const int shift[4] = { 0, 2, 4, 6 };
|
|
|
|
// Write color block.
|
|
for (uint j = 0; j < 4; j++) {
|
|
for (uint i = 0; i < 4; i++) {
|
|
if (img.valid(x + i, y + j)) {
|
|
uint idx = (block.row[j] & masks[i]) >> shift[i];
|
|
color_array[idx].a = alpha_array[bit_array[j * 4 + i]];
|
|
scanline[j][x + i] = qRgb(color_array[idx].a, color_array[idx].g, color_array[idx].b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool LoadATI2(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
const uint w = header.width;
|
|
const uint h = header.height;
|
|
|
|
BlockDXTAlphaLinear xblock;
|
|
BlockDXTAlphaLinear yblock;
|
|
QRgb *scanline[4];
|
|
|
|
for (uint y = 0; y < h; y += 4) {
|
|
for (uint j = 0; j < 4; j++) {
|
|
scanline[j] = (QRgb *) img.scanLine(y + j);
|
|
}
|
|
for (uint x = 0; x < w; x += 4) {
|
|
|
|
// Read 128bit color block.
|
|
s >> xblock;
|
|
s >> yblock;
|
|
|
|
// Decode color block.
|
|
uchar xblock_array[8];
|
|
xblock.GetAlphas(xblock_array);
|
|
|
|
uchar xbit_array[16];
|
|
xblock.GetBits(xbit_array);
|
|
|
|
uchar yblock_array[8];
|
|
yblock.GetAlphas(yblock_array);
|
|
|
|
uchar ybit_array[16];
|
|
yblock.GetBits(ybit_array);
|
|
|
|
// Write color block.
|
|
for (uint j = 0; j < 4; j++) {
|
|
for (uint i = 0; i < 4; i++) {
|
|
if (img.valid(x + i, y + j)) {
|
|
const uchar nx = xblock_array[xbit_array[j * 4 + i]];
|
|
const uchar ny = yblock_array[ybit_array[j * 4 + i]];
|
|
|
|
const float fx = float(nx) / 127.5f - 1.0f;
|
|
const float fy = float(ny) / 127.5f - 1.0f;
|
|
const float fz = sqrtf(1.0f - fx * fx - fy * fy);
|
|
const uchar nz = uchar((fz + 1.0f) * 127.5f);
|
|
|
|
scanline[j][x + i] = qRgb(nx, ny, nz);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
typedef bool (* TextureLoader)(QDataStream &s, const DDSHeader &header, QImage &img);
|
|
|
|
// Get an appropriate texture loader for the given type.
|
|
static TextureLoader GetTextureLoader(DDSType type)
|
|
{
|
|
switch (type) {
|
|
case DDS_A8R8G8B8:
|
|
return LoadA8R8G8B8;
|
|
case DDS_A1R5G5B5:
|
|
return LoadA1R5G5B5;
|
|
case DDS_A4R4G4B4:
|
|
return LoadA4R4G4B4;
|
|
case DDS_R8G8B8:
|
|
return LoadR8G8B8;
|
|
case DDS_R5G6B5:
|
|
return LoadR5G6B5;
|
|
case DDS_DXT1:
|
|
return LoadDXT1;
|
|
case DDS_DXT2:
|
|
return LoadDXT2;
|
|
case DDS_DXT3:
|
|
return LoadDXT3;
|
|
case DDS_DXT4:
|
|
return LoadDXT4;
|
|
case DDS_DXT5:
|
|
return LoadDXT5;
|
|
case DDS_RXGB:
|
|
return LoadRXGB;
|
|
case DDS_ATI2:
|
|
return LoadATI2;
|
|
default:
|
|
return NULL;
|
|
};
|
|
}
|
|
|
|
// Load a 2d texture.
|
|
static bool LoadTexture(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
// Create dst image.
|
|
img = QImage(header.width, header.height, QImage::Format_RGB32);
|
|
|
|
// Read image.
|
|
DDSType type = GetType(header);
|
|
|
|
// Enable alpha buffer for transparent or DDS images.
|
|
if (HasAlpha(header) || type >= DDS_DXT1) {
|
|
img = img.convertToFormat(QImage::Format_ARGB32);
|
|
}
|
|
|
|
TextureLoader loader = GetTextureLoader(type);
|
|
if (loader == NULL) {
|
|
return false;
|
|
}
|
|
|
|
return loader(s, header, img);
|
|
}
|
|
|
|
static int FaceOffset(const DDSHeader &header)
|
|
{
|
|
|
|
DDSType type = GetType(header);
|
|
|
|
int mipmap = qMax(header.mipmapcount, 1U);
|
|
int size = 0;
|
|
int w = header.width;
|
|
int h = header.height;
|
|
|
|
if (type >= DDS_DXT1) {
|
|
int multiplier = (type == DDS_DXT1) ? 8 : 16;
|
|
do {
|
|
int face_size = qMax(w / 4, 1) * qMax(h / 4, 1) * multiplier;
|
|
size += face_size;
|
|
w >>= 1;
|
|
h >>= 1;
|
|
} while (--mipmap);
|
|
} else {
|
|
int multiplier = header.pf.bitcount / 8;
|
|
do {
|
|
int face_size = w * h * multiplier;
|
|
size += face_size;
|
|
w = qMax(w >> 1, 1);
|
|
h = qMax(h >> 1, 1);
|
|
} while (--mipmap);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
#if CUBE_LAYOUT == HORIZONTAL
|
|
static int face_offset[6][2] = { {2, 1}, {0, 1}, {1, 0}, {1, 2}, {1, 1}, {3, 1} };
|
|
#elif CUBE_LAYOUT == VERTICAL
|
|
static int face_offset[6][2] = { {2, 1}, {0, 1}, {1, 0}, {1, 2}, {1, 1}, {1, 3} };
|
|
#endif
|
|
static int face_flags[6] = {
|
|
DDSCAPS2_CUBEMAP_POSITIVEX,
|
|
DDSCAPS2_CUBEMAP_NEGATIVEX,
|
|
DDSCAPS2_CUBEMAP_POSITIVEY,
|
|
DDSCAPS2_CUBEMAP_NEGATIVEY,
|
|
DDSCAPS2_CUBEMAP_POSITIVEZ,
|
|
DDSCAPS2_CUBEMAP_NEGATIVEZ
|
|
};
|
|
|
|
// Load unwrapped cube map.
|
|
static bool LoadCubeMap(QDataStream &s, const DDSHeader &header, QImage &img)
|
|
{
|
|
// Create dst image.
|
|
#if CUBE_LAYOUT == HORIZONTAL
|
|
img = QImage(4 * header.width, 3 * header.height, QImage::Format_RGB32);
|
|
#elif CUBE_LAYOUT == VERTICAL
|
|
img = QImage(3 * header.width, 4 * header.height, QImage::Format_RGB32);
|
|
#endif
|
|
|
|
DDSType type = GetType(header);
|
|
|
|
// Enable alpha buffer for transparent or DDS images.
|
|
if (HasAlpha(header) || type >= DDS_DXT1) {
|
|
img = img.convertToFormat(QImage::Format_ARGB32);
|
|
}
|
|
|
|
// Select texture loader.
|
|
TextureLoader loader = GetTextureLoader(type);
|
|
if (loader == NULL) {
|
|
return false;
|
|
}
|
|
|
|
// Clear background.
|
|
img.fill(0);
|
|
|
|
// Create face image.
|
|
QImage face(header.width, header.height, QImage::Format_RGB32);
|
|
|
|
int offset = s.device()->pos();
|
|
int size = FaceOffset(header);
|
|
|
|
for (int i = 0; i < 6; i++) {
|
|
|
|
if (!(header.caps.caps2 & face_flags[i])) {
|
|
// Skip face.
|
|
continue;
|
|
}
|
|
|
|
// Seek device.
|
|
s.device()->seek(offset);
|
|
offset += size;
|
|
|
|
// Load face from stream.
|
|
if (!loader(s, header, face)) {
|
|
return false;
|
|
}
|
|
|
|
#if CUBE_LAYOUT == VERTICAL
|
|
if (i == 5) {
|
|
face = face.mirror(true, true);
|
|
}
|
|
#endif
|
|
|
|
// Compute face offsets.
|
|
int offset_x = face_offset[i][0] * header.width;
|
|
int offset_y = face_offset[i][1] * header.height;
|
|
|
|
// Copy face on the image.
|
|
for (uint y = 0; y < header.height; y++) {
|
|
QRgb *src = (QRgb *) face.scanLine(y);
|
|
QRgb *dst = (QRgb *) img.scanLine(y + offset_y) + offset_x;
|
|
memcpy(dst, src, sizeof(QRgb) * header.width);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
DDSHandler::DDSHandler()
|
|
{
|
|
}
|
|
|
|
bool DDSHandler::canRead() const
|
|
{
|
|
if (canRead(device())) {
|
|
setFormat("dds");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DDSHandler::read(QImage *image)
|
|
{
|
|
QDataStream s(device());
|
|
s.setByteOrder(QDataStream::LittleEndian);
|
|
|
|
// Validate header.
|
|
uint fourcc;
|
|
s >> fourcc;
|
|
if (fourcc != FOURCC_DDS) {
|
|
// qDebug() << "This is not a DDS file.";
|
|
return false;
|
|
}
|
|
|
|
// Read image header.
|
|
DDSHeader header;
|
|
s >> header;
|
|
|
|
// Check image file format.
|
|
if (s.atEnd() || !IsValid(header)) {
|
|
// qDebug() << "This DDS file is not valid.";
|
|
return false;
|
|
}
|
|
|
|
// Determine image type, by now, we only support 2d textures.
|
|
if (!IsSupported(header)) {
|
|
// qDebug() << "This DDS file is not supported.";
|
|
return false;
|
|
}
|
|
|
|
bool result;
|
|
|
|
if (IsCubeMap(header)) {
|
|
result = LoadCubeMap(s, header, *image);
|
|
} else {
|
|
result = LoadTexture(s, header, *image);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool DDSHandler::canRead(QIODevice *device)
|
|
{
|
|
if (!device) {
|
|
qWarning("DDSHandler::canRead() called with no device");
|
|
return false;
|
|
}
|
|
|
|
qint64 oldPos = device->pos();
|
|
|
|
char head[3];
|
|
qint64 readBytes = device->read(head, sizeof(head));
|
|
if (readBytes != sizeof(head)) {
|
|
if (device->isSequential()) {
|
|
while (readBytes > 0) {
|
|
device->ungetChar(head[readBytes-- - 1]);
|
|
}
|
|
} else {
|
|
device->seek(oldPos);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (device->isSequential()) {
|
|
while (readBytes > 0) {
|
|
device->ungetChar(head[readBytes-- - 1]);
|
|
}
|
|
} else {
|
|
device->seek(oldPos);
|
|
}
|
|
|
|
return qstrncmp(head, "DDS", 3) == 0;
|
|
}
|
|
|
|
QImageIOPlugin::Capabilities DDSPlugin::capabilities(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
if (format == "dds") {
|
|
return Capabilities(CanRead);
|
|
}
|
|
if (!format.isEmpty()) {
|
|
return 0;
|
|
}
|
|
if (!device->isOpen()) {
|
|
return 0;
|
|
}
|
|
|
|
Capabilities cap;
|
|
if (device->isReadable() && DDSHandler::canRead(device)) {
|
|
cap |= CanRead;
|
|
}
|
|
return cap;
|
|
}
|
|
|
|
QImageIOHandler *DDSPlugin::create(QIODevice *device, const QByteArray &format) const
|
|
{
|
|
QImageIOHandler *handler = new DDSHandler;
|
|
handler->setDevice(device);
|
|
handler->setFormat(format);
|
|
return handler;
|
|
}
|