From 5f3810c0d177dd8cb78c7360401357ff6e7600b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Ker=C3=A4nen?= Date: Thu, 26 Dec 2019 20:24:31 +0200 Subject: [PATCH] Resources: Use libgui's TGA loader libdoomsday had the old TGA loader that works with FS1. That is now replaced with a call to de::Image::fromData() that uses Qt for now, and stb_image in the future. libgui's TGA loader can read RLE-formatted images unlike the old TGA loader. libgui's TGA loader was modified to allow reading colormapped 8-bit images. IssueID #2381 --- .../include}/resource/tga.h | 19 +- doomsday/apps/client/src/resource/image.cpp | 18 +- doomsday/apps/client/src/resource/tga.cpp | 60 ++++++ .../apps/libdoomsday/src/resource/tga.cpp | 202 ------------------ doomsday/sdk/libgui/src/graphics/image.cpp | 43 +++- 5 files changed, 112 insertions(+), 230 deletions(-) rename doomsday/apps/{libdoomsday/include/doomsday => client/include}/resource/tga.h (74%) create mode 100644 doomsday/apps/client/src/resource/tga.cpp delete mode 100644 doomsday/apps/libdoomsday/src/resource/tga.cpp diff --git a/doomsday/apps/libdoomsday/include/doomsday/resource/tga.h b/doomsday/apps/client/include/resource/tga.h similarity index 74% rename from doomsday/apps/libdoomsday/include/doomsday/resource/tga.h rename to doomsday/apps/client/include/resource/tga.h index 6ad3906bcc..e88a74c9d1 100644 --- a/doomsday/apps/libdoomsday/include/doomsday/resource/tga.h +++ b/doomsday/apps/client/include/resource/tga.h @@ -1,6 +1,6 @@ /** @file tga.h Truevision TGA (a.k.a Targa) image reader * - * @authors Copyright © 2003-2017 Jaakko Keränen + * @authors Copyright © 2003-2019 Jaakko Keränen * @authors Copyright © 2009-2013 Daniel Swanson * * @par License @@ -18,17 +18,13 @@ * 02110-1301 USA */ -#ifndef LIBDOOMSDAY_RESOURCE_TGA_H -#define LIBDOOMSDAY_RESOURCE_TGA_H +#pragma once #ifdef __cplusplus #include #include -/// @addtogroup resource -///@{ - /** * Loads a 24-bit or a 32-bit image (24-bit color + 8-bit alpha). * @@ -37,15 +33,6 @@ * * @return Non-zero iff the image is loaded successfully. */ -LIBDOOMSDAY_PUBLIC uint8_t *TGA_Load(de::FileHandle &file, de::Vector2ui &outSize, int &pixelSize); - -/** - * @return Textual message detailing the last error encountered else @c 0. - */ -LIBDOOMSDAY_PUBLIC char const *TGA_LastError(); - -///@} +uint8_t *TGA_Load(de::FileHandle &file, de::Vector2ui &outSize, int &pixelSize); #endif // __cplusplus - -#endif // LIBDOOMSDAY_RESOURCE_TGA_H diff --git a/doomsday/apps/client/src/resource/image.cpp b/doomsday/apps/client/src/resource/image.cpp index 7915752d1c..d3fb22e98a 100644 --- a/doomsday/apps/client/src/resource/image.cpp +++ b/doomsday/apps/client/src/resource/image.cpp @@ -20,6 +20,7 @@ #include "de_platform.h" #include "resource/image.h" +#include "resource/tga.h" #include #include @@ -35,7 +36,6 @@ #include #include -#include #include "gl/gl_tex.h" @@ -58,7 +58,7 @@ struct GraphicFileType bool (*interpretFunc)(FileHandle &hndl, String filePath, image_t &img); - char const *(*getLastErrorFunc)(); ///< Can be NULL. + //char const *(*getLastErrorFunc)(); ///< Can be NULL. }; static bool interpretPcx(FileHandle &hndl, String /*filePath*/, image_t &img) @@ -82,16 +82,16 @@ static bool interpretTga(FileHandle &hndl, String /*filePath*/, image_t &img) { Image_Init(img); img.pixels = TGA_Load(hndl, img.size, img.pixelSize); - return (0 != img.pixels); + return img.pixels != nullptr; } // Graphic resource types. static GraphicFileType const graphicTypes[] = { - { "PNG", "png", interpretPng, 0 }, - { "JPG", "jpg", interpretJpg, 0 }, // TODO: add alternate "jpeg" extension - { "TGA", "tga", interpretTga, TGA_LastError }, - { "PCX", "pcx", interpretPcx, PCX_LastError }, - { "", "", 0, 0 } // Terminate. + { "PNG", "png", interpretPng /*, 0*/ }, + { "JPG", "jpg", interpretJpg /*, 0*/ }, // TODO: add alternate "jpeg" extension + { "TGA", "tga", interpretTga /*, TGA_LastError*/ }, + { "PCX", "pcx", interpretPcx /*, PCX_LastError*/ }, + { "", "", nullptr /*, 0*/ } // Terminate. }; static GraphicFileType const *guessGraphicFileTypeFromFileName(String fileName) @@ -109,7 +109,7 @@ static GraphicFileType const *guessGraphicFileTypeFromFileName(String fileName) } } } - return 0; // Unknown. + return nullptr; // Unknown. } static void interpretGraphic(FileHandle &hndl, String filePath, image_t &img) diff --git a/doomsday/apps/client/src/resource/tga.cpp b/doomsday/apps/client/src/resource/tga.cpp new file mode 100644 index 0000000000..01bedb0b43 --- /dev/null +++ b/doomsday/apps/client/src/resource/tga.cpp @@ -0,0 +1,60 @@ +/** @file tga.cpp Truevision TGA (a.k.a Targa) image reader + * + * @authors Copyright © 2003-2019 Jaakko Keränen + * @authors Copyright © 2009-2013 Daniel Swanson + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the 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 program is distributed in the hope that it + * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. You should have received a copy of the GNU + * General Public License along with this program; if not, see: + * http://www.gnu.org/licenses + */ + +#include "resource/tga.h" +#include "dd_share.h" + +#include +#include +#include +#include +#include + +using namespace de; + +uint8_t *TGA_Load(FileHandle &file, Vector2ui &outSize, int &pixelSize) +{ + const size_t initPos = file.tell(); + + // Read the file into a memory buffer. + Block fileContents(file.length() - initPos); + file.read(fileContents.data(), fileContents.size()); + file.seek(initPos, SeekSet); + + Image img = Image::fromData(fileContents, ".tga"); + if (img.isNull()) + { + return nullptr; + } + + // De-index colormapped images. + if (img.toQImage().format() == QImage::Format_Indexed8) + { + img = img.toQImage().convertToFormat(QImage::Format_RGBA8888); + } + + outSize = img.size(); + pixelSize = img.depth() / 8; + + // Return a copy of the pixel data. + const size_t numBytes = size_t(img.byteCount()); + uint8_t * retBuf = reinterpret_cast(M_Malloc(numBytes)); + memcpy(retBuf, img.bits(), numBytes); + return retBuf; +} diff --git a/doomsday/apps/libdoomsday/src/resource/tga.cpp b/doomsday/apps/libdoomsday/src/resource/tga.cpp deleted file mode 100644 index e72ea2043b..0000000000 --- a/doomsday/apps/libdoomsday/src/resource/tga.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/** @file tga.cpp Truevision TGA (a.k.a Targa) image reader - * - * @authors Copyright © 2003-2017 Jaakko Keränen - * @authors Copyright © 2009-2013 Daniel Swanson - * - * @par License - * GPL: http://www.gnu.org/licenses/gpl.html - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the 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 program is distributed in the hope that it - * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. You should have received a copy of the GNU - * General Public License along with this program; if not, see: - * http://www.gnu.org/licenses - */ - -#include "doomsday/resource/tga.h" -#include "dd_share.h" - -#include -#include -#include -#include - -using namespace de; - -enum { - TGA_FALSE, - TGA_TRUE, - TGA_TARGA24, // rgb888 - TGA_TARGA32 // rgba8888 -}; - -#pragma pack(1) -typedef struct { - uint8_t idLength; // Identification field size in bytes. - uint8_t colorMapType; // Type of the color map. - uint8_t imageType; // Image type code. -} tga_header_t; - -// Color map specification. -typedef struct { - int16_t index; // Index of first color map entry. - int16_t length; // Number of color map entries. - uint8_t entrySize; // Number of bits in a color map entry (16/24/32). -} tga_colormapspec_t; - -// Image specification flags: -#define ISF_SCREEN_ORIGIN_UPPER 0x1 // Upper left-hand corner screen origin. -// Data interleaving: -#define ISF_INTERLEAVE_TWOWAY 0x2 // Two-way (even/odd) interleaving. -#define ISF_INTERLEAVE_FOURWAY 0x4 // Four-way interleaving. - -// Image specification. -typedef struct { - uint8_t flags; - int16_t xOrigin; // X coordinate of lower left corner. - int16_t yOrigin; // Y coordinate of lower left corner. - int16_t width; // Width of the image in pixels. - int16_t height; // Height of the image in pixels. - uint8_t pixelDepth; // Number of bits in a pixel (16/24/32). - uint8_t attributeBits; -} tga_imagespec_t; -#pragma pack() - -static char *lastTgaErrorMsg = 0; /// @todo potentially never free'd - -static void TGA_SetLastError(char const *msg) -{ - size_t len; - if (0 == msg || 0 == (len = strlen(msg))) - { - if (lastTgaErrorMsg != 0) - { - M_Free(lastTgaErrorMsg); - } - lastTgaErrorMsg = 0; - return; - } - - lastTgaErrorMsg = (char *) M_Realloc(lastTgaErrorMsg, len+1); - strcpy(lastTgaErrorMsg, msg); -} - -static uint8_t readByte(FileHandle &f) -{ - uint8_t v; - f.read(&v, 1); - return v; -} - -static int16_t readShort(FileHandle &f) -{ - int16_t v; - f.read((uint8_t *)&v, sizeof(v)); - return DD_SHORT(v); -} - -static void readHeader(tga_header_t *dst, FileHandle &file) -{ - dst->idLength = readByte(file); - dst->colorMapType = readByte(file); - dst->imageType = readByte(file); -} - -static void readColorMapSpec(tga_colormapspec_t *dst, FileHandle &file) -{ - dst->index = readShort(file); - dst->length = readShort(file); - dst->entrySize = readByte(file); -} - -static void readImageSpec(tga_imagespec_t *dst, FileHandle &file) -{ - dst->xOrigin = readShort(file); - dst->yOrigin = readShort(file); - dst->width = readShort(file); - dst->height = readShort(file); - dst->pixelDepth = readByte(file); - - uint8_t bits = readByte(file); - - /* - * attributeBits:4; // Attribute bits associated with each pixel. - * reserved:1; // A reserved bit; must be 0. - * screenOrigin:1; // Location of screen origin; must be 0. - * dataInterleave:2; // TGA_INTERLEAVE_* - */ - dst->flags = 0 | ((bits & 6)? ISF_SCREEN_ORIGIN_UPPER : 0) | - ((bits & 7)? ISF_INTERLEAVE_TWOWAY : (bits & 8)? ISF_INTERLEAVE_FOURWAY : 0); - dst->attributeBits = (bits & 0xf); -} - -const char* TGA_LastError(void) -{ - if (lastTgaErrorMsg) - return lastTgaErrorMsg; - return 0; -} - -uint8_t *TGA_Load(FileHandle &file, Vector2ui &outSize, int &pixelSize) -{ - uint8_t *dstBuf = 0; - - size_t const initPos = file.tell(); - - tga_header_t header; - readHeader(&header, file); - - tga_colormapspec_t colorMapSpec; - readColorMapSpec(&colorMapSpec, file); - - tga_imagespec_t imageSpec; - readImageSpec(&imageSpec, file); - - outSize = Vector2ui(imageSpec.width, imageSpec.height); - - if (header.imageType != 2 || - (imageSpec.pixelDepth != 32 && imageSpec.pixelDepth != 24) || - (imageSpec.attributeBits != 8 && - imageSpec.attributeBits != 0) || - (imageSpec.flags & ISF_SCREEN_ORIGIN_UPPER)) - { - TGA_SetLastError("Unsupported format."); - file.seek(initPos, SeekSet); - return 0; - } - - // Determine format. - //int const format = (imageSpec.pixelDepth == 24? TGA_TARGA24 : TGA_TARGA32); - - pixelSize = (imageSpec.pixelDepth == 24? 3 : 4); - - // Read the pixel data. - int const numPels = outSize.x * outSize.y; - uint8_t *srcBuf = (uint8_t *) M_Malloc(numPels * pixelSize); - file.read(srcBuf, numPels * pixelSize); - - // "Unpack" the pixels (origin in the lower left corner). - // TGA pixels are in BGRA format. - dstBuf = (uint8_t *) M_Malloc(4 * numPels); - uint8_t const *src = srcBuf; - for (int y = outSize.y - 1; y >= 0; y--) - for (int x = 0; x < (signed) outSize.x; x++) - { - uint8_t *dst = &dstBuf[(y * outSize.x + x) * pixelSize]; - - dst[2] = *src++; - dst[1] = *src++; - dst[0] = *src++; - if (pixelSize == 4) dst[3] = *src++; - } - M_Free(srcBuf); - - TGA_SetLastError(0); // Success. - file.seek(initPos, SeekSet); - - return dstBuf; -} diff --git a/doomsday/sdk/libgui/src/graphics/image.cpp b/doomsday/sdk/libgui/src/graphics/image.cpp index 495d439936..300621db19 100644 --- a/doomsday/sdk/libgui/src/graphics/image.cpp +++ b/doomsday/sdk/libgui/src/graphics/image.cpp @@ -149,6 +149,8 @@ static QImage load(Block const &data) namespace tga { +/// @todo Replace this Targa loader with stb_image. + struct Header : public IReadable { enum Flag @@ -168,8 +170,9 @@ struct Header : public IReadable enum ImageType { - RGB = 2, ///< Uncompressed RGB. - RleRGB = 10 ///< Run length encoded RGB. + ColorMapped = 1, // Uncompressed and color-mapped. + RGB = 2, // Uncompressed RGB. + RleRGB = 10 // Run-length encoded RGB. }; Block identification; @@ -236,6 +239,11 @@ static bool recognize(Block const &data) { Header header; Reader(data) >> header; + if (header.imageType == Header::ColorMapped && header.colorMapType == Header::ColorMap256 && + header.depth == 8) + { + return true; + } return (header.imageType == Header::RGB || header.imageType == Header::RleRGB) && header.colorMapType == Header::ColorMapNone && (header.depth == 24 || header.depth == 32); @@ -254,7 +262,7 @@ static QImage load(Block const &data) int const pixelSize = header.depth / 8; QImage img(QSize(header.size.x, header.size.y), - pixelSize == 4? QImage::Format_ARGB32 : QImage::Format_RGB888); + pixelSize == 4 || header.attrib > 0? QImage::Format_ARGB32 : QImage::Format_RGB888); dbyte *base = img.bits(); bool const isUpperOrigin = header.flags.testFlag(Header::ScreenOriginUpper); @@ -317,6 +325,35 @@ static QImage load(Block const &data) } } } + else if (header.imageType == Header::ColorMapped) + { + DENG2_ASSERT(header.colorMapType == Header::ColorMap256); + DENG2_ASSERT(header.depth == 8); + + // Read the colormap. + QVector colorTable(256); + for (int i = 0; i < header.mapCount; ++i) + { + uint8_t color[4] = {0, 0, 0, 255}; + ByteRefArray buf(color, 4); + input.readBytes(header.mapEntrySize / 8, buf); + + DENG2_ASSERT(header.mapIndex + i < 256); + + colorTable[header.mapIndex + i] = qRgba(color[0], color[1], color[2], color[3]); + } + + img = QImage(QSize(header.size.x, header.size.y), QImage::Format_Indexed8); + img.setColorTable(colorTable); + + dbyte *base = img.bits(); + for (int y = 0; y < header.size.y; y++) + { + int inY = (isUpperOrigin? y : (header.size.y - y - 1)); + ByteRefArray line(base + inY * img.bytesPerLine(), header.size.x); + input.readBytesFixedSize(line); + } + } if (pixelSize == 3) {