Files
kimageformats/src/imageformats/tga.cpp
Friedrich W. H. Kossebau ed6a3c520d Add explicit moc includes to sources for moc-covered headers
* speeds up incremental builds as changes to a header will not always
  need the full mocs_compilation.cpp for all the target's headers rebuild,
  while having a moc file sourced into a source file only adds minor
  extra costs, due to small own code and the used headers usually
  already covered by the source file, being for the same class/struct
* seems to not slow down clean builds, due to empty mocs_compilation.cpp
  resulting in those quickly processed, while the minor extra cost of the
  sourced moc files does not outweigh that in summary.
  Measured times actually improved by some percent points.
  (ideally CMake would just skip empty mocs_compilation.cpp & its object
  file one day)
* enables compiler to see all methods of a class in same compilation unit
  to do some sanity checks
* potentially more inlining in general, due to more in the compilation unit
* allows to keep using more forward declarations in the header, as with the
  moc code being sourced into the cpp file there definitions can be ensured
  and often are already for the needs of the normal class methods

(cherry picked from commit 34ed3bad27)
2023-07-02 11:55:33 +02:00

532 lines
14 KiB
C++

/*
This file is part of the KDE project
SPDX-FileCopyrightText: 2003 Dominik Seichter <domseichter@web.de>
SPDX-FileCopyrightText: 2004 Ignacio Castaño <castano@ludicon.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
/* this code supports:
* reading:
* uncompressed and run length encoded indexed, grey and color tga files.
* image types 1, 2, 3, 9, 10 and 11.
* only RGB color maps with no more than 256 colors.
* pixel formats 8, 16, 24 and 32.
* writing:
* uncompressed true color tga files
*/
#include "tga_p.h"
#include "util_p.h"
#include <assert.h>
#include <QDataStream>
#include <QDebug>
#include <QImage>
typedef quint32 uint;
typedef quint16 ushort;
typedef quint8 uchar;
namespace // Private.
{
// Header format of saved files.
uchar targaMagic[12] = {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0};
enum TGAType {
TGA_TYPE_INDEXED = 1,
TGA_TYPE_RGB = 2,
TGA_TYPE_GREY = 3,
TGA_TYPE_RLE_INDEXED = 9,
TGA_TYPE_RLE_RGB = 10,
TGA_TYPE_RLE_GREY = 11,
};
#define TGA_INTERLEAVE_MASK 0xc0
#define TGA_INTERLEAVE_NONE 0x00
#define TGA_INTERLEAVE_2WAY 0x40
#define TGA_INTERLEAVE_4WAY 0x80
#define TGA_ORIGIN_MASK 0x30
#define TGA_ORIGIN_LEFT 0x00
#define TGA_ORIGIN_RIGHT 0x10
#define TGA_ORIGIN_LOWER 0x00
#define TGA_ORIGIN_UPPER 0x20
/** Tga Header. */
struct TgaHeader {
uchar id_length;
uchar colormap_type;
uchar image_type;
ushort colormap_index;
ushort colormap_length;
uchar colormap_size;
ushort x_origin;
ushort y_origin;
ushort width;
ushort height;
uchar pixel_size;
uchar flags;
enum {
SIZE = 18,
}; // const static int SIZE = 18;
};
static QDataStream &operator>>(QDataStream &s, TgaHeader &head)
{
s >> head.id_length;
s >> head.colormap_type;
s >> head.image_type;
s >> head.colormap_index;
s >> head.colormap_length;
s >> head.colormap_size;
s >> head.x_origin;
s >> head.y_origin;
s >> head.width;
s >> head.height;
s >> head.pixel_size;
s >> head.flags;
/*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type;
qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size;
qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize:
" << head.pixel_size << " - flags: " << head.flags;*/
return s;
}
static bool IsSupported(const TgaHeader &head)
{
if (head.image_type != TGA_TYPE_INDEXED && head.image_type != TGA_TYPE_RGB && head.image_type != TGA_TYPE_GREY && head.image_type != TGA_TYPE_RLE_INDEXED
&& head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) {
return false;
}
if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) {
if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) {
return false;
}
}
if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) {
if (head.colormap_type != 0) {
return false;
}
}
if (head.width == 0 || head.height == 0) {
return false;
}
if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) {
return false;
}
return true;
}
struct Color555 {
ushort b : 5;
ushort g : 5;
ushort r : 5;
};
struct TgaHeaderInfo {
bool rle;
bool pal;
bool rgb;
bool grey;
TgaHeaderInfo(const TgaHeader &tga)
: rle(false)
, pal(false)
, rgb(false)
, grey(false)
{
switch (tga.image_type) {
case TGA_TYPE_RLE_INDEXED:
rle = true;
Q_FALLTHROUGH();
// no break is intended!
case TGA_TYPE_INDEXED:
pal = true;
break;
case TGA_TYPE_RLE_RGB:
rle = true;
Q_FALLTHROUGH();
// no break is intended!
case TGA_TYPE_RGB:
rgb = true;
break;
case TGA_TYPE_RLE_GREY:
rle = true;
Q_FALLTHROUGH();
// no break is intended!
case TGA_TYPE_GREY:
grey = true;
break;
default:
// Error, unknown image type.
break;
}
}
};
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
{
// Create image.
img = imageAlloc(tga.width, tga.height, QImage::Format_RGB32);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false;
}
TgaHeaderInfo info(tga);
// Bits 0-3 are the numbers of alpha bits (can be zero!)
const int numAlphaBits = tga.flags & 0xf;
// However alpha exists only in the 32 bit format.
if ((tga.pixel_size == 32) && (tga.flags & 0xf)) {
img = imageAlloc(tga.width, tga.height, QImage::Format_ARGB32);
if (img.isNull()) {
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
return false;
}
if (numAlphaBits > 8) {
return false;
}
}
uint pixel_size = (tga.pixel_size / 8);
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
if (size < 1) {
// qDebug() << "This TGA file is broken with size " << size;
return false;
}
// Read palette.
static const int max_palette_size = 768;
char palette[max_palette_size];
if (info.pal) {
// @todo Support palettes in other formats!
const int palette_size = 3 * tga.colormap_length;
if (palette_size > max_palette_size) {
return false;
}
const int dataRead = s.readRawData(palette, palette_size);
if (dataRead < 0) {
return false;
}
if (dataRead < max_palette_size) {
memset(&palette[dataRead], 0, max_palette_size - dataRead);
}
}
// Allocate image.
uchar *const image = reinterpret_cast<uchar *>(malloc(size));
if (!image) {
return false;
}
bool valid = true;
if (info.rle) {
// Decode image.
char *dst = (char *)image;
char *imgEnd = dst + size;
qint64 num = size;
while (num > 0 && valid) {
if (s.atEnd()) {
valid = false;
break;
}
// Get packet header.
uchar c;
s >> c;
uint count = (c & 0x7f) + 1;
num -= count * pixel_size;
if (num < 0) {
valid = false;
break;
}
if (c & 0x80) {
// RLE pixels.
assert(pixel_size <= 8);
char pixel[8];
const int dataRead = s.readRawData(pixel, pixel_size);
if (dataRead < (int)pixel_size) {
memset(&pixel[dataRead], 0, pixel_size - dataRead);
}
do {
if (dst + pixel_size > imgEnd) {
qWarning() << "Trying to write out of bounds!" << ptrdiff_t(dst) << (ptrdiff_t(imgEnd) - ptrdiff_t(pixel_size));
valid = false;
break;
}
memcpy(dst, pixel, pixel_size);
dst += pixel_size;
} while (--count);
} else {
// Raw pixels.
count *= pixel_size;
const int dataRead = s.readRawData(dst, count);
if (dataRead < 0) {
free(image);
return false;
}
if ((uint)dataRead < count) {
const size_t toCopy = count - dataRead;
if (&dst[dataRead] + toCopy > imgEnd) {
qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);
;
valid = false;
break;
}
memset(&dst[dataRead], 0, toCopy);
}
dst += count;
}
}
} else {
// Read raw image.
const int dataRead = s.readRawData((char *)image, size);
if (dataRead < 0) {
free(image);
return false;
}
if (dataRead < size) {
memset(&image[dataRead], 0, size - dataRead);
}
}
if (!valid) {
free(image);
return false;
}
// Convert image to internal format.
int y_start;
int y_step;
int y_end;
if (tga.flags & TGA_ORIGIN_UPPER) {
y_start = 0;
y_step = 1;
y_end = tga.height;
} else {
y_start = tga.height - 1;
y_step = -1;
y_end = -1;
}
uchar *src = image;
for (int y = y_start; y != y_end; y += y_step) {
QRgb *scanline = (QRgb *)img.scanLine(y);
if (info.pal) {
// Paletted.
for (int x = 0; x < tga.width; x++) {
uchar idx = *src++;
scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]);
}
} else if (info.grey) {
// Greyscale.
for (int x = 0; x < tga.width; x++) {
scanline[x] = qRgb(*src, *src, *src);
src++;
}
} else {
// True Color.
if (tga.pixel_size == 16) {
for (int x = 0; x < tga.width; x++) {
Color555 c = *reinterpret_cast<Color555 *>(src);
scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2));
src += 2;
}
} else if (tga.pixel_size == 24) {
for (int x = 0; x < tga.width; x++) {
scanline[x] = qRgb(src[2], src[1], src[0]);
src += 3;
}
} else if (tga.pixel_size == 32) {
for (int x = 0; x < tga.width; x++) {
// ### TODO: verify with images having really some alpha data
const uchar alpha = (src[3] << (8 - numAlphaBits));
scanline[x] = qRgba(src[2], src[1], src[0], alpha);
src += 4;
}
}
}
}
// Free image.
free(image);
return true;
}
} // namespace
TGAHandler::TGAHandler()
{
}
bool TGAHandler::canRead() const
{
if (canRead(device())) {
setFormat("tga");
return true;
}
return false;
}
bool TGAHandler::read(QImage *outImage)
{
// qDebug() << "Loading TGA file!";
QDataStream s(device());
s.setByteOrder(QDataStream::LittleEndian);
// Read image header.
TgaHeader tga;
s >> tga;
s.device()->seek(TgaHeader::SIZE + tga.id_length);
// Check image file format.
if (s.atEnd()) {
// qDebug() << "This TGA file is not valid.";
return false;
}
// Check supported file types.
if (!IsSupported(tga)) {
// qDebug() << "This TGA file is not supported.";
return false;
}
QImage img;
bool result = LoadTGA(s, tga, img);
if (result == false) {
// qDebug() << "Error loading TGA file.";
return false;
}
*outImage = img;
return true;
}
bool TGAHandler::write(const QImage &image)
{
QDataStream s(device());
s.setByteOrder(QDataStream::LittleEndian);
QImage img(image);
const bool hasAlpha = img.hasAlphaChannel();
if (hasAlpha && img.format() != QImage::Format_ARGB32) {
img = img.convertToFormat(QImage::Format_ARGB32);
} else if (!hasAlpha && img.format() != QImage::Format_RGB32) {
img = img.convertToFormat(QImage::Format_RGB32);
}
if (img.isNull()) {
qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
return false;
}
static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
static constexpr quint8 alphaChannel8Bits = 0x08;
for (int i = 0; i < 12; i++) {
s << targaMagic[i];
}
// write header
s << quint16(img.width()); // width
s << quint16(img.height()); // height
s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha)
s << quint8(hasAlpha ? originTopLeft + alphaChannel8Bits : originTopLeft); // top left image (0x20) + 8 bit alpha (0x8)
for (int y = 0; y < img.height(); y++) {
auto ptr = reinterpret_cast<QRgb *>(img.scanLine(y));
for (int x = 0; x < img.width(); x++) {
auto color = *(ptr + x);
s << quint8(qBlue(color));
s << quint8(qGreen(color));
s << quint8(qRed(color));
if (hasAlpha) {
s << quint8(qAlpha(color));
}
}
}
return true;
}
bool TGAHandler::canRead(QIODevice *device)
{
if (!device) {
qWarning("TGAHandler::canRead() called with no device");
return false;
}
qint64 oldPos = device->pos();
QByteArray head = device->read(TgaHeader::SIZE);
int readBytes = head.size();
if (device->isSequential()) {
for (int pos = readBytes - 1; pos >= 0; --pos) {
device->ungetChar(head[pos]);
}
} else {
device->seek(oldPos);
}
if (readBytes < TgaHeader::SIZE) {
return false;
}
QDataStream stream(head);
stream.setByteOrder(QDataStream::LittleEndian);
TgaHeader tga;
stream >> tga;
return IsSupported(tga);
}
QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const
{
if (format == "tga") {
return Capabilities(CanRead | CanWrite);
}
if (!format.isEmpty()) {
return {};
}
if (!device->isOpen()) {
return {};
}
Capabilities cap;
if (device->isReadable() && TGAHandler::canRead(device)) {
cap |= CanRead;
}
if (device->isWritable()) {
cap |= CanWrite;
}
return cap;
}
QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const
{
QImageIOHandler *handler = new TGAHandler;
handler->setDevice(device);
handler->setFormat(format);
return handler;
}
#include "moc_tga_p.cpp"