Merge pull request #15139 from huffman/feat/tga-textures

Case 21220: Add more complete support for .tga files
This commit is contained in:
Thijs Wenker 2019-03-08 23:53:21 +01:00 committed by GitHub
commit ded58b96f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 238 additions and 0 deletions

View file

@ -25,6 +25,8 @@
#include <StatTracker.h>
#include <GLMHelpers.h>
#include "TGAReader.h"
#include "ImageLogging.h"
using namespace gpu;
@ -203,6 +205,16 @@ QImage processRawImageData(QIODevice& content, const std::string& filename) {
// Help the QImage loader by extracting the image file format from the url filename ext.
// Some tga are not created properly without it.
auto filenameExtension = filename.substr(filename.find_last_of('.') + 1);
content.open(QIODevice::ReadOnly);
if (filenameExtension == "tga") {
QImage image = image::readTGA(content);
if (!image.isNull()) {
return image;
}
content.reset();
}
QImageReader imageReader(&content, filenameExtension.c_str());
if (imageReader.canRead()) {

View file

@ -0,0 +1,202 @@
//
// TGAReader.cpp
// image/src/image
//
// Created by Ryan Huffman
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TGAReader.h"
#include "ImageLogging.h"
#include <QIODevice>
#include <QDebug>
QImage image::readTGA(QIODevice& content) {
enum class TGAImageType : uint8_t {
NoImageData = 0,
UncompressedColorMapped = 1,
UncompressedTrueColor = 2,
UncompressedBlackWhite = 3,
RunLengthEncodedColorMapped = 9,
RunLengthEncodedTrueColor = 10,
RunLengthEncodedBlackWhite = 11,
};
struct TGAHeader {
uint8_t idLength;
uint8_t colorMapType;
TGAImageType imageType;
struct {
uint64_t firstEntryIndex : 16;
uint64_t length : 16;
uint64_t entrySize : 8;
} colorMap;
uint16_t xOrigin;
uint16_t yOrigin;
uint16_t width;
uint16_t height;
uint8_t pixelDepth;
struct {
uint8_t attributeBitsPerPixel : 4;
uint8_t reserved : 1;
uint8_t orientation : 1;
uint8_t padding : 2;
} imageDescriptor;
};
constexpr bool WANT_DEBUG { false };
constexpr qint64 TGA_HEADER_SIZE_BYTES { 18 };
// BottomLeft: 0, TopLeft: 1
constexpr uint8_t ORIENTATION_BOTTOM_LEFT { 0 };
TGAHeader header;
if (content.isSequential()) {
qWarning(imagelogging) << "TGA - Sequential devices are not supported for reading";
return QImage();
}
if (content.bytesAvailable() < TGA_HEADER_SIZE_BYTES) {
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
return QImage();
}
content.read((char*)&header.idLength, 1);
content.read((char*)&header.colorMapType, 1);
content.read((char*)&header.imageType, 1);
content.read((char*)&header.colorMap, 5);
content.read((char*)&header.xOrigin, 2);
content.read((char*)&header.yOrigin, 2);
content.read((char*)&header.width, 2);
content.read((char*)&header.height, 2);
content.read((char*)&header.pixelDepth, 1);
content.read((char*)&header.imageDescriptor, 1);
if (WANT_DEBUG) {
qDebug(imagelogging) << "Id Length: " << (int)header.idLength;
qDebug(imagelogging) << "Color map: " << (int)header.colorMap.firstEntryIndex << header.colorMap.length << header.colorMap.entrySize;
qDebug(imagelogging) << "Color map type: " << (int)header.colorMapType;
qDebug(imagelogging) << "Image type: " << (int)header.imageType;
qDebug(imagelogging) << "Origin: " << header.xOrigin << header.yOrigin;
qDebug(imagelogging) << "Size: " << header.width << header.height;
qDebug(imagelogging) << "Depth: " << header.pixelDepth;
qDebug(imagelogging) << "Image desc: " << header.imageDescriptor.attributeBitsPerPixel << (int)header.imageDescriptor.orientation;
}
if (header.xOrigin != 0 || header.yOrigin != 0) {
qWarning(imagelogging) << "TGA - origin not supporter";
return QImage();
}
if (!(header.pixelDepth == 24 && header.imageDescriptor.attributeBitsPerPixel == 0) && header.pixelDepth != 32) {
qWarning(imagelogging) << "TGA - Only pixel depths of 24 (with no alpha) and 32 bits are supported";
return QImage();
}
if (header.imageDescriptor.attributeBitsPerPixel != 0 && header.imageDescriptor.attributeBitsPerPixel != 8) {
qWarning(imagelogging) << "TGA - Only 0 or 8 bits for the alpha channel is supported";
return QImage();
}
char alphaMask = header.imageDescriptor.attributeBitsPerPixel == 8 ? 0x00 : 0xFF;
int bytesPerPixel = header.pixelDepth / 8;
content.skip(header.idLength);
if (header.imageType == TGAImageType::UncompressedTrueColor) {
qint64 minimumSize = header.width * header.height * bytesPerPixel;
if (content.bytesAvailable() < minimumSize) {
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
return QImage();
}
QImage image{ header.width, header.height, QImage::Format_ARGB32 };
char* line;
for (int y = 0; y < header.height; ++y) {
if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) {
line = (char*)image.scanLine(header.height - y - 1);
} else {
line = (char*)image.scanLine(y);
}
for (int x = 0; x < header.width; ++x) {
content.read(line, bytesPerPixel);
*(line + 3) |= alphaMask;
line += 4;
}
}
return image;
} else if (header.imageType == TGAImageType::RunLengthEncodedTrueColor) {
QImage image{ header.width, header.height, QImage::Format_ARGB32 };
for (int y = 0; y < header.height; ++y) {
char* line;
if (header.imageDescriptor.orientation == ORIENTATION_BOTTOM_LEFT) {
line = (char*)image.scanLine(header.height - y - 1);
} else {
line = (char*)image.scanLine(y);
}
int col = 0;
while (col < header.width) {
constexpr char IS_REPETITION_MASK{ (char)0x80 };
constexpr char LENGTH_MASK{ (char)0x7f };
char repetition;
if (content.read(&repetition, 1) != 1) {
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
return QImage();
}
bool isRepetition = repetition & IS_REPETITION_MASK;
// The length in `repetition` is always 1 less than the number of following pixels,
// so we need to increment it by 1. Because of this, the length is never 0.
int length = (repetition & LENGTH_MASK) + 1;
if (isRepetition) {
// Read into temporary buffer
char color[4];
if (content.read(color, bytesPerPixel) != bytesPerPixel) {
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
return QImage();
}
color[3] |= alphaMask;
// Copy `length` number of times
col += length;
while (length-- > 0) {
*line = color[0];
*(line + 1) = color[1];
*(line + 2) = color[2];
*(line + 3) = color[3];
line += 4;
}
} else {
qint64 minimumSize = length * bytesPerPixel;
if (content.bytesAvailable() < minimumSize) {
qWarning(imagelogging) << "TGA - Unexpectedly reached end of file";
return QImage();
}
// Read in `length` number of pixels
col += length;
while (length-- > 0) {
content.read(line, bytesPerPixel);
*(line + 3) |= alphaMask;
line += 4;
}
}
}
}
return image;
} else {
qWarning(imagelogging) << "TGA - Unsupported image type: " << (int)header.imageType;
}
return QImage();
}

View file

@ -0,0 +1,24 @@
//
// TGAReader.h
// image/src/image
//
// Created by Ryan Huffman
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_image_TGAReader_h
#define hifi_image_TGAReader_h
#include <QImage>
namespace image {
// TODO Move this into a plugin that QImageReader can use
QImage readTGA(QIODevice& contents);
}
#endif // hifi_image_TGAReader_h