/* * CiderPress * Copyright (C) 2007 by faddenSoft, LLC. All Rights Reserved. * See the file LICENSE for distribution terms. */ /* * Reformat hi-res graphics into a bitmap. */ #include "StdAfx.h" #include "HiRes.h" /* * Hi-Res image format: * * <16-bit load address> [DOS 3.3 only] * <16-bit file length> [DOS 3.3 only] * lines, in wonky format * * Each line is 40 bytes long. Each byte holds 7 bits that define a color * value from 0 to 3, and the high byte determines which set of colors to * use. * * The colors are: * 0 black0 4 black1 00 00 / 80 80 * 1 green 5 orange 2a 55 / aa d5 * 2 purple 6 blue 55 2a / d5 aa * 3 white0 7 white1 7f 7f / ff ff * * There is also a "half-shift" phenomenon for the second set, which appear * to be shifted half a pixel to the right. The stair-step looks like this: * * white0 * purple * white1 * blue * green * orange * * white1/blue/orange have a half-pixel shift, while green/orange have a * full-pixel shift because the colors don't start until the appropriate * bit (and stop on the appropriate bit). The area between two identical * colors is filled. If you have a "1 0 0 1" bit situation, a black gap * is formed, and a "0 1 1 0" situation creates a white area, but "0 1 0 1" * forms a solid color starting and stopping on the 1/280th pixels where * the 1s are. * * Some transition examples: * purpleBBBorange * purpleBBgreen * blueBgreen * * orangeWWblue * greenWWpurple * * The IIgs monochrome mode is not enabled on the RGB output unless you * turn off AN3 by hitting $c05e (it can be re-enabled by hitting $c05f). * This register turns off the half-pixel shift, so it doesn't appear to * be possible to view hi-res output on an RGB monitor with the half-pixel * shift intact. On the composite output, the removal of the half-pixel * shift is quite visible. */ /* * Regarding color... * [ Found on http://www.geocities.com/jonrelay/software/a2info/munafo.htm ] From: munafo@gcctech.com Newsgroups: comp.emulators.apple2,comp.sys.apple2.programmer Subject: RGB values for the standard Apple ][ colors Date: Thu, 12 Oct 2000 03:13:33 GMT Lines: 137 I am giving a table of all the lores and hires colors with their values in RGB. First, though there is a bit to explain... [...] Here is the table. It was used by taking the chroma/luma values and transforming to R-Y and B-Y, then transforming to YUV and finally to RGB. The last step requires a gamma correction for display on an RGB monitor, I used Y to the power of -0.4. For reference, the NTSC "color bars" test pattern colors and the YIQ axis colors are also given. --chroma-- Color name phase ampl luma -R- -G- -B- black COLOR=0 0 0 0 0 0 0 gray COLOR=5 0 0 50 156 156 156 grey COLOR=10 0 0 50 156 156 156 white COLOR=15 0 0 100 255 255 255 dk blue COLOR=2 0 60 25 96 78 189 lt blue COLOR=7 0 60 75 208 195 255 purple COLOR=3 45 100 50 255 68 253 purple HCOLOR=2 45 100 50 255 68 253 red COLOR=1 90 60 25 227 30 96 pink COLOR=11 90 60 75 255 160 208 orange COLOR=9 135 100 50 255 106 60 orange HCOLOR=5 135 100 50 255 106 60 brown COLOR=8 180 60 25 96 114 3 yellow COLOR=13 180 60 75 208 221 141 lt green COLOR=12 225 100 50 20 245 60 green HCOLOR=1 225 100 50 20 245 60 dk green COLOR=4 270 60 25 0 163 96 aqua COLOR=14 270 60 75 114 255 208 med blue COLOR=6 315 100 50 20 207 253 blue HCOLOR=6 315 100 50 20 207 253 NTSC Hsync 0 0 -40 0 0 0 NTSC black 0 0 7.5 41 41 41 NTSC Gray75 0 0 77 212 212 212 YIQ +Q 33 100 50 255 81 255 NTSC magenta 61 82 36 255 40 181 NTSC red 104 88 28 255 28 76 YIQ +I 123 100 50 255 89 82 NTSC yellow 167 62 69 221 198 121 Color burst 180 40 0 0 4 0 YIQ -Q 213 100 50 51 232 41 NTSC green 241 82 48 12 234 97 NTSC cyan 284 88 56 10 245 198 YIQ -I 303 100 50 0 224 231 NTSC blue 347 62 15 38 65 155 --- I don't think these are 100% correct, e.g. he only found one value for both grey colors, even though one is noticeably darker than the other on-screen. The IIgs tech note #63 uses the following for border colors: Color Color Register Master Color Name Value Value --------------------------------------- Black $0 $0000 Deep Red $1 $0D03 Dark Blue $2 $0009 Purple $3 $0D2D Dark Green $4 $0072 Dark Gray $5 $0555 Medium Blue $6 $022F Light Blue $7 $06AF Brown $8 $0850 Orange $9 $0F60 Light Gray $A $0AAA Pink $B $0F98 Light Green $C $01D0 Yellow $D $0FF0 Aquamarine $E $04F9 White $F $0FFF --------------------------------------- KEGS uses this (rearranged slightly to match the order above): const int g_dbhires_colors[] = { \* rgb *\ 0x000, \* 0x0 black *\ 0xd03, \* 0x1 deep red *\ 0x009, \* 0x8 dark blue *\ 0xd0d, \* 0x9 purple *\ 0x070, \* 0x4 dark green *\ 0x555, \* 0x5 dark gray *\ 0x22f, \* 0xc medium blue *\ 0x6af, \* 0xd light blue *\ 0x852, \* 0x2 brown *\ 0xf60, \* 0x3 orange *\ 0xaaa, \* 0xa light gray *\ 0xf98, \* 0xb pink *\ 0x0d0, \* 0x6 green *\ 0xff0, \* 0x7 yellow *\ 0x0f9, \* 0xe aquamarine *\ 0xfff \* 0xf white *\ }; The Apple values seem to be good. */ /* * Decide whether or not we want to handle this file. * * FOT with auxtype $4000 is a compressed hi-res image. FOT with auxtype * $4001 is a compressed DHR image. In practice, nobody uses these, so * any FOT file with a correct-looking length is treated as hi-res. */ void ReformatHiRes::Examine(ReformatHolder* pHolder) { ReformatHolder::ReformatApplies applies = ReformatHolder::kApplicNot; long fileLen = pHolder->GetSourceLen(ReformatHolder::kPartData); long fileType = pHolder->GetFileType(); long auxType = pHolder->GetAuxType(); bool dosStructure = (pHolder->GetSourceFormat() == ReformatHolder::kSourceFormatDOS); bool relaxed; relaxed = pHolder->GetOption(ReformatHolder::kOptRelaxGfxTypeCheck) != 0; if (dosStructure) { if (fileType == kTypeBIN && (fileLen >= kExpectedSize-8 && fileLen <= kExpectedSize+1) /*&& (auxType == 0x2000 || auxType == 0x4000)*/) { applies = ReformatHolder::kApplicProbably; } } else { if (fileType == kTypeFOT) { if (auxType == 0x8066) { // fhpack LZ4FH-compressed file applies = ReformatHolder::kApplicYes; } else if (fileLen >= kExpectedSize-8 && fileLen <= kExpectedSize + 1) { // uncompressed FOT file... probably applies = ReformatHolder::kApplicProbably; } } else if (relaxed && fileType == kTypeBIN && (fileLen >= kExpectedSize-8 && fileLen <= kExpectedSize+1)) { applies = ReformatHolder::kApplicProbably; } } pHolder->SetApplic(ReformatHolder::kReformatHiRes, applies, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); pHolder->SetApplic(ReformatHolder::kReformatHiRes_BW, applies, ReformatHolder::kApplicNot, ReformatHolder::kApplicNot); /* * Set the "preferred" flag on one option. */ if (pHolder->GetOption(ReformatHolder::kOptHiResBW) == 0) pHolder->SetApplicPreferred(ReformatHolder::kReformatHiRes); else pHolder->SetApplicPreferred(ReformatHolder::kReformatHiRes_BW); } /* * Convert a Hi-Res image to a bitmap. */ int ReformatHiRes::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 (id == ReformatHolder::kReformatHiRes_BW) { fBlackWhite = true; } uint8_t expandBuf[kExpectedSize]; if (pHolder->GetFileType() == kTypeFOT && pHolder->GetAuxType() == 0x8066) { srcLen = ExpandLZ4FH(expandBuf, srcBuf, srcLen); if (srcLen == 0) { goto bail; // fail } srcBuf = expandBuf; } if (srcLen > kExpectedSize+1 || srcLen < kExpectedSize-8) { LOGI(" HiRes file is not ~%d bytes long (got %d)", kExpectedSize, srcLen); goto bail; } InitLineOffset(fLineOffset); pDib = HiResScreenToBitmap(srcBuf); if (pDib == NULL) goto bail; SetResultBuffer(pOutput, pDib); retval = 0; bail: return retval; } /* * Set up the line offset table. * * Table must be able to hold kNumLines values. */ /*static*/ void ReformatHiRes::InitLineOffset(int* pOffsetBuf) { long offset; int line; ASSERT(pOffsetBuf != NULL); for (line = 0; line < kNumLines; line++) { offset = (line & 0x07) << 10 | (line & 0x38) << 4 | (line & 0xc0) >> 1 | (line & 0xc0) >> 3; ASSERT(offset >= 0 && offset < (8192 - 40)); pOffsetBuf[line] = offset; } } /* * (Lifted directly from fhpack.) * * Uncompress LZ4FH data from "srcBuf" to "dstBuf". "dstBuf" must hold * kExpectedSize bytes. * * Returns the uncompressed length on success, 0 on failure. */ /*static*/ long ReformatHiRes::ExpandLZ4FH(uint8_t* outBuf, const uint8_t* inBuf, long inLen) { // constants static const uint8_t LZ4FH_MAGIC = 0x66; static const int MIN_MATCH_LEN = 4; static const int INITIAL_LEN = 15; static const int EMPTY_MATCH_TOKEN = 253; static const int EOD_MATCH_TOKEN = 254; static const int MAX_SIZE = kExpectedSize; uint8_t* outPtr = outBuf; const uint8_t* inPtr = inBuf; LOGD("Expanding LZ4FH"); if (*inPtr++ != LZ4FH_MAGIC) { LOGE("Missing LZ4FH magic"); return 0; } while (true) { uint8_t mixedLen = *inPtr++; int literalLen = mixedLen >> 4; if (literalLen != 0) { if (literalLen == INITIAL_LEN) { literalLen += *inPtr++; } if ((outPtr - outBuf) + literalLen > MAX_SIZE || (inPtr - inBuf) + literalLen > inLen) { LOGE("Buffer overrun"); return 0; } memcpy(outPtr, inPtr, literalLen); outPtr += literalLen; inPtr += literalLen; } int matchLen = mixedLen & 0x0f; if (matchLen == INITIAL_LEN) { uint8_t addon = *inPtr++; if (addon == EMPTY_MATCH_TOKEN) { matchLen = - MIN_MATCH_LEN; } else if (addon == EOD_MATCH_TOKEN) { break; // out of while } else { matchLen += addon; } } matchLen += MIN_MATCH_LEN; if (matchLen != 0) { int matchOffset = *inPtr++; matchOffset |= (*inPtr++) << 8; // Can't use memcpy() here, because we need to guarantee // that the match is overlapping. uint8_t* srcPtr = outBuf + matchOffset; if ((outPtr - outBuf) + matchLen > MAX_SIZE || (srcPtr - outBuf) + matchLen > MAX_SIZE) { LOGE("Buffer overrun"); return 0; } while (matchLen-- != 0) { *outPtr++ = *srcPtr++; } } } if (inPtr - inBuf != (long) inLen) { LOGW("Warning: LZ4FH uncompress used only %ld of %zd bytes", inPtr - inBuf, inLen); } return outPtr - outBuf; } /* * Convert an 8KB buffer of hi-res data to a 16-color 560x384 DIB. */ MyDIBitmap* ReformatHiRes::HiResScreenToBitmap(const uint8_t* buf) { MyDIBitmap* pDib = new MyDIBitmap; uint8_t* outBuf; const int kLeadIn = 4; unsigned int colorBuf[kLeadIn+kOutputWidth +1]; // 560 half-pixels int pixelBits[kPixelsPerLine]; int shiftBits[kPixelsPerLine]; int line; /* color map */ enum { kColorBlack0 = 0, kColorGreen, kColorPurple, kColorWhite0, kColorBlack1, kColorOrange, kColorBlue, kColorWhite1, kColorNone, // really only useful for debugging kNumColors }; RGBQUAD colorConv[kNumColors]; colorConv[0] = fPalette[kPaletteBlack]; colorConv[1] = fPalette[kPaletteGreen]; colorConv[2] = fPalette[kPalettePurple]; colorConv[3] = fPalette[kPaletteWhite]; colorConv[4] = fPalette[kPaletteBlack]; colorConv[5] = fPalette[kPaletteOrange]; colorConv[6] = fPalette[kPaletteMediumBlue]; colorConv[7] = fPalette[kPaletteWhite]; colorConv[8] = fPalette[kPaletteBlack]; // for "blank spaces" ASSERT(kOutputWidth == 2*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. */ for (line = 0; line < kNumLines; line++) { const uint8_t* lineData = buf + fLineOffset[line]; int* bitPtr = pixelBits; int* shiftPtr = shiftBits; /* unravel the bits */ for (int byt = 0; byt < kPixelsPerLine / 7; byt++) { uint8_t val = *lineData; int shifted = (val & 0x80) != 0; for (int bit = 0; bit < 7; bit++) { *bitPtr++ = val & 0x01; *shiftPtr++ = shifted; val >>= 1; } lineData++; } ASSERT(lineData <= buf + kExpectedSize); ASSERT((char*)bitPtr == (char*)pixelBits + sizeof(pixelBits)); ASSERT((char*)shiftPtr == (char*)shiftBits + sizeof(shiftBits)); /* * Convert the bits to colors, taking half-pixel shifts into account. */ int idx; for (idx = 0; idx < NELEM(colorBuf); idx++) colorBuf[idx] = kColorNone; if (fBlackWhite) { for (idx = 0; idx < kPixelsPerLine; idx ++) { int bufShift = (int) shiftBits[idx]; // simulate GS RGB by setting bufShift=0 ASSERT(bufShift == 0 || bufShift == 1); int bufTarget = kLeadIn + idx * 2 + bufShift; if (!pixelBits[idx]) { colorBuf[bufTarget] = kColorBlack0; colorBuf[bufTarget+1] = kColorBlack0; } else { colorBuf[bufTarget] = kColorWhite0; colorBuf[bufTarget+1] = kColorWhite0; } } } else { for (idx = 0; idx < kPixelsPerLine; idx ++) { int bufShift = (int) shiftBits[idx]; ASSERT(bufShift == 0 || bufShift == 1); int colorShift = 4 * bufShift; int bufTarget = kLeadIn + idx * 2 + bufShift; if (!pixelBits[idx]) { colorBuf[bufTarget] = kColorBlack0 + colorShift; colorBuf[bufTarget+1] = kColorBlack0 + colorShift; } else { if (colorBuf[bufTarget-2] != kColorBlack0 && colorBuf[bufTarget-2] != kColorBlack1 && colorBuf[bufTarget-2] != kColorNone) { /* previous bit was set, this is white */ colorBuf[bufTarget] = kColorWhite0 + colorShift; colorBuf[bufTarget+1] = kColorWhite0 + colorShift; /* make sure the previous bit is in with us */ colorBuf[bufTarget-2] = kColorWhite0 + colorShift; colorBuf[bufTarget-1] = kColorWhite0 + colorShift; } else { /* previous bit was zero, this is color */ if (idx & 0x01) { colorBuf[bufTarget] = kColorGreen + colorShift; colorBuf[bufTarget+1] = kColorGreen + colorShift; } else { colorBuf[bufTarget] = kColorPurple + colorShift; colorBuf[bufTarget+1] = kColorPurple + colorShift; } /* * Do we have a run of the same color? If so, smooth * the color out. Note that white blends smoothly * with everything. */ if (colorBuf[bufTarget-4] == colorBuf[bufTarget] || colorBuf[bufTarget-4] == kColorWhite0 || colorBuf[bufTarget-4] == kColorWhite1) { /* back-fill previous gap with color */ ASSERT(colorBuf[bufTarget-2] == kColorBlack0 || colorBuf[bufTarget-2] == kColorBlack1); colorBuf[bufTarget-2] = colorBuf[bufTarget]; colorBuf[bufTarget-1] = colorBuf[bufTarget]; } } } } /*for boolean pixels*/ } /*!black&white*/ /* convert colors to 4-bit bitmap pixels */ /* (NOTE: should advance by GetPitch(), not assume it's equal to width) */ #define SetPix(x, y, twoval) \ outBuf[((kOutputHeight-1) - (y)) * (kOutputWidth/2) + (x)] = twoval uint8_t pix4; for (int pix = 0; pix < kPixelsPerLine; pix++) { int bufPosn = kLeadIn + 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); //SetPix(pix*2, line*2, colorBuf[bufPosn]); //SetPix(pix*2, line*2+1, colorBuf[bufPosn]); //SetPix(pix*2+1, line*2, colorBuf[bufPosn+1]); //SetPix(pix*2+1, line*2+1, colorBuf[bufPosn+1]); } } /*for each line*/ #undef SetPix bail: return pDib; }