#include "CoreDefs.h" #include "PLCTabReducer.h" #include "QDPictDecoder.h" #include "QDPictEmitContext.h" #include "QDPictHeader.h" #include "QDPictOpcodes.h" #include "QDPictEmitScanlineParameters.h" #include "IOStream.h" #include "RGBAColor.h" #include "Vec2i.h" #include #include namespace PortabilityLayer { QDPictDecoder::QDPictDecoder() : m_stream(nullptr) { } bool QDPictDecoder::DecodePict(IOStream *stream, QDPictEmitContext *emitContext) { QDPictHeader header; if (!header.Load(stream)) return false; emitContext->SpecifyFrame(header.GetFrame()); const Rect constrainedFrame = emitContext->ConstrainRegion(header.GetFrame()); Rect activeFrame = constrainedFrame; BERect scratchBERect; Rect scratchRect; BEUInt16_t scratchUInt16; BEUInt32_t scratchUInt32; const int pictVersion = header.GetVersion(); if (pictVersion != 1 && pictVersion != 2) return false; const size_t opcodeSize = (pictVersion == 1) ? 1 : 2; uint8_t scratchBytes[64]; for (;;) { if (stream->Read(scratchBytes, opcodeSize) != opcodeSize) return false; bool finished = false; uint16_t opcode = scratchBytes[0]; if (header.GetVersion() == 2) opcode = (opcode << 8) | scratchBytes[1]; int rasterOpErrorCode = 0; switch (opcode) { case QDOpcodes::kNoop: break; case QDOpcodes::kClipRegion: if (stream->Read(scratchBytes, 10) != 10 || scratchBytes[0] != 0 || scratchBytes[1] != 10) return false; // Unknown format region GP_STATIC_ASSERT(sizeof(scratchBERect) == 8); memcpy(&scratchBERect, scratchBytes + 2, 8); scratchRect = scratchBERect.ToRect(); if (!scratchRect.IsValid()) return false; break; case QDOpcodes::kShortComment: if (!stream->SeekCurrent(2)) return false; break; case QDOpcodes::kLongComment: { if (stream->Read(scratchBytes, 4) != 4) return false; const uint16_t commentKind = (scratchBytes[0] << 8) | scratchBytes[1]; const uint16_t commentSize = (scratchBytes[2] << 8) | scratchBytes[3]; if (!stream->SeekCurrent(commentSize)) return false; } break; case QDOpcodes::kBitsRect: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), false, false, false, activeFrame, emitContext); if (rasterOpErrorCode) return false; break; case QDOpcodes::kPackBitsRect: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, false, false, activeFrame, emitContext); if (rasterOpErrorCode) return false; break; case QDOpcodes::kPackBitsRgn: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, true, false, activeFrame, emitContext); if (rasterOpErrorCode) return false; break; case QDOpcodes::kDirectBitsRect: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, false, true, activeFrame, emitContext); if (rasterOpErrorCode) return false; break; case QDOpcodes::kDirectBitsRgn: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, true, true, activeFrame, emitContext); if (rasterOpErrorCode) return false; break; case QDOpcodes::kDefaultHilite: break; case QDOpcodes::kOpColor: if (!stream->SeekCurrent(6)) return false; break; case QDOpcodes::kEndOfPicture: finished = true; break; default: // Unknown opcode return false; } if (finished) return true; } } int QDPictDecoder::ProcessRasterOp(IOStream *stream, int pictVersion, bool isPackedFlag, bool hasRegion, bool isDirect, const Rect &constraintRect, QDPictEmitContext *context) { uint16_t rowSizeBytes = 0; bool isPixMap = false; if (isDirect) { isPixMap = true; // Skip base address if (!stream->SeekCurrent(4)) return 1; if (stream->Read(&rowSizeBytes, sizeof(uint16_t)) != sizeof(uint16_t)) return 2; ByteSwap::BigUInt16(rowSizeBytes); rowSizeBytes &= 0x7fff; } else { if (stream->Read(&rowSizeBytes, sizeof(uint16_t)) != sizeof(uint16_t)) return 3; ByteSwap::BigUInt16(rowSizeBytes); if (pictVersion == 2) { if ((rowSizeBytes & 0x8000) != 0) { isPixMap = true; rowSizeBytes &= 0x7fff; } // ... else this is a bitmap } } Rect srcRect; Rect destRect; uint16_t transferMode; BEColorTableHeader clutHeader; BEColorTableItem clutItems[256]; BEPixMap pixMapBE; int packType = 0; RGBAColor colors[256]; size_t numColors = 0; if (!isPixMap) { BEBitMap bitMap; if (stream->Read(&bitMap, sizeof(BEBitMap)) != sizeof(BEBitMap)) return 4; packType = (isPackedFlag && rowSizeBytes >= 8) ? 0 : 1; pixMapBE.m_bounds = bitMap.m_bounds; pixMapBE.m_version = 2; pixMapBE.m_packType = packType; pixMapBE.m_hRes = 72 << 16; pixMapBE.m_vRes = 72 << 16; pixMapBE.m_vRes = 72 << 16; pixMapBE.m_pixelType = 16; // FIXME: Use direct instead pixMapBE.m_pixelSize = 1; pixMapBE.m_componentCount = 1; pixMapBE.m_componentSize = 1; pixMapBE.m_planeSizeBytes = 0; pixMapBE.m_clutHandle = 0; pixMapBE.m_unused = 0; BERect srcRectBE; if (stream->Read(&srcRectBE, sizeof(BERect)) != sizeof(BERect)) return 5; BERect destRectBE; if (stream->Read(&destRectBE, sizeof(BERect)) != sizeof(BERect)) return 6; srcRect = srcRectBE.ToRect(); destRect = destRectBE.ToRect(); if (!srcRect.IsValid()) return 7; if (!destRect.IsValid()) return 8; if (stream->Read(&transferMode, 2) != 2) return 9; ByteSwap::BigUInt16(transferMode); if (hasRegion) { BEUInt16_t regionSize; if (stream->Read(®ionSize, 2) != 2) return 10; if (regionSize < 2) return 11; if (!stream->SeekCurrent(regionSize - 2)) return 12; } colors[0].r = colors[0].g = colors[0].b = 255; colors[0].a = 255; colors[1].r = colors[1].g = colors[1].b = 0; colors[1].a = 255; numColors = 0; } else { // If rowBytes < 8 or pack type == 1, data is unpacked // If pack type == 2, pad byte of 32-bit data is dropped (direct pixels only, 32-bit images) // If pack type == 3, 16-bit RLE // If pack type == 4, 8-bit planar component RLE GP_STATIC_ASSERT(sizeof(BEPixMap) == 44); if (stream->Read(&pixMapBE, sizeof(BEPixMap)) != sizeof(BEPixMap)) return 13; if (isDirect) { } else { if (stream->Read(&clutHeader, sizeof(BEColorTableHeader)) != sizeof(BEColorTableHeader)) return 14; const uint16_t numItemsMinusOne = clutHeader.m_numItemsMinusOne; if (numItemsMinusOne > 255) return 15; numColors = clutHeader.m_numItemsMinusOne + 1; if (stream->Read(clutItems, sizeof(BEColorTableItem) * numColors) != sizeof(BEColorTableItem) * numColors) return 16; for (size_t i = 0; i < numColors; i++) colors[i] = CTabReducer::DecodeClutItem(clutItems[i]); } BERect srcRectBE; if (stream->Read(&srcRectBE, sizeof(BERect)) != sizeof(BERect)) return 17; BERect destRectBE; if (stream->Read(&destRectBE, sizeof(BERect)) != sizeof(BERect)) return 18; srcRect = srcRectBE.ToRect(); destRect = destRectBE.ToRect(); if (!srcRect.IsValid() || !destRect.IsValid()) return 19; if (stream->Read(&transferMode, 2) != 2) return 20; ByteSwap::BigUInt16(transferMode); if (!isPackedFlag && rowSizeBytes >= 8 && pixMapBE.m_packType != 1) return 21; if (hasRegion) { uint16_t regionSize; if (stream->Read(®ionSize, 2) != 2) return 22; ByteSwap::BigUInt16(regionSize); if (regionSize < 2) return 23; if (!stream->SeekCurrent(regionSize - 2)) return 24; } const unsigned int pixelSize = pixMapBE.m_pixelSize; const unsigned int componentCount = pixMapBE.m_componentCount; const unsigned int componentSize = pixMapBE.m_componentSize; packType = pixMapBE.m_packType; if (isDirect) { if (packType == 0) { switch (pixelSize) { case 16: packType = 3; break; case 32: packType = 4; break; default: break; } } if (packType == 4) { if (pixMapBE.m_componentCount != 3) return 25; if (pixMapBE.m_componentSize != 8) return 26; if (pixelSize != 32) return 27; } else if (packType == 3) { if (componentCount != 3) return 28; if (componentSize != 5) return 29; if (pixelSize != 16) return 30; } else { switch (pixelSize) { case 32: if (componentCount != 3 || componentSize != 8) return 31; break; case 16: if (componentCount != 3 || componentSize != 5) return 32; break; case 8: if (componentCount != 1 || componentSize != 8) return 33; break; default: return 34; } } } else { switch (pixMapBE.m_componentSize) { case 1: case 2: case 4: case 8: break; default: return 35; } if (componentSize != pixelSize) return 36; } if (packType > 4) return 37; } const int componentSize = pixMapBE.m_componentSize; // We only support rect moves that are the same size if (srcRect.right - srcRect.left != destRect.right - destRect.left) return 38; if (srcRect.bottom - srcRect.top != destRect.bottom - destRect.top) return 39; const Rect pixMapBounds = pixMapBE.m_bounds.ToRect(); if (!pixMapBounds.IsValid()) return 40; if (srcRect.left < pixMapBounds.left || srcRect.right > pixMapBounds.right || srcRect.top < pixMapBounds.top || srcRect.bottom > pixMapBounds.bottom) return 41; const Vec2i pixMapOriginRelativeToSrcRect = Vec2i(pixMapBounds.left, pixMapBounds.top) - Vec2i(srcRect.left, srcRect.top); const Vec2i pixMapOriginRelativeToDestRect = pixMapOriginRelativeToSrcRect; const Vec2i pixMapOrigin = pixMapOriginRelativeToDestRect + Vec2i(destRect.left, destRect.top); const Vec2i pixMapBottomRight = pixMapOrigin + Vec2i(pixMapBounds.right, pixMapBounds.bottom) - Vec2i(pixMapBounds.left, pixMapBounds.top); const Rect constrainedDestRect = constraintRect.Intersect(destRect).MakeValid(); bool skipAll = false; if (!constrainedDestRect.IsValid()) skipAll = true; if (rowSizeBytes < 8) packType = 1; if (!isDirect) { if (packType != 0 && packType != 1) return 42; } // NOT CURRENTLY SUPPORTED if (packType == 2) return 43; size_t decompressedRowSize = rowSizeBytes; const size_t minimumRowSize = (static_cast(pixMapBounds.right - pixMapBounds.left) * static_cast(pixMapBE.m_pixelSize) + 7) / 8; if (decompressedRowSize < minimumRowSize) return 56; if (packType == 4) { if ((rowSizeBytes & 3) != 0) return 55; // Fudge decompressed row size in planar RGB case decompressedRowSize = (rowSizeBytes / 4) * 3; } // Max compressed size is 4 const size_t maxCompressedSize = decompressedRowSize + (decompressedRowSize / 128) + 4; uint8_t *decompressedScanlineBuffer = nullptr; uint8_t *compressedScanlineBuffer = nullptr; if (!context->AllocTempBuffers(decompressedScanlineBuffer, decompressedRowSize, compressedScanlineBuffer, maxCompressedSize)) return 57; bool started = false; QDPictEmitScanlineParameters params; params.m_scanlineOriginX = pixMapOrigin.m_x; params.m_firstY = constrainedDestRect.top; params.m_constrainedRegionLeft = constrainedDestRect.left; params.m_constrainedRegionRight = constrainedDestRect.right; params.m_colors = colors; params.m_numColors = numColors; params.m_planarSeparation = pixMapBottomRight.m_x - pixMapOrigin.m_x; for (int32_t y = pixMapOrigin.m_y; y < pixMapBottomRight.m_y; y++) { bool isLineValid = !skipAll; if (y < constrainedDestRect.top || y >= constrainedDestRect.bottom) isLineValid = false; if (packType == 0 || packType > 2) { // RLE uint16_t lineByteCount; if (rowSizeBytes > 250) { if (stream->Read(&lineByteCount, 2) != 2) return 44; ByteSwap::BigUInt16(lineByteCount); } else { uint8_t lineByteCountSmall; if (stream->Read(&lineByteCountSmall, 1) != 1) return 45; lineByteCount = lineByteCountSmall; } if (!isLineValid) { if (!stream->SeekCurrent(lineByteCount)) return 46; continue; } if (lineByteCount > maxCompressedSize) return 58; if (stream->Read(compressedScanlineBuffer, lineByteCount) != lineByteCount) return 47; if (packType == 3) { // 16-bit RLE if (!UnpackBits16(decompressedScanlineBuffer, decompressedRowSize, compressedScanlineBuffer, lineByteCount)) return 48; if (!started) { context->Start(QDPictBlitSourceType_RGB15, params); started = true; } context->BlitScanlineAndAdvance(decompressedScanlineBuffer); } else { // 8-bit RLE if (!UnpackBits8(decompressedScanlineBuffer, decompressedRowSize, compressedScanlineBuffer, lineByteCount)) return 49; if (!started) { if (packType == 0) { switch (componentSize) { case 1: context->Start(isPixMap ? QDPictBlitSourceType_Indexed1Bit : QDPictBlitSourceType_1Bit, params); break; case 2: context->Start(QDPictBlitSourceType_Indexed2Bit, params); break; case 4: context->Start(QDPictBlitSourceType_Indexed4Bit, params); break; case 8: context->Start(QDPictBlitSourceType_Indexed8Bit, params); break; default: return 50; // ??? } } else { assert(packType == 4); context->Start(QDPictBlitSourceType_RGB24_Multiplane, params); } started = true; } context->BlitScanlineAndAdvance(decompressedScanlineBuffer); } } else if (packType == 1) { if (stream->Read(decompressedScanlineBuffer, rowSizeBytes) != rowSizeBytes) return 51; if (!started) { switch (componentSize) { case 1: context->Start(isPixMap ? QDPictBlitSourceType_Indexed1Bit : QDPictBlitSourceType_1Bit, params); break; case 2: context->Start(QDPictBlitSourceType_Indexed2Bit, params); break; case 4: context->Start(QDPictBlitSourceType_Indexed4Bit, params); break; case 8: context->Start(QDPictBlitSourceType_Indexed8Bit, params); break; default: return 52; // ??? } started = true; } context->BlitScanlineAndAdvance(decompressedScanlineBuffer); } else return 53; } // Either undocumented behavior or non-compliant PICT resources, not sure if (isPixMap && (stream->Tell() & 1) != 0) { if (!stream->SeekCurrent(1)) return 54; } return 0; } bool QDPictDecoder::UnpackBits8(uint8_t *dest, size_t destSize, const uint8_t *src, size_t srcSize) { while (srcSize > 0) { int8_t headerByte = *reinterpret_cast(src); src++; srcSize--; if (headerByte >= 0) { const size_t litCount = headerByte + 1; if (destSize < litCount || srcSize < litCount) return false; memcpy(dest, src, litCount); src += litCount; srcSize -= litCount; dest += litCount; destSize -= litCount; } else { const size_t repCount = static_cast(1 - headerByte); if (srcSize < 1) return false; if (destSize < repCount) return false; const uint8_t repValue = *src; memset(dest, repValue, repCount); src++; srcSize--; dest += repCount; destSize -= repCount; } } return (destSize == 0); } bool QDPictDecoder::UnpackBits16(uint8_t *dest8, size_t destSize, const uint8_t *src, size_t srcSize) { uint16_t *dest16 = reinterpret_cast(dest8); destSize /= 2; while (srcSize > 0) { int8_t headerByte = *reinterpret_cast(src); src++; srcSize--; if (headerByte >= 0) { const size_t litCount = headerByte + 1; if (destSize < litCount || srcSize < litCount * 2) return false; memcpy(dest16, src, 2 * litCount); for (size_t i = 0; i < litCount; i++) ByteSwap::BigUInt16(dest16[i]); src += litCount * 2; srcSize -= litCount * 2; dest16 += litCount; destSize -= litCount; } else { const size_t repCount = static_cast(1 - headerByte); if (srcSize < 2) return false; if (destSize < repCount) return false; const uint16_t repValue = (src[0] << 8) | src[1]; for (size_t i = 0; i < repCount; i++) dest16[i] = repValue; src += 2; srcSize -= 2; dest16 += repCount; destSize -= repCount; } } return (destSize == 0); } }