diff --git a/SourceGen/AppSettings.cs b/SourceGen/AppSettings.cs
index d747182..62abcd5 100644
--- a/SourceGen/AppSettings.cs
+++ b/SourceGen/AppSettings.cs
@@ -131,6 +131,7 @@ namespace SourceGen {
public const string EXPORT_TEXT_MODE = "export-text-mode";
public const string EXPORT_SELECTION_ONLY = "export-selection-only";
public const string EXPORT_LONG_LABEL_NEW_LINE = "export-long-label-new-line";
+ public const string EXPORT_GENERATE_IMAGE_FILES = "export-generate-image-files";
// Internal debugging features.
public const string DEBUG_MENU_ENABLED = "debug-menu-enabled";
diff --git a/SourceGen/Exporter.cs b/SourceGen/Exporter.cs
index 0e2fa1c..b12ff19 100644
--- a/SourceGen/Exporter.cs
+++ b/SourceGen/Exporter.cs
@@ -19,6 +19,7 @@ using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Media;
+using System.Windows.Media.Imaging;
using Asm65;
using CommonUtil;
@@ -43,6 +44,11 @@ namespace SourceGen {
///
public bool IncludeNotes { get; set; }
+ ///
+ /// Should image files be generated?
+ ///
+ public bool GenerateImageFiles { get; set; }
+
///
/// If set, labels that are wider than the label column should go on their own line.
///
@@ -83,6 +89,11 @@ namespace SourceGen {
///
private Formatter mFormatter;
+ ///
+ /// Directory path for image files.
+ ///
+ private string mImageDirPath;
+
///
/// The cumulative width of the columns determines the start point.
///
@@ -315,7 +326,7 @@ namespace SourceGen {
TextUtil.AppendPaddedString(sb, parts.Comment, mColStart[(int)Col.Label]);
break;
case LineListGen.Line.Type.VisualizationSet:
- break; // TODO(xyzzy)
+ return; // show nothing
case LineListGen.Line.Type.Blank:
break;
default:
@@ -371,8 +382,8 @@ namespace SourceGen {
///
/// Generates HTML output to the specified path.
///
- /// Full pathname of output file. This defines the root
- /// directory if there are additional files.
+ /// Full pathname of output file (including ".html"). This
+ /// defines the root directory if there are additional files.
/// If set, existing CSS file will be replaced.
public void OutputToHtml(string pathName, bool overwriteCss) {
string exportTemplate = RuntimeDataAccess.GetPathName(HTML_EXPORT_TEMPLATE);
@@ -388,6 +399,41 @@ namespace SourceGen {
return;
}
+ // We should only need the _IMG directory if there are visualizations.
+ if (GenerateImageFiles && mProject.VisualizationSets.Count != 0) {
+ string imageDirName = Path.GetFileNameWithoutExtension(pathName) + "_IMG";
+ string imageDirPath = Path.Combine(Path.GetDirectoryName(pathName), imageDirName);
+ bool exists = false;
+ try {
+ FileAttributes attr = File.GetAttributes(imageDirPath);
+ if ((attr & FileAttributes.Directory) != FileAttributes.Directory) {
+ string msg = string.Format(Res.Strings.ERR_FILE_EXISTS_NOT_DIR_FMT,
+ imageDirPath);
+ MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+ exists = true;
+ } catch (FileNotFoundException) {
+ } catch (DirectoryNotFoundException) {
+ }
+
+ if (!exists) {
+ try {
+ Directory.CreateDirectory(imageDirPath);
+ } catch (Exception ex) {
+ string msg = string.Format(Res.Strings.ERR_DIR_CREATE_FAILED_FMT,
+ imageDirPath, ex.Message);
+ MessageBox.Show(msg, Res.Strings.ERR_FILE_GENERIC_CAPTION,
+ MessageBoxButton.OK, MessageBoxImage.Error);
+ return;
+ }
+ }
+
+ // All good.
+ mImageDirPath = imageDirPath;
+ }
+
// Perform some quick substitutions. This could be done more efficiently,
// but we're only doing this on the template file, which should be small.
tmplStr = tmplStr.Replace("$ProjectName$", mProject.DataFileName);
@@ -620,7 +666,16 @@ namespace SourceGen {
parts.Comment.Length);
break;
case LineListGen.Line.Type.VisualizationSet:
- break; // TODO(xyzzy)
+ if (!GenerateImageFiles) {
+ // generate nothing at all
+ return;
+ }
+ while (colPos < mColStart[(int)Col.Label]) {
+ sb.Append(' ');
+ colPos++;
+ }
+ OutputVisualizationSet(line.FileOffset, sb);
+ break;
case LineListGen.Line.Type.Blank:
break;
default:
@@ -631,6 +686,73 @@ namespace SourceGen {
tw.WriteLine(sb);
}
+ ///
+ /// Generate one or more GIF image files, and generate references to them.
+ ///
+ /// Visualization set file offset.
+ /// String builder for the HTML output.
+ private void OutputVisualizationSet(int offset, StringBuilder sb) {
+ if (!mProject.VisualizationSets.TryGetValue(offset,
+ out VisualizationSet visSet)) {
+ sb.Append("Internal error - visualization set missing");
+ Debug.Assert(false);
+ return;
+ }
+ if (visSet.Count == 0) {
+ sb.Append("Internal error - empty visualization set");
+ Debug.Assert(false);
+ return;
+ }
+
+ string imageDirFileName = Path.GetFileName(mImageDirPath);
+
+ for (int index = 0; index < visSet.Count; index++) {
+ Visualization vis = visSet[index];
+
+ // Encode a GIF the same size as the original bitmap.
+ GifBitmapEncoder encoder = new GifBitmapEncoder();
+ encoder.Frames.Add(BitmapFrame.Create(vis.CachedImage));
+
+ // Create new or replace existing image file.
+ string fileName = "vis" + offset.ToString("x6") + "_" + index.ToString("d2") +
+ ".gif";
+ using (FileStream stream = new FileStream(Path.Combine(mImageDirPath, fileName),
+ FileMode.Create)) {
+ encoder.Save(stream);
+ }
+
+ // Create as thumbnail, preserving proportions. I'm assuming most images
+ // will be small enough that generating a separate thumbnail would be
+ // counter-productive. This seems to look best if the height is consistent
+ // across all visualization lines, but that can create some monsters (e.g.
+ // a bitmap that's 1 pixel high and 40 wide).
+ int dimMult = 64;
+ //double maxDim = Math.Max(vis.CachedImage.Width, vis.CachedImage.Height);
+ double maxDim = vis.CachedImage.Height;
+ if (vis.CachedImage.Width > vis.CachedImage.Height * 2) {
+ // Too proportionally wide, so use the width as the limit. Allow it to
+ // up to 2x the max width (which can't cause the thumb height to exceed
+ // the height limit).
+ maxDim = vis.CachedImage.Width;
+ dimMult *= 2;
+ }
+ int thumbWidth = (int)Math.Round(dimMult * (vis.CachedImage.Width / maxDim));
+ int thumbHeight = (int)Math.Round(dimMult * (vis.CachedImage.Height / maxDim));
+ Debug.WriteLine(vis.CachedImage.Width + "x" + vis.CachedImage.Height + " --> " +
+ thumbWidth + "x" + thumbHeight + " (" + maxDim + ")");
+
+ if (index != 0) {
+ sb.Append(" ");
+ }
+
+ sb.Append("");
+ }
+ }
+
///
/// Appends a string to the string buffer. If the number of characters in the buffer
/// is less than the desired start position, spaces will be added. At least one space
diff --git a/SourceGen/LineListGen.cs b/SourceGen/LineListGen.cs
index 0bb9088..2138fe2 100644
--- a/SourceGen/LineListGen.cs
+++ b/SourceGen/LineListGen.cs
@@ -957,6 +957,12 @@ namespace SourceGen {
mProject.GetAnattrib(offset - 1).IsData) {
// Transition from data to code. (Don't add blank line for inline data.)
lines.Add(GenerateBlankLine(offset));
+ } else if (mProject.VisualizationSets.ContainsKey(offset) && !addBlank &&
+ offset != 0) {
+ // Blank line before visualization set helps keep image visually grouped
+ // with its data. (Slightly weird things happen with .ORG at the start of
+ // the file; don't try to add a blank there.)
+ lines.Add(GenerateBlankLine(offset));
} else if (addBlank) {
// Previous instruction wanted to be followed by a blank line.
lines.Add(GenerateBlankLine(offset));
diff --git a/SourceGen/MainController.cs b/SourceGen/MainController.cs
index dac50bb..fbc64c6 100644
--- a/SourceGen/MainController.cs
+++ b/SourceGen/MainController.cs
@@ -2205,6 +2205,7 @@ namespace SourceGen {
Exporter eport = new Exporter(mProject, CodeLineList, mOutputFormatter,
dlg.ColFlags, rightWidths);
eport.IncludeNotes = dlg.IncludeNotes;
+ eport.GenerateImageFiles = dlg.GenerateImageFiles;
eport.LongLabelNewLine = dlg.LongLabelNewLine;
if (dlg.SelectionOnly) {
DisplayListSelection selection = mMainWin.CodeDisplayList.SelectedIndices;
diff --git a/SourceGen/Res/Strings.xaml b/SourceGen/Res/Strings.xaml
index 886ca7a..df8de73 100644
--- a/SourceGen/Res/Strings.xaml
+++ b/SourceGen/Res/Strings.xaml
@@ -56,6 +56,7 @@ limitations under the License.
Type hint not recognized
Invalid visualization item: {0}
Invalid visualization set at +{0:x6}
+ Unable to create directory {0}: {1}
Removed duplicate label '{0}' (offset +{1:x6})
Failed copying {0} to {1}: {2}.
The file {0} exists, but is not a directory.
diff --git a/SourceGen/Res/Strings.xaml.cs b/SourceGen/Res/Strings.xaml.cs
index acaa626..38dd8a4 100644
--- a/SourceGen/Res/Strings.xaml.cs
+++ b/SourceGen/Res/Strings.xaml.cs
@@ -93,6 +93,8 @@ namespace SourceGen.Res {
(string)Application.Current.FindResource("str_ErrBadVisualizationFmt");
public static string ERR_BAD_VISUALIZATION_SET_FMT =
(string)Application.Current.FindResource("str_ErrBadVisualizationSetFmt");
+ public static string ERR_DIR_CREATE_FAILED_FMT =
+ (string)Application.Current.FindResource("str_ErrDirCreateFailedFmt");
public static string ERR_DUPLICATE_LABEL_FMT =
(string)Application.Current.FindResource("str_ErrDuplicateLabelFmt");
public static string ERR_FILE_COPY_FAILED_FMT =
diff --git a/SourceGen/RuntimeData/Help/codegen.html b/SourceGen/RuntimeData/Help/codegen.html
index 6f3949e..7b75b53 100644
--- a/SourceGen/RuntimeData/Help/codegen.html
+++ b/SourceGen/RuntimeData/Help/codegen.html
@@ -323,7 +323,7 @@ assembler.
The text output is similar to what you'd get by copying lines to the
clipboard and pasting them into a text file, except that you have greater
control over which columns are included. The HTML version is augmented
-with links.
+with links and (optionally) images.
Use File > Export to open the export dialog. You have several
options:
@@ -346,6 +346,8 @@ options:
plain text and Comma-Separated Value format. The latter is useful
for importing source code into another application, such as a
spreadsheet.
+ Generate image files. When exporting to HTML, selecting this
+ will cause GIF images to be generated for visualizations.
Overwrite CSS file. Some aspects of the HTML output's format
are defined by a file called "SGStyle.css", which may be shared between
multiple HTML files and customized. The file is copied out
diff --git a/SourceGen/RuntimeData/SGStyle.css b/SourceGen/RuntimeData/SGStyle.css
index e75b426..3be2f60 100644
--- a/SourceGen/RuntimeData/SGStyle.css
+++ b/SourceGen/RuntimeData/SGStyle.css
@@ -23,4 +23,20 @@ a[href^="#"]:link, a[href^="#"]:visited {
a[href^="#"]:hover {
color: blue;
text-decoration: underline;
-}
\ No newline at end of file
+}
+
+/*
+ * Put a border around visualizations. Disable smoothing so they don't
+ * blur out.
+ *
+ * Pixelated rendering works in Chrome, Safari, and IE11, but doesn't in
+ * current versions of Edge and Firefox. We could do it with Javascript
+ * Canvas (e.g. https://stackoverflow.com/a/19129822/294248), or by
+ * generating scaled thumbnail files directly.
+ */
+.vis {
+ border: 1px solid gray;
+
+ -ms-interpolation-mode: nearest-neighbor;
+ image-rendering: pixelated;
+}
diff --git a/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65 b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65
index a064771..2db7f7d 100644
--- a/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65
+++ b/SourceGen/SGTestData/Visualization/apple2-bitmap-test#061000.dis65
@@ -19,7 +19,10 @@
"Comments":{
},
"LongComments":{
-},
+"222":{
+"Text":"\r\nPurple/green diagonal\r\n\r\n","BoxMode":false,"MaxWidth":80,"BackgroundColor":0},
+"0":{
+"Text":"Some images:","BoxMode":false,"MaxWidth":80,"BackgroundColor":0}},
"Notes":{
"222":{
"Text":"Bitmaps here","BoxMode":false,"MaxWidth":80,"BackgroundColor":-256}},
@@ -43,6 +46,19 @@
"LvTables":{
},
"VisualizationSets":{
+"0":{
+"Items":[{
+"Tag":"multi1","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
+"offset":222,"byteWidth":1,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}},
+{
+"Tag":"multi2","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
+"offset":230,"byteWidth":1,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}},
+{
+"Tag":"multi3","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
+"offset":238,"byteWidth":12,"height":1,"colStride":0,"rowStride":0,"isColor":false,"isFirstOdd":false}},
+{
+"Tag":"multi4","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
+"offset":270,"byteWidth":3,"height":8,"colStride":0,"rowStride":0,"isColor":true,"isFirstOdd":false}}]},
"222":{
"Items":[{
"Tag":"vis0000de","VisGenIdent":"apple2-hi-res-bitmap","VisGenParams":{
diff --git a/SourceGen/Sandbox/PluginDllCache.cs b/SourceGen/Sandbox/PluginDllCache.cs
index adf5f8c..e36fd40 100644
--- a/SourceGen/Sandbox/PluginDllCache.cs
+++ b/SourceGen/Sandbox/PluginDllCache.cs
@@ -79,6 +79,7 @@ namespace SourceGen.Sandbox {
/// Prepares the plugin directory. Creates it and copies PluginCommon.dll in.
/// Throws an exception if something fails.
///
+ /// Various failures from file/dir operations.
public static void PreparePluginDir() {
string dstDir = GetPluginDirPath();
if (File.Exists(dstDir) && !Directory.Exists(dstDir)) {
diff --git a/SourceGen/Visualization.cs b/SourceGen/Visualization.cs
index 64e0fc6..3ece55d 100644
--- a/SourceGen/Visualization.cs
+++ b/SourceGen/Visualization.cs
@@ -47,7 +47,8 @@ namespace SourceGen {
public ReadOnlyDictionary VisGenParams { get; private set; }
///
- /// Cached reference to 2D image, useful for thumbnails. Not serialized.
+ /// Cached reference to 2D image, useful for thumbnails. Not serialized. This always
+ /// has an image reference; in times of trouble it will point at BROKEN_IMAGE.
///
///
/// Because the underlying data never changes, we only need to regenerate the
diff --git a/SourceGen/WpfGui/Export.xaml b/SourceGen/WpfGui/Export.xaml
index de21f46..6b246c5 100644
--- a/SourceGen/WpfGui/Export.xaml
+++ b/SourceGen/WpfGui/Export.xaml
@@ -102,6 +102,7 @@ limitations under the License.
+
diff --git a/SourceGen/WpfGui/Export.xaml.cs b/SourceGen/WpfGui/Export.xaml.cs
index 96694dd..b19eabe 100644
--- a/SourceGen/WpfGui/Export.xaml.cs
+++ b/SourceGen/WpfGui/Export.xaml.cs
@@ -165,6 +165,12 @@ namespace SourceGen.WpfGui {
set { mOverwriteCss = value; OnPropertyChanged(); }
}
+ private bool mGenerateImageFiles;
+ public bool GenerateImageFiles {
+ get { return mGenerateImageFiles; }
+ set { mGenerateImageFiles = value; OnPropertyChanged(); }
+ }
+
private enum TextMode {
Unknown = 0,
PlainText,
@@ -210,6 +216,8 @@ namespace SourceGen.WpfGui {
SelectionOnly = AppSettings.Global.GetBool(AppSettings.EXPORT_SELECTION_ONLY, false);
LongLabelNewLine =
AppSettings.Global.GetBool(AppSettings.EXPORT_LONG_LABEL_NEW_LINE, false);
+ GenerateImageFiles =
+ AppSettings.Global.GetBool(AppSettings.EXPORT_GENERATE_IMAGE_FILES, true);
int[] colWidths = new int[] { 9, 8, 11, 72 }; // 100-col output
string colStr = AppSettings.Global.GetString(AppSettings.EXPORT_COL_WIDTHS, null);
@@ -248,6 +256,7 @@ namespace SourceGen.WpfGui {
AppSettings.Global.SetBool(AppSettings.EXPORT_SHOW_ATTR, ShowAttr);
AppSettings.Global.SetBool(AppSettings.EXPORT_SELECTION_ONLY, SelectionOnly);
AppSettings.Global.SetBool(AppSettings.EXPORT_LONG_LABEL_NEW_LINE, LongLabelNewLine);
+ AppSettings.Global.SetBool(AppSettings.EXPORT_GENERATE_IMAGE_FILES,GenerateImageFiles);
int[] colWidths = new int[] {
AsmLabelColWidth, AsmOpcodeColWidth, AsmOperandColWidth, AsmCommentColWidth
};