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.)
This commit is contained in:
Andy McFadden 2019-12-04 15:50:19 -08:00
parent 2f56c56e5c
commit df04de61e6
17 changed files with 631 additions and 205 deletions

View File

@ -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<TKey, TValue>(
ReadOnlyDictionary<TKey, TValue> dict1, ReadOnlyDictionary<TKey, TValue> 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();
}
}
}

View File

@ -15,6 +15,7 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace PluginCommon {
/// <summary>
@ -150,13 +151,18 @@ namespace PluginCommon {
/// <param name="parms">Parameter set.</param>
/// <returns>2D visualization object reference, or null if something went
/// wrong (unknown ident, bad parameters, etc).</returns>
IVisualization2d Generate2d(VisDescr descr, Dictionary<string, object> parms);
IVisualization2d Generate2d(VisDescr descr, ReadOnlyDictionary<string, object> parms);
}
/// <summary>
/// Visualization generator descriptor. IPlugin_Visualizer instances publish a list of
/// these to tell the application about the generators it supports.
/// </summary>
[Serializable]
public class VisDescr {
/// <summary>
/// Unique identifier. This is stored in the project file.
/// Unique identifier. This is stored in the project file. Names beginning with
/// underscores ('_') are reserved.
/// </summary>
public string Ident { get; private set; }
@ -181,6 +187,9 @@ namespace PluginCommon {
/// </summary>
public VisParamDescr[] VisParamDescrs { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
public VisDescr(string ident, string uiName, VisType type, VisParamDescr[] descrs) {
Ident = ident;
UiName = uiName;
@ -192,24 +201,59 @@ namespace PluginCommon {
/// <summary>
/// Visualization parameter descriptor.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
[Serializable]
public class VisParamDescr {
/// <summary>
/// Special feature enumeration.
/// </summary>
public enum SpecialMode {
None = 0, Offset
}
/// <summary>
/// Label to show in the UI.
/// </summary>
public string UiLabel { get; private set; }
/// <summary>
/// Name to use internally. Do not use names that start with an underscore ('_'), as
/// these are reserved for future internal use.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Parameter data type.
/// </summary>
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.
/// <summary>
/// Minimum allowable value for int/float (and perhaps string length).
/// </summary>
public object Min { get; private set; }
/// <summary>
/// Maximum allowable value for int/float (and perhaps string length).
/// </summary>
public object Max { get; private set; }
/// <summary>
/// Set to a value if the field requires special treatment.
/// </summary>
public SpecialMode Special { get; private set; }
/// <summary>
/// Default value for this field.
/// </summary>
public object DefaultValue { get; private set; }
/// <summary>
/// Constructor.
/// </summary>
public VisParamDescr(string uiLabel, string name, Type csType, object min, object max,
SpecialMode special, object defVal) {
UiLabel = uiLabel;

View File

@ -15,6 +15,7 @@
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommonUtil;
namespace PluginCommon {
@ -107,7 +108,8 @@ namespace PluginCommon {
/// <param name="defVal">Default value.</param>
/// <returns>Value found, or the default if the key doesn't exist or the value has the
/// wrong type.</returns>
public static T GetFromObjDict<T>(Dictionary<string, object> dict, string key, T defVal) {
public static T GetFromObjDict<T>(ReadOnlyDictionary<string, object> dict, string key,
T defVal) {
if (dict.TryGetValue(key, out object objVal)) {
if (objVal is T) {
return (T)objVal;

View File

@ -22,17 +22,33 @@ limitations under the License.
<ResourceDictionary>
<FontFamily x:Key="GeneralMonoFont">Consolas</FontFamily>
<!-- gradient background for bitmap images -->
<LinearGradientBrush x:Key="BitmapBackground" StartPoint="0,0" EndPoint="1,1">
<GradientStop Color="#707070" Offset="0.0"/>
<GradientStop Color="#f0f0f0" Offset="1.0"/>
</LinearGradientBrush>
<!-- checkerboard background for bitmap images -->
<!-- https://stackoverflow.com/a/47049174/294248 -->
<DrawingBrush x:Key="CheckerBackground" TileMode="Tile" Viewport="0,0,16,16" ViewportUnits="Absolute">
<DrawingBrush x:Key="CheckerBackgroundHG" TileMode="Tile" Viewport="0,0,16,16" ViewportUnits="Absolute">
<!-- draw two squares, leaving the other two untouched (probably window bkgnd color) -->
<DrawingBrush.Drawing>
<GeometryDrawing Geometry="M0,0 H1 V1 H2 V2 H1 V1 H0Z" Brush="LightGray"/>
</DrawingBrush.Drawing>
</DrawingBrush>
<DrawingBrush x:Key="CheckerBackground" TileMode="Tile" Viewport="0,0,16,16" ViewportUnits="Absolute">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<!-- not sure if it's better to do this with overdraw (background
then two squares) or by explicitly drawing four squares -->
<GeometryDrawing Geometry="M0,0 H2 V2 H0Z" Brush="#e8e8e8"/>
<GeometryDrawing Geometry="M0,0 H1 V1 H2 V2 H1 V1 H0Z" Brush="#f0f0f0"/>
<!--<GeometryDrawing Geometry="M0,2 H1 V1 H2 V0 H1 V1 H0Z" Brush="#e8e8e8"/>-->
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Res/Strings.xaml"/>

View File

@ -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<string, object>(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<string, object>(parms));
outVisSet.Add(vis);
}
return true;

View File

@ -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<string, object> parms) {
ReadOnlyDictionary<string, object> parms) {
switch (descr.Ident) {
case VIS_GEN_BITMAP:
return GenerateBitmap(parms);
@ -116,7 +117,7 @@ namespace RuntimeData.Apple {
}
}
private IVisualization2d GenerateBitmap(Dictionary<string, object> parms) {
private IVisualization2d GenerateBitmap(ReadOnlyDictionary<string, object> 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.
/// <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
}
// 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

View File

@ -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",

View File

@ -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.

View File

@ -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
lda #>bitmap1
sta src_ptr+1
lda #$00
sta xoff
lda #$01 ;1x8
sta width
jsr draw
lda #<bitmap2
sta src_ptr
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
lda #>bitmap3
sta src_ptr+1
lda #$06
sta xoff
lda #$02 ;2x8
sta width
jsr draw
lda #<bitmap4
sta src_ptr
lda #>bitmap4
sta src_ptr+1
lda #$0a
sta xoff
lda #$02 ;2x8
sta width
jsr draw
lda #<bitmap5
sta src_ptr
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

View File

@ -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}}]}}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

View File

@ -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 {
/// <summary>
/// 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.
/// </summary>
public class Visualization {
/// <summary>
/// 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.
/// </summary>
public string Tag { get; private set; }
@ -41,9 +42,9 @@ namespace SourceGen {
public string VisGenIdent { get; private set; }
/// <summary>
/// Parameters passed to the visualization generator.
/// Parameters to be passed to the visualization generator.
/// </summary>
public Dictionary<string, object> VisGenParams { get; private set; }
public ReadOnlyDictionary<string, object> VisGenParams { get; private set; }
/// <summary>
/// Cached reference to 2D image, useful for thumbnails. Not serialized.
@ -68,7 +69,7 @@ namespace SourceGen {
/// <param name="visGenIdent">Visualization generator identifier.</param>
/// <param name="visGenParams">Parameters for visualization generator.</param>
public Visualization(string tag, string visGenIdent,
Dictionary<string, object> visGenParams) {
ReadOnlyDictionary<string, object> visGenParams) {
Tag = tag;
VisGenIdent = visGenIdent;
VisGenParams = visGenParams;

View File

@ -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<string, object>(vis.VisGenParams));
if (vis2d == null) {
Debug.WriteLine("Vis generator returned null");
}

View File

@ -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<string, object> valueDict = CreateVisGenParams();
ReadOnlyDictionary<string, object> 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<string, object> CreateVisGenParams() {
private ReadOnlyDictionary<string, object> CreateVisGenParams() {
// Generate value dictionary.
Dictionary<string, object> valueDict =
new Dictionary<string, object>(ParameterList.Count);
@ -285,7 +285,7 @@ namespace SourceGen.WpfGui {
}
}
return valueDict;
return new ReadOnlyDictionary<string, object>(valueDict);
}
private bool ParseIntObj(object val, VisParamDescr.SpecialMode special, out int intVal) {

View File

@ -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.
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Name="visualizationGrid" Grid.Column="0" Grid.Row="0" Margin="4,4,4,0"
<TextBlock Grid.Row="0" Foreground="Red" Margin="0,0,0,4"
Visibility="{Binding ScriptWarningVisible}"
Text="NOTE: no extension scripts with visualization generators are loaded"/>
<DataGrid Name="visualizationGrid" Grid.Column="0" Grid.Row="1" Margin="4,4,4,0"
ItemsSource="{Binding VisualizationList}"
IsReadOnly="True"
FontFamily="{StaticResource GeneralMonoFont}"
@ -76,26 +81,27 @@ limitations under the License.
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Tag" Width="155" Binding="{Binding Tag}"/>
<DataGridTextColumn Header="Visualization Generator" Width="220" Binding="{Binding VisGenIdent}"/>
<DataGridTextColumn Header="Tag" Width="170" Binding="{Binding Tag}"/>
<DataGridTextColumn Header="Visualization Generator" Width="212" Binding="{Binding VisGenIdent}"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Column="1" Grid.Row="0">
<Button Width="75" Margin="4" Content="_New..."
<StackPanel Grid.Column="1" Grid.Row="1">
<Button Width="110" Margin="4" Content="_New Bitmap..."
IsEnabled="{Binding HasVisPlugins}" Click="NewButton_Click"/>
<Button Width="75" Margin="4" Content="_Edit"
<Button Width="110" Margin="4,20,4,4" Content="_Edit..."
IsEnabled="{Binding IsEditEnabled}" Click="EditButton_Click"/>
<Button Width="75" Margin="4" Content="_Remove"
<Button Width="110" Margin="4" Content="_Remove"
IsEnabled="{Binding IsRemoveEnabled}" Click="RemoveButton_Click"/>
<Button Width="75" Margin="4,20,4,4" Content="_Up"
<Button Width="110" Margin="4,20,4,4" Content="_Up"
IsEnabled="{Binding IsUpEnabled}" Click="UpButton_Click"/>
<Button Width="75" Margin="4,4" Content="_Down"
<Button Width="110" Margin="4,4" Content="_Down"
IsEnabled="{Binding IsDownEnabled}" Click="DownButton_Click"/>
</StackPanel>
<DockPanel Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,8,0,0" LastChildFill="False">
<DockPanel Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Margin="0,8,0,0" LastChildFill="False">
<Button DockPanel.Dock="Right" Content="Cancel" Width="70" Margin="8,0,0,0" IsCancel="True"/>
<Button DockPanel.Dock="Right" Grid.Column="1" Content="OK" Width="70"
IsDefault="True" Click="OkButton_Click"/>

View File

@ -50,6 +50,11 @@ namespace SourceGen.WpfGui {
}
private bool mHasVisPlugins;
public Visibility ScriptWarningVisible {
get { return mHasVisPlugins ? Visibility.Collapsed : Visibility.Visible; }
// this can't change while the dialog is open, so don't need OnPropertyChanged
}
public bool IsEditEnabled {
get { return mIsEditEnabled; }
set { mIsEditEnabled = value; OnPropertyChanged(); }
@ -92,11 +97,14 @@ namespace SourceGen.WpfGui {
mOffset = offset;
if (curSet != null) {
// Populate the ItemsSource.
// Populate the data grid ItemsSource.
foreach (Visualization vis in curSet) {
VisualizationList.Add(vis);
}
}
if (VisualizationList.Count > 0) {
visualizationGrid.SelectedIndex = 0;
}
// Check to see if we have any relevant plugins. If not, disable New/Edit.
List<IPlugin> plugins = project.GetActivePlugins();
@ -163,6 +171,7 @@ namespace SourceGen.WpfGui {
return;
}
VisualizationList.Add(dlg.NewVis);
visualizationGrid.SelectedIndex = VisualizationList.Count - 1;
}
private void EditButton_Click(object sender, RoutedEventArgs e) {
@ -185,11 +194,19 @@ namespace SourceGen.WpfGui {
int index = VisualizationList.IndexOf(item);
VisualizationList.Remove(item);
VisualizationList.Insert(index, dlg.NewVis);
visualizationGrid.SelectedIndex = index;
}
private void RemoveButton_Click(object sender, RoutedEventArgs e) {
Visualization item = (Visualization)visualizationGrid.SelectedItem;
int index = VisualizationList.IndexOf(item);
VisualizationList.Remove(item);
if (index == VisualizationList.Count) {
index--;
}
if (index >= 0) {
visualizationGrid.SelectedIndex = index;
}
}
private void UpButton_Click(object sender, RoutedEventArgs e) {