1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-07-02 16:38:41 +00:00
luanti/irr/src/CImageLoaderTGA.cpp
Ælla Chiana Moskopp 60cd1e4529 Correctly render transparency in TGA type 1 with color format A1R5G5B5
The branch removed in this patch handled color format A1R5G5B5 specially
when creating a texture from a TGA type 1 file, i.e. an image that has a
colormap. It did not handle the 1-bit alpha channel correctly, rendering
transparent pixels black instead.

Since the colormap is converted to A8R8G8B8 earlier anyways, the code
for the general case is able to handle this scenario already – at the
expense of making the created texture use twice as much GPU memory as
necessary.
2024-11-06 20:07:05 +01:00

235 lines
7.6 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CImageLoaderTGA.h"
#include "IReadFile.h"
#include "coreutil.h"
#include "os.h"
#include "CColorConverter.h"
#include "CImage.h"
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
namespace irr
{
namespace video
{
//! returns true if the file maybe is able to be loaded by this class
//! based on the file extension (e.g. ".tga")
bool CImageLoaderTGA::isALoadableFileExtension(const io::path &filename) const
{
return core::hasFileExtension(filename, "tga");
}
//! loads a compressed tga.
u8 *CImageLoaderTGA::loadCompressedImage(io::IReadFile *file, const STGAHeader &header) const
{
// This was written and sent in by Jon Pry, thank you very much!
// I only changed the formatting a little bit.
const u32 bytesPerPixel = header.PixelDepth / 8;
const u32 imageSize = header.ImageHeight * header.ImageWidth * bytesPerPixel;
u8 *data = new u8[imageSize];
u32 currentByte = 0;
while (currentByte < imageSize) {
u8 chunkheader = 0;
file->read(&chunkheader, sizeof(u8)); // Read The Chunk's Header
if (chunkheader < 128) { // If The Chunk Is A 'RAW' Chunk
chunkheader++; // Add 1 To The Value To Get Total Number Of Raw Pixels
const u32 bytesToRead = bytesPerPixel * chunkheader;
if (currentByte + bytesToRead <= imageSize) {
file->read(&data[currentByte], bytesToRead);
currentByte += bytesToRead;
} else {
os::Printer::log("Compressed TGA file RAW chunk tries writing beyond buffer", file->getFileName(), ELL_WARNING);
break;
}
} else {
// thnx to neojzs for some fixes with this code
// If It's An RLE Header
chunkheader -= 127; // Subtract 127 To Get Rid Of The ID Bit
u32 dataOffset = currentByte;
if (dataOffset + bytesPerPixel < imageSize) {
file->read(&data[dataOffset], bytesPerPixel);
currentByte += bytesPerPixel;
} else {
os::Printer::log("Compressed TGA file RLE headertries writing beyond buffer", file->getFileName(), ELL_WARNING);
break;
}
for (u32 counter = 1; counter < chunkheader; counter++) {
if (currentByte + bytesPerPixel <= imageSize) {
for (u32 elementCounter = 0; elementCounter < bytesPerPixel; elementCounter++) {
data[currentByte + elementCounter] = data[dataOffset + elementCounter];
}
}
currentByte += bytesPerPixel;
}
}
}
return data;
}
//! returns true if the file maybe is able to be loaded by this class
bool CImageLoaderTGA::isALoadableFileFormat(io::IReadFile *file) const
{
if (!file)
return false;
STGAFooter footer;
memset(&footer, 0, sizeof(STGAFooter));
file->seek(file->getSize() - sizeof(STGAFooter));
file->read(&footer, sizeof(STGAFooter));
return (!strcmp(footer.Signature, "TRUEVISION-XFILE.")); // very old tgas are refused.
}
//! creates a surface from the file
IImage *CImageLoaderTGA::loadImage(io::IReadFile *file) const
{
STGAHeader header;
u32 *palette = 0;
file->read(&header, sizeof(STGAHeader));
#ifdef __BIG_ENDIAN__
header.ColorMapLength = os::Byteswap::byteswap(header.ColorMapLength);
header.ImageWidth = os::Byteswap::byteswap(header.ImageWidth);
header.ImageHeight = os::Byteswap::byteswap(header.ImageHeight);
#endif
if (!checkImageDimensions(header.ImageWidth, header.ImageHeight)) {
os::Printer::log("Image dimensions too large in file", file->getFileName(), ELL_ERROR);
return 0;
}
// skip image identification field
if (header.IdLength)
file->seek(header.IdLength, true);
if (header.ColorMapType) {
// Create 32 bit palette
// `core::max_()` is not used here because it takes its inputs as references. Since `header` is packed, use the macro `MAX()` instead:
const irr::u16 paletteSize = MAX((u16)256u, header.ColorMapLength); // ColorMapLength can lie, but so far we only use palette for 8-bit, so ensure it has 256 entries
palette = new u32[paletteSize];
if (paletteSize > header.ColorMapLength) {
// To catch images using palette colors with invalid indices
const irr::u32 errorCol = irr::video::SColor(255, 255, 0, 205).color; // bright magenta
for (irr::u16 i = header.ColorMapLength; i < paletteSize; ++i)
palette[i] = errorCol;
}
// read color map
u8 *colorMap = new u8[header.ColorMapEntrySize / 8 * header.ColorMapLength];
file->read(colorMap, header.ColorMapEntrySize / 8 * header.ColorMapLength);
// convert to 32-bit palette
switch (header.ColorMapEntrySize) {
case 16:
CColorConverter::convert_A1R5G5B5toA8R8G8B8(colorMap, header.ColorMapLength, palette);
break;
case 24:
CColorConverter::convert_B8G8R8toA8R8G8B8(colorMap, header.ColorMapLength, palette);
break;
case 32:
CColorConverter::convert_B8G8R8A8toA8R8G8B8(colorMap, header.ColorMapLength, palette);
break;
}
delete[] colorMap;
}
// read image
u8 *data = 0;
if (header.ImageType == 1 || // Uncompressed, color-mapped images.
header.ImageType == 2 || // Uncompressed, RGB images
header.ImageType == 3 // Uncompressed, black and white images
) {
const s32 imageSize = header.ImageHeight * header.ImageWidth * (header.PixelDepth / 8);
data = new u8[imageSize];
file->read(data, imageSize);
} else if (header.ImageType == 10) {
// Runlength encoded RGB images
data = loadCompressedImage(file, header);
} else {
os::Printer::log("Unsupported TGA file type", file->getFileName(), ELL_ERROR);
delete[] palette;
return 0;
}
IImage *image = 0;
switch (header.PixelDepth) {
case 8: {
if (header.ImageType == 3) { // grey image
image = new CImage(ECF_R8G8B8,
core::dimension2d<u32>(header.ImageWidth, header.ImageHeight));
if (image)
CColorConverter::convert8BitTo24Bit((u8 *)data,
(u8 *)image->getData(),
header.ImageWidth, header.ImageHeight,
0, 0, (header.ImageDescriptor & 0x20) == 0);
} else {
// Colormap is converted to A8R8G8B8 at this point thus the code can handle all color formats.
// This wastes some texture memory, but is less of a third of the code that does this optimally.
// If you want to refactor this: The possible color formats here are A1R5G5B5, B8G8R8, B8G8R8A8.
image = new CImage(ECF_A8R8G8B8, core::dimension2d<u32>(header.ImageWidth, header.ImageHeight));
if (image)
CColorConverter::convert8BitTo32Bit((u8 *)data,
(u8 *)image->getData(),
header.ImageWidth, header.ImageHeight,
(u8 *)palette, 0,
(header.ImageDescriptor & 0x20) == 0);
}
} break;
case 16:
image = new CImage(ECF_A1R5G5B5,
core::dimension2d<u32>(header.ImageWidth, header.ImageHeight));
if (image)
CColorConverter::convert16BitTo16Bit((s16 *)data,
(s16 *)image->getData(), header.ImageWidth, header.ImageHeight, 0, (header.ImageDescriptor & 0x20) == 0);
break;
case 24:
image = new CImage(ECF_R8G8B8,
core::dimension2d<u32>(header.ImageWidth, header.ImageHeight));
if (image)
CColorConverter::convert24BitTo24Bit(
(u8 *)data, (u8 *)image->getData(), header.ImageWidth, header.ImageHeight, 0, (header.ImageDescriptor & 0x20) == 0, true);
break;
case 32:
image = new CImage(ECF_A8R8G8B8,
core::dimension2d<u32>(header.ImageWidth, header.ImageHeight));
if (image)
CColorConverter::convert32BitTo32Bit((s32 *)data,
(s32 *)image->getData(), header.ImageWidth, header.ImageHeight, 0, (header.ImageDescriptor & 0x20) == 0);
break;
default:
os::Printer::log("Unsupported TGA format", file->getFileName(), ELL_ERROR);
break;
}
delete[] data;
delete[] palette;
return image;
}
//! creates a loader which is able to load tgas
IImageLoader *createImageLoaderTGA()
{
return new CImageLoaderTGA();
}
} // end namespace video
} // end namespace irr