image::Image 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(); }