mirror of
https://github.com/fadden/6502bench.git
synced 2024-08-20 11:28:58 +00:00
The calculations were wrong for certain situations, generating answers that were useless or that caused a false-positive overflow error. This adds a couple of simple regression tests, modeled after layout of the Lode Runner sprite sheet (which worked fine before) and the Empire II EWS3 font (which failed). This also bumps up some of the arbitrary limits in the visualizer. (issue #94)
634 lines
31 KiB
C#
634 lines
31 KiB
C#
/*
|
|
* Copyright 2019 faddenSoft
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
using System;
|
|
using System.Collections.ObjectModel;
|
|
|
|
using PluginCommon;
|
|
|
|
namespace RuntimeData.Apple {
|
|
/// <summary>
|
|
/// Visualizer for Apple II hi-res bitmaps.
|
|
/// </summary>
|
|
public class VisHiRes : MarshalByRefObject, IPlugin, IPlugin_Visualizer {
|
|
// IPlugin
|
|
public string Identifier {
|
|
get { return "Apple II Hi-Res Graphic Visualizer"; }
|
|
}
|
|
private IApplication mAppRef;
|
|
private byte[] mFileData;
|
|
private AddressTranslate mAddrTrans;
|
|
|
|
// Visualization identifiers; DO NOT change or projects that use them will break.
|
|
private const string VIS_GEN_BITMAP = "apple2-hi-res-bitmap";
|
|
private const string VIS_GEN_BITMAP_GRID = "apple2-hi-res-bitmap-grid";
|
|
private const string VIS_GEN_BITMAP_FONT = "apple2-hi-res-bitmap-font";
|
|
private const string VIS_GEN_HR_SCREEN = "apple2-hi-res-screen";
|
|
|
|
private const string P_OFFSET = "offset";
|
|
private const string P_BYTE_WIDTH = "byteWidth";
|
|
private const string P_HEIGHT = "height";
|
|
private const string P_COL_STRIDE = "colStride";
|
|
private const string P_ROW_STRIDE = "rowStride";
|
|
private const string P_CELL_STRIDE = "cellStride";
|
|
private const string P_IS_COLOR = "isColor";
|
|
private const string P_IS_FIRST_ODD = "isFirstOdd";
|
|
private const string P_IS_HIGH_BIT_FLIPPED = "isHighBitFlipped";
|
|
private const string P_COLOR_CONV_MODE = "colorConvMode";
|
|
private const string P_ITEM_BYTE_WIDTH = "itemByteWidth";
|
|
private const string P_ITEM_HEIGHT = "itemHeight";
|
|
private const string P_COUNT = "count";
|
|
|
|
private const int MAX_DIM = 4096;
|
|
|
|
// Visualization descriptors.
|
|
private VisDescr[] mDescriptors = new VisDescr[] {
|
|
new VisDescr(VIS_GEN_BITMAP, "Apple II Hi-Res Bitmap", VisDescr.VisType.Bitmap,
|
|
new VisParamDescr[] {
|
|
new VisParamDescr("File offset (hex)",
|
|
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
|
|
new VisParamDescr("Width (in bytes)",
|
|
P_BYTE_WIDTH, typeof(int), 1, 256, 0, 1),
|
|
new VisParamDescr("Height",
|
|
P_HEIGHT, typeof(int), 1, 1024, 0, 1),
|
|
new VisParamDescr("Column stride (bytes)",
|
|
P_COL_STRIDE, typeof(int), 0, 4096, 0, 0),
|
|
new VisParamDescr("Row stride (bytes)",
|
|
P_ROW_STRIDE, typeof(int), 0, 4096, 0, 0),
|
|
new VisParamDescr("Color",
|
|
P_IS_COLOR, typeof(bool), 0, 0, 0, true),
|
|
new VisParamDescr("First col odd",
|
|
P_IS_FIRST_ODD, typeof(bool), 0, 0, 0, false),
|
|
new VisParamDescr("High bit flipped",
|
|
P_IS_HIGH_BIT_FLIPPED, typeof(bool), 0, 0, 0, false),
|
|
//new VisParamDescr("Color conv mode",
|
|
// P_COLOR_CONV_MODE, typeof(int), (int)ColorMode.SimpleColor,
|
|
// (int)ColorMode.IIgsRGB, 0, (int)ColorMode.SimpleColor),
|
|
//new VisParamDescr("Test Float",
|
|
// "floaty", typeof(float), -5.0f, 5.0f, 0, 0.1f),
|
|
}),
|
|
new VisDescr(VIS_GEN_BITMAP_GRID, "Apple II Hi-Res Sprite Sheet", VisDescr.VisType.Bitmap,
|
|
new VisParamDescr[] {
|
|
new VisParamDescr("File offset (hex)",
|
|
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
|
|
new VisParamDescr("Cell width (in bytes)",
|
|
P_ITEM_BYTE_WIDTH, typeof(int), 1, 40, 0, 1),
|
|
new VisParamDescr("Cell height",
|
|
P_ITEM_HEIGHT, typeof(int), 1, 192, 0, 1),
|
|
new VisParamDescr("Column stride (bytes)",
|
|
P_COL_STRIDE, typeof(int), 0, 4096, 0, 0),
|
|
new VisParamDescr("Row stride (bytes)",
|
|
P_ROW_STRIDE, typeof(int), 0, 4096, 0, 0),
|
|
new VisParamDescr("Cell stride (bytes)",
|
|
P_CELL_STRIDE, typeof(int), 0, 4096, 0, 0),
|
|
new VisParamDescr("Number of items",
|
|
P_COUNT, typeof(int), 1, 4096, 0, 64),
|
|
new VisParamDescr("Color",
|
|
P_IS_COLOR, typeof(bool), 0, 0, 0, true),
|
|
new VisParamDescr("First col odd",
|
|
P_IS_FIRST_ODD, typeof(bool), 0, 0, 0, false),
|
|
new VisParamDescr("High bit flipped",
|
|
P_IS_HIGH_BIT_FLIPPED, typeof(bool), 0, 0, 0, false),
|
|
}),
|
|
new VisDescr(VIS_GEN_BITMAP_FONT, "Apple II Hi-Res Bitmap Font", VisDescr.VisType.Bitmap,
|
|
new VisParamDescr[] {
|
|
new VisParamDescr("File offset (hex)",
|
|
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
|
|
new VisParamDescr("Item width (in bytes)",
|
|
P_ITEM_BYTE_WIDTH, typeof(int), 1, 40, 0, 1),
|
|
new VisParamDescr("Item height",
|
|
P_ITEM_HEIGHT, typeof(int), 1, 192, 0, 8),
|
|
new VisParamDescr("Number of items",
|
|
P_COUNT, typeof(int), 1, 256, 0, 96),
|
|
}),
|
|
new VisDescr(VIS_GEN_HR_SCREEN, "Apple II Hi-Res Screen Image", VisDescr.VisType.Bitmap,
|
|
new VisParamDescr[] {
|
|
new VisParamDescr("File offset (hex)",
|
|
P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
|
|
new VisParamDescr("Color",
|
|
P_IS_COLOR, typeof(bool), 0, 0, 0, true),
|
|
}),
|
|
};
|
|
|
|
|
|
// IPlugin
|
|
public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) {
|
|
mAppRef = appRef;
|
|
mFileData = fileData;
|
|
mAddrTrans = addrTrans;
|
|
}
|
|
|
|
// IPlugin
|
|
public void Unprepare() {
|
|
mAppRef = null;
|
|
mFileData = null;
|
|
mAddrTrans = null;
|
|
}
|
|
|
|
// IPlugin_Visualizer
|
|
public VisDescr[] GetVisGenDescrs() {
|
|
// We're using a static set, but it could be generated based on file contents.
|
|
// Confirm that we're prepared.
|
|
if (mFileData == null) {
|
|
return null;
|
|
}
|
|
return mDescriptors;
|
|
}
|
|
|
|
// IPlugin_Visualizer
|
|
public IVisualization2d Generate2d(VisDescr descr,
|
|
ReadOnlyDictionary<string, object> parms) {
|
|
switch (descr.Ident) {
|
|
case VIS_GEN_BITMAP:
|
|
return GenerateBitmap(parms);
|
|
case VIS_GEN_BITMAP_GRID:
|
|
return GenerateBitmapGrid(parms);
|
|
case VIS_GEN_BITMAP_FONT:
|
|
return GenerateBitmapFont(parms);
|
|
case VIS_GEN_HR_SCREEN:
|
|
return GenerateScreen(parms);
|
|
default:
|
|
mAppRef.ReportError("Unknown ident " + descr.Ident);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private IVisualization2d GenerateBitmap(ReadOnlyDictionary<string, object> parms) {
|
|
int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
|
|
int byteWidth = Util.GetFromObjDict(parms, P_BYTE_WIDTH, 1); // width ignoring colStride
|
|
int height = Util.GetFromObjDict(parms, P_HEIGHT, 1);
|
|
int colStride = Util.GetFromObjDict(parms, P_COL_STRIDE, 0);
|
|
int rowStride = Util.GetFromObjDict(parms, P_ROW_STRIDE, 0);
|
|
bool isColor = Util.GetFromObjDict(parms, P_IS_COLOR, true);
|
|
bool isFirstOdd = Util.GetFromObjDict(parms, P_IS_FIRST_ODD, false);
|
|
bool isHighBitFlipped = Util.GetFromObjDict(parms, P_IS_HIGH_BIT_FLIPPED, false);
|
|
int colorConvMode = !isColor ? (int)ColorMode.Mono :
|
|
Util.GetFromObjDict(parms, P_COLOR_CONV_MODE, (int)ColorMode.SimpleColor);
|
|
|
|
// We allow the stride entries to be zero to indicate a "dense" bitmap.
|
|
if (colStride == 0) {
|
|
colStride = 1;
|
|
}
|
|
if (rowStride == 0) {
|
|
rowStride = byteWidth * colStride;
|
|
}
|
|
|
|
if (offset < 0 || offset >= mFileData.Length ||
|
|
byteWidth <= 0 || byteWidth > MAX_DIM ||
|
|
height <= 0 || height > MAX_DIM) {
|
|
// the UI should flag these based on range (and ideally wouldn't have called us)
|
|
mAppRef.ReportError("Invalid parameter");
|
|
return null;
|
|
}
|
|
if (colStride <= 0 || colStride > MAX_DIM) {
|
|
mAppRef.ReportError("Invalid column stride");
|
|
return null;
|
|
}
|
|
// This used to be limited to rowStride >= byteWidth * colStride - (colStride - 1),
|
|
// but that doesn't allow for a 2x8 bitmap in column-major order. In that case
|
|
// you want a row stride of 1 (because each row starts 1 byte farther on) and a
|
|
// column stride of 8 (because each column is separated by 8 bytes).
|
|
if (rowStride < 1 || rowStride > MAX_DIM) {
|
|
mAppRef.ReportError("Invalid row stride");
|
|
return null;
|
|
}
|
|
|
|
int lastOffset = offset + rowStride * (height - 1) +
|
|
colStride * (byteWidth - 1);
|
|
if (lastOffset >= mFileData.Length) {
|
|
mAppRef.ReportError("Bitmap runs off end of file (last offset +" +
|
|
lastOffset.ToString("x6") + ")");
|
|
return null;
|
|
//} else {
|
|
// mAppRef.DebugLog("last offset=+" + lastOffset.ToString("x6"));
|
|
}
|
|
|
|
VisBitmap8 vb = new VisBitmap8(byteWidth * 7, height);
|
|
SetHiResPalette(vb);
|
|
|
|
RenderBitmap(mFileData, offset, byteWidth, height, colStride, rowStride,
|
|
(ColorMode)colorConvMode, isFirstOdd, isHighBitFlipped, vb, 0, 0);
|
|
return vb;
|
|
}
|
|
|
|
private IVisualization2d GenerateBitmapGrid(ReadOnlyDictionary<string, object> parms) {
|
|
int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
|
|
int itemByteWidth = Util.GetFromObjDict(parms, P_ITEM_BYTE_WIDTH, 1);
|
|
int itemHeight = Util.GetFromObjDict(parms, P_ITEM_HEIGHT, 8);
|
|
int colStride = Util.GetFromObjDict(parms, P_COL_STRIDE, 0);
|
|
int rowStride = Util.GetFromObjDict(parms, P_ROW_STRIDE, 0);
|
|
int cellStride = Util.GetFromObjDict(parms, P_CELL_STRIDE, 0);
|
|
int count = Util.GetFromObjDict(parms, P_COUNT, 96);
|
|
bool isColor = Util.GetFromObjDict(parms, P_IS_COLOR, true);
|
|
bool isFirstOdd = Util.GetFromObjDict(parms, P_IS_FIRST_ODD, false);
|
|
bool isHighBitFlipped = Util.GetFromObjDict(parms, P_IS_HIGH_BIT_FLIPPED, false);
|
|
|
|
ColorMode colorConvMode = !isColor ? ColorMode.Mono :
|
|
(ColorMode)Util.GetFromObjDict(parms, P_COLOR_CONV_MODE, (int)ColorMode.SimpleColor);
|
|
|
|
return GenerateGrid(offset, itemByteWidth, itemHeight, colStride, rowStride,
|
|
cellStride, count, colorConvMode, isFirstOdd, isHighBitFlipped);
|
|
}
|
|
|
|
private IVisualization2d GenerateBitmapFont(ReadOnlyDictionary<string, object> parms) {
|
|
int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
|
|
int itemByteWidth = Util.GetFromObjDict(parms, P_ITEM_BYTE_WIDTH, 1);
|
|
int itemHeight = Util.GetFromObjDict(parms, P_ITEM_HEIGHT, 8);
|
|
int count = Util.GetFromObjDict(parms, P_COUNT, 96);
|
|
|
|
return GenerateGrid(offset, itemByteWidth, itemHeight, 0, 0, 0, count,
|
|
ColorMode.Mono, false, false);
|
|
}
|
|
|
|
private IVisualization2d GenerateGrid(int offset, int itemByteWidth, int itemHeight,
|
|
int colStride, int rowStride, int cellStride, int count,
|
|
ColorMode colorConvMode, bool isFirstOdd, bool isHighBitFlipped) {
|
|
|
|
// We allow the stride entries to be zero to indicate a "dense" bitmap.
|
|
if (colStride == 0) {
|
|
colStride = 1;
|
|
}
|
|
if (rowStride == 0) {
|
|
rowStride = itemByteWidth * colStride;
|
|
}
|
|
if (cellStride == 0) {
|
|
cellStride = rowStride * itemHeight;
|
|
}
|
|
|
|
if (offset < 0 || offset >= mFileData.Length ||
|
|
itemByteWidth <= 0 || itemByteWidth > MAX_DIM ||
|
|
itemHeight <= 0 || itemHeight > MAX_DIM ||
|
|
count <= 0 || count > MAX_DIM) {
|
|
// should be caught by editor
|
|
mAppRef.ReportError("Invalid parameter");
|
|
return null;
|
|
}
|
|
|
|
if (colStride <= 0 || colStride > MAX_DIM) {
|
|
mAppRef.ReportError("Invalid column stride");
|
|
return null;
|
|
}
|
|
if (rowStride < itemByteWidth * colStride - (colStride - 1) || rowStride > MAX_DIM) {
|
|
mAppRef.ReportError("Invalid row stride");
|
|
return null;
|
|
}
|
|
|
|
int lastOffset = offset + (cellStride * (count - 1)) +
|
|
rowStride * (itemHeight - 1) +
|
|
colStride * (itemByteWidth - 1);
|
|
if (lastOffset >= mFileData.Length) {
|
|
mAppRef.ReportError("Bitmap runs off end of file (last offset +" +
|
|
lastOffset.ToString("x6") + ")");
|
|
return null;
|
|
//} else {
|
|
// mAppRef.DebugLog("lastOffset=+" + lastOffset.ToString("x6") +
|
|
// ", len=" + mFileData.Length.ToString("x6"));
|
|
}
|
|
|
|
// Set the number of horizontal cells. For small counts we try to make it square,
|
|
// for larger counts we use a reasonable power of 2.
|
|
int hcells;
|
|
if (count > 128) {
|
|
hcells = 32;
|
|
} else if (count > 64) {
|
|
hcells = 16;
|
|
} else if (count >= 32) {
|
|
hcells = 8;
|
|
} else {
|
|
hcells = (int)Math.Sqrt(count + 1);
|
|
}
|
|
|
|
int vcells = (count + hcells - 1) / hcells;
|
|
|
|
// Create a bitmap with room for each cell, plus a 1-pixel transparent boundary
|
|
// between them and around the edges.
|
|
VisBitmap8 vb = new VisBitmap8(1 + hcells * itemByteWidth * 7 + hcells,
|
|
1 + vcells * itemHeight + vcells);
|
|
SetHiResPalette(vb);
|
|
|
|
int cellx = 1;
|
|
int celly = 1;
|
|
for (int idx = 0; idx < count; idx++) {
|
|
RenderBitmap(mFileData, offset + idx * cellStride,
|
|
itemByteWidth, itemHeight, colStride, rowStride,
|
|
colorConvMode, isFirstOdd, isHighBitFlipped,
|
|
vb, cellx, celly);
|
|
|
|
cellx += itemByteWidth * 7 + 1;
|
|
if (cellx == vb.Width) {
|
|
cellx = 1;
|
|
celly += itemHeight + 1;
|
|
}
|
|
}
|
|
return vb;
|
|
}
|
|
|
|
private IVisualization2d GenerateScreen(ReadOnlyDictionary<string, object> parms) {
|
|
//const int RAW_IMAGE_SIZE = 0x1ff8;
|
|
const int HR_WIDTH = 280;
|
|
const int HR_BYTE_WIDTH = HR_WIDTH / 7;
|
|
const int HR_HEIGHT = 192;
|
|
|
|
int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
|
|
bool isColor = Util.GetFromObjDict(parms, P_IS_COLOR, true);
|
|
|
|
if (offset < 0 || offset >= mFileData.Length) {
|
|
// should be caught by editor
|
|
mAppRef.ReportError("Invalid parameter");
|
|
return null;
|
|
}
|
|
|
|
//int lastOffset = offset + RAW_IMAGE_SIZE - 1;
|
|
//if (lastOffset >= mFileData.Length) {
|
|
// mAppRef.ReportError("Bitmap runs off end of file (last offset +" +
|
|
// lastOffset.ToString("x6") + ")");
|
|
// return null;
|
|
//}
|
|
|
|
// Linearize the data. To handle programs that move themselves around before
|
|
// executing we use the address translator (e.g. the title screen in Space Eggs
|
|
// is contiguous in memory but split in half in the file). This is slower, but
|
|
// mAddrTrans is a local (not proxy) object, so it's not too bad.
|
|
byte[] buf = new byte[HR_BYTE_WIDTH * HR_HEIGHT];
|
|
int outIdx = 0;
|
|
int baseAddr = mAddrTrans.OffsetToAddress(offset);
|
|
for (int row = 0; row < HR_HEIGHT; row++) {
|
|
// If row is ABCDEFGH, we want pppFGHCD EABAB000 (where p would be $20/$40).
|
|
int low = ((row & 0xc0) >> 1) | ((row & 0xc0) >> 3) | ((row & 0x08) << 4);
|
|
int high = ((row & 0x07) << 2) | ((row & 0x30) >> 4);
|
|
int rowAddr = baseAddr + ((high << 8) | low);
|
|
|
|
// Not expecting the data to wrap around, but it's possible.
|
|
rowAddr = (baseAddr & 0xff0000) | (rowAddr & 0xffff);
|
|
|
|
for (int col = 0; col < HR_BYTE_WIDTH; col++) {
|
|
int srcOffset = mAddrTrans.AddressToOffset(offset, rowAddr + col);
|
|
if (srcOffset < 0) {
|
|
mAppRef.ReportError("Address $" + (rowAddr + col).ToString("x4") +
|
|
" is outside of file");
|
|
return null;
|
|
}
|
|
buf[outIdx++] = mFileData[srcOffset];
|
|
}
|
|
}
|
|
|
|
VisBitmap8 vb = new VisBitmap8(HR_WIDTH, HR_HEIGHT);
|
|
SetHiResPalette(vb);
|
|
RenderBitmap(buf, 0, HR_BYTE_WIDTH, HR_HEIGHT, 1, HR_BYTE_WIDTH,
|
|
isColor ? ColorMode.SimpleColor : ColorMode.Mono, false, false,
|
|
vb, 0, 0);
|
|
return vb;
|
|
}
|
|
|
|
|
|
private enum ColorMode { Mono, SimpleColor, IIgsRGB };
|
|
|
|
/// <summary>
|
|
/// Renders bitmap data.
|
|
/// </summary>
|
|
/// <param name="data">Data source, typically the file data.</param>
|
|
/// <param name="offset">Offset into data of the first byte.</param>
|
|
/// <param name="byteWidth">Width, in bytes, of the data to render. Each byte
|
|
/// represents 7 pixels in the output (more or less).</param>
|
|
/// <param name="height">Height, in lines, of the data to render.</param>
|
|
/// <param name="colStride">Column stride. The number of bytes used to hold each
|
|
/// byte of data. Must be >= 1.</param>
|
|
/// <param name="rowStride">Row stride. The number of bytes used to hold each row
|
|
/// of data. Must be >= (colStride * byteWidth - (colStride - 1)).</param>
|
|
/// <param name="colorMode">Color conversion mode.</param>
|
|
/// <param name="isFirstOdd">If true, render as if we're starting on an odd column.
|
|
/// This affects the colors.</param>
|
|
/// <param name="isHighBitFlipped">If true, render as if the high bit has the
|
|
/// opposite value. This affects the colors.</param>
|
|
/// <param name="vb">Output bitmap object.</param>
|
|
/// <param name="xstart">Initial X position in the output.</param>
|
|
/// <param name="ystart">Initial Y position in the output.</param>
|
|
private void RenderBitmap(byte[] data, int offset, int byteWidth, int height,
|
|
int colStride, int rowStride, ColorMode colorMode, bool isFirstOdd,
|
|
bool isHighBitFlipped, VisBitmap8 vb, int xstart, int ystart) {
|
|
int bx = xstart;
|
|
int by = ystart;
|
|
switch (colorMode) {
|
|
case ColorMode.Mono: {
|
|
// Since we're not displaying this we don't need to worry about
|
|
// half-pixel shifts, and can just convert 7 bits to pixels.
|
|
for (int row = 0; row < height; row++) {
|
|
int colIdx = 0;
|
|
for (int col = 0; col < byteWidth; col++) {
|
|
byte val = data[offset + colIdx];
|
|
for (int bit = 0; bit < 7; bit++) {
|
|
if ((val & 0x01) == 0) {
|
|
vb.SetPixelIndex(bx, by, (int)HiResColors.Black0);
|
|
} else {
|
|
vb.SetPixelIndex(bx, by, (int)HiResColors.White0);
|
|
}
|
|
val >>= 1;
|
|
bx++;
|
|
}
|
|
colIdx += colStride;
|
|
}
|
|
bx = xstart;
|
|
by++;
|
|
offset += rowStride;
|
|
}
|
|
}
|
|
break;
|
|
case ColorMode.SimpleColor: {
|
|
// Straightforward conversion, with no funky border effects. This
|
|
// represents an idealized version of the hardware.
|
|
|
|
// Bits for every byte, plus a couple of "fake" bits on the ends so
|
|
// we don't have to throw range-checks everywhere.
|
|
const int OVER = 2;
|
|
bool[] lineBits = new bool[OVER + byteWidth * 7 + OVER];
|
|
bool[] hiFlags = new bool[OVER + byteWidth * 7 + OVER];
|
|
for (int row = 0; row < height; row++) {
|
|
// Unravel the bits. Note we do each byte "backwards", i.e. the
|
|
// low bit (which is generally considered to be on the right) is
|
|
// the leftmost pixel.
|
|
int idx = OVER; // start past "fake" bits
|
|
int colIdx = 0;
|
|
for (int col = 0; col < byteWidth; col++) {
|
|
byte val = data[offset + colIdx];
|
|
bool hiBitSet = (val & 0x80) != 0;
|
|
|
|
for (int bit = 0; bit < 7; bit++) {
|
|
hiFlags[idx] = hiBitSet ^ isHighBitFlipped;
|
|
lineBits[idx] = (val & 0x01) != 0;
|
|
idx++;
|
|
val >>= 1;
|
|
}
|
|
colIdx += colStride;
|
|
}
|
|
|
|
// Convert to color.
|
|
int lastBit = byteWidth * 7;
|
|
for (idx = OVER; idx < lastBit + OVER; idx++) {
|
|
int colorShift = hiFlags[idx] ? 2 : 0;
|
|
if (lineBits[idx] && (lineBits[idx - 1] || lineBits[idx + 1])) {
|
|
// [X]11 or [1]1X; two 1s in a row is always white
|
|
vb.SetPixelIndex(bx++, by, (byte)HiResColors.White0);
|
|
} else if (lineBits[idx]) {
|
|
// [0]10, color pixel
|
|
bool isOdd = ((idx & 0x01) != 0) ^ isFirstOdd;
|
|
if (isOdd) {
|
|
vb.SetPixelIndex(bx++, by,
|
|
(byte)((int)HiResColors.Green + colorShift));
|
|
} else {
|
|
vb.SetPixelIndex(bx++, by,
|
|
(byte)((int)HiResColors.Purple + colorShift));
|
|
}
|
|
} else if (lineBits[idx - 1] && lineBits[idx + 1]) {
|
|
// [1]01, keep color going
|
|
bool isOdd = ((idx & 0x01) != 0) ^ isFirstOdd;
|
|
if (isOdd) {
|
|
vb.SetPixelIndex(bx++, by,
|
|
(byte)((int)HiResColors.Purple + colorShift));
|
|
} else {
|
|
vb.SetPixelIndex(bx++, by,
|
|
(byte)((int)HiResColors.Green + colorShift));
|
|
}
|
|
} else {
|
|
// [0]0X or [X]01
|
|
vb.SetPixelIndex(bx++, by, (byte)HiResColors.Black0);
|
|
}
|
|
}
|
|
|
|
// move to next row
|
|
bx = xstart;
|
|
by++;
|
|
offset += rowStride;
|
|
}
|
|
}
|
|
break;
|
|
case ColorMode.IIgsRGB: {
|
|
// Color conversion similar to what CiderPress does, but without the
|
|
// half-pixel shift (we're trying to create a 1:1 bitmap, not 1:2).
|
|
//
|
|
// This replicates some of the oddness in Apple IIgs RGB monitor output,
|
|
// but it's not quite right though. For example:
|
|
//
|
|
// observed generated
|
|
// d5 2a: blue [dk blue] purple ... black ...
|
|
// aa 55: orange [yellow] green ... white ...
|
|
// 55 aa: purple [lt blue] blue ... black ...
|
|
// 2a d5: green [brown] orange ... black ...
|
|
//
|
|
// KEGS doesn't seem to try to model this; it shows solid colors with no
|
|
// wackiness. AppleWin in "Color TV" mode shows similar effects, but is
|
|
// much blurrier (by design).
|
|
bool[] lineBits = new bool[byteWidth * 7];
|
|
bool[] hiFlags = new bool[byteWidth * 7]; // overkill, but simpler
|
|
int[] colorBuf = new int[byteWidth * 7];
|
|
for (int row = 0; row < height; row++) {
|
|
// Unravel the bits.
|
|
int idx = 0;
|
|
int colIdx = 0;
|
|
for (int col = 0; col < byteWidth; col++) {
|
|
byte val = data[offset + colIdx];
|
|
bool hiBitSet = (val & 0x80) != 0;
|
|
|
|
for (int bit = 0; bit < 7; bit++) {
|
|
hiFlags[idx] = hiBitSet ^ isHighBitFlipped;
|
|
lineBits[idx] = (val & 0x01) != 0;
|
|
idx++;
|
|
val >>= 1;
|
|
}
|
|
colIdx += colStride;
|
|
}
|
|
|
|
// Convert to color.
|
|
int lastBit = byteWidth * 7;
|
|
for (idx = 0; idx < lastBit; idx++) {
|
|
int colorShift = hiFlags[idx] ? 2 : 0;
|
|
if (!lineBits[idx]) {
|
|
// Bit not set, set pixel to black.
|
|
colorBuf[idx] = (int)HiResColors.Black0;
|
|
} else {
|
|
// Bit set, set pixel to white or color.
|
|
if (idx > 0 && colorBuf[idx - 1] != (int)HiResColors.Black0) {
|
|
// previous bit was also set, this is white
|
|
colorBuf[idx] = (int)HiResColors.White0;
|
|
|
|
// the previous pixel is part of a run of white
|
|
colorBuf[idx - 1] = (int)HiResColors.White0;
|
|
} else {
|
|
// previous bit not set *or* was first pixel in line;
|
|
// set color based on whether this is even or odd pixel col
|
|
bool isOdd = ((idx & 0x01) != 0) ^ isFirstOdd;
|
|
if (isOdd) {
|
|
colorBuf[idx] = (int)HiResColors.Green + colorShift;
|
|
} else {
|
|
colorBuf[idx] = (int)HiResColors.Purple + colorShift;
|
|
}
|
|
}
|
|
|
|
// Do we have a run of the same color? If so, smooth the
|
|
// color out. Note that white blends smoothly with everything.
|
|
if (idx > 1 && (colorBuf[idx - 2] == colorBuf[idx] ||
|
|
colorBuf[idx - 2] == (int)HiResColors.White0)) {
|
|
colorBuf[idx - 1] = colorBuf[idx];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Write to bitmap.
|
|
for (idx = 0; idx < lastBit; idx++) {
|
|
vb.SetPixelIndex(bx++, by, (byte)colorBuf[idx]);
|
|
}
|
|
|
|
// move to next row
|
|
bx = xstart;
|
|
by++;
|
|
offset += rowStride;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
// just leave the bitmap empty
|
|
mAppRef.ReportError("Unknown ColorMode " + colorMode);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Map hi-res colors to palette entries.
|
|
/// </summary>
|
|
private enum HiResColors : byte {
|
|
Black0 = 1,
|
|
Green = 3,
|
|
Purple = 4,
|
|
White0 = 2,
|
|
Black1 = 1,
|
|
Orange = 5,
|
|
Blue = 6,
|
|
White1 = 2
|
|
}
|
|
|
|
private void SetHiResPalette(VisBitmap8 vb) {
|
|
// These don't match directly to hi-res color numbers because we want to
|
|
// avoid adding black/white twice. The colors correspond to Apple IIgs RGB
|
|
// monitor output.
|
|
vb.AddColor(0, 0, 0, 0); // 0=transparent
|
|
vb.AddColor(0xff, 0x00, 0x00, 0x00); // 1=black0/black1
|
|
vb.AddColor(0xff, 0xff, 0xff, 0xff); // 2=white0/white1
|
|
vb.AddColor(0xff, 0x11, 0xdd, 0x00); // 3=green
|
|
vb.AddColor(0xff, 0xdd, 0x22, 0xdd); // 4=purple
|
|
vb.AddColor(0xff, 0xff, 0x66, 0x00); // 5=orange
|
|
vb.AddColor(0xff, 0x22, 0x22, 0xff); // 6=blue
|
|
}
|
|
}
|
|
}
|