Files
kimageformats/src/imageformats/tga.cpp
Alex Merry c9ca1f1862 Rename headers to end with _p.h
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).
2014-08-03 18:08:49 +01:00

440 lines
12 KiB
C++

/* This file is part of the KDE project
Copyright (C) 2003 Dominik Seichter <domseichter@web.de>
Copyright (C) 2004 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.
*/
/* 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 <assert.h>
#include <QImage>
#include <QtCore/QDataStream>
// #include <QDebug>
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;
// no break is intended!
case TGA_TYPE_INDEXED:
pal = true;
break;
case TGA_TYPE_RLE_RGB:
rle = true;
// no break is intended!
case TGA_TYPE_RGB:
rgb = true;
break;
case TGA_TYPE_RLE_GREY:
rle = true;
// 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 = QImage(tga.width, tga.height, QImage::Format_RGB32);
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 = QImage(tga.width, tga.height, QImage::Format_ARGB32);
}
uint pixel_size = (tga.pixel_size / 8);
uint size = tga.width * tga.height * pixel_size;
if (size < 1) {
// qDebug() << "This TGA file is broken with size " << size;
return false;
}
// Read palette.
char palette[768];
if (info.pal) {
// @todo Support palettes in other formats!
s.readRawData(palette, 3 * tga.colormap_length);
}
// Allocate image.
uchar *const image = new uchar[size];
if (info.rle) {
// Decode image.
char *dst = (char *)image;
int num = size;
while (num > 0) {
// Get packet header.
uchar c;
s >> c;
uint count = (c & 0x7f) + 1;
num -= count * pixel_size;
if (c & 0x80) {
// RLE pixels.
assert(pixel_size <= 8);
char pixel[8];
s.readRawData(pixel, pixel_size);
do {
memcpy(dst, pixel, pixel_size);
dst += pixel_size;
} while (--count);
} else {
// Raw pixels.
count *= pixel_size;
s.readRawData(dst, count);
dst += count;
}
}
} else {
// Read raw image.
s.readRawData((char *)image, size);
}
// Convert image to internal format.
int y_start, y_step, 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.
delete [] 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);
const QImage &img = image;
const bool hasAlpha = (img.format() == QImage::Format_ARGB32);
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 ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4)
for (int y = 0; y < img.height(); y++)
for (int x = 0; x < img.width(); x++) {
const QRgb color = img.pixel(x, y);
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 0;
}
if (!device->isOpen()) {
return 0;
}
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;
}