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.

Visualizations and Visualization Sets

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

+
    +
  1.  black 
  2. +
  3.  white 
  4. +
  5.  red 
  6. +
  7.  cyan 
  8. +
  9.  purple 
  10. +
  11.  green 
  12. +
  13.  blue 
  14. +
  15.  yellow 
  16. +
  17.  orange 
  18. +
  19.  brown 
  20. +
  21.  light red 
  22. +
  23.  dark grey 
  24. +
  25.  grey 
  26. +
  27.  light green 
  28. +
  29.  light blue 
  30. +
  31.  light grey 
  32. +
+ +

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.

+