/*
 * 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 hires 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...
 * [ Fround 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 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 /*&& auxType < 0x4000*/ &&
			(fileLen >= kExpectedSize-8 && fileLen <= kExpectedSize+1))
		{
			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 unsigned char* srcBuf = pHolder->GetSourceBuf(part);
	long srcLen = pHolder->GetSourceLen(part);
	int retval = -1;

	if (id == ReformatHolder::kReformatHiRes_BW)
		fBlackWhite = true;

	if (srcLen > kExpectedSize+1 || srcLen < kExpectedSize-8) {
		WMSG2(" HiRes file is not ~%d bytes long (got %d)\n",
			kExpectedSize, srcLen);
		goto bail;
	}

	InitLineOffset(fLineOffset);

	pDib = HiResScreenToBitmap(srcBuf);
	if (pDib == nil)
		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 != nil);

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


/*
 * Convert a buffer of hires data to a 16-color DIB.
 */
MyDIBitmap*
ReformatHiRes::HiResScreenToBitmap(const unsigned char* buf)
{
	MyDIBitmap* pDib = new MyDIBitmap;
	unsigned char* 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 == nil)
		goto bail;

	outBuf = (unsigned char*) pDib->Create(kOutputWidth, kOutputHeight,
									4, kNumColors);
	if (outBuf == nil) {
		delete pDib;
		pDib = nil;
		goto bail;
	}
	pDib->SetColorTable(colorConv);

	/*
	 * Run through the lines.
	 */
	for (line = 0; line < kNumLines; line++) {
		const unsigned char* lineData = buf + fLineOffset[line];
		int* bitPtr = pixelBits;
		int* shiftPtr = shiftBits;

		/* unravel the bits */
		for (int byt = 0; byt < kPixelsPerLine / 7; byt++) {
			unsigned char 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

		unsigned char 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;
}
