From df04de61e6f00f5a70214d1469ed85d25f3dcd9b Mon Sep 17 00:00:00 2001 From: Andy McFadden Date: Wed, 4 Dec 2019 15:50:19 -0800 Subject: [PATCH] Improve visualization Various improvements: - Switched to ReadOnlyDictionary in Visualization to make it clear that the parameter dictionary should not be modified. - Added a warning to the Visualization Set editor that appears when there are no plugins that implement a visualizer. - Make sure an item is selected in the set editor after edit/remove. - Replaced the checkerboard background with one that's a little bit more grey, so it's more distinct from white pixel data. - Added a new Apple II hi-res color converter whose output more closely matches KEGS and AppleWin RGB. - Added VisHiRes.cs to some Apple II system definitions. - Added some test bitmaps for Apple II hi-res to the test directory. (These are not part of an automated test.) --- CommonUtil/Container.cs | 17 + PluginCommon/Interfaces.cs | 54 ++- PluginCommon/Util.cs | 4 +- SourceGen/App.xaml | 18 +- SourceGen/ProjectFile.cs | 6 +- SourceGen/RuntimeData/Apple/VisHiRes.cs | 441 +++++++++++------- SourceGen/RuntimeData/SystemDefs.json | 7 +- SourceGen/SGTestData/README.md | 6 + .../Visualization/apple2-bitmap-test#061000 | Bin 0 -> 232 bytes .../Visualization/apple2-bitmap-test#061000.S | 166 +++++++ .../apple2-bitmap-test#061000.dis65 | 47 ++ .../apple2-bitmap-test-AW-RGB.png | Bin 0 -> 713 bytes SourceGen/Visualization.cs | 13 +- SourceGen/VisualizationSet.cs | 4 +- SourceGen/WpfGui/EditVisualization.xaml.cs | 6 +- SourceGen/WpfGui/EditVisualizationSet.xaml | 28 +- SourceGen/WpfGui/EditVisualizationSet.xaml.cs | 19 +- 17 files changed, 631 insertions(+), 205 deletions(-) create mode 100644 SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000 create mode 100644 SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.S create mode 100644 SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65 create mode 100644 SourceGen/SGTestData/Visualization/apple2-bitmap-test-AW-RGB.png diff --git a/CommonUtil/Container.cs b/CommonUtil/Container.cs index d297690..069fdea 100644 --- a/CommonUtil/Container.cs +++ b/CommonUtil/Container.cs @@ -15,6 +15,7 @@ */ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; namespace CommonUtil { @@ -78,5 +79,21 @@ namespace CommonUtil { return !dict1.Except(dict2).Any(); #endif } + + public static bool CompareDicts( + ReadOnlyDictionary dict1, ReadOnlyDictionary dict2) { + if (dict1 == dict2) { + return true; + } + if (dict1 == null || dict2 == null) { + return false; + } + if (dict1.Count != dict2.Count) { + return false; + } + + // Check to see if there are any elements in the first that are not in the second. + return !dict1.Except(dict2).Any(); + } } } diff --git a/PluginCommon/Interfaces.cs b/PluginCommon/Interfaces.cs index 9a2a3a4..8044b19 100644 --- a/PluginCommon/Interfaces.cs +++ b/PluginCommon/Interfaces.cs @@ -15,6 +15,7 @@ */ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; namespace PluginCommon { /// @@ -150,13 +151,18 @@ namespace PluginCommon { /// Parameter set. /// 2D visualization object reference, or null if something went /// wrong (unknown ident, bad parameters, etc). - IVisualization2d Generate2d(VisDescr descr, Dictionary parms); + IVisualization2d Generate2d(VisDescr descr, ReadOnlyDictionary parms); } + /// + /// Visualization generator descriptor. IPlugin_Visualizer instances publish a list of + /// these to tell the application about the generators it supports. + /// [Serializable] public class VisDescr { /// - /// Unique identifier. This is stored in the project file. + /// Unique identifier. This is stored in the project file. Names beginning with + /// underscores ('_') are reserved. /// public string Ident { get; private set; } @@ -181,6 +187,9 @@ namespace PluginCommon { /// public VisParamDescr[] VisParamDescrs { get; private set; } + /// + /// Constructor. + /// public VisDescr(string ident, string uiName, VisType type, VisParamDescr[] descrs) { Ident = ident; UiName = uiName; @@ -192,24 +201,59 @@ namespace PluginCommon { /// /// Visualization parameter descriptor. /// + /// + /// We provide min/max for individual numeric fields, but there's no way to check other + /// fields to see if e.g. Stride >= Width. We'd need a "verify" function and a way to + /// report errors that the GUI could use to point out what was wrong. + /// [Serializable] public class VisParamDescr { + /// + /// Special feature enumeration. + /// public enum SpecialMode { None = 0, Offset } + /// + /// Label to show in the UI. + /// public string UiLabel { get; private set; } + + /// + /// Name to use internally. Do not use names that start with an underscore ('_'), as + /// these are reserved for future internal use. + /// public string Name { get; private set; } + + /// + /// Parameter data type. + /// public Type CsType { get; private set; } - // Min/Max values for ints and floats. Could also be length for strings. - // NOTE: ideally we'd provide a "verify" function that tested individual fields and - // could also see other fields, e.g. to confirm that Stride >= Width. + /// + /// Minimum allowable value for int/float (and perhaps string length). + /// public object Min { get; private set; } + + /// + /// Maximum allowable value for int/float (and perhaps string length). + /// public object Max { get; private set; } + + /// + /// Set to a value if the field requires special treatment. + /// public SpecialMode Special { get; private set; } + + /// + /// Default value for this field. + /// public object DefaultValue { get; private set; } + /// + /// Constructor. + /// public VisParamDescr(string uiLabel, string name, Type csType, object min, object max, SpecialMode special, object defVal) { UiLabel = uiLabel; diff --git a/PluginCommon/Util.cs b/PluginCommon/Util.cs index 2911748..6ed5894 100644 --- a/PluginCommon/Util.cs +++ b/PluginCommon/Util.cs @@ -15,6 +15,7 @@ */ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using CommonUtil; namespace PluginCommon { @@ -107,7 +108,8 @@ namespace PluginCommon { /// Default value. /// Value found, or the default if the key doesn't exist or the value has the /// wrong type. - public static T GetFromObjDict(Dictionary dict, string key, T defVal) { + public static T GetFromObjDict(ReadOnlyDictionary dict, string key, + T defVal) { if (dict.TryGetValue(key, out object objVal)) { if (objVal is T) { return (T)objVal; diff --git a/SourceGen/App.xaml b/SourceGen/App.xaml index 0066d93..58ca079 100644 --- a/SourceGen/App.xaml +++ b/SourceGen/App.xaml @@ -22,17 +22,33 @@ limitations under the License. Consolas + + - + + + + + + + + + + + + + + diff --git a/SourceGen/ProjectFile.cs b/SourceGen/ProjectFile.cs index 3d39cd8..4d76ce9 100644 --- a/SourceGen/ProjectFile.cs +++ b/SourceGen/ProjectFile.cs @@ -15,6 +15,7 @@ */ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Text; @@ -368,7 +369,7 @@ namespace SourceGen { public SerVisualization(Visualization vis) { Tag = vis.Tag; VisGenIdent = vis.VisGenIdent; - VisGenParams = vis.VisGenParams; // Dictionary + VisGenParams = new Dictionary(vis.VisGenParams); } } public class SerVisualizationSet { @@ -1002,7 +1003,8 @@ namespace SourceGen { parms.Add(kvp.Key, val); } - Visualization vis = new Visualization(serVis.Tag, serVis.VisGenIdent, parms); + Visualization vis = new Visualization(serVis.Tag, serVis.VisGenIdent, + new ReadOnlyDictionary(parms)); outVisSet.Add(vis); } return true; diff --git a/SourceGen/RuntimeData/Apple/VisHiRes.cs b/SourceGen/RuntimeData/Apple/VisHiRes.cs index b9434d8..14dbd2c 100644 --- a/SourceGen/RuntimeData/Apple/VisHiRes.cs +++ b/SourceGen/RuntimeData/Apple/VisHiRes.cs @@ -15,6 +15,7 @@ */ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text; using PluginCommon; @@ -65,8 +66,8 @@ namespace RuntimeData.Apple { 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("Test Float", - "floaty", typeof(float), -5.0f, 5.0f, 0, 0.1f), + //new VisParamDescr("Test Float", + // "floaty", typeof(float), -5.0f, 5.0f, 0, 0.1f), }), new VisDescr(VIS_GEN_BITMAP_FONT, "Apple II Hi-Res Bitmap Font", VisDescr.VisType.Bitmap, new VisParamDescr[] { @@ -103,7 +104,7 @@ namespace RuntimeData.Apple { // IPlugin_Visualizer public IVisualization2d Generate2d(VisDescr descr, - Dictionary parms) { + ReadOnlyDictionary parms) { switch (descr.Ident) { case VIS_GEN_BITMAP: return GenerateBitmap(parms); @@ -116,7 +117,7 @@ namespace RuntimeData.Apple { } } - private IVisualization2d GenerateBitmap(Dictionary parms) { + private IVisualization2d GenerateBitmap(ReadOnlyDictionary parms) { int offset, byteWidth, height, colStride, rowStride; bool isColor, isFirstOdd; @@ -147,7 +148,7 @@ namespace RuntimeData.Apple { mAppRef.DebugLog("Invalid column stride"); return null; } - if (rowStride < byteWidth * colStride - (colStride-1) || rowStride > MAX_DIM) { + if (rowStride < byteWidth * colStride - (colStride - 1) || rowStride > MAX_DIM) { mAppRef.DebugLog("Invalid row stride"); return null; } @@ -159,187 +160,283 @@ namespace RuntimeData.Apple { return null; } + VisBitmap8 vb = new VisBitmap8(byteWidth * 7, height); SetHiResPalette(vb); - if (!isColor) { - // B&W mode. Since we're not displaying this we don't need to worry about - // half-pixel shifts, and can just convert 7 bits to pixels. - int bx = 0; - int by = 0; - for (int row = 0; row < height; row++) { - int colIdx = 0; - for (int col = 0; col < byteWidth; col++) { - byte val = mFileData[offset + colIdx]; - for (int bit = 0; bit < 7; bit++) { - vb.SetPixelIndex(bx, by, (byte)((val & 0x01) + 1)); // black or white - val >>= 1; - bx++; - } - colIdx += colStride; - } - bx = 0; - by++; - offset += rowStride; - } - } else { - int bx = 0; - int by = 0; - -#if false - // Color mode. We treat the data as a strictly 140-mode bitmap, which doesn't - // quite match up with how the pixels will be displayed, but does allow a - // straightforward conversion between file formats. Color fringing is severe. - for (int row = 0; row < height; row++) { - int lastBit; - if (isFirstOdd) { - lastBit = 0; // pretend we already have one bit - } else { - lastBit = -1; - } - - for (int colByte = 0; colByte < byteWidth; colByte += colStride) { - byte val = mFileData[offset + colByte]; - bool hiBitSet = (val & 0x80) != 0; - - // Grab 3 or 4 pairs of bits. - int pairCount = (lastBit < 0) ? 3 : 4; - while (pairCount-- > 0) { - int twoBits; - if (lastBit >= 0) { - // merge with bit from previous byte - twoBits = (lastBit << 1) | (val & 0x01); - val >>= 1; - lastBit = -1; - } else { - // grab two bits - twoBits = (val & 0x03); - val >>= 2; - } - - if (hiBitSet) { - twoBits += 4; - } - - // We're in 140 mode, so set two adjacent pixels. - vb.SetPixelIndex(bx++, by, sHiResColorMap[twoBits]); - vb.SetPixelIndex(bx++, by, sHiResColorMap[twoBits]); - } - - bool thisEven = ((colByte & 0x01) == 0) ^ isFirstOdd; - if (thisEven) { - // started in even column we have one bit left over - lastBit = val & 0x01; - } else { - // started in odd column, all bits consumed - lastBit = -1; - } - } - bx = 0; - by++; - offset += rowStride; - } -#else - // 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). - bool[] lineBits = new bool[byteWidth * 7]; - bool[] hiFlags = new bool[byteWidth * 7]; // overkill, but simplifies things - 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 = mFileData[offset + colIdx]; - bool hiBitSet = (val & 0x80) != 0; - - for (int bit = 0; bit < 7; bit++) { - hiFlags[idx] = hiBitSet; - 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] ? 4 : 0; - if (!lineBits[idx]) { - // Bit not set, set pixel to black. - colorBuf[idx] = (int)HiResColors.Black0 + colorShift; - } else { - // Bit set, set pixel to white or color. - if (idx > 0 && colorBuf[idx - 1] != (int)HiResColors.Black0 && - colorBuf[idx - 1] != (int)HiResColors.Black1) { - // previous bit was also set, this is white - colorBuf[idx] = (int)HiResColors.White0 + colorShift; - - // the previous pixel is part of a run of white - colorBuf[idx - 1] = (int)HiResColors.White0 + colorShift; - } 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 - 2] == (int)HiResColors.White1)) { - - //if (colorBuf[idx - 1] != (int)HiResColors.Black0 && - // colorBuf[idx - 1] != (int) HiResColors.Black1) { - // mAppRef.DebugLog("Unexpected color at row=" + by + - // " idx=" + idx + ": " + colorBuf[idx - 1]); - //} - - colorBuf[idx - 1] = colorBuf[idx]; - } - } - } - - // Write to bitmap. - for (idx = 0; idx < lastBit; idx++) { - vb.SetPixelIndex(bx++, by, sHiResColorMap[colorBuf[idx]]); - } - - // move to next row - bx = 0; - by++; - offset += rowStride; - } -#endif - } + RenderBitmap(offset, byteWidth, height, colStride, rowStride, + isColor ? ColorMode.SimpleColor : ColorMode.Mono, isFirstOdd, vb); return vb; } - private enum HiResColors { - Black0 = 0, - Green = 1, - Purple = 2, - White0 = 3, - Black1 = 4, - Orange = 5, - Blue = 6, - White1 = 7 + private enum ColorMode { Mono, ClunkyColor, SimpleColor, IIgsRGB }; + + private void RenderBitmap(int offset, int byteWidth, int height, int colStride, + int rowStride, ColorMode colorMode, bool isFirstOdd, VisBitmap8 vb) { + int bx = 0; + int by = 0; + 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 = mFileData[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 = 0; + by++; + offset += rowStride; + } + } + break; + case ColorMode.ClunkyColor: { + // We treat the data as a strictly 140-mode bitmap, which doesn't match + // up well with how the pixels will be displayed, but does allow a + // straightforward conversion between file formats. Color fringing is + // severe. + for (int row = 0; row < height; row++) { + int lastBit; + if (isFirstOdd) { + lastBit = 0; // pretend we already have one bit + } else { + lastBit = -1; + } + + for (int colByte = 0; colByte < byteWidth; colByte += colStride) { + byte val = mFileData[offset + colByte]; + bool hiBitSet = (val & 0x80) != 0; + + // Grab 3 or 4 pairs of bits. + int pairCount = (lastBit < 0) ? 3 : 4; + while (pairCount-- > 0) { + int twoBits; + if (lastBit >= 0) { + // merge with bit from previous byte + twoBits = (lastBit << 1) | (val & 0x01); + val >>= 1; + lastBit = -1; + } else { + // grab two bits + twoBits = (val & 0x03); + val >>= 2; + } + + if (hiBitSet) { + twoBits += 4; + } + + // We're in 140 mode, so set two adjacent pixels. + vb.SetPixelIndex(bx++, by, sHiResColorMap[twoBits]); + vb.SetPixelIndex(bx++, by, sHiResColorMap[twoBits]); + } + + bool thisEven = ((colByte & 0x01) == 0) ^ isFirstOdd; + if (thisEven) { + // started in even column we have one bit left over + lastBit = val & 0x01; + } else { + // started in odd column, all bits consumed + lastBit = -1; + } + } + bx = 0; + 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 = mFileData[offset + colIdx]; + bool hiBitSet = (val & 0x80) != 0; + + for (int bit = 0; bit < 7; bit++) { + hiFlags[idx] = hiBitSet; + 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 = 0; + 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 = mFileData[offset + colIdx]; + bool hiBitSet = (val & 0x80) != 0; + + for (int bit = 0; bit < 7; bit++) { + hiFlags[idx] = hiBitSet; + 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 = 0; + by++; + offset += rowStride; + } + } + break; + default: + // just leave the bitmap empty + mAppRef.DebugLog("Unknown ColorMode " + colorMode); + break; + } } - // Maps HiResColors to the palette entries. + /// + /// Map hi-res colors to palette entries. + /// + private enum HiResColors : byte { + Black0 = 1, + Green = 3, + Purple = 4, + White0 = 2, + Black1 = 1, + Orange = 5, + Blue = 6, + White1 = 2 + } + + // Maps HiResColors to the palette entries. Used for mapping 2-bit values to colors. private static readonly byte[] sHiResColorMap = new byte[8] { 1, 3, 4, 2, 1, 5, 6, 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. + // 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 diff --git a/SourceGen/RuntimeData/SystemDefs.json b/SourceGen/RuntimeData/SystemDefs.json index 6336fa0..a18b4b6 100644 --- a/SourceGen/RuntimeData/SystemDefs.json +++ b/SourceGen/RuntimeData/SystemDefs.json @@ -15,7 +15,8 @@ "RT:Apple/ProDOS8.sym65" ], "ExtensionScripts" : [ - "RT:Apple/ProDOS8.cs" + "RT:Apple/ProDOS8.cs", + "RT:Apple/VisHiRes.cs" ], "Parameters" : { "load-address":"0x2000" @@ -33,6 +34,7 @@ "RT:Apple/DOS33.sym65" ], "ExtensionScripts" : [ + "RT:Apple/VisHiRes.cs" ], "Parameters" : { "load-address":"0x2000" @@ -51,7 +53,8 @@ "RT:Apple/IIgs-ROM.sym65" ], "ExtensionScripts" : [ - "RT:Apple/ProDOS8.cs" + "RT:Apple/ProDOS8.cs", + "RT:Apple/VisHiRes.cs" ], "Parameters" : { "load-address":"0x2000", diff --git a/SourceGen/SGTestData/README.md b/SourceGen/SGTestData/README.md index 3c01a94..d81caaa 100644 --- a/SourceGen/SGTestData/README.md +++ b/SourceGen/SGTestData/README.md @@ -93,3 +93,9 @@ This is a collection of project files with deliberate errors. These exist to exercise the load-time error reporting. See the README in that directory for a full explanation. + +## Visualization ## + +Some test projects and data files for exercising the visualization generators. +Not part of a formal test; load the projects and eyeball the results. + diff --git a/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000 b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000 new file mode 100644 index 0000000000000000000000000000000000000000..5eb048cdb348f389bbf3b787833567192acec661 GIT binary patch literal 232 zcmdN{AfU5CKxc!1&H(|P3jzv{KCfKR%CJ(Pm2o9QE7MBGR%V4Fft4$uLd*!E4NxIA zpb!&8)d8pw7eeR)REQ5K#0*xIuz;b3Wf{xf=>i`ld%FdeGWB)|EMnNmFqc8$#EU1) z7rs7Yx$q!CK}AEyz{J9afq{{Ug+oBW;lP3k9W70b4OdsKx*Dpb6?#=`RcPoct*Z_| l6%LFE%nnRf!BX}0^~@Xz%rXhgb_vY&3C`t-$;rt;K>)fUQRe^v literal 0 HcmV?d00001 diff --git a/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.S b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.S new file mode 100644 index 0000000..72b75a6 --- /dev/null +++ b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.S @@ -0,0 +1,166 @@ +; Copyright 2019 faddenSoft. All Rights Reserved. +; See the LICENSE.txt file for distribution terms (Apache 2.0). +; +; Assembler: Merlin 32 + +HGR equ $f3e2 +src_ptr equ $00 +xoff equ $02 +width equ $03 +line equ $04 +count equ $05 + + org $1000 + + bit bitmap1 ;give the disassembler a reason to label them + bit bitmap2 + bit bitmap3 + bit bitmap4 + bit bitmap5 + + jsr HGR ;clear screen, set hi-res mode + lda #bitmap1 + sta src_ptr+1 + lda #$00 + sta xoff + lda #$01 ;1x8 + sta width + jsr draw + + lda #bitmap2 + sta src_ptr+1 + lda #$03 ;start in odd column + sta xoff + lda #$01 ;1x8 + sta width + jsr draw + + lda #bitmap3 + sta src_ptr+1 + lda #$06 + sta xoff + lda #$02 ;2x8 + sta width + jsr draw + + lda #bitmap4 + sta src_ptr+1 + lda #$0a + sta xoff + lda #$02 ;2x8 + sta width + jsr draw + + lda #bitmap5 + sta src_ptr+1 + lda #$0e + sta xoff + lda #$03 ;3x8 + sta width + jsr draw + + rts + +; Copies an Nx8 bitmap to the hi-res screen. +; +; Put X offset in "xoff", width-1 in "width_m1", and a pointer to the bitmap +; data in "src_ptr". +draw + ldy #$00 ;index to input byte + sty line ;init line index to zero +:loop ldx line + lda addrs,x + beq :done + sta _copy+2 + lda xoff + sta _copy+1 + + ldx #$00 +:loop1 lda (src_ptr),y +_copy sta $2000,x + iny + inx + cpx width + bne :loop1 + inc line + bne :loop ;always (more or less) + +:done rts + +; hi byte of base addresses on hi-res screen; must be 8 of them +addrs + dfb $20 + dfb $24 + dfb $28 + dfb $2c + dfb $30 + dfb $34 + dfb $38 + dfb $3c + dfb $00 + +; 1x8 +bitmap1 + dfb $00 + dfb $01 + dfb $02 + dfb $04 + dfb $08 + dfb $10 + dfb $20 + dfb $40 + +; 1x8 (odd byte) +bitmap2 + dfb $c0 + dfb $a0 + dfb $90 + dfb $88 + dfb $84 + dfb $82 + dfb $81 + dfb $80 + +; 2x8 +bitmap3 + dfb $d5,$aa ;pure colors + dfb $aa,$d5 + dfb $55,$2a + dfb $2a,$55 + dfb $d5,$2a ;high bit split + dfb $aa,$55 + dfb $55,$aa + dfb $2a,$d5 + +; 2x8 +bitmap4 + dfb $40,$00 ;bits at the edge + dfb $00,$01 + dfb $40,$01 + dfb $60,$03 + dfb $40,$02 + dfb $d5,$d5 ;same byte odd/even + dfb $aa,$aa + dfb $d5,$55 + +; 3x8 (do in B&W) +bitmap5 + dfb $7f,$7f,$7f + dfb $03,$08,$60 + dfb $03,$1c,$60 + dfb $03,$3e,$60 + dfb $03,$7f,$60 + dfb $43,$77,$61 + dfb $63,$63,$63 + dfb $7f,$7f,$7f + diff --git a/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65 b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65 new file mode 100644 index 0000000..4e6fa01 --- /dev/null +++ b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65 @@ -0,0 +1,47 @@ +### 6502bench SourceGen dis65 v1.0 ### +{ +"_ContentVersion":3,"FileDataLength":232,"FileDataCrc32":-2081570119,"ProjectProps":{ +"CpuName":"65C02","IncludeUndocumentedInstr":false,"TwoByteBrk":false,"EntryFlags":32702671,"AutoLabelStyle":"Simple","AnalysisParams":{ +"AnalyzeUncategorizedData":true,"DefaultTextScanMode":"LowHighAscii","MinCharsForString":4,"SeekNearbyTargets":true,"SmartPlpHandling":true}, +"PlatformSymbolFileIdentifiers":["RT:Apple/F8-ROM.sym65","RT:Apple/Cxxx-IO.sym65"],"ExtensionScriptFileIdentifiers":["RT:Apple/VisHiRes.cs"],"ProjectSyms":{ +}}, +"AddressMap":[{ +"Offset":0,"Addr":4096}],"TypeHints":[{ +"Low":0,"High":0,"Hint":"Code"}],"StatusFlagOverrides":{ +}, +"Comments":{ +}, +"LongComments":{ +"-2147483647":{ +"Text":"6502bench SourceGen v1.5.0-dev1","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}}, +"Notes":{ +}, +"UserLabels":{ +"114":{ +"Label":"Draw","Value":4210,"Source":"User","Type":"GlobalAddr","LabelAnno":"None"}}, +"OperandFormats":{ +"151":{ +"Length":9,"Format":"Dense","SubFormat":"None","SymbolRef":null}}, +"LvTables":{ +}, +"VisualizationSets":{ +"160":{ +"Items":[{ +"Tag":"vis0000a0","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{ +"offset":160,"byteWidth":1,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}}]}, +"168":{ +"Items":[{ +"Tag":"vis0000a8","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{ +"offset":168,"byteWidth":1,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":true}}]}, +"176":{ +"Items":[{ +"Tag":"vis0000b0","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{ +"offset":176,"byteWidth":2,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}}]}, +"192":{ +"Items":[{ +"Tag":"vis0000c0","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{ +"offset":192,"byteWidth":2,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}}]}, +"208":{ +"Items":[{ +"Tag":"vis0000d0","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{ +"offset":208,"byteWidth":3,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}}]}}} diff --git a/SourceGen/SGTestData/Visualization/apple2-bitmap-test-AW-RGB.png b/SourceGen/SGTestData/Visualization/apple2-bitmap-test-AW-RGB.png new file mode 100644 index 0000000000000000000000000000000000000000..4e731f5dd690a2d483a9a8da76d76b7647a90bbf GIT binary patch literal 713 zcmeAS@N?(olHy`uVBq!ia0vp^Ux8SLgAGXT{Pj~GNHG=%xjQkeJ16rJ$YDu$^mSxl z*x1kgCy^D%=PdAuEM{QfI}E~%$MaXDFfcI1dAc};RK&f#_Apo4kf-h8pVuO>%G@RG zXKu*uayWKE>hDuG(pxH7nBl zqt8y~{~D9A;`&0xJ*KzJm3d9$`#&v|d;G_JYW**dw&r{Lk6g`o-&3c@r}--gfYRm5A`E&zQwyzumL` z{hIc`+4jk;3+aOLuM9V=Son`k>XxL{4`pn!6%#*tzi4*2ZuC-rZpS~NyWQVn&+h9{ zs(JBq`-OknKLh$rz5mZkTX=q3&5Gj(9<{dpsw(l=e#7x@Gf*90P7A;7f3aKjs`9Tk zroFs>S?9~mX&_hqc{Hzm#qR6kd)@0f-?<((aQ-cSlJ9;;y!&~lKf9EF1o#_sugy$b zXxa-_?Bp!#%q;ELUe)%^ev<4R!To*nT7Q(f1HG9h7IhiuV=0g~cC3_l+NCD{%iEx< zO|j`)TrXR}k+ylYE#Kl-f}<_(Qo!*GjIs+Gdl&2tUwlw@rr_<#T8zC}r*9Hxx(!@ZQSBDmLTxoR`T*-~5VA4Fl{|wHCK^1>zWzGgBKL$@% KKbLh*2~7YrP)$<+ literal 0 HcmV?d00001 diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs index 1653a37..64e0fc6 100644 --- a/SourceGen/Visualization.cs +++ b/SourceGen/Visualization.cs @@ -15,6 +15,7 @@ */ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Text; using System.Windows.Media; using System.Windows.Media.Imaging; @@ -26,12 +27,12 @@ namespace SourceGen { /// /// Graphical visualization object. Useful for displaying 2D bitmaps and 3D objects. /// - /// Treat this as generally immutable, i.e. don't modify VisGenParams. The CachedImage - /// field is mutable. + /// This is generally immutable, except for the CachedImage field. /// public class Visualization { /// - /// Unique user-specified tag. Contents are arbitrary, but may not be empty. + /// Unique user-specified tag. This may be any valid string that is at least two + /// characters long after the leading and trailing whitespace have been trimmed. /// public string Tag { get; private set; } @@ -41,9 +42,9 @@ namespace SourceGen { public string VisGenIdent { get; private set; } /// - /// Parameters passed to the visualization generator. + /// Parameters to be passed to the visualization generator. /// - public Dictionary VisGenParams { get; private set; } + public ReadOnlyDictionary VisGenParams { get; private set; } /// /// Cached reference to 2D image, useful for thumbnails. Not serialized. @@ -68,7 +69,7 @@ namespace SourceGen { /// Visualization generator identifier. /// Parameters for visualization generator. public Visualization(string tag, string visGenIdent, - Dictionary visGenParams) { + ReadOnlyDictionary visGenParams) { Tag = tag; VisGenIdent = visGenIdent; VisGenParams = visGenParams; diff --git a/SourceGen/VisualizationSet.cs b/SourceGen/VisualizationSet.cs index 0dfeaf1..9c683a4 100644 --- a/SourceGen/VisualizationSet.cs +++ b/SourceGen/VisualizationSet.cs @@ -16,6 +16,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using PluginCommon; @@ -147,7 +148,8 @@ namespace SourceGen { IVisualization2d vis2d; try { - vis2d = vplug.Generate2d(visDescr, vis.VisGenParams); + vis2d = vplug.Generate2d(visDescr, + new ReadOnlyDictionary(vis.VisGenParams)); if (vis2d == null) { Debug.WriteLine("Vis generator returned null"); } diff --git a/SourceGen/WpfGui/EditVisualization.xaml.cs b/SourceGen/WpfGui/EditVisualization.xaml.cs index 8642929..3d0ba1b 100644 --- a/SourceGen/WpfGui/EditVisualization.xaml.cs +++ b/SourceGen/WpfGui/EditVisualization.xaml.cs @@ -254,7 +254,7 @@ namespace SourceGen.WpfGui { private void OkButton_Click(object sender, RoutedEventArgs e) { VisualizationItem item = (VisualizationItem)visComboBox.SelectedItem; Debug.Assert(item != null); - Dictionary valueDict = CreateVisGenParams(); + ReadOnlyDictionary valueDict = CreateVisGenParams(); string trimTag = Visualization.TrimAndValidateTag(TagString, out bool isTagValid); Debug.Assert(isTagValid); NewVis = new Visualization(trimTag, item.VisDescriptor.Ident, valueDict); @@ -263,7 +263,7 @@ namespace SourceGen.WpfGui { DialogResult = true; } - private Dictionary CreateVisGenParams() { + private ReadOnlyDictionary CreateVisGenParams() { // Generate value dictionary. Dictionary valueDict = new Dictionary(ParameterList.Count); @@ -285,7 +285,7 @@ namespace SourceGen.WpfGui { } } - return valueDict; + return new ReadOnlyDictionary(valueDict); } private bool ParseIntObj(object val, VisParamDescr.SpecialMode special, out int intVal) { diff --git a/SourceGen/WpfGui/EditVisualizationSet.xaml b/SourceGen/WpfGui/EditVisualizationSet.xaml index 25613c0..046367e 100644 --- a/SourceGen/WpfGui/EditVisualizationSet.xaml +++ b/SourceGen/WpfGui/EditVisualizationSet.xaml @@ -23,7 +23,7 @@ limitations under the License. xmlns:local="clr-namespace:SourceGen.WpfGui" mc:Ignorable="d" Title="Edit Visualization Set" - Width="560" Height="400" ResizeMode="NoResize" + Width="600" Height="400" ResizeMode="NoResize" ShowInTaskbar="False" WindowStartupLocation="CenterOwner" Closing="Window_Closing"> @@ -38,11 +38,16 @@ limitations under the License. + - + + - - + + - -