diff --git a/SourceGen/RuntimeData/Apple/VisShapeTable.cs b/SourceGen/RuntimeData/Apple/VisShapeTable.cs new file mode 100644 index 0000000..62a492f --- /dev/null +++ b/SourceGen/RuntimeData/Apple/VisShapeTable.cs @@ -0,0 +1,233 @@ +/* + * Copyright 2020 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 { + /// + /// Visualizer for Apple II shape tables. While the only drawing functions for shape + /// tables are defined as Applesoft hi-res functions, the format is not inherently + /// limited to hi-res graphics. It's really just a list of vectors. + /// + /// + /// See the Applesoft BASIC Programming Reference Manual, pages 91-100, for a full + /// description of the format. + /// + /// Table format: + /// +00 number of shapes + /// +01 (unused) + /// +02/03 offset from start of table to first shape + /// +04/05 offset from start of table to second shape + /// ... + /// +xx shape #1 data + /// +yy shape #2 data + /// + /// Shape data is a series of bytes ending in $00. Each byte holds three vectors, + /// CCBBBAAA. AAA and BBB specify a direction (up/right/down/left) and whether or + /// not to plot a point. CC cannot specify whether to plot and cannot move up (a 00 + /// in CC means "do nothing"). + /// + public class VisShapeTable : MarshalByRefObject, IPlugin, IPlugin_Visualizer { + // IPlugin + public string Identifier { + get { return "Apple II Shape Table Visualizer"; } + } + private IApplication mAppRef; + private byte[] mFileData; + + // Visualization identifiers; DO NOT change or projects that use them will break. + private const string VIS_GEN_SHAPE_TABLE = "apple2-shape-table"; + + private const string P_OFFSET = "offset"; + private const string P_INDEX = "index"; + + // Visualization descriptors. + private VisDescr[] mDescriptors = new VisDescr[] { + new VisDescr(VIS_GEN_SHAPE_TABLE, "Apple II Shape Table", VisDescr.VisType.Bitmap, + new VisParamDescr[] { + new VisParamDescr("File offset (hex)", + P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0), + new VisParamDescr("Image index", + P_INDEX, typeof(int), 0, 256, 0, 0), + }), + }; + + + // IPlugin + public void Prepare(IApplication appRef, byte[] fileData, AddressTranslate addrTrans) { + mAppRef = appRef; + mFileData = fileData; + } + + // IPlugin + public void Unprepare() { + mAppRef = null; + mFileData = null; + } + + // IPlugin_Visualizer + public VisDescr[] GetVisGenDescrs() { + if (mFileData == null) { + return null; + } + return mDescriptors; + } + + // IPlugin_Visualizer + public IVisualization2d Generate2d(VisDescr descr, + ReadOnlyDictionary parms) { + switch (descr.Ident) { + case VIS_GEN_SHAPE_TABLE: + return GenerateBitmap(parms); + default: + mAppRef.ReportError("Unknown ident " + descr.Ident); + return null; + } + } + + private IVisualization2d GenerateBitmap(ReadOnlyDictionary parms) { + int offset = Util.GetFromObjDict(parms, P_OFFSET, 0); + int shapeIndex = Util.GetFromObjDict(parms, P_INDEX, 0); + + if (offset < 0 || offset >= mFileData.Length || shapeIndex < 0 || shapeIndex > 255) { + mAppRef.ReportError("Invalid parameter"); + return null; + } + + int maxIndex = mFileData[offset]; // count + 1 + if (shapeIndex >= maxIndex) { + mAppRef.ReportError("Image index " + shapeIndex + + " exceeds table size " + maxIndex); + return null; + } + + int offOffset = offset + 2 + shapeIndex * 2; + if (offOffset + 1 >= mFileData.Length) { + mAppRef.ReportError("Offset for shape " + shapeIndex + " is outside file bounds"); + return null; + } + + int shapeOffset = mFileData[offOffset] | (mFileData[offOffset + 1] << 8); + // can't know the length of the shape ahead of time; will need to check as we go + + int xmin, xmax, ymin, ymax; + xmin = ymin = 1000; + xmax = ymax = -1000; + + // Execute once to get min/max and detect errors. + VisBitmap8 vb; + if (!PlotVectors(shapeOffset, ref xmin, ref xmax, ref ymin, ref ymax, false, out vb)) { + return null; + } + //mAppRef.DebugLog("PLOTTED: xmin=" + xmin + " xmax=" + xmax + + // " ymin=" + ymin + " ymax=" + ymax); + + // Execute a second time to actually draw. + PlotVectors(shapeOffset, ref xmin, ref xmax, ref ymin, ref ymax, true, out vb); + return vb; + } + + private bool PlotVectors(int shapeOffset, ref int xmin, ref int xmax, + ref int ymin, ref int ymax, bool doPlot, out VisBitmap8 vb) { + if (doPlot) { + vb = new VisBitmap8(xmax - xmin + 1, ymax - ymin + 1); + SetHiResPalette(vb); + } else { + vb = null; + } + + int xc = 0; + int yc = 0; + + while (true) { + if (shapeOffset >= mFileData.Length) { + mAppRef.ReportError("Shape definition ran off end of file"); + return false; + } + byte val = mFileData[shapeOffset++]; + if (val == 0) { + // done + break; + } + + int bits = val & 0x07; + DrawVector(bits, false, ref xc, ref yc, ref xmin, ref xmax, ref ymin, ref ymax, vb); + bits = (val >> 3) & 0x07; + DrawVector(bits, false, ref xc, ref yc, ref xmin, ref xmax, ref ymin, ref ymax, vb); + bits = (val >> 6) & 0x03; + DrawVector(bits, true, ref xc, ref yc, ref xmin, ref xmax, ref ymin, ref ymax, vb); + } + + return true; + } + + /// + /// Plots a point if appropriate, and updates xc/yc according to the vector. The + /// min/max values are updated if a point is plotted -- no need to expand the bitmap + /// outside the range of actual plotted points. + /// + private void DrawVector(int bits, bool isC, ref int xc, ref int yc, + ref int xmin, ref int xmax, ref int ymin, ref int ymax, VisBitmap8 vb) { + if ((bits & 0x04) != 0) { + if (vb != null) { + // plot a point + vb.SetPixelIndex(xc - xmin, yc - ymin, 1); + } + if (xmin > xc) { + xmin = xc; + } + if (xmax < xc) { + xmax = xc; + } + if (ymin > yc) { + ymin = yc; + } + if (ymax < yc) { + ymax = yc; + } + } + switch (bits & 0x03) { + case 0x00: + if (isC) { + // do nothing + } else { + // move up + yc--; + } + break; + case 0x01: + // move right + xc++; + break; + case 0x02: + // move down + yc++; + break; + case 0x03: + // move left + xc--; + break; + } + } + + private void SetHiResPalette(VisBitmap8 vb) { + vb.AddColor(0x00, 0x00, 0x00, 0x00); // 0=transparent + vb.AddColor(0xff, 0x20, 0x20, 0xe0); // 1=solid (mostly blue) + } + } +}