mirror of
https://github.com/elasota/Aerofoil.git
synced 2025-09-23 06:53:43 +00:00
878 lines
21 KiB
C++
878 lines
21 KiB
C++
#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 <assert.h>
|
|
|
|
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;
|
|
|
|
// 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<size_t>(pixMapBounds.right - pixMapBounds.left) * static_cast<uint16_t>(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 (!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;
|
|
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<const int8_t*>(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<size_t>(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<uint16_t*>(dest8);
|
|
destSize /= 2;
|
|
|
|
while (srcSize > 0)
|
|
{
|
|
int8_t headerByte = *reinterpret_cast<const int8_t*>(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<size_t>(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);
|
|
}
|
|
}
|