ciderpress/reformat/SuperHiRes.cpp
Andy McFadden d35a9670b5 Rework Super Hi-Res handling, particularly APF
We weren't handling oddly sized images well.  This was mostly a
problem for Apple Preferred Format, PNT/$0002.
2017-09-22 16:23:33 -07:00

1232 lines
41 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Various super-hi-res conversions, including 3200 color images.
*/
#include "StdAfx.h"
#include "SuperHiRes.h"
/*
* ==========================================================================
* ReformatSHR (base class)
* ==========================================================================
*/
/*
* The basic Super Hi-Res "PIC" file format ($c1/0000) is just a dump of
* memory from $2000-$9fff.
*
* $2000-9cff: pixel data, 160 bytes per line
* $9d00-9dc7: SCB bytes (8 bits each)
* 0-3 : color table number (0 to 15)
* 4 : reserved, must be 0
* 5 : fill mode (1=on, 0=off)
* 6 : interrupts (1=generated, 0=not)
* 7 : number of pixels (0=320, 1=640)
* $9e00-9fff: color tables (16 sets of 2 bytes)
* 0-3 : blue intensity
* 4-7 : green intensity
* 8-11: red intensity
* 12-15: reserved, must be 0
*
* Packed images ($c0/0001) are just the above packed with PackBytes. The
* "Apple Preferred" $c0/0002 format is funky, and has arbitrary resolution.
*
* 3200 color images do away with the SCB (they're always 320 mode and don't
* support fill mode) and instead follow the pixel data with 200 sets of
* color tables. According to the FTN for $c1/0002 ("Brooks" format), the
* color table is reversed, with color #15 appearing first in each table.
*/
MyDIBitmap* ReformatSHR::SHRScreenToBitmap8(const SHRScreen* pScreen)
{
return SHRDataToBitmap8(pScreen->pixels, pScreen->scb,
pScreen->colorTable, kPixelBytesPerLine, kNumLines,
kOutputWidth, kOutputHeight);
}
MyDIBitmap* ReformatSHR::SHRDataToBitmap8(const uint8_t* pPixels,
const uint8_t* pSCB, const uint8_t* pColorTable,
unsigned int bytesPerLine, unsigned int numScanLines,
unsigned int outputWidthPix, unsigned int outputHeightPix)
{
MyDIBitmap* pDib = new MyDIBitmap;
if (pDib == NULL)
goto bail;
// We can have an odd number of pixels, which results in 1-3 empty
// spaces in the last byte. We double the 4bpp pixels, so we're
// always outputting 4 pixels per byte.
if (outputWidthPix > bytesPerLine * 4 ||
outputWidthPix < bytesPerLine * 4 - 3) {
LOGW(" Invalid params: outputWidthPix=%u bytesPerLine=%u",
outputWidthPix, bytesPerLine);
ASSERT(false);
goto bail;
}
const uint8_t* pSCBStart = pSCB; // sanity check only
/*
* Set up a DIB to hold the data. "Create" returns a pointer to the
* pixel storage.
*/
uint8_t* outBuf = (uint8_t*) pDib->Create(outputWidthPix, outputHeightPix,
8, kNumColorTables * kNumEntriesPerColorTable);
if (outBuf == NULL) {
delete pDib;
pDib = NULL;
goto bail;
}
int outputStride = pDib->GetPitch();
/*
* Convert color palette.
*/
const uint16_t* pClrTable;
pClrTable = (const uint16_t*) pColorTable;
for (int table = 0; table < kNumColorTables; table++) {
for (int entry = 0; entry < kNumEntriesPerColorTable; entry++) {
GSColor(*pClrTable++, &fColorTables[table][entry]);
}
}
pDib->SetColorTable((RGBQUAD*)fColorTables);
if (false) {
LOGI(" SHR color table 0");
for (int ii = 0; ii < kNumEntriesPerColorTable; ii++) {
LOGI(" %2d: 0x%02x %02x %02x",
ii,
fColorTables[0][ii].rgbRed,
fColorTables[0][ii].rgbGreen,
fColorTables[0][ii].rgbBlue);
}
}
/*
* Set the pixels to palette indices.
*/
unsigned int line;
for (line = 0; line < numScanLines; line++) {
bool mode640, fillMode;
int colorTableOffset;
unsigned int byteCount;
uint8_t pixelVal;
uint8_t colorIndex;
int x = 0;
mode640 = (*pSCB & kSCBNumPixels) != 0;
fillMode = (*pSCB & kSCBFillMode) != 0;
colorTableOffset = (*pSCB & kSCBColorTableMask) * kNumEntriesPerColorTable;
if (!line) {
LOGI(" SHR line 0 mode640=%d", mode640);
}
if (fillMode) {
/* I doubt anyone uses this in still images */
LOGI(" SHR FILL MODE!!");
DebugBreak();
}
#define SetPix(x, y, colridx) \
outBuf[((outputHeightPix-1) - (y)) * outputStride + (x)] = colridx
if (mode640) {
/* 640 mode, one byte becomes four non-doubled pixels (1:4) */
unsigned int fullBytesPerLine = outputWidthPix / 4;
ASSERT(fullBytesPerLine <= bytesPerLine);
for (byteCount = 0; byteCount < fullBytesPerLine; byteCount++) {
uint8_t pixelByte = *pPixels++;
pixelVal = (pixelByte >> 6) & 0x03;
colorIndex = colorTableOffset + pixelVal + 8;
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
pixelVal = (pixelByte >> 4) & 0x03;
colorIndex = colorTableOffset + pixelVal + 12;
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
pixelVal = (pixelByte >> 2) & 0x03;
colorIndex = colorTableOffset + pixelVal + 0;
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
pixelVal = pixelByte & 0x03;
//rgbval = fColorLookup[colorTable][pixelVal];
colorIndex = colorTableOffset + pixelVal + 4;
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
}
if (byteCount != bytesPerLine) {
// 1-3 pixels in last byte
uint8_t pixelByte = *pPixels++;
int rem = outputWidthPix - fullBytesPerLine * 4;
if (rem >= 3) {
pixelVal = (pixelByte >> 6) & 0x03;
colorIndex = colorTableOffset + pixelVal + 8;
SetPix(x, line * 2, colorIndex);
SetPix(x++, line * 2 + 1, colorIndex);
}
if (rem >= 2) {
pixelVal = (pixelByte >> 4) & 0x03;
colorIndex = colorTableOffset + pixelVal + 12;
SetPix(x, line * 2, colorIndex);
SetPix(x++, line * 2 + 1, colorIndex);
}
if (rem >= 1) {
pixelVal = (pixelByte >> 2) & 0x03;
colorIndex = colorTableOffset + pixelVal + 0;
SetPix(x, line * 2, colorIndex);
SetPix(x++, line * 2 + 1, colorIndex);
}
}
} else {
/* 320 mode, one byte becomes two doubled pixels (1:4) */
unsigned int fullBytesPerLine = outputWidthPix / 4;
ASSERT(fullBytesPerLine <= bytesPerLine);
for (byteCount = 0; byteCount < fullBytesPerLine; byteCount++) {
uint8_t pixelByte = *pPixels++;
pixelVal = (pixelByte >> 4) & 0x0f;
colorIndex = colorTableOffset + pixelVal;
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
pixelVal = pixelByte & 0x0f;
colorIndex = colorTableOffset + pixelVal;
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
SetPix(x, line*2, colorIndex);
SetPix(x++, line*2+1, colorIndex);
}
if (byteCount != bytesPerLine) {
// 1 pixel in last byte
uint8_t pixelByte = *pPixels++;
pixelVal = (pixelByte >> 4) & 0x0f;
colorIndex = colorTableOffset + pixelVal;
SetPix(x, line * 2, colorIndex);
SetPix(x++, line * 2 + 1, colorIndex);
SetPix(x, line * 2, colorIndex);
SetPix(x++, line * 2 + 1, colorIndex);
}
}
#undef SetPix
ASSERT(x == outputWidthPix);
pSCB++;
}
ASSERT(line == outputHeightPix/2);
ASSERT(pSCB == pSCBStart + numScanLines);
bail:
return pDib;
}
/*
* ==========================================================================
* ReformatUnpackedSHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*
* Occasionally somebody slaps the wrong aux type on, so if we're in
* "relaxed" mode we accept just about anything that's the right size.
*/
void ReformatUnpackedSHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
const char* nameExt = pHolder->GetNameExt();
bool relaxed;
relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* uncompressed $c1/0000, but many variations on aux type */
if ((fileType == kTypePIC && auxType == 0x0000 && fileLen == 32768) ||
(relaxed &&
(
(fileType == kTypePIC && fileLen == 32768) ||
(fileType == kTypeBIN && fileLen == 32768 &&
(stricmp(nameExt, ".PIC") == 0 || stricmp(nameExt, ".SHR") == 0))
)
))
{
applies = ReformatHolder::kApplicProbably;
}
pHolder->SetApplic(ReformatHolder::kReformatSHR_PIC, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert an unpacked Super Hi-Res image.
*/
int ReformatUnpackedSHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
int retval = -1;
if (pHolder->GetSourceLen(part) != kTotalSize) {
LOGI(" SHR file is not %d bytes long!", kTotalSize);
goto bail;
}
ASSERT(sizeof(SHRScreen) == kTotalSize);
memcpy(&fScreen, pHolder->GetSourceBuf(part), sizeof(fScreen));
pDib = SHRScreenToBitmap8(&fScreen);
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
return retval;
}
/*
* ==========================================================================
* ReformatAppSHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*
* This file type seems exclusive to the IIgs version of "John Elway
* Quarterback".
*/
void ReformatJEQSHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
const char* nameExt = pHolder->GetNameExt();
bool relaxed;
relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* uncompressed $c1/0000, but many variations on aux type */
if (fileType == kTypeBIN && auxType == 0x0000 &&
fileLen == kExpectedLen && stricmp(nameExt, ".APP") == 0)
{
applies = ReformatHolder::kApplicProbably;
}
pHolder->SetApplic(ReformatHolder::kReformatSHR_JEQ, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert one of these odd-format images. It appears to be a dump of the
* SHR screen minus the "reserved" section between the SCB and color table,
* and only one color table is stored. Total savings of 480 bytes -- less
* than a ProDOS block. Sorta dumb.
*/
int ReformatJEQSHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
long srcLen = pHolder->GetSourceLen(part);
int retval = -1;
if (pHolder->GetSourceLen(part) != kExpectedLen) {
LOGI(" SHR file is not %d bytes long!", kTotalSize);
goto bail;
}
ASSERT(sizeof(SHRScreen) == kTotalSize);
memcpy(&fScreen.pixels, srcBuf, 32000);
memcpy(&fScreen.scb, srcBuf + 0x7d00, 256);
memcpy(&fScreen.colorTable, srcBuf + 0x7e00, 32);
pDib = SHRScreenToBitmap8(&fScreen);
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
return retval;
}
/*
* ==========================================================================
* ReformatPaintworksSHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*/
void ReformatPaintworksSHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
//long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
//const char* nameExt = pHolder->GetNameExt();
//bool relaxed;
//relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* PNT $c0/0000 */
if (fileType == kTypePNT && auxType == 0x0000)
applies = ReformatHolder::kApplicProbably;
pHolder->SetApplic(ReformatHolder::kReformatSHR_Paintworks, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert a PaintWorks format Super Hi-Res image.
*
* The format is loosely documented in the file type note for $C0/0000:
* +$000 to +$01F: Bytes
* Super Hi-Res Palette
* +$020 to +$021: Word
* Background color
* +$022 to +$221: Bytes
* Patterns. 16 QuickDraw II patterns, each 32 bytes in length.
* +$222 to EOF: Bytes
* Packed graphics data. Note that the unpacked data could be longer
* than one Super Hi-Res screen (Paintworks allows full-page sized
* documents).
*
* Sometimes it runs a few bytes over. If it runs significantly under, it's
* probably a generic packed image (PNT/0001) with the wrong auxtype.
*/
int ReformatPaintworksSHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
const int kPWOutputLines = 396;
const int kPWOutputSize = kPWOutputLines * kPixelBytesPerLine;
const int kPWDataOffset = 0x222;
uint8_t scb[kPWOutputLines];
uint8_t colorTable[kNumEntriesPerColorTable * kColorTableEntrySize];
uint8_t* unpackBuf = NULL;
int retval = -1;
int i, result;
if (pHolder->GetSourceLen(part) < kMinSize) {
LOGW(" SHR file too short (%ld)", pHolder->GetSourceLen(part));
goto bail;
}
/*
* Copy the color table out.
*/
memcpy(colorTable, pHolder->GetSourceBuf(part), sizeof(colorTable));
/*
* Unpack the packed data.
*
* For some reason it's fairly common to have exactly 9 bytes left over
* in the input. It appears that these files have 400 lines of graphics,
* and the first 8 bytes are PackBytes for the 4 lines. The very last
* byte is a zero, for no apparent reason. Best guess is something other
* than PaintWorks wrote these, because SuperConvert expects them to
* have 396 lines and only recognizes that many.
*/
unpackBuf = new uint8_t[kPWOutputSize];
if (unpackBuf == NULL)
goto bail;
memset(unpackBuf, 0, sizeof(unpackBuf)); // in case we fall short
result = UnpackBytes(unpackBuf,
pHolder->GetSourceBuf(part) + kPWDataOffset,
kPWOutputSize,
pHolder->GetSourceLen(part) - kPWDataOffset);
if (result != kPWOutputSize) {
LOGI("WARNING: UnpackBytes wasn't happy");
#if 0
/* thd282.shk (rev76.2) has a large collection of these */
if (UnpackBytes((uint8_t*) &fScreen,
pHolder->GetSourceBuf(part),
kTotalSize,
pHolder->GetSourceLen(part)) == 0)
{
pDib = SHRScreenToBitmap8(&fScreen);
if (pDib == NULL)
goto bail;
goto gotit;
}
#endif
}
for (i = 0; i < kPWOutputLines; i++)
scb[i] = 0; // 320 mode
pDib = SHRDataToBitmap8(unpackBuf, scb, colorTable, kPixelBytesPerLine,
kPWOutputLines, kOutputWidth, kPWOutputLines * 2);
if (pDib == NULL)
goto bail;
//gotit:
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
delete[] unpackBuf;
return retval;
}
/*
* ==========================================================================
* ReformatPackedSHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*/
void ReformatPackedSHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
//long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
//const char* nameExt = pHolder->GetNameExt();
//bool relaxed;
//relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* compressed $c0/0001 */
if (fileType == kTypePNT && auxType == 0x0001)
applies = ReformatHolder::kApplicProbably;
pHolder->SetApplic(ReformatHolder::kReformatSHR_Packed, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert a packed Super Hi-Res image (PNT/$0001).
*/
int ReformatPackedSHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
int retval = -1;
if (pHolder->GetSourceLen(part) < 4) {
LOGW(" SHR file too short (%ld)", pHolder->GetSourceLen(part));
goto bail;
}
ASSERT(sizeof(SHRScreen) == kTotalSize);
if (UnpackBytes((uint8_t*) &fScreen,
pHolder->GetSourceBuf(part), kTotalSize,
pHolder->GetSourceLen(part)) != kTotalSize)
{
LOGW(" SHR UnpackBytes failed");
goto bail;
}
pDib = SHRScreenToBitmap8(&fScreen);
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
return retval;
}
/*
* ==========================================================================
* ReformatAPFSHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*/
void ReformatAPFSHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
//long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
//const char* nameExt = pHolder->GetNameExt();
//bool relaxed;
//relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* compressed $c0/0002 */
if (fileType == kTypePNT && auxType == 0x0002)
applies = ReformatHolder::kApplicProbably;
pHolder->SetApplic(ReformatHolder::kReformatSHR_APF, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert a Super Hi-Res image in APF format (PNT/$0002).
*
* Blocks are a 32-bit length followed by a pascal string describing the
* contents. Common blocks are "MAIN", "PATS", and "SCIB", but could be
* anything (e.g. "Platinum Paint").
*
* All we're really interested in is the "MAIN" block, though we need
* to handle MULTIPAL as well for 3200-color images.
*/
int ReformatAPFSHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
const uint8_t* srcPtr = pHolder->GetSourceBuf(part);
long srcLen = pHolder->GetSourceLen(part);
uint8_t multiPal[kNumLines *
kNumEntriesPerColorTable * kColorTableEntrySize];
bool is3200 = false;
bool haveGraphics = false;
int retval = -1;
if (srcLen < 4) {
LOGW(" SHR file too short (%ld)", srcLen);
goto bail;
}
ASSERT(sizeof(SHRScreen) == kTotalSize);
while (srcLen > 0) {
CString blockName;
const uint8_t* nextBlock;
long blockLen, dataLen, nextLen;
int nameLen;
blockLen = Read32(&srcPtr, &srcLen);
if (blockLen > srcLen + 4) {
LOGI(" APFSHR WARNING: found blockLen=%ld, remaining len=%ld",
blockLen, srcLen);
break;
//goto bail;
}
nextBlock = srcPtr + (blockLen-4);
nextLen = srcLen - (blockLen-4);
nameLen = ::GetPascalString(srcPtr, srcLen, &blockName);
if (nameLen < 0) {
LOGI(" APFSHR failed getting pascal name, bailing");
goto bail;
}
srcPtr += nameLen+1;
srcLen -= nameLen+1;
dataLen = blockLen - (nameLen+1 + 4);
LOGI(" APFSHR block='%ls' blockLen=%ld (dataLen=%ld) start=0x%p",
(LPCWSTR) blockName, blockLen, dataLen, srcPtr);
if (blockName == "MAIN") {
if (UnpackMain(srcPtr, dataLen) != 0)
goto bail;
haveGraphics = true;
} else if (blockName == "MULTIPAL") {
if (UnpackMultipal(multiPal, srcPtr, dataLen) == 0)
is3200 = true;
} else if (blockName == "NOTE") {
UnpackNote(srcPtr, dataLen);
} else {
LOGI(" APFSHR (ignoring segment '%ls')", (LPCWSTR) blockName);
}
srcPtr = nextBlock;
srcLen = nextLen;
}
if (!haveGraphics)
goto bail;
// TODO: we don't currently handle MULTIPAL with anything other than
// a 320x200 image. Fortunately this is rare.
if (is3200 && !fNonStandard) {
Reformat3200SHR ref3200(&fScreen, multiPal);
pDib = ref3200.SHR3200ToBitmap24();
} else if (fNonStandard) {
pDib = SHRDataToBitmap8(fPixelStore, fSCBStore,
fScreen.colorTable, fPixelBytesPerLine, fNumScanLines,
fOutputWidth, fOutputHeight);
} else {
pDib = SHRScreenToBitmap8(&fScreen);
}
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
return retval;
}
/*
* Unpack the "MAIN" segment of an APF paint file into fScreen.
*
* +$00 MasterMode: 16-bit ModeWord value for MasterSCB. A ModeWord
* with a high byte of zero has the SCB in the low byte.
* +$02 PixelsPerScanLine: 16-bit value, usually 320 or 640 but may
* be something different.
* +$04 NumColorTables: 16-bit value, should be between 0 and 15
* +$06 ColorTableArray: [0..NumColorTables-1] of ColorTable, where
* a ColorTable is 16 sets of 16-bit $0RGB values.
* +$xx NumScanLines: 16-bit count of scan lines, often 200.
* +$xx+2 ScanLineDirectory: [0..NumScanLines-1] of DirEntry. Each
* DirEntry has two parts, a 16-bit count of packed-data-length
* for each line, followed by a 16-bit ModeWord value.
* +$yy PackedScanLines: [0..NumScanLines-1] of packed data. Each
* scan line is individually packed with PackBytes.
*
* Returns 0 on success, -1 on failure.
*
* Question: if the image is 640x200, is it in 640 mode? Can we depend on
* the MasterMode word to tell us if it's a 320-mode 4-bit-per-pixel image
* that happens to be 640 pixels wide? Did all //gs applications set this
* correctly? Can the format be different on every line?
*/
int ReformatAPFSHR::UnpackMain(const uint8_t* srcPtr, long srcLen)
{
const int kColorTableSize = kNumEntriesPerColorTable * kColorTableEntrySize;
uint16_t masterMode;
unsigned int numColorTables;
uint16_t* packedDataLen = NULL;
int retval = -1;
if (srcLen < 64) {
/* can't possibly be this small */
LOGI(" APFSHR unlikely srcLen %d", srcLen);
goto bail;
}
masterMode = Read16(&srcPtr, &srcLen);
fPixelsPerScanLine = Read16(&srcPtr, &srcLen);
numColorTables = Read16(&srcPtr, &srcLen);
if (fPixelsPerScanLine == 0 || fPixelsPerScanLine > kMaxPixelsPerScan) {
LOGI(" APFSHR unsupported pixelsPerScanLine %d",
fPixelsPerScanLine);
goto bail;
}
LOGD(" APFSHR masterMode=0x%04x, ppsl=%d, nct=%d",
masterMode, fPixelsPerScanLine, numColorTables);
if (numColorTables == 0 || numColorTables > kNumColorTables) {
// Zero is valid per the spec, but we don't know how to render that.
// Rather than showing an all-black bitmap, just fail.
LOGI(" APFSHR unsupported numColorTables %d", numColorTables);
goto bail;
}
/*
* Load the color tables.
*/
memset(fScreen.colorTable, 0, sizeof(fScreen.colorTable));
for (unsigned int i = 0; i < numColorTables; i++) {
if (srcLen < kColorTableSize) {
LOGI(" APFSHR underrun while copying color tables");
goto bail;
}
memcpy(&fScreen.colorTable[i * kColorTableSize], srcPtr,
kColorTableSize);
srcPtr += kColorTableSize;
srcLen -= kColorTableSize;
}
/*
* Get the scanline count.
*
* fOutputHeight is twice the #of scan lines because we use pixel
* doubling so we can handle both 320 mode and 640 mode.
*/
fNumScanLines = Read16(&srcPtr, &srcLen);
if (fNumScanLines == 0 || fNumScanLines > kMaxScanLines) {
LOGI(" APFSHR unsupported numScanLines %d", fNumScanLines);
goto bail;
}
if ((fPixelsPerScanLine == 320 || fPixelsPerScanLine == 640) &&
fNumScanLines == kNumLines)
{
/* standard-sized image, use fScreen */
ASSERT(!fNonStandard);
LOGD(" Assuming this is a standard, full-width SHR image");
fPixelBytesPerLine = kPixelBytesPerLine; // 160
fPixelStore = fScreen.pixels;
fSCBStore = fScreen.scb;
// These don't matter if "fNonStandard" is false, but set them anyway
// so the debug output looks okay.
fOutputWidth = 640;
fOutputHeight = kNumLines * 2;
} else {
/* non-standard image, allocate storage */
fNonStandard = true;
LOGI(" NOTE: non-standard image size %dx%d, 2-bit-mode=%d",
fPixelsPerScanLine, fNumScanLines,
(masterMode & kSCBNumPixels) != 0);
fOutputHeight = fNumScanLines * 2;
if ((masterMode & kSCBNumPixels) == 0) {
fOutputWidth = fPixelsPerScanLine * 2;
fPixelBytesPerLine = (fPixelsPerScanLine + 1) / 2; // "320 mode"
} else {
fOutputWidth = fPixelsPerScanLine;
fPixelBytesPerLine = (fPixelsPerScanLine + 3) / 4; // "640 mode"
}
fPixelStore = new uint8_t[fPixelBytesPerLine * fNumScanLines];
if (fPixelStore == NULL) {
LOGI(" APFSHR ERROR: alloc of %d bytes fPixelStore failed",
fPixelBytesPerLine * fNumScanLines);
goto bail;
}
fSCBStore = new uint8_t[fNumScanLines];
if (fSCBStore == NULL) {
LOGI(" APFSHR ERROR: alloc of %d bytes fSCBStore failed",
fNumScanLines);
goto bail;
}
}
LOGD(" APFSHR numScanLines=%d, outputWidth=%d, pixelBytesPerLine=%d",
fNumScanLines, fOutputWidth, fPixelBytesPerLine);
/*
* Get the per-scanline data.
*/
packedDataLen = new uint16_t[fNumScanLines];
if (packedDataLen == NULL)
goto bail;
for (unsigned int i = 0; i < fNumScanLines; i++) {
packedDataLen[i] = Read16(&srcPtr, &srcLen);
if (packedDataLen[i] > fPixelsPerScanLine) {
/* each pixel is 2 or 4 bits, so this is a 2-4x expansion */
LOGI(" APFSHR got funky packed len %d for line %d",
packedDataLen[i], i);
goto bail;
}
uint16_t mode = Read16(&srcPtr, &srcLen);
if (mode >> 8 == 0)
fSCBStore[i] = (uint8_t) mode;
else {
LOGI(" APFSHR odd mode 0x%04x on line %d", mode, i);
fSCBStore[i] = 0; // ?
}
}
/*
* Unpack the scan lines into the pixel storage. Some APF files
* use PackBytes in a not-quite-safe way, e.g. a 28x30 image with
* 14 bytes per line uses the 32-bit pattern repeat mode (0x3fff),
* yielding 16 bytes. We need to unpack to a temp buffer.
*
* I've found a few from 816/Paint that appear to have the wrong width
* value; they claim 320 but look like they're actually 640 (the image
* seems to be cut in half, and we unpack 2x as much data as we expect).
*/
for (unsigned int i = 0; i < fNumScanLines; i++) {
// PackBytes can generate 256 bytes from a single pair of bytes.
uint8_t tmpBuf[ReformatSHR::kMaxPixelsPerScan];
if (srcLen <= 0) {
LOGI(" APFSHR ran out of data while unpacking pixels");
goto bail;
}
int actual = UnpackBytes(tmpBuf, srcPtr, sizeof(tmpBuf), packedDataLen[i]);
if (actual < (int) fPixelBytesPerLine) { // includes actual < 0
LOGI(" APFSHR UnpackBytes failed on line %d (pbpl=%d actual=%d)",
i, fPixelBytesPerLine, actual);
goto bail;
}
//LOGD("+++ unpackbytes %d pbpl=%d pdl=%d --> %d",
// i, fPixelBytesPerLine, packedDataLen[i], actual);
memcpy(&fPixelStore[i * fPixelBytesPerLine], tmpBuf, fPixelBytesPerLine);
srcPtr += packedDataLen[i];
srcLen -= packedDataLen[i];
}
retval = 0;
bail:
delete[] packedDataLen;
return retval;
}
/*
* Unpack the "multipal" data.
*
* Unfortunately the guy who wrote "SuperView" decided to un-swap the
* Brooks format by stuffing them into the file the wrong way. We could
* fix it here, but we can't reliably detect the files.
*/
int ReformatAPFSHR::UnpackMultipal(uint8_t* dstPtr,
const uint8_t* srcPtr, long srcLen)
{
const int kMultipalSize = kNumLines *
kNumEntriesPerColorTable * kColorTableEntrySize;
int numColorTables;
/* check the size; use (size+2) to factor in 16-bit count */
if (srcLen < kMultipalSize+2) {
LOGI(" APFSHR got too-small multipal size %ld", srcLen);
return -1;
} else if (srcLen > kMultipalSize+2) {
LOGI(" APFSHR WARNING: oversized multipal (%ld, expected %ld)",
srcLen, kMultipalSize);
/* keep going */
}
/* get the #of color tables; should always be 200 */
numColorTables = Read16(&srcPtr, &srcLen);
if (numColorTables != kNumLines) {
/* expecting one palette per line */
LOGI(" APFSHR ignoring multipal with %d color tables",
numColorTables);
return -1;
}
if (1) {
memcpy(dstPtr, srcPtr, kMultipalSize);
} else {
#if 0
/* swap entries */
const uint16_t* pSrcTable;
uint16_t* pDstTable = (uint16_t*) dst;
int table, entry;
for (table = 0; table < kNumLines; table++) {
pSrcTable = (const uint16_t*)src +
(kNumLines - table) * kNumEntriesPerColorTable;
for (entry = 0; entry < kNumEntriesPerColorTable; entry++) {
pDstTable[entry] = *pSrcTable++;
}
pDstTable += kNumEntriesPerColorTable;
}
#endif
}
return 0;
}
/*
* Unpack a "NOTE" chunk. This seems to be a 16-bit count followed by
* ASCII data.
*/
void ReformatAPFSHR::UnpackNote(const uint8_t* srcPtr, long srcLen)
{
int numChars;
numChars = Read16(&srcPtr, &srcLen);
if (numChars != srcLen) {
LOGI(" APFSHR note chunk has numChars=%d but dataLen=%ld, bailing",
numChars, srcLen);
return;
}
CString str;
while (srcLen--) {
str += *srcPtr++;
}
LOGI(" APFSHR note: '%ls'", (LPCWSTR) str);
}
/*
* ==========================================================================
* Reformat3200SHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*/
void Reformat3200SHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
const char* nameExt = pHolder->GetNameExt();
bool relaxed;
relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* should be $c1/0002, but there are many variations */
if ((fileType == kTypePIC && auxType == 0x0002 && fileLen == 38400) ||
(fileType == kTypeBIN && fileLen == 38400 &&
stricmp(nameExt, ".3200") == 0) ||
(relaxed &&
(
(fileType == kTypePIC && fileLen == 38400)
)
))
{
applies = ReformatHolder::kApplicProbably;
}
pHolder->SetApplic(ReformatHolder::kReformatSHR_3200, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert a 3200-color Super Hi-Res Image.
*/
int Reformat3200SHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
int retval = -1;
if (pHolder->GetSourceLen(part) != kExtTotalSize) {
LOGI(" SHR file is not %d bytes long!", kExtTotalSize);
return retval;
}
ASSERT(sizeof(fScreen.pixels) + sizeof(fExtColorTable) == kExtTotalSize);
ASSERT(sizeof(fScreen.pixels) == 32000);
ASSERT(sizeof(fExtColorTable) == 32 * 200);
memcpy(fScreen.pixels, pHolder->GetSourceBuf(part), sizeof(fScreen.pixels));
//memcpy(fExtColorTable, (*ppBuf) + sizeof(fScreen.pixels),
// sizeof(fExtColorTable));
/* "Brooks" format color tables are stored in reverse order */
const uint16_t* pSrcTable = (const uint16_t*)
(pHolder->GetSourceBuf(part) + sizeof(fScreen.pixels));
uint16_t* pDstTable = (uint16_t*) fExtColorTable;
for (int table = 0; table < kExtNumColorTables; table++) {
int entry;
for (entry = 0; entry < kNumEntriesPerColorTable; entry++) {
pDstTable[(kNumEntriesPerColorTable-1) - entry] = *pSrcTable++;
}
pDstTable += kNumEntriesPerColorTable;
}
pDib = SHR3200ToBitmap24();
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
return retval;
}
/*
* Convert a SHRScreen struct to a 24-bit DIB, using a different
* color palette for each line.
*
* This is shared among the 3200-color SHR formats.
*/
MyDIBitmap* Reformat3200SHR::SHR3200ToBitmap24(void)
{
MyDIBitmap* pDib = new MyDIBitmap;
RGBTRIPLE colorLookup[kExtNumColorTables][kNumEntriesPerColorTable];
RGBTRIPLE* rgbBuf;
const uint8_t* pPixels = fScreen.pixels;
if (pDib == NULL)
goto bail;
/*
* Set up a DIB to hold the data.
*/
rgbBuf = (RGBTRIPLE*) pDib->Create(kOutputWidth, kOutputHeight, 24, 0);
if (rgbBuf == NULL) {
delete pDib;
pDib = NULL;
goto bail;
}
/*
* Convert color palette data to RGBTRIPLE form.
*/
const uint16_t* pClrTable;
pClrTable = (const uint16_t*) fExtColorTable;
for (int table = 0; table < kExtNumColorTables; table++) {
for (int entry = 0; entry < kNumEntriesPerColorTable; entry++) {
GSColor(*pClrTable++, &colorLookup[table][entry]);
//if (!table) {
// LOGI(" table %2d entry %2d value=0x%04x",
// table, entry, *pClrTable);
//}
}
}
/*
* Set the pixels in our private RGB buffer.
*/
int line;
for (line = 0; line < kNumLines; line++) {
int byteCount, pixelByte;
uint8_t pixelVal;
RGBTRIPLE rgbval;
int x = 0;
#define SetPix(x, y, rgbval) \
rgbBuf[((kOutputHeight-1) - (y)) * kOutputWidth + (x)] = rgbval
for (byteCount = 0; byteCount < kPixelBytesPerLine; byteCount++) {
pixelByte = *pPixels++;
pixelVal = (pixelByte >> 4) & 0x0f;
rgbval = colorLookup[line][pixelVal];
SetPix(x, line*2, rgbval);
SetPix(x++, line*2+1, rgbval);
SetPix(x, line*2, rgbval);
SetPix(x++, line*2+1, rgbval);
pixelVal = pixelByte & 0x0f;
rgbval = colorLookup[line][pixelVal];
SetPix(x, line*2, rgbval);
SetPix(x++, line*2+1, rgbval);
SetPix(x, line*2, rgbval);
SetPix(x++, line*2+1, rgbval);
}
#undef SetPix
ASSERT(x == kOutputWidth);
}
ASSERT(line == kOutputHeight/2);
bail:
return pDib;
}
/*
* ==========================================================================
* Reformat3201SHR
* ==========================================================================
*/
/*
* Decide whether or not we want to handle this file.
*
* This *might* also be $c0/0004, but there's no file type note
* for that.
*/
void Reformat3201SHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
//long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
const char* nameExt = pHolder->GetNameExt();
//bool relaxed;
//relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
/* only identified by name */
if (stricmp(nameExt, ".3201") == 0)
applies = ReformatHolder::kApplicProbably;
pHolder->SetApplic(ReformatHolder::kReformatSHR_3201, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
}
/*
* Convert a 3201-format packed 3200-color Super Hi-Res Image.
*/
int Reformat3201SHR::Process(const ReformatHolder* pHolder,
ReformatHolder::ReformatID id, ReformatHolder::ReformatPart part,
ReformatOutput* pOutput)
{
MyDIBitmap* pDib;
const uint8_t* srcBuf = pHolder->GetSourceBuf(part);
long srcLen = pHolder->GetSourceLen(part);
long length = srcLen;
uint8_t* tmpBuf = NULL;
int retval = -1;
if (srcLen < 16 || srcLen > kExtTotalSize) {
LOGI(" SHR3201 file funky length (%d)", srcLen);
return retval;
}
const long* pMagic = (const long*) pHolder->GetSourceBuf(part);
if (*pMagic != 0x00d0d0c1) { // "APP\0"
LOGI(" SHR3201 didn't find magic 'APP'");
return retval;
}
srcBuf += 4;
length -= 4;
ASSERT(sizeof(fScreen.pixels) == 32000);
ASSERT(sizeof(fExtColorTable) == 32 * 200);
ASSERT(sizeof(fScreen.pixels) + sizeof(fExtColorTable) == kExtTotalSize);
/* "Brooks" format color tables are stored in reverse order */
const uint16_t* pSrcTable;
uint16_t* pDstTable;
pSrcTable = (const uint16_t*) srcBuf;
pDstTable = (uint16_t*) fExtColorTable;
int table;
for (table = 0; table < kExtNumColorTables; table++) {
int entry;
for (entry = 0; entry < kNumEntriesPerColorTable; entry++) {
pDstTable[(kNumEntriesPerColorTable-1) - entry] = *pSrcTable++;
}
pDstTable += kNumEntriesPerColorTable;
}
srcBuf = (const uint8_t*) pSrcTable;
length -= srcBuf - (pHolder->GetSourceBuf(part) +4);
/* now unpack the PackBytes-format pixels */
if (UnpackBytes(fScreen.pixels, srcBuf, sizeof(fScreen.pixels), length)
!= sizeof(fScreen.pixels)) {
goto bail;
}
pDib = SHR3200ToBitmap24();
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
delete[] tmpBuf;
return retval;
}