mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-06-03 17:08:08 -04:00
1037 lines
28 KiB
C++
1037 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.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;
|
|
}
|