Fix loading some particular TGA files (multiple scanlines compressed, invalid alpha information, etc.)

This commit is contained in:
David Capello 2020-03-19 20:52:03 -03:00
parent de1019342a
commit 1826d66f10

View File

@ -1,12 +1,9 @@
// Aseprite
// Copyright (C) 2019 Igara Studio S.A.
// Copyright (C) 2019-2020 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
//
// tga.c - Based on the code of Tim Gunn, Michal Mertl, Salvador
// Eduardo Tropea and Peter Wang.
#ifdef HAVE_CONFIG_H
#include "config.h"
@ -59,150 +56,431 @@ FileFormat* CreateTgaFormat()
return new TgaFormat;
}
static void rle_tga_read8(FILE* f, uint8_t* address, int w, int type)
{
uint8_t* end = address + (w * (type == 1 ? 1: 2));
uint8_t value;
int count, g;
int c = 0;
namespace {
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
value = fgetc(f);
while (count--) {
if (type == 1) {
if (address+1 < end)
*(address++) = value;
class TgaDecoder {
struct Header {
uint8_t idLength;
uint8_t palType;
uint8_t imageType;
uint16_t palOrigin;
uint16_t palLength;
uint8_t palDepth;
uint16_t xOrigin;
uint16_t yOrigin;
uint16_t width;
uint16_t height;
uint8_t bitsPerPixel;
uint8_t imageDescriptor;
};
enum ImageType {
NoImage = 0,
UncompressedIndexed = 1,
UncompressedRgb = 2,
UncompressedGray = 3,
RleIndexed = 9,
RleRgb = 10,
RleGray = 11,
};
public:
TgaDecoder(FILE* f)
: m_f(f)
, m_palette(0, 0)
, m_alphaHistogram(256) {
readHeader();
if (m_header.palType == 1)
readPaletteData();
}
int imageType() const { return m_header.imageType; }
int bitsPerPixel() const { return m_header.bitsPerPixel; }
bool hasPalette() const { return (m_header.palLength > 0); }
const Palette& palette() const { return m_palette; }
bool hasAlpha() const { return m_hasAlpha; }
bool isGray() const {
return (m_header.imageType == UncompressedGray ||
m_header.imageType == RleGray);
}
bool validPalType() const {
return
// Indexed with palette
((m_header.imageType == UncompressedIndexed || m_header.imageType == RleIndexed) &&
m_header.bitsPerPixel == 8 &&
m_header.palType == 1) ||
// Grayscale without palette
((m_header.imageType == UncompressedGray || m_header.imageType == RleGray) &&
m_header.bitsPerPixel == 8 &&
m_header.palType == 0) ||
// Non-indexed without palette
(m_header.bitsPerPixel > 8 &&
m_header.palType == 0);
}
bool getImageSpec(ImageSpec& spec) const {
switch (m_header.imageType) {
case UncompressedIndexed:
case RleIndexed:
if (m_header.bitsPerPixel != 8)
return false;
spec = ImageSpec(ColorMode::INDEXED,
m_header.width,
m_header.height);
return true;
case UncompressedRgb:
case RleRgb:
if (m_header.bitsPerPixel != 15 &&
m_header.bitsPerPixel != 16 &&
m_header.bitsPerPixel != 24 &&
m_header.bitsPerPixel != 32)
return false;
spec = ImageSpec(ColorMode::RGB,
m_header.width,
m_header.height);
return true;
case UncompressedGray:
case RleGray:
if (m_header.bitsPerPixel != 8)
return false;
spec = ImageSpec(ColorMode::GRAYSCALE,
m_header.width,
m_header.height);
return true;
}
return false;
}
bool readImageData(FileOp* fop, Image* image) {
// Bit 4 means right-to-left, else left-to-right
// Bit 5 means top-to-bottom, else bottom-to-top
m_iterator = ImageDataIterator(
image,
(m_header.imageDescriptor & 0x10) ? false: true,
(m_header.imageDescriptor & 0x20) ? true: false,
m_header.width,
m_header.height);
for (int y=0; y<m_header.height; ++y) {
switch (m_header.imageType) {
case UncompressedIndexed:
ASSERT(m_header.bitsPerPixel == 8);
if (readUncompressedData<uint8_t>(&TgaDecoder::fget8_as_index))
return true;
break;
case UncompressedRgb:
switch (m_header.bitsPerPixel) {
case 15:
case 16:
if (readUncompressedData<uint32_t>(&TgaDecoder::fget16_as_rgba))
return true;
break;
case 24:
if (readUncompressedData<uint32_t>(&TgaDecoder::fget24_as_rgba))
return true;
break;
case 32:
if (readUncompressedData<uint32_t>(&TgaDecoder::fget32_as_rgba))
return true;
break;
default:
ASSERT(false);
break;
}
break;
case UncompressedGray:
ASSERT(m_header.bitsPerPixel == 8);
if (readUncompressedData<uint16_t>(&TgaDecoder::fget8_as_gray))
return true;
break;
case RleIndexed:
ASSERT(m_header.bitsPerPixel == 8);
if (readRleData<uint8_t>(&TgaDecoder::fget8_as_gray))
return true;
break;
case RleRgb:
switch (m_header.bitsPerPixel) {
case 15:
case 16:
if (readRleData<uint32_t>(&TgaDecoder::fget16_as_rgba))
return true;
break;
case 24:
if (readRleData<uint32_t>(&TgaDecoder::fget24_as_rgba))
return true;
break;
case 32:
if (readRleData<uint32_t>(&TgaDecoder::fget32_as_rgba))
return true;
break;
default:
ASSERT(false);
break;
}
break;
case RleGray:
ASSERT(m_header.bitsPerPixel == 8);
if (readRleData<uint16_t>(&TgaDecoder::fget8_as_gray))
return true;
break;
}
fop->setProgress(float(y) / float(m_header.height));
if (fop->isStop())
break;
}
return true;
}
// Fix alpha channel for images with invalid alpha channel values
void postProcessImageData(Image* image) {
if (image->colorMode() != ColorMode::RGB ||
!m_hasAlpha)
return;
int count = 0;
for (int i=0; i<256; ++i)
if (m_alphaHistogram[i] > 0)
++count;
// If all pixels are transparent (alpha=0), make all pixels opaque
// (alpha=255).
if (count == 1 && m_alphaHistogram[0] > 0) {
LockImageBits<RgbTraits> bits(image);
auto it = bits.begin(), end = bits.end();
for (; it != end; ++it) {
color_t c = *it;
*it = rgba(rgba_getr(c),
rgba_getg(c),
rgba_getb(c), 255);
}
}
}
private:
class ImageDataIterator {
public:
ImageDataIterator() { }
ImageDataIterator(Image* image,
bool leftToRight,
bool topToBottom,
int w, int h) {
m_image = image;
m_w = w;
m_h = h;
m_x = (leftToRight ? 0: w-1);
m_y = (topToBottom ? 0: h-1);
m_dx = (leftToRight ? +1: -1);
m_dy = (topToBottom ? +1: -1);
calcPtr();
}
template<typename T>
bool next(const T value) {
*((T*)m_ptr) = value;
m_x += m_dx;
m_ptr += m_dx*sizeof(T);
if ((m_dx < 0 && m_x < 0) ||
(m_dx > 0 && m_x == m_w)) {
m_x = (m_dx > 0 ? 0: m_w-1);
m_y += m_dy;
if ((m_dy < 0 && m_y < 0) ||
(m_dy > 0 && m_y == m_h)) {
return true;
}
else {
if (address+2 < end)
*((uint16_t*)address) = graya(value, 255);
address += 2;
calcPtr();
}
return false;
}
private:
void calcPtr() {
m_ptr = m_image->getPixelAddress(m_x, m_y);
}
Image* m_image;
int m_x, m_y;
int m_w, m_h;
int m_dx, m_dy;
uint8_t* m_ptr;
};
void readHeader() {
m_header.idLength = fgetc(m_f);
m_header.palType = fgetc(m_f);
m_header.imageType = fgetc(m_f);
m_header.palOrigin = fgetw(m_f);
m_header.palLength = fgetw(m_f);
m_header.palDepth = fgetc(m_f);
m_header.xOrigin = fgetw(m_f);
m_header.yOrigin = fgetw(m_f);
m_header.width = fgetw(m_f);
m_header.height = fgetw(m_f);
m_header.bitsPerPixel = fgetc(m_f);
m_header.imageDescriptor = fgetc(m_f);
char imageId[256];
if (m_header.idLength > 0)
fread(imageId, 1, m_header.idLength, m_f);
#if 0
// In the best case the "alphaBits" should be valid, but there are
// invalid TGA files out there which don't indicate the
// "alphaBits" correctly, so they could be 0 and use the alpha
// channel anyway on each pixel.
int alphaBits = (m_header.imageDescriptor & 15);
TRACEARGS("TGA: bitsPerPixel", (int)m_header.bitsPerPixel,
"alphaBits", alphaBits);
m_hasAlpha =
(m_header.bitsPerPixel == 32 && alphaBits == 8) ||
(m_header.bitsPerPixel == 16 && alphaBits == 1);
#else
// So to detect if a 32bpp or 16bpp TGA image has alpha, we'll use
// the "m_alphaHistogram" to check if there are different alpha
// values. If there is only one alpha value (all 0 or all 255),
// we create an opaque image anyway.
m_hasAlpha =
(m_header.bitsPerPixel == 32) ||
(m_header.bitsPerPixel == 16);
#endif
}
void readPaletteData() {
m_palette.resize(m_header.palLength);
for (int i=0; i<m_header.palLength; ++i) {
switch (m_header.palDepth) {
case 15:
case 16: {
const int c = fgetw(m_f);
m_palette.setEntry(
i,
doc::rgba(scale_5bits_to_8bits((c >> 10) & 0x1F),
scale_5bits_to_8bits((c >> 5) & 0x1F),
scale_5bits_to_8bits(c & 0x1F), 255));
break;
}
case 24:
case 32: {
const int b = fgetc(m_f);
const int g = fgetc(m_f);
const int r = fgetc(m_f);
int a;
if (m_header.palDepth == 32)
a = fgetc(m_f);
else
a = 255;
m_palette.setEntry(i, doc::rgba(r, g, b, a));
break;
}
}
}
else {
count++;
c += count;
if (type == 1) {
if (address+count < end)
fread(address, 1, count, f);
address += count;
}
template<typename T>
bool readUncompressedData(color_t (TgaDecoder::*readPixel)()) {
for (int x=0; x<m_header.width; ++x) {
if (m_iterator.next<T>((this->*readPixel)()))
return true;
}
return false;
}
// In the best case (TGA 2.0 spec) this should read just one
// scanline, but in old TGA versions (1.0) it was possible to save
// several scanlines with the same RLE data.
//
// Returns true when are are done.
template<typename T>
bool readRleData(color_t (TgaDecoder::*readPixel)()) {
for (int x=0; x<m_header.width && !feof(m_f); ) {
int c = fgetc(m_f);
if (c & 0x80) {
c = (c & 0x7f) + 1;
x += c;
const T pixel = (this->*readPixel)();
while (c-- > 0)
if (m_iterator.next<T>(pixel))
return true;
}
else {
for (g=0; g<count; g++) {
int c = fgetc(f);
if (address+2 < end)
*((uint16_t*)address) = graya(c, 255);
address += 2;
++c;
x += c;
while (c-- > 0) {
if (m_iterator.next<T>((this->*readPixel)()))
return true;
}
}
}
} while (c < w);
}
return false;
}
static void rle_tga_read32(FILE* f, uint32_t* address, int w, bool withAlpha)
{
uint32_t* end = address + w;
unsigned char value[4];
int count;
int c = 0;
color_t fget8_as_index() {
return fgetc(m_f);
}
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
fread(value, 1, 4, f);
while (count--) {
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0],
withAlpha ? value[3]: 255);
}
}
color_t fget8_as_gray() {
return doc::graya(fgetc(m_f), 255);
}
color_t fget32_as_rgba() {
int b = fgetc(m_f);
int g = fgetc(m_f);
int r = fgetc(m_f);
uint8_t a = fgetc(m_f);
if (!m_hasAlpha)
a = 255;
else {
count++;
c += count;
while (count--) {
fread(value, 1, 4, f);
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0],
withAlpha ? value[3]: 255);
}
++m_alphaHistogram[a];
}
} while (c < w);
}
return doc::rgba(r, g, b, a);
}
static void rle_tga_read24(FILE* f, uint32_t* address, int w)
{
uint32_t* end = address + w;
unsigned char value[4];
int count;
int c = 0;
color_t fget24_as_rgba() {
const int b = fgetc(m_f);
const int g = fgetc(m_f);
const int r = fgetc(m_f);
return doc::rgba(r, g, b, 255);
}
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
fread(value, 1, 3, f);
while (count--) {
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0], 255);
}
color_t fget16_as_rgba() {
const uint16_t v = fgetw(m_f);
uint8_t alpha = 255;
if (m_hasAlpha) {
if ((v & 0x8000) == 0)
alpha = 0;
++m_alphaHistogram[alpha];
}
else {
count++;
c += count;
while (count--) {
fread(value, 1, 3, f);
if (address+1 < end)
*(address++) = rgba(value[2], value[1], value[0], 255);
}
}
} while (c < w);
}
return doc::rgba(scale_5bits_to_8bits((v >> 10) & 0x1F),
scale_5bits_to_8bits((v >> 5) & 0x1F),
scale_5bits_to_8bits(v & 0x1F),
alpha);
}
static void rle_tga_read16(uint32_t* address, int w, FILE *f)
{
uint32_t* end = address + w;
unsigned int value;
uint32_t color;
int count;
int c = 0;
FILE* m_f;
Header m_header;
bool m_hasAlpha = false;
Palette m_palette;
ImageDataIterator m_iterator;
std::vector<uint32_t> m_alphaHistogram;
};
do {
count = fgetc(f);
if (count & 0x80) {
count = (count & 0x7F) + 1;
c += count;
value = fgetw(f);
color = rgba(scale_5bits_to_8bits((value >> 10) & 0x1F),
scale_5bits_to_8bits((value >> 5) & 0x1F),
scale_5bits_to_8bits(value & 0x1F), 255);
while (count--) {
if (address+1 < end)
*(address++) = color;
}
}
else {
count++;
c += count;
while (count--) {
value = fgetw(f);
color = rgba(scale_5bits_to_8bits((value >> 10) & 0x1F),
scale_5bits_to_8bits((value >> 5) & 0x1F),
scale_5bits_to_8bits(value & 0x1F), 255);
if (address+1 < end)
*(address++) = color;
}
}
} while (c < w);
}
// Loads a 256 color or 24 bit uncompressed TGA file, returning a bitmap
@ -210,192 +488,59 @@ static void rle_tga_read16(uint32_t* address, int w, FILE *f)
// should be an array of at least 256 RGB structures).
bool TgaFormat::onLoad(FileOp* fop)
{
unsigned char image_id[256], image_palette[256][3], rgb[4];
unsigned char id_length, palette_type, image_type, palette_entry_size;
unsigned char bpp, descriptor_bits;
short unsigned int palette_colors;
short unsigned int image_width, image_height;
unsigned int c, i, x, y, yc;
int compressed;
FileHandle handle(open_file_with_exception(fop->filename(), "rb"));
FILE* f = handle.get();
id_length = fgetc(f);
palette_type = fgetc(f);
image_type = fgetc(f);
fgetw(f); // first_color
palette_colors = fgetw(f);
palette_entry_size = fgetc(f);
fgetw(f); // "left" field
fgetw(f); // "top" field
image_width = fgetw(f);
image_height = fgetw(f);
bpp = fgetc(f);
descriptor_bits = fgetc(f);
TgaDecoder decoder(f);
fread(image_id, 1, id_length, f);
if (palette_type == 1) {
for (i=0; i<palette_colors; i++) {
switch (palette_entry_size) {
case 16:
c = fgetw(f);
image_palette[i][0] = scale_5bits_to_8bits(c & 0x1F);
image_palette[i][1] = scale_5bits_to_8bits((c >> 5) & 0x1F);
image_palette[i][2] = scale_5bits_to_8bits((c >> 10) & 0x1F);
break;
case 24:
case 32:
image_palette[i][0] = fgetc(f);
image_palette[i][1] = fgetc(f);
image_palette[i][2] = fgetc(f);
if (palette_entry_size == 32)
fgetc(f);
break;
}
}
}
else if (palette_type != 0) {
ImageSpec spec(ColorMode::RGB, 1, 1);
if (!decoder.getImageSpec(spec)) {
fop->setError("Unsupported color depth in TGA file: %d bpp, image type=%d.\n",
decoder.bitsPerPixel(),
decoder.imageType());
return false;
}
/* Image type:
* 0 = no image data
* 1 = uncompressed color mapped
* 2 = uncompressed true color
* 3 = grayscale
* 9 = RLE color mapped
* 10 = RLE true color
* 11 = RLE grayscale
*/
compressed = (image_type & 8);
image_type &= 7;
PixelFormat pixelFormat;
bool withAlpha = false;
switch (image_type) {
/* paletted image */
case 1:
if ((palette_type != 1) || (bpp != 8)) {
return false;
}
for (i=0; i<palette_colors; i++) {
fop->sequenceSetColor(i,
image_palette[i][2],
image_palette[i][1],
image_palette[i][0]);
}
pixelFormat = IMAGE_INDEXED;
break;
/* truecolor image */
case 2:
if ((palette_type != 0) ||
((bpp != 15) && (bpp != 16) &&
(bpp != 24) && (bpp != 32))) {
return false;
}
withAlpha = ((descriptor_bits & 0xf) == 8);
if (withAlpha)
fop->sequenceSetHasAlpha(true);
pixelFormat = IMAGE_RGB;
break;
/* grayscale image */
case 3:
if ((palette_type != 0) || (bpp != 8)) {
return false;
}
for (i=0; i<256; i++)
fop->sequenceSetColor(i, i, i, i);
pixelFormat = IMAGE_GRAYSCALE;
break;
default:
/* TODO add support for more TGA types? */
return false;
if (!decoder.validPalType()) {
fop->setError("Invalid palette type in TGA file.\n");
return false;
}
Image* image = fop->sequenceImage(pixelFormat, image_width, image_height);
// Palette from TGA file
if (decoder.hasPalette()) {
const Palette& pal = decoder.palette();
for (int i=0; i<pal.size(); ++i) {
color_t c = pal.getEntry(i);
fop->sequenceSetColor(i,
doc::rgba_getr(c),
doc::rgba_getg(c),
doc::rgba_getb(c));
if (doc::rgba_geta(c) < 255)
fop->sequenceSetAlpha(i, doc::rgba_geta(c));
}
}
// Generate grayscale palette
else if (decoder.isGray()) {
for (int i=0; i<256; ++i)
fop->sequenceSetColor(i, i, i, i);
}
if (decoder.hasAlpha())
fop->sequenceSetHasAlpha(true);
Image* image = fop->sequenceImage((doc::PixelFormat)spec.colorMode(),
spec.width(),
spec.height());
if (!image)
return false;
for (y=image_height; y; y--) {
yc = (descriptor_bits & 0x20) ? image_height-y : y-1;
switch (image_type) {
case 1:
case 3:
if (compressed)
rle_tga_read8(f, image->getPixelAddress(0, yc), image_width, image_type);
else if (image_type == 1)
fread(image->getPixelAddress(0, yc), 1, image_width, f);
else {
for (x=0; x<image_width; x++)
put_pixel_fast<GrayscaleTraits>(image, x, yc, graya(fgetc(f), 255));
}
break;
case 2:
if (bpp == 32) {
if (compressed) {
rle_tga_read32(f, (uint32_t*)image->getPixelAddress(0, yc), image_width,
withAlpha);
}
else {
for (x=0; x<image_width; x++) {
fread(rgb, 1, 4, f);
put_pixel_fast<RgbTraits>(image, x, yc,
rgba(rgb[2], rgb[1], rgb[0],
withAlpha ? rgb[3]: 255));
}
}
}
else if (bpp == 24) {
if (compressed) {
rle_tga_read24(f, (uint32_t*)image->getPixelAddress(0, yc), image_width);
}
else {
for (x=0; x<image_width; x++) {
fread(rgb, 1, 3, f);
put_pixel_fast<RgbTraits>(image, x, yc, rgba(rgb[2], rgb[1], rgb[0], 255));
}
}
}
else {
if (compressed) {
rle_tga_read16((uint32_t*)image->getPixelAddress(0, yc), image_width, f);
}
else {
for (x=0; x<image_width; x++) {
c = fgetw(f);
put_pixel_fast<RgbTraits>(
image, x, yc, rgba(scale_5bits_to_8bits((c >> 10) & 0x1F),
scale_5bits_to_8bits((c >> 5) & 0x1F),
scale_5bits_to_8bits(c & 0x1F), 255));
}
}
}
break;
}
if (image_height > 1) {
fop->setProgress((float)(image_height-y) / (float)(image_height));
if (fop->isStop())
break;
}
if (!decoder.readImageData(fop, image)) {
fop->setError("Error loading image data from TGA file.\n");
return false;
}
decoder.postProcessImageData(image);
if (ferror(f)) {
fop->setError("Error reading file.\n");
return false;
@ -419,24 +564,25 @@ bool TgaFormat::onSave(FileOp* fop)
FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb"));
FILE* f = handle.get();
fputc(0, f); /* id length (no id saved) */
fputc((need_pal) ? 1 : 0, f); /* palette type */
/* image type */
fputc(0, f); // id length (no id saved)
fputc((need_pal) ? 1 : 0, f); // palette type
// image type
fputc((image->pixelFormat() == IMAGE_RGB ) ? 2 :
(image->pixelFormat() == IMAGE_GRAYSCALE) ? 3 :
(image->pixelFormat() == IMAGE_INDEXED ) ? 1 : 0, f);
fputw(0, f); /* first colour */
fputw((need_pal) ? 256 : 0, f); /* number of colours */
fputc((need_pal) ? 24 : 0, f); /* palette entry size */
fputw(0, f); /* left */
fputw(0, f); /* top */
fputw(image->width(), f); /* width */
fputw(image->height(), f); /* height */
fputc(depth, f); /* bits per pixel */
fputw(0, f); // first colour
fputw((need_pal) ? 256 : 0, f); // number of colours
fputc((need_pal) ? 24 : 0, f); // palette entry size
fputw(0, f); // left
fputw(0, f); // top
fputw(image->width(), f); // width
fputw(image->height(), f); // height
fputc(depth, f); // bits per pixel
/* descriptor (bottom to top, 8-bit alpha) */
fputc(image->pixelFormat() == IMAGE_RGB &&
!fop->document()->sprite()->isOpaque() ? 8: 0, f);
// descriptor (bottom to top, 8-bit alpha)
fputc(
(image->pixelFormat() == IMAGE_RGB &&
!fop->document()->sprite()->isOpaque() ? 8: 0), f);
if (need_pal) {
for (y=0; y<256; y++) {