#include "CoreDefs.h" #include "PLCTabReducer.h" #include "QDPictDecoder.h" #include "QDPictEmitContext.h" #include "QDPictHeader.h" #include "QDPictOpcodes.h" #include "QDPictEmitScanlineParameters.h" #include "GpIOStream.h" #include "RGBAColor.h" #include "Vec2i.h" #include struct QDPictDecodeState { PortabilityLayer::RGBAColor m_foreColor; PortabilityLayer::RGBAColor m_backColor; Point m_penPos; Point m_penSize; Point m_origin; uint16_t m_penMode; uint8_t m_penPattern[8]; QDPictDecodeState() { m_foreColor = PortabilityLayer::RGBAColor::Create(0, 0, 0, 255); m_backColor = PortabilityLayer::RGBAColor::Create(255, 255, 255, 255); m_penPos = Point::Create(0, 0); m_origin = Point::Create(0, 0); m_penSize = Point::Create(1, 1); for (int i = 0; i < 8; i++) m_penPattern[i] = 0xff; m_penMode = 0; } }; namespace PortabilityLayer { QDPictDecoder::QDPictDecoder() : m_stream(nullptr) { } bool QDPictDecoder::DecodePict(GpIOStream *stream, QDPictEmitContext *emitContext) { QDPictHeader header; QDPictDecodeState decodeState; if (!header.Load(stream)) { emitContext->ReportError(QDPictEmitContext::kMissingHeader, 0); 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) { emitContext->ReportError(QDPictEmitContext::kUnsupportedClipRegionFormat, 0); return false; } GP_STATIC_ASSERT(sizeof(scratchBERect) == 8); memcpy(&scratchBERect, scratchBytes + 2, 8); scratchRect = scratchBERect.ToRect(); if (!scratchRect.IsValid()) { emitContext->ReportError(QDPictEmitContext::kInvalidRegionRect, 0); return false; } break; case QDOpcodes::kShortComment: if (!stream->SeekCurrent(2)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } break; case QDOpcodes::kLongComment: { if (stream->Read(scratchBytes, 4) != 4) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); 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, decodeState.m_origin, emitContext); if (rasterOpErrorCode) { emitContext->ReportError(QDPictEmitContext::kRasterOpFailure, rasterOpErrorCode); return false; } break; case QDOpcodes::kPackBitsRect: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, false, false, activeFrame, decodeState.m_origin, emitContext); if (rasterOpErrorCode) { emitContext->ReportError(QDPictEmitContext::kRasterOpFailure, rasterOpErrorCode); return false; } break; case QDOpcodes::kPackBitsRgn: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, true, false, activeFrame, decodeState.m_origin, emitContext); if (rasterOpErrorCode) { emitContext->ReportError(QDPictEmitContext::kRasterOpFailure, rasterOpErrorCode); return false; } break; case QDOpcodes::kDirectBitsRect: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, false, true, activeFrame, decodeState.m_origin, emitContext); if (rasterOpErrorCode) { emitContext->ReportError(QDPictEmitContext::kRasterOpFailure, rasterOpErrorCode); return false; } break; case QDOpcodes::kDirectBitsRgn: rasterOpErrorCode = ProcessRasterOp(stream, header.GetVersion(), true, true, true, activeFrame, decodeState.m_origin, emitContext); if (rasterOpErrorCode) { emitContext->ReportError(QDPictEmitContext::kRasterOpFailure, rasterOpErrorCode); return false; } break; case QDOpcodes::kDefaultHilite: break; if (!stream->SeekCurrent(6)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } break; case QDOpcodes::kEndOfPicture: finished = true; break; case QDOpcodes::kPenSize: { BEPoint point; if (!stream->Read(&point, 4)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } decodeState.m_penSize = point.ToPoint(); } break; case QDOpcodes::kPenPattern: { if (!stream->Read(decodeState.m_penPattern, 8)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } } break; case QDOpcodes::kPenMode: { BEUInt16_t penMode; if (!stream->Read(&penMode, 2)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } decodeState.m_penMode = penMode; } break; case QDOpcodes::kOrigin: { // NOTE: This is intentionally not the same order as Point BEInt16_t dxdy[2]; if (!stream->Read(&dxdy, 4)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } decodeState.m_origin.h += dxdy[0]; decodeState.m_origin.v += dxdy[1]; } break; case QDOpcodes::kOpColor: // ??? Not sure what the difference between these two is case QDOpcodes::kRGBForeColor: { uint8_t rgbBytes[6]; if (!stream->Read(rgbBytes, 6)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } decodeState.m_foreColor = RGBAColor::Create(rgbBytes[0], rgbBytes[2], rgbBytes[4], 255); } break; case QDOpcodes::kRGBBackColor: { uint8_t rgbBytes[6]; if (!stream->Read(rgbBytes, 6)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } decodeState.m_backColor = RGBAColor::Create(rgbBytes[0], rgbBytes[2], rgbBytes[4], 255); } break; case QDOpcodes::kCompressedQT: case QDOpcodes::kUncompressedQT: { BEUInt32_t dataSize; if (!stream->Read(&dataSize, 4)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } if (!emitContext->EmitQTContent(stream, dataSize, opcode == QDOpcodes::kCompressedQT)) { emitContext->ReportError(QDPictEmitContext::kMalformedArguments, opcode); return false; } } break; default: // Unknown opcode emitContext->ReportError(QDPictEmitContext::kUnsupportedOpcode, opcode); return false; } if (finished) return true; } } int QDPictDecoder::ProcessRasterOp(GpIOStream *stream, int pictVersion, bool isPackedFlag, bool hasRegion, bool isDirect, const Rect &constraintRect, const Point &origin, 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; const int componentCount = pixMapBE.m_componentCount; // 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; // Offset by origin destRect.left -= origin.h; destRect.right -= origin.v; destRect.top -= origin.v; destRect.bottom -= origin.v; 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_RGB15Native, 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 (!isLineValid) { if (!stream->SeekCurrent(rowSizeBytes)) return 59; continue; } 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; case 5: if (componentCount == 3) context->Start(QDPictBlitSourceType_RGB15BE, params); else return 52; break; default: return 53; // ??? } started = true; } context->BlitScanlineAndAdvance(decompressedScanlineBuffer); } else return 54; } // Either undocumented behavior or non-compliant PICT resources, not sure if (isPixMap && (stream->Tell() & 1) != 0) { if (!stream->SeekCurrent(1)) return 55; } 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); } }