ciderpress/reformat/DoubleHiRes.cpp

535 lines
20 KiB
C++

/*
* CiderPress
* Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved.
* See the file LICENSE for distribution terms.
*/
/*
* Multiple conversions for double-hi-res graphics.
*/
#include "StdAfx.h"
#include "DoubleHiRes.h"
#include "HiRes.h"
/*
* The screen layout is described in detail in Apple //e technote #3.
*
* Summary: pixel data is byte-interleaved between main and auxilliary
* 8K pages. Seven pixels of each byte contribute to the image; the high
* bit of each byte is ignored. There are 16 possible colors, which match
* up with the 16 lo-res colors.
*
* The interference patterns caused by adjacent colors are extremely
* difficult to model accurately, especially considering that RGB and
* composite outputs seem to be different.
*/
/*
* Decide whether or not we want to handle this file.
*/
void ReformatDHR::Examine(ReformatHolder* pHolder)
{
ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot;
long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData);
long fileType = pHolder->GetFileType();
long auxType = pHolder->GetAuxType();
long dhrAlg;
bool relaxed;
relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0;
if ((fileType == kTypeFOT && auxType < 0x4000 && fileLen == 16384) ||
(relaxed &&
(
(fileType == kTypeBIN && fileLen == 16376) ||
(fileType == kTypeBIN && fileLen == 16380) ||
(fileType == kTypeBIN && fileLen == 16384)
)
))
{
applies = ReformatHolder::kApplicProbably;
}
pHolder->SetApplic(ReformatHolder::kReformatDHR_Latched, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
pHolder->SetApplic(ReformatHolder::kReformatDHR_BW, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
pHolder->SetApplic(ReformatHolder::kReformatDHR_Plain140, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
pHolder->SetApplic(ReformatHolder::kReformatDHR_Window, applies,
ReformatHolder::kApplicNot, ReformatHolder::kApplicNot);
/*
* Set the "preferred" flag on one option.
*/
dhrAlg = pHolder->GetOption(ReformatHolder::kOptDHRAlgorithm);
switch ((Algorithms) dhrAlg) {
case kDHRLatched:
pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_Latched);
break;
case kDHRBlackWhite:
pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_BW);
break;
case kDHRPlain140:
pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_Plain140);
break;
case kDHRWindow:
pHolder->SetApplicPreferred(ReformatHolder::kReformatDHR_Window);
break;
default:
LOGI("GLITCH: DHR algorithm %d not recognized", dhrAlg);
break;
}
}
/*
* Convert a Double-Hi-Res image to a bitmap.
*/
int ReformatDHR::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;
switch (id) {
case ReformatHolder::kReformatDHR_Latched:
fAlgorithm = kDHRLatched;
break;
case ReformatHolder::kReformatDHR_BW:
fAlgorithm = kDHRBlackWhite;
break;
case ReformatHolder::kReformatDHR_Plain140:
fAlgorithm = kDHRPlain140;
break;
case ReformatHolder::kReformatDHR_Window:
fAlgorithm = kDHRWindow;
break;
default:
LOGI("GLITCH: bad id %d", id);
fAlgorithm = kDHRLatched;
break;
}
if (srcLen > kExpectedSize || srcLen < kExpectedSize-8) {
LOGI(" DHR file is not ~%d bytes long (got %d)",
kExpectedSize, srcLen);
goto bail;
}
/* line layout is same as standard hires */
ReformatHiRes::InitLineOffset(fLineOffset);
InitColorLookup();
pDib = DHRScreenToBitmap(srcBuf);
if (pDib == NULL)
goto bail;
SetResultBuffer(pOutput, pDib);
retval = 0;
bail:
return retval;
}
/*
* Initialize the 4-bit-window color lookup table.
*/
void ReformatDHR::InitColorLookup(void)
{
for (int ii = 0; ii < 4; ii++) {
for (int jj = 0; jj < kNumDHRColors; jj++) {
int num = jj;
for (int kk = 0; kk < ii; kk++) {
if (num & 0x01)
num |= 0x10;
num >>= 1;
num &= 0x0f;
}
fColorLookup[ii][jj] = num;
}
}
return;
}
/*
* Convert a buffer of double-hires data to a 16-color DIB.
*/
MyDIBitmap* ReformatDHR::DHRScreenToBitmap(const uint8_t* buf)
{
MyDIBitmap* pDib = new MyDIBitmap;
uint8_t* outBuf;
const int kMaxLook = 4; // padding to adjust for lookbehind/lookahead
int pixelBits[kMaxLook+kPixelsPerLine+kMaxLook]; // 560 mono pixels
unsigned int colorBuf[kOutputWidth]; // 560 color pixels
int line;
/* color map */
enum {
kColorBlack = 0, // 0000
kColorRed, // 0001
kColorBrown, // 0010
kColorOrange, // 0011 hcolor=5
kColorDarkGreen, // 0100
kColorGrey1, // 0101
kColorGreen, // 0110 hcolor=1
kColorYellow, // 0111
kColorDarkBlue, // 1000
kColorPurple, // 1001 hcolor=2
kColorGrey2, // 1010
kColorPink, // 1011
kColorMediumBlue, // 1100 hcolor=6
kColorLightBlue, // 1101
kColorAqua, // 1110
kColorWhite, // 1110
kNumColors
};
RGBQUAD colorConv[kNumColors];
colorConv[0] = fPalette[kPaletteBlack];
colorConv[1] = fPalette[kPaletteRed];
colorConv[2] = fPalette[kPaletteBrown];
colorConv[3] = fPalette[kPaletteOrange];
colorConv[4] = fPalette[kPaletteDarkGreen];
colorConv[5] = fPalette[kPaletteDarkGrey];
colorConv[6] = fPalette[kPaletteGreen];
colorConv[7] = fPalette[kPaletteYellow];
colorConv[8] = fPalette[kPaletteDarkBlue];
colorConv[9] = fPalette[kPalettePurple];
colorConv[10] = fPalette[kPaletteLightGrey];
colorConv[11] = fPalette[kPalettePink];
colorConv[12] = fPalette[kPaletteMediumBlue];
colorConv[13] = fPalette[kPaletteLightBlue];
colorConv[14] = fPalette[kPaletteAqua];
colorConv[15] = fPalette[kPaletteWhite];
ASSERT(kNumColors == kPaletteSize);
ASSERT(kNumDHRColors == kNumColors);
ASSERT(kOutputWidth == kPixelsPerLine);
if (pDib == NULL)
goto bail;
outBuf = (uint8_t*) pDib->Create(kOutputWidth, kOutputHeight,
4, kNumColors);
if (outBuf == NULL) {
delete pDib;
pDib = NULL;
goto bail;
}
pDib->SetColorTable(colorConv);
/*
* Run through the lines.
*
* This is reasonably inefficient, since I'm splitting things into
* their constituent bits and then, for the most part, just stuffing
* them right back together. Someday, if fatally bored, this should
* be optimized.
*/
for (line = 0; line < kNumLines; line++) {
const uint8_t* lineData = buf + fLineOffset[line];
int* bitPtr = pixelBits + kMaxLook;
/* this is really just to clear the fore and aft MaxLook bits */
memset(pixelBits, 0, sizeof(pixelBits));
/* unravel the bits */
for (int byt = 0; byt < kPixelsPerLine / 7; byt++) {
uint8_t val;
if (byt & 0x01) {
/* odd pixels come from main memory */
val = *(lineData+kPageSize);
lineData++;
} else {
/* even pixels come from aux mem */
val = *lineData;
}
for (int bit = 0; bit < 7; bit++) {
*bitPtr++ = val & 0x01;
val >>= 1;
}
}
ASSERT(lineData <= buf + kPageSize);
ASSERT((char*)bitPtr == (char*)pixelBits +
sizeof(pixelBits) - kMaxLook*sizeof(pixelBits[0]));
/*
* Convert the bits to colors.
*/
int idx;
if (fAlgorithm == kDHRBlackWhite) {
for (idx = 0; idx < kPixelsPerLine; idx ++) {
int bufTarget = idx;
if (!pixelBits[idx]) {
colorBuf[bufTarget] = kColorBlack;
} else {
colorBuf[bufTarget] = kColorWhite;
}
}
} else if (fAlgorithm == kDHRPlain140) {
/*
* Very simple: every four pixels is a solid color. Not too
* close to reality, but easy to implement.
*/
int pixVal = 0;
bitPtr = pixelBits + kMaxLook;
for (idx = 0; idx < kPixelsPerLine/4; idx++) {
pixVal = *bitPtr++;
pixVal = (pixVal << 1) | (*bitPtr++);
pixVal = (pixVal << 1) | (*bitPtr++);
pixVal = (pixVal << 1) | (*bitPtr++);
colorBuf[idx*4] = pixVal;
colorBuf[idx*4+1] = pixVal;
colorBuf[idx*4+2] = pixVal;
colorBuf[idx*4+3] = pixVal;
}
ASSERT(bitPtr == pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]) - kMaxLook);
} else if (fAlgorithm == kDHRWindow) {
/*
* The way we determine the value of the color at pixel N
* is by looking at the pixels at N-3, N-2, N-1, and N.
*
* We manage this with a continously shifting 4-bit-wide
* window.
*
* Note to self: interesting case at line 87, pixels 200-240,
* in "screen2".
*/
int pixVal = 0;
bitPtr = pixelBits + kMaxLook;
for (idx = 0; idx < kPixelsPerLine; idx++) {
pixVal = (pixVal << 1) & 0x0f;
if (*bitPtr++)
pixVal |= 1;
colorBuf[idx] = fColorLookup[(idx+1) & 0x03][pixVal];
}
ASSERT(bitPtr <= pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]));
#if 0
/*
* Zero-stop pass, where we set the luminosity to zero if we
* see four zeros in a row.
*/
bitPtr = pixelBits + kMaxLook;
for (idx = 0; idx < kPixelsPerLine; idx++) {
if (!bitPtr[idx] && !bitPtr[idx+1] &&
!bitPtr[idx+2] && !bitPtr[idx+3])
{
//if (line == 87 && idx > 200 && idx < 240) {
// LOGI(" %4d ERASE", idx);
//}
/*colorBuf[idx] =*/ colorBuf[idx+1] =
colorBuf[idx+2] = /*colorBuf[idx+3] =*/ kColorBlack;
}
}
#endif
} else if (fAlgorithm == kDHRLatched) {
/*
* We determine the value of the color at pixel N is by looking
* at the pixels at N-3, N-2, N-1, and N. When we see a color
* transition, we also look at (N+1..N+4) to special-case
* white/black transitions. This is necessary to reduce the
* color fringes around sharply defined objects.
*
* Once a color is "latched", we keep outputting that color
* until we find a new one that we like more.
*
* We manage this with a continously shifting 8-bit-wide window.
*
* Note to self: interesting case at line=98, pixels 50-80.
*/
unsigned int whole;
int newColor, oldColor;
bitPtr = pixelBits;
whole = 0;
for (idx = 0; idx < 8; idx++) {
whole <<= 1;
if (*bitPtr++)
whole |= 1;
}
ASSERT((whole & (~0xff)) == 0);
/* grab the color of the "previous" 4 pixels */
oldColor = fColorLookup[idx & 0x03][whole & 0x0f];
for (idx = 0; idx < kPixelsPerLine; idx++, bitPtr++) {
//if (line == 98 && idx > 50 && idx < 80) {
// if (!(idx % 4)) {
// LOGI(" idx %3d: bits=0x%02x PPPPCNNN",
// idx, whole & 0xff);
// }
//}
/* shift another bit in, to give us 3 prev and 4 next */
/* looks like PPPCNNNN */
ASSERT(*bitPtr == 0 || *bitPtr == 1);
whole = (whole << 1) | *bitPtr;
whole &= 0xff; // not needed; useful for printfs
/* get the new color (from PPPC bits) */
newColor = fColorLookup[(idx+1) & 0x03][(whole & 0xf0) >> 4];
//if (line == 98 && idx > 50 && idx < 80) {
// LOGI(" idx %3d: old=%-2d new=%-2d (bits=0x%02x)", idx,
// oldColor, newColor, whole & 0xff);
//}
if (newColor != oldColor) {
/*
* Transition to new color; check for white/black blocks
* in *next* chunk of pixels. The goal is to eliminate
* color fringes on white/black boundaries, which are the
* most easily visible.
*/
int shift1, shift2, shift3;
shift1 = (whole >> 3) & 0x0f; // PPCN
shift2 = (whole >> 2) & 0x0f; // PCNN
shift3 = (whole >> 1) & 0x0f; // CNNN
if (shift1 == 0x0f || shift2 == 0x0f || shift3 == 0x0f)
newColor = kColorWhite;
else if (shift1 == 0 || shift2 == 0 || shift3 == 0)
newColor = kColorBlack;
//if (line == 98 && idx > 50 && idx < 80) {
// LOGI(" idx %3d: S new=%-2d", idx, newColor);
//}
}
//if (line == 98 && idx > 50 && idx < 80) {
//newColor = kColorYellow;
//}
colorBuf[idx] = newColor;
/*
* Use the new color as the old color for the next iteration.
* This is *NOT* the same as getting the color from PPPP
* before shifting next round, because we might have
* overridden white or black above. In that case, the new
* color would be compared against white or black in the
* transition check, instead of comparing against the actual
* color of the PPPP pixels.
*/
oldColor = newColor; // latch it
//oldColor = fColorLookup[idx & 0x03][whole & 0x0f];
}
ASSERT(bitPtr <= pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]));
} else {
ASSERT(false);
#if defined(DHR_MULTIPASS)
unsigned int colorBuf1[kMaxLook+kOutputWidth+kMaxLook];
int pixVal = 0;
bitPtr = pixelBits + kMaxLook;
/*
* Start simple.
*/
memset(colorBuf1, 0, sizeof(colorBuf1));
for (idx = 0; idx < kPixelsPerLine/4; idx++) {
pixVal = *bitPtr++;
pixVal = (pixVal << 1) | (int)(*bitPtr++);
pixVal = (pixVal << 1) | (int)(*bitPtr++);
pixVal = (pixVal << 1) | (int)(*bitPtr++);
colorBuf1[idx*4 +kMaxLook] = pixVal;
colorBuf1[idx*4+1 +kMaxLook] = pixVal;
colorBuf1[idx*4+2 +kMaxLook] = pixVal;
colorBuf1[idx*4+3 +kMaxLook] = pixVal;
}
ASSERT(bitPtr <= pixelBits + sizeof(pixelBits)/sizeof(pixelBits[0]));
/*
* Now go back and patch all of the transitions between colors.
* The most significant are transitions to and from black,
* which have to truncate bits.
*/
for (idx = 3; idx < kMaxLook+kPixelsPerLine; idx += 4) {
if (colorBuf1[idx] != colorBuf1[idx+1]) {
/* color change */
if (colorBuf1[idx] == 0) {
/* trim pixels on left */
pixVal = colorBuf1[idx+1];
ASSERT(pixVal != 0);
unsigned int* iptr = &colorBuf1[idx+1];
while (!(pixVal & 0x08)) {
*iptr++ = kColorBlack;
pixVal <<= 1;
}
} else if (colorBuf1[idx+1] == 0) {
/* trim pixels on right */
pixVal = colorBuf1[idx];
ASSERT(pixVal != 0);
unsigned int* iptr = &colorBuf1[idx];
while (!(pixVal & 0x01)) {
*iptr-- = kColorBlack;
pixVal >>= 1;
}
} else {
/*
* The center four pixels have a color determined
* like this:
*
* orange = 0011
* pink = 1011
*
* Take last two of orange and first two of pink,
* and swap them so first two of pink come first,
* e.g. 1011. The color, which also happens to be
* pink, is what we set the middle four pixels to.
*
* Can't explain it, but that's how it works...
* usually.
*/
uint8_t mergePix;
mergePix = colorBuf1[idx] & 0x03;
mergePix |= colorBuf1[idx+1] & 0x0c;
ASSERT((mergePix & 0xf0) == 0);
if (line == 191) {
LOGI("idx=0x%02x idx+1=0x%02x merge=0x%02x",
colorBuf1[idx], colorBuf1[idx+1], mergePix);
}
colorBuf1[idx-1] = colorBuf1[idx] = colorBuf1[idx+1] =
colorBuf1[idx+2] = mergePix;
}
}
}
memcpy(colorBuf, colorBuf1+kMaxLook, sizeof(colorBuf));
#endif
} /* color algorithm choices */
/* convert colors to 4-bit bitmap pixels, with line-doubling */
#define SetPix(x, y, twoval) \
outBuf[((kOutputHeight-1) - (y)) * (kOutputWidth/2) + (x)] = twoval
uint8_t pix4;
for (int pix = 0; pix < kPixelsPerLine/2; pix++) {
int bufPosn = pix * 2;
ASSERT(colorBuf[bufPosn] < kNumColors);
ASSERT(colorBuf[bufPosn+1] < kNumColors);
pix4 = colorBuf[bufPosn] << 4 | colorBuf[bufPosn+1];
SetPix(pix, line*2, pix4);
SetPix(pix, line*2+1, pix4);
}
} /*for each line*/
#undef SetPix
bail:
return pDib;
}