/* * 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; }