From ed66e129cdc761bfd9bbd740c43ee17cd19559c0 Mon Sep 17 00:00:00 2001
From: Andy McFadden
Date: Fri, 6 Dec 2019 21:25:31 -0800
Subject: [PATCH] Add C64 sprite visualization generator
Handles high-resolution and multi-color sprites. The visualization
can be doubled in height and/or width.
I created some test sprites with SpritePad.
---
PluginCommon/VisBitmap8.cs | 10 +
SourceGen/RuntimeData/Commodore/VisC64.cs | 256 ++++++++++++++++++
SourceGen/RuntimeData/Help/visualization.html | 33 +++
SourceGen/RuntimeData/SystemDefs.json | 3 +
.../Visualization/c64-sprites.dis65 | 55 ++++
.../SGTestData/Visualization/c64-sprites.raw | Bin 0 -> 2048 bytes
.../SGTestData/Visualization/c64-sprites.spd | Bin 0 -> 2051 bytes
7 files changed, 357 insertions(+)
create mode 100644 SourceGen/RuntimeData/Commodore/VisC64.cs
create mode 100644 SourceGen/SGTestData/Visualization/c64-sprites.dis65
create mode 100644 SourceGen/SGTestData/Visualization/c64-sprites.raw
create mode 100644 SourceGen/SGTestData/Visualization/c64-sprites.spd
diff --git a/PluginCommon/VisBitmap8.cs b/PluginCommon/VisBitmap8.cs
index f2e8a00..4645e7f 100644
--- a/PluginCommon/VisBitmap8.cs
+++ b/PluginCommon/VisBitmap8.cs
@@ -72,6 +72,16 @@ namespace PluginCommon {
mData[x + y * Width] = colorIndex;
}
+ public void SetAllPixelIndices(byte colorIndex) {
+ if (colorIndex < 0 || colorIndex >= mNextColor) {
+ throw new ArgumentException("Bad color: " + colorIndex + " (nextCol=" +
+ mNextColor + ")");
+ }
+ for (int i = 0; i < mData.Length; i++) {
+ mData[i] = colorIndex;
+ }
+ }
+
// IVisualization2d
public byte[] GetPixels() {
return mData;
diff --git a/SourceGen/RuntimeData/Commodore/VisC64.cs b/SourceGen/RuntimeData/Commodore/VisC64.cs
new file mode 100644
index 0000000..4fb8c84
--- /dev/null
+++ b/SourceGen/RuntimeData/Commodore/VisC64.cs
@@ -0,0 +1,256 @@
+/*
+ * 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.Generic;
+using System.Collections.ObjectModel;
+using System.Text;
+
+using PluginCommon;
+
+namespace RuntimeData.Commodore {
+ public class VisC64 : MarshalByRefObject, IPlugin, IPlugin_Visualizer {
+ // IPlugin
+ public string Identifier {
+ get { return "C64 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_HI_RES_SPRITE = "c64-hi-res-sprite";
+ private const string VIS_GEN_MULTI_COLOR_SPRITE = "c64-multi-color-sprite";
+
+ private const string P_OFFSET = "offset";
+ private const string P_DOUBLE_WIDE = "doubleWide";
+ private const string P_DOUBLE_HIGH = "doubleHigh";
+ private const string P_COLOR = "color"; // sprite color (hi-res or multi-color)
+ private const string P_COLOR_01 = "color01"; // multi-color 1
+ private const string P_COLOR_11 = "color11"; // multi-color 2
+
+ private const int MAX_COLOR = 15;
+ private const int BYTE_WIDTH = 3;
+ private const int HEIGHT = 21;
+ private const int SPRITE_SIZE = BYTE_WIDTH * HEIGHT; // 63
+
+ // Visualization descriptors.
+ private VisDescr[] mDescriptors = new VisDescr[] {
+ new VisDescr(VIS_GEN_HI_RES_SPRITE, "C64 Hi-Res Sprite", VisDescr.VisType.Bitmap,
+ new VisParamDescr[] {
+ new VisParamDescr("File offset (hex)",
+ P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
+ new VisParamDescr("Sprite color",
+ P_COLOR, typeof(int), 0, 15, 0, 0),
+ new VisParamDescr("Double wide",
+ P_DOUBLE_WIDE, typeof(bool), 0, 0, 0, false),
+ new VisParamDescr("Double high",
+ P_DOUBLE_HIGH, typeof(bool), 0, 0, 0, false),
+ }),
+ new VisDescr(VIS_GEN_MULTI_COLOR_SPRITE, "C64 Multi-Color Sprite", VisDescr.VisType.Bitmap,
+ new VisParamDescr[] {
+ new VisParamDescr("File offset (hex)",
+ P_OFFSET, typeof(int), 0, 0x00ffffff, VisParamDescr.SpecialMode.Offset, 0),
+ new VisParamDescr("Sprite color",
+ P_COLOR, typeof(int), 0, 15, 0, 1),
+ new VisParamDescr("Multi-color 1",
+ P_COLOR_01, typeof(int), 0, 15, 0, 0),
+ new VisParamDescr("Multi-color 2",
+ P_COLOR_11, typeof(int), 0, 15, 0, 2),
+ new VisParamDescr("Double wide",
+ P_DOUBLE_WIDE, typeof(bool), 0, 0, 0, false),
+ new VisParamDescr("Double high",
+ P_DOUBLE_HIGH, typeof(bool), 0, 0, 0, false),
+ }),
+ };
+
+
+ // 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 parms) {
+ switch (descr.Ident) {
+ case VIS_GEN_HI_RES_SPRITE:
+ return GenerateHiResSprite(parms);
+ case VIS_GEN_MULTI_COLOR_SPRITE:
+ return GenerateMultiColorSprite(parms);
+ default:
+ mAppRef.ReportError("Unknown ident " + descr.Ident);
+ return null;
+ }
+ }
+
+ private IVisualization2d GenerateHiResSprite(ReadOnlyDictionary parms) {
+ int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
+ byte color = (byte)Util.GetFromObjDict(parms, P_COLOR, 0);
+ bool isDoubleWide = Util.GetFromObjDict(parms, P_DOUBLE_WIDE, false);
+ bool isDoubleHigh = Util.GetFromObjDict(parms, P_DOUBLE_HIGH, false);
+
+ if (offset < 0 || offset >= mFileData.Length || color < 0 || color > MAX_COLOR) {
+ // the UI should flag these based on range (and ideally wouldn't have called us)
+ mAppRef.ReportError("Invalid parameter");
+ return null;
+ }
+
+ int lastOffset = offset + SPRITE_SIZE - 1;
+ if (lastOffset >= mFileData.Length) {
+ mAppRef.ReportError("Sprite runs off end of file (last offset +" +
+ lastOffset.ToString("x6") + ")");
+ return null;
+ }
+
+ int xwide = isDoubleWide ? 2 : 1;
+ int xhigh = isDoubleHigh ? 2 : 1;
+
+ VisBitmap8 vb = new VisBitmap8(BYTE_WIDTH * 8 * xwide, HEIGHT * xhigh);
+ SetPalette(vb);
+
+ // Clear all pixels to transparent, then just draw the non-transparent ones.
+ vb.SetAllPixelIndices(TRANSPARENT);
+
+ for (int row = 0; row < HEIGHT; row++) {
+ for (int col = 0; col < BYTE_WIDTH; col++) {
+ byte val = mFileData[offset + row * BYTE_WIDTH + col];
+ for (int bit = 0; bit < 8; bit++) {
+ if ((val & 0x80) != 0) {
+ int xc = (col * 8 + bit) * xwide;
+ int yc = row * xhigh;
+ vb.SetPixelIndex(xc, yc, color);
+ if (isDoubleWide || isDoubleHigh) {
+ // Draw doubled pixels. If we're only doubled in one dimension
+ // this will draw pixels twice.
+ vb.SetPixelIndex(xc + xwide - 1, yc, color);
+ vb.SetPixelIndex(xc, yc + xhigh - 1, color);
+ vb.SetPixelIndex(xc + xwide - 1, yc + xhigh - 1, color);
+ }
+ }
+ val <<= 1;
+ }
+ }
+ }
+ return vb;
+ }
+
+ private IVisualization2d GenerateMultiColorSprite(ReadOnlyDictionary parms) {
+ int offset = Util.GetFromObjDict(parms, P_OFFSET, 0);
+ byte color = (byte)Util.GetFromObjDict(parms, P_COLOR, 0);
+ byte color01 = (byte)Util.GetFromObjDict(parms, P_COLOR_01, 0);
+ byte color11 = (byte)Util.GetFromObjDict(parms, P_COLOR_11, 0);
+ bool isDoubleWide = Util.GetFromObjDict(parms, P_DOUBLE_WIDE, false);
+ bool isDoubleHigh = Util.GetFromObjDict(parms, P_DOUBLE_HIGH, false);
+
+ if (offset < 0 || offset >= mFileData.Length ||
+ color < 0 || color > MAX_COLOR ||
+ color01 < 0 || color01 > MAX_COLOR ||
+ color11 < 0 || color11 > MAX_COLOR) {
+ // the UI should flag these based on range (and ideally wouldn't have called us)
+ mAppRef.ReportError("Invalid parameter");
+ return null;
+ }
+
+ int lastOffset = offset + SPRITE_SIZE - 1;
+ if (lastOffset >= mFileData.Length) {
+ mAppRef.ReportError("Sprite runs off end of file (last offset +" +
+ lastOffset.ToString("x6") + ")");
+ return null;
+ }
+
+ int xwide = isDoubleWide ? 2 : 1;
+ int xhigh = isDoubleHigh ? 2 : 1;
+
+ VisBitmap8 vb = new VisBitmap8(BYTE_WIDTH * 8 * xwide, HEIGHT * xhigh);
+ SetPalette(vb);
+ vb.SetAllPixelIndices(TRANSPARENT);
+
+ for (int row = 0; row < HEIGHT; row++) {
+ for (int col = 0; col < BYTE_WIDTH; col++) {
+ byte val = mFileData[offset + row * BYTE_WIDTH + col];
+ for (int bit = 0; bit < 8; bit += 2) {
+ byte pixColor = 0;
+ switch (val & 0xc0) {
+ case 0x00: pixColor = TRANSPARENT; break;
+ case 0x80: pixColor = color; break;
+ case 0x40: pixColor = color01; break;
+ case 0xc0: pixColor = color11; break;
+ }
+ int xc = (col * 8 + bit) * xwide;
+ int yc = row * xhigh;
+ vb.SetPixelIndex(xc, yc, pixColor);
+ vb.SetPixelIndex(xc+1, yc, pixColor);
+ if (isDoubleWide || isDoubleHigh) {
+ // Draw doubled pixels. If we're only doubled in one dimension
+ // this will draw pixels twice.
+ vb.SetPixelIndex(xc + xwide*2 - 2, yc, pixColor);
+ vb.SetPixelIndex(xc + xwide*2 - 1, yc, pixColor);
+ vb.SetPixelIndex(xc, yc + xhigh - 1, pixColor);
+ vb.SetPixelIndex(xc + 1, yc + xhigh - 1, pixColor);
+ vb.SetPixelIndex(xc + xwide*2 - 2, yc + xhigh - 1, pixColor);
+ vb.SetPixelIndex(xc + xwide*2 - 1, yc + xhigh - 1, pixColor);
+ }
+ val <<= 2;
+ }
+ }
+ }
+ return vb;
+ }
+
+ private const byte TRANSPARENT = 16;
+
+ // C64 colors, from http://unusedino.de/ec64/technical/misc/vic656x/colors/
+ // (the ones on https://www.c64-wiki.com/wiki/Color looked wrong)
+ private void SetPalette(VisBitmap8 vb) {
+ vb.AddColor(0xff, 0x00, 0x00, 0x00); // 0=black
+ vb.AddColor(0xff, 0xff, 0xff, 0xff); // 1=white
+ vb.AddColor(0xff, 0x68, 0x37, 0x2b); // 2=red
+ vb.AddColor(0xff, 0x70, 0xa4, 0xb2); // 3=cyan
+ vb.AddColor(0xff, 0x6f, 0x3d, 0x86); // 4=purple
+ vb.AddColor(0xff, 0x58, 0x8d, 0x43); // 5=green
+ vb.AddColor(0xff, 0x35, 0x28, 0x79); // 6=blue
+ vb.AddColor(0xff, 0xb8, 0xc7, 0x6f); // 7=yellow
+ vb.AddColor(0xff, 0x6f, 0x4f, 0x25); // 8=orange
+ vb.AddColor(0xff, 0x43, 0x39, 0x00); // 9-brown
+ vb.AddColor(0xff, 0x9a, 0x67, 0x59); // 10=light red
+ vb.AddColor(0xff, 0x44, 0x44, 0x44); // 11=dark grey
+ vb.AddColor(0xff, 0x6c, 0x6c, 0x6c); // 12=grey
+ vb.AddColor(0xff, 0x9a, 0xd2, 0x84); // 13=light green
+ vb.AddColor(0xff, 0x6c, 0x5e, 0xb5); // 14=light blue
+ vb.AddColor(0xff, 0x95, 0x95, 0x95); // 15=light grey
+ vb.AddColor(0, 0, 0, 0); // 16=transparent
+ }
+ }
+}
diff --git a/SourceGen/RuntimeData/Help/visualization.html b/SourceGen/RuntimeData/Help/visualization.html
index da59cdc..e9fcd26 100644
--- a/SourceGen/RuntimeData/Help/visualization.html
+++ b/SourceGen/RuntimeData/Help/visualization.html
@@ -38,6 +38,10 @@ may be included in HTML exports.
they will become "hidden" if the offset isn't at the start of a line,
e.g. it's in the middle of a multi-byte instruction or data item. The
editors will try to prevent you from doing this.
+Bitmaps will always be scaled up as much as possible to make them
+easy to see. This means that small shapes and large shapes may appears
+to be the same size. Bear in mind that this is a disassembler, not a
+graphics conversion tool.
@@ -156,6 +160,35 @@ generator works for data stored in a straightforward fashion.
to repeat each row N times to adjust the proportions.
+Commodore 64 - Commodore/VisC64
+
+The Commodore 64 has a 64-bit sprite format defined by the hardware.
+It supports a single-color "high resolution" 24x21 format, and a 12x21
+"multi-color" format. Sprites can be doubled in width or height.
+Colors come from a hardware-defined palette of 16:
+
+ - black
+ - white
+ - red
+ - cyan
+ - purple
+ - green
+ - blue
+ - yellow
+ - orange
+ - brown
+ - light red
+ - dark grey
+ - grey
+ - light green
+ - light blue
+ - light grey
+
+
+Bear in mind that the editor scales images to their maximum size, so
+a sprite that is doubled in both width and height will look exactly like
+a sprite that is not doubled at all.
+