mirror of
https://github.com/fadden/ciderpress.git
synced 2024-11-27 08:49:20 +00:00
92942437d9
Also, bump version to 4.0.1-a3.
554 lines
20 KiB
C++
554 lines
20 KiB
C++
/*
|
|
* 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;
|
|
}
|