mirror of
https://invent.kde.org/frameworks/kimageformats.git
synced 2025-09-21 11:54:25 -04:00
TGA: memory optimizations and native grayscale support
- Removed temporary buffer of uncompressed image size when reading (loads images using half the memory) - Added native support for Grayscale images (they are no longer converted to RGB(A) 32 when reading/writing) - Fixes wrong X channel value on RGBX32 images (CCBUG: 499584) It should also reduce loading times for corrupted images. Closes #33
This commit is contained in:
committed by
Albert Astals Cid
parent
bc8b5b56b1
commit
ebc366b3c5
Binary file not shown.
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 9.0 KiB |
@ -2,6 +2,7 @@
|
||||
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-FileCopyrightText: 2025 Mirco Miranda <mircomir@outlook.com>
|
||||
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
@ -14,6 +15,8 @@
|
||||
* pixel formats 8, 16, 24 and 32.
|
||||
* writing:
|
||||
* uncompressed true color tga files
|
||||
* uncompressed grayscale tga files
|
||||
* uncompressed indexed tga files
|
||||
*/
|
||||
|
||||
#include "tga_p.h"
|
||||
@ -191,11 +194,13 @@ static QImage::Format imageFormat(const TgaHeader &head)
|
||||
format = QImage::Format_ARGB32;
|
||||
}
|
||||
// Anyway, GIMP also saves gray images with alpha in TGA format
|
||||
} else if((info.grey) && (head.pixel_size == 16) && (numAlphaBits)) {
|
||||
} else if ((info.grey) && (head.pixel_size == 16) && (numAlphaBits)) {
|
||||
if (numAlphaBits == 8) {
|
||||
format = QImage::Format_ARGB32;
|
||||
}
|
||||
} else if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) {
|
||||
} else if (info.grey) {
|
||||
format = QImage::Format_Grayscale8;
|
||||
} else if (info.pal) {
|
||||
format = QImage::Format_Indexed8;
|
||||
} else {
|
||||
format = QImage::Format_RGB32;
|
||||
@ -220,20 +225,101 @@ static bool peekHeader(QIODevice *device, TgaHeader &header)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
/*!
|
||||
* \brief readTgaLine
|
||||
* Read a scan line from the raw data.
|
||||
* \param dev The current device.
|
||||
* \param pixel_size The number of bytes per pixel.
|
||||
* \param size The size of the uncompressed TGA raw line
|
||||
* \param rle True if the stream is RLE compressed, otherwise false.
|
||||
* \param cache The cache buffer used to store data (only used when the stream is RLE).
|
||||
* \return The uncompressed raw data of a line or an empty array on error.
|
||||
*/
|
||||
static QByteArray readTgaLine(QIODevice *dev, qint32 pixel_size, qint32 size, bool rle, QByteArray &cache)
|
||||
{
|
||||
// uncompressed stream
|
||||
if (!rle) {
|
||||
auto ba = dev->read(size);
|
||||
if (ba.size() != size)
|
||||
ba.clear();
|
||||
return ba;
|
||||
}
|
||||
|
||||
// RLE compressed stream
|
||||
if (cache.size() < qsizetype(size)) {
|
||||
// Decode image.
|
||||
qint64 num = size;
|
||||
|
||||
while (num > 0) {
|
||||
if (dev->atEnd()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Get packet header.
|
||||
char cc;
|
||||
if (dev->read(&cc, 1) != 1) {
|
||||
cache.clear();
|
||||
break;
|
||||
}
|
||||
auto c = uchar(cc);
|
||||
|
||||
uint count = (c & 0x7f) + 1;
|
||||
QByteArray tmp(count * pixel_size, char());
|
||||
auto dst = tmp.data();
|
||||
num -= count * pixel_size;
|
||||
|
||||
if (c & 0x80) { // RLE pixels.
|
||||
assert(pixel_size <= 8);
|
||||
char pixel[8];
|
||||
const int dataRead = dev->read(pixel, pixel_size);
|
||||
if (dataRead < (int)pixel_size) {
|
||||
memset(&pixel[dataRead], 0, pixel_size - dataRead);
|
||||
}
|
||||
do {
|
||||
memcpy(dst, pixel, pixel_size);
|
||||
dst += pixel_size;
|
||||
} while (--count);
|
||||
} else { // Raw pixels.
|
||||
count *= pixel_size;
|
||||
const int dataRead = dev->read(dst, count);
|
||||
if (dataRead < 0) {
|
||||
cache.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
if ((uint)dataRead < count) {
|
||||
const size_t toCopy = count - dataRead;
|
||||
memset(&dst[dataRead], 0, toCopy);
|
||||
}
|
||||
dst += count;
|
||||
}
|
||||
|
||||
cache.append(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
auto data = cache.left(size);
|
||||
cache.remove(0, size);
|
||||
if (data.size() != size)
|
||||
data.clear();
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool LoadTGA(QIODevice *dev, const TgaHeader &tga, QImage &img)
|
||||
{
|
||||
img = imageAlloc(tga.width, tga.height, imageFormat(tga));
|
||||
if (img.isNull()) {
|
||||
qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
||||
qWarning() << "LoadTGA: Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height);
|
||||
return false;
|
||||
}
|
||||
|
||||
TgaHeaderInfo info(tga);
|
||||
|
||||
const int numAlphaBits = tga.flags & 0xf;
|
||||
uint pixel_size = (tga.pixel_size / 8);
|
||||
qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size;
|
||||
|
||||
bool hasAlpha = img.hasAlphaChannel();
|
||||
qint32 pixel_size = (tga.pixel_size / 8);
|
||||
qint32 line_size = qint32(tga.width) * pixel_size;
|
||||
qint64 size = qint64(tga.height) * line_size;
|
||||
if (size < 1) {
|
||||
// qDebug() << "This TGA file is broken with size " << size;
|
||||
return false;
|
||||
@ -251,7 +337,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
if (tga.colormap_size == 32) { // BGRA.
|
||||
char data[4];
|
||||
for (QRgb &rgb : colorTable) {
|
||||
const auto dataRead = s.readRawData(data, 4);
|
||||
const auto dataRead = dev->read(data, 4);
|
||||
if (dataRead < 4) {
|
||||
return false;
|
||||
}
|
||||
@ -261,7 +347,7 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
} else if (tga.colormap_size == 24) { // BGR.
|
||||
char data[3];
|
||||
for (QRgb &rgb : colorTable) {
|
||||
const auto dataRead = s.readRawData(data, 3);
|
||||
const auto dataRead = dev->read(data, 3);
|
||||
if (dataRead < 3) {
|
||||
return false;
|
||||
}
|
||||
@ -276,96 +362,8 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
img.setColorTable(colorTable);
|
||||
}
|
||||
|
||||
// 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.
|
||||
bool valid = true;
|
||||
int y_start;
|
||||
int y_step;
|
||||
int y_end;
|
||||
@ -379,9 +377,15 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
y_end = -1;
|
||||
}
|
||||
|
||||
uchar *src = image;
|
||||
|
||||
QByteArray cache;
|
||||
for (int y = y_start; y != y_end; y += y_step) {
|
||||
auto tgaLine = readTgaLine(dev, pixel_size, line_size, info.rle, cache);
|
||||
if (tgaLine.size() != qsizetype(line_size)) {
|
||||
qWarning() << "LoadTGA: Error while decoding a TGA raw line";
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
auto src = tgaLine.data();
|
||||
if (info.pal) {
|
||||
// Paletted.
|
||||
auto scanline = img.scanLine(y);
|
||||
@ -394,15 +398,16 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
scanline[x] = idx;
|
||||
}
|
||||
} else if (info.grey) {
|
||||
if (tga.pixel_size == 16) { // Greyscale with alpha.
|
||||
auto scanline = reinterpret_cast<QRgb *>(img.scanLine(y));
|
||||
// Greyscale.
|
||||
for (int x = 0; x < tga.width; x++) {
|
||||
if (tga.pixel_size == 16) {
|
||||
scanline[x] = qRgba(*src, *src, *src, *(src + 1));
|
||||
src += 2;
|
||||
}
|
||||
else {
|
||||
scanline[x] = qRgb(*src, *src, *src);
|
||||
} else { // Greyscale.
|
||||
auto scanline = img.scanLine(y);
|
||||
for (int x = 0; x < tga.width; x++) {
|
||||
scanline[x] = *src;
|
||||
src++;
|
||||
}
|
||||
}
|
||||
@ -421,9 +426,12 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
src += 3;
|
||||
}
|
||||
} else if (tga.pixel_size == 32) {
|
||||
auto div = (1 << numAlphaBits) - 1;
|
||||
if (div == 0)
|
||||
hasAlpha = false;
|
||||
for (int x = 0; x < tga.width; x++) {
|
||||
// ### TODO: verify with images having really some alpha data
|
||||
const uchar alpha = (src[3] << (8 - numAlphaBits));
|
||||
const int alpha = hasAlpha ? int((src[3]) << (8 - numAlphaBits)) * 255 / div : 255;
|
||||
scanline[x] = qRgba(src[2], src[1], src[0], alpha);
|
||||
src += 4;
|
||||
}
|
||||
@ -431,8 +439,11 @@ static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img)
|
||||
}
|
||||
}
|
||||
|
||||
// Free image.
|
||||
free(image);
|
||||
#ifdef QT_DEBUG
|
||||
if (!cache.isEmpty() && valid) {
|
||||
qDebug() << "LoadTGA: Found unused image data";
|
||||
}
|
||||
#endif
|
||||
|
||||
return valid;
|
||||
}
|
||||
@ -480,19 +491,14 @@ bool TGAHandler::read(QImage *outImage)
|
||||
dev->seek(TgaHeader::SIZE + tga.id_length);
|
||||
}
|
||||
|
||||
QDataStream s(dev);
|
||||
s.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
// Check image file format.
|
||||
if (s.atEnd()) {
|
||||
if (dev->atEnd()) {
|
||||
// qDebug() << "This TGA file is not valid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage img;
|
||||
bool result = LoadTGA(s, tga, img);
|
||||
|
||||
if (result == false) {
|
||||
if (!LoadTGA(dev, tga, img)) {
|
||||
// qDebug() << "Error loading TGA file.";
|
||||
return false;
|
||||
}
|
||||
@ -505,6 +511,8 @@ bool TGAHandler::write(const QImage &image)
|
||||
{
|
||||
if (image.format() == QImage::Format_Indexed8)
|
||||
return writeIndexed(image);
|
||||
if (image.format() == QImage::Format_Grayscale8 || image.format() == QImage::Format_Grayscale16)
|
||||
return writeGrayscale(image);
|
||||
return writeRGBA(image);
|
||||
}
|
||||
|
||||
@ -527,7 +535,7 @@ bool TGAHandler::writeIndexed(const QImage &image)
|
||||
|
||||
s << quint16(img.width()); // Image Width
|
||||
s << quint16(img.height()); // Image Height
|
||||
s << quint8(8); // Pixe Depth
|
||||
s << quint8(8); // Pixel Depth
|
||||
s << quint8(TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT); // Image Descriptor
|
||||
|
||||
for (auto &&rgb : ct) {
|
||||
@ -554,6 +562,51 @@ bool TGAHandler::writeIndexed(const QImage &image)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TGAHandler::writeGrayscale(const QImage &image)
|
||||
{
|
||||
QDataStream s(device());
|
||||
s.setByteOrder(QDataStream::LittleEndian);
|
||||
|
||||
QImage img(image);
|
||||
if (img.format() != QImage::Format_Grayscale8) {
|
||||
img = img.convertToFormat(QImage::Format_Grayscale8);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
qCritical() << "TGAHandler::writeGrayscale: image conversion to 8 bits grayscale failed!";
|
||||
return false;
|
||||
}
|
||||
|
||||
s << quint8(0); // ID Length
|
||||
s << quint8(0); // Color Map Type
|
||||
s << quint8(TGA_TYPE_GREY); // Image Type
|
||||
s << quint16(0); // First Entry Index
|
||||
s << quint16(0); // Color Map Length
|
||||
s << quint8(0); // Color map Entry Size
|
||||
s << quint16(0); // X-origin of Image
|
||||
s << quint16(0); // Y-origin of Image
|
||||
|
||||
s << quint16(img.width()); // Image Width
|
||||
s << quint16(img.height()); // Image Height
|
||||
s << quint8(8); // Pixel Depth
|
||||
s << quint8(TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT); // Image Descriptor
|
||||
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = 0; y < img.height(); y++) {
|
||||
auto ptr = img.constScanLine(y);
|
||||
for (int x = 0; x < img.width(); x++) {
|
||||
s << *(ptr + x);
|
||||
}
|
||||
if (s.status() != QDataStream::Ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TGAHandler::writeRGBA(const QImage &image)
|
||||
{
|
||||
QDataStream s(device());
|
||||
@ -574,7 +627,7 @@ bool TGAHandler::writeRGBA(const QImage &image)
|
||||
img = img.convertToFormat(QImage::Format_RGB32);
|
||||
}
|
||||
if (img.isNull()) {
|
||||
qDebug() << "TGAHandler::write: image conversion to 32 bits failed!";
|
||||
qCritical() << "TGAHandler::writeRGBA: image conversion to 32 bits failed!";
|
||||
return false;
|
||||
}
|
||||
static constexpr quint8 originTopLeft = TGA_ORIGIN_UPPER + TGA_ORIGIN_LEFT; // 0x20
|
||||
|
@ -29,6 +29,8 @@ public:
|
||||
private:
|
||||
bool writeIndexed(const QImage &image);
|
||||
|
||||
bool writeGrayscale(const QImage &image);
|
||||
|
||||
bool writeRGBA(const QImage &image);
|
||||
|
||||
const QScopedPointer<TGAHandlerPrivate> d;
|
||||
|
Reference in New Issue
Block a user