├── LICENSE ├── Properties └── AssemblyInfo.cs ├── README.md ├── Source ├── Format.cs ├── Forms │ ├── ColorPicker.cs │ ├── ColorPicker.resx │ ├── MainForm.Designer.cs │ ├── MainForm.cs │ └── MainForm.resx ├── GFX │ ├── Collage.cs │ ├── ColorList.cs │ ├── ColorPattern.cs │ ├── ColorTable.cs │ └── Tile.cs ├── PaletteUI │ ├── ColorBox.cs │ ├── DigitImages.cs │ ├── IPalette.cs │ └── PalettePanel.cs ├── Program.cs ├── Selection │ ├── Drag.cs │ ├── Edge.cs │ ├── Selection.cs │ └── Transfer.cs ├── Suffix.cs ├── Tiles │ ├── MDTile.cs │ ├── NESTile.cs │ └── SNESTile.cs ├── ToolBox │ ├── ITab.cs │ ├── ITabCollection.cs │ ├── InputControlsTab.cs │ ├── PaletteTab.cs │ ├── SpriteControlsTab.cs │ ├── SpriteControlsTab.resx │ ├── ToolBox.cs │ └── ToolBoxButton.cs ├── Utils.cs └── Windows │ ├── InputWindow.Designer.cs │ ├── InputWindow.cs │ ├── SpriteWindow.Designer.cs │ ├── SpriteWindow.cs │ ├── TileWindow.Designer.cs │ └── TileWindow.cs ├── SpriteWave.csproj ├── SpriteWave.sln ├── Tests ├── 0_CompileTest.bat ├── 1_RunTest.bat ├── SuffixChars.cs └── Tests.cs ├── Tools ├── 0_CompileGui.bat ├── OrderAndDepthGUI.cs ├── SuffixTool.cs ├── colour_gen.html ├── palette_tab.html └── palette_tab_v2.html ├── app.config └── images ├── screenshot4.png ├── screenshot5.png └── screenshot6.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jack Bendtsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region Using directives 2 | 3 | using System; 4 | using System.Reflection; 5 | using System.Runtime.InteropServices; 6 | 7 | #endregion 8 | 9 | // General Information about an assembly is controlled through the following 10 | // set of attributes. Change these attribute values to modify the information 11 | // associated with an assembly. 12 | [assembly: AssemblyTitle("SpriteWave")] 13 | [assembly: AssemblyDescription("")] 14 | [assembly: AssemblyConfiguration("")] 15 | [assembly: AssemblyCompany("")] 16 | [assembly: AssemblyProduct("SpriteWave")] 17 | [assembly: AssemblyCopyright("Copyright 2019")] 18 | [assembly: AssemblyTrademark("")] 19 | [assembly: AssemblyCulture("")] 20 | 21 | // This sets the default COM visibility of types in the assembly to invisible. 22 | // If you need to expose a type to COM, use [ComVisible(true)] on that type. 23 | [assembly: ComVisible(false)] 24 | 25 | // The assembly version has following format : 26 | // 27 | // Major.Minor.Build.Revision 28 | // 29 | // You can specify all the values or you can use the default the Revision and 30 | // Build Numbers by using the '*' as shown below: 31 | [assembly: AssemblyVersion("1.0.*")] 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpriteWave 2 | 3 | SpriteWave is an interactive tool for arranging images from 2D games. 4 | 5 | ---- 6 | 7 | 8 | 9 | ### About 10 | 11 | This tool lets you extract tiles from inside of a NES, SNES or Genesis ROM and paste them into a separate window, where you can rearrange and recolour them to form an image, which can then be exported to PNG. 12 | 13 | The interface is mostly driven by the mouse, with drag-and-drop being the main way to move tiles around. 14 | 15 | ### Features 16 | * Tile rotation and mirroring 17 | * Dynamic image resizing 18 | * Palette and RGBA pickers 19 | * Colour transparency 20 | * Offset input data 21 | * Image exporting: 22 | * Flexible naming scheme 23 | * Image scaling 24 | -------------------------------------------------------------------------------- /Source/Format.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public enum FormatKind 6 | { 7 | NES, SNES, MD 8 | } 9 | 10 | public class FileFormat 11 | { 12 | private Type _type; 13 | private ColorTable _table; 14 | private string _name; 15 | private string[] _exts; 16 | 17 | public string Name { get { return _name; } } 18 | 19 | public FileFormat(string n, Type t, string[] e, ColorTable c) 20 | { 21 | _type = t; 22 | _table = c; 23 | _name = n; 24 | _exts = e; 25 | } 26 | 27 | public ColorTable ColorTable { get { return _table; } } 28 | 29 | public string Filter 30 | { 31 | get { 32 | string flt = ""; 33 | foreach (string e in _exts) 34 | flt += "*." + e + ";"; 35 | 36 | flt = flt.Remove(flt.Length-1); 37 | 38 | return _name + " file (" + flt + ")|" + flt + "|"; 39 | } 40 | } 41 | 42 | public Tile NewTile() 43 | { 44 | return Activator.CreateInstance(_type) as Tile; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Source/Forms/ColorPicker.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 123 | iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 124 | IwAALiMBeKU/dgAAAAd0SU1FB+MHEQQyAZv/410AAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 125 | TVBXgQ4XAAAAiElEQVQoU52S3QnAIAyE7Rhu4QBO43QOkAl8yjzie5sLUWyhlLTwQbzcJf0LzLwopZAw 126 | hNNATbtnGjOatdbzeUGzYN4Do7Wmht57FA4jQkMPHg1IQXMyTHKGtoCGnm0inW7mCENK6YaFdBO8EFCv 127 | 6S8B3QLvr4D7lnwP7X6tFvB9uImIH78Ghwtap09oQXDXDgAAAABJRU5ErkJggg== 128 | 129 | 130 | 131 | 132 | iVBORw0KGgoAAAANSUhEUgAAACAAAAAMCAYAAAADFL+5AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 133 | IwAALiMBeKU/dgAAAAd0SU1FB+MHEQQyEh9BooMAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 134 | TVBXgQ4XAAAAO0lEQVQ4T2MoKCj4P5AY7ICBAoPPAUA2iE9zDLUOuwMcHBxoikcdMPgdQA8MtQ7TAfQG 135 | g8cBA4cL/gMAeEmWmnTvZaYAAAAASUVORK5CYII= 136 | 137 | 138 | 139 | 140 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 141 | IwAALiMBeKU/dgAAAAd0SU1FB+MHEgYkDgbpMJsAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 142 | TVBXgQ4XAAABU0lEQVRIS72WS07DQBBEs4e9b8MFOBVcBQlxIMR1zLxRD6ruqfysDIsnuas/NY6dTk77 143 | vv8rVlyJFVdixcq2bTfheitWVGTga+OzsRfQyN1kakUYA2JYNTnHMO5xnQmTANEE76P5Dt7GdZ0LkwCt 144 | +JzZR+Ol8RRwjVbrOnUuTEIrhPox/jSeGyNfIffd0B6Y5qeAgkCbMNOcQ+uVnlePFERBvbtLdzbQeqXn 145 | 1SMFUaCvPs9n6IdRjxREgZ6QlyI1H0E9UhAFXwJvYmo+gnqkwBUfgEPqoZNHCkg+AB6DPpbkkQKSD0AX 146 | AS9g8kgByUBPqGiNg6+Q1vfdqh4pIBlok6I1DpbEVK8eKYAo0iZgbV1bbdVsujuYBCiNyq3Lm8VvZ1tR 147 | Gv9+au6gm4GbbUVp5rru1kv0jxHcXLCiMgbEsHV/MZQx7Bqut2LFlVhxJVZcx376BVuFcRKUZjh3AAAA 148 | AElFTkSuQmCC 149 | 150 | 151 | 152 | 153 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 154 | YQUAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfjBxMAEQ4Y6en6AAAAGXRFWHRDb21tZW50AENy 155 | ZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAVlJREFUSEu9kgFuw0AIBPMLf7c/dxkEZDnjpol0QRodLAvEUh7n 156 | eX6VUdzJKO5kFHfSiuM4XkJM+iv+PDhpsB5ba7ibTW01nDBpDIHFj2itR45BZildT62aQEObqhnM1jF6 157 | WtPnRZRZys8PTn2LPOp96phzsl/+TICGNlUz7vpK9ScNahDWpmpGy6Vf6NeIXl8NNRimfx+E6BeEvT4v 158 | OgdrXyVhaguD0izWP8mEHyCP11Y/b9wdnMgletQJzftap1dvjAcnzfCFoSnrUfV7rvsqgcmQmkG++pPy 159 | 5FelTq0z6wIfnDTDl2sv+s1DZM5Lrf5WhNHSq2aQN3/0m+frB7O2O5c/DLQihiy9akYtWvqrR2uzPL3Q 160 | iskkw5Xn0Tstv279cdCKHJ40ox1Q4kD6Oeg+Xt0FrcCcg6umxMKLln5CNaUVuuAdiKkmdD9chN2M4k5G 161 | cSejuI/z8QuU5gDdO6r90AAAAABJRU5ErkJggg== 162 | 163 | 164 | 165 | 166 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 167 | YQUAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfjBxMAFhJDqSNyAAAAGXRFWHRDb21tZW50AENy 168 | ZWF0ZWQgd2l0aCBHSU1QV4EOFwAAASJJREFUSEu9klFuwzAMQ3OLXHc39yxDFEhGSVts7sfDKNKisLbH 169 | GOOrtOZOWnMnrbkTGc7z3MbjQZ//A3SWyAPDOY7jZ/6txZzvctlNVvZ40GeU8jE77AflbczoLMEHOi+W 170 | PG+Ospa8diACD9mbvJ2nBrJTi4GH7E1Ev8qJ5eF9LQYesjdxjZyR/O2PNB4nmOF57lTOx2J+ddC5FN7A 171 | O/L28SDreEywB73wnY/+wxVcf/KSQxNyMOCO2Fm9EIGHmHNR8pzLa7Tk6Hw8yN5E8pzbPLXk6KviwEP2 172 | JpLn3OapJUdfFQcesje5Ll+/Z9aff4fsTaSgOXb7Nmb0leDyzoulwIrKT9iXHH0lslxC9v4K+raUd7QH 173 | v0Fr7qQ1d9Ka+xjHL3bam6hJrB4yAAAAAElFTkSuQmCC 174 | 175 | 176 | -------------------------------------------------------------------------------- /Source/Forms/MainForm.Designer.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Created by SharpDevelop. 3 | * User: 101111482 4 | * Date: 28/04/2019 5 | * Time: 9:48 AM 6 | * 7 | * To change this template use Tools | Options | Coding | Edit Standard Headers. 8 | */ 9 | 10 | using System; 11 | using System.Drawing; 12 | using System.ComponentModel; 13 | using System.Windows.Forms; 14 | 15 | namespace SpriteWave 16 | { 17 | partial class MainForm 18 | { 19 | /// 20 | /// Designer variable used to keep track of non-visual components. 21 | /// 22 | private IContainer components = null; 23 | 24 | private MenuStrip menuStrip1; 25 | private ToolStripMenuItem fileToolStripMenuItem; 26 | private ToolStripMenuItem openBinaryToolStripMenuItem; 27 | private ToolStripMenuItem openNESFileStripMenuItem; 28 | private ToolStripMenuItem openSNESFileStripMenuItem; 29 | private ToolStripMenuItem openMDFileStripMenuItem; 30 | private ToolStripSeparator toolStripSeparator1; 31 | private ToolStripMenuItem closeToolStripMenuItem; 32 | private ToolStripMenuItem quitToolStripMenuItem; 33 | private OpenFileDialog openFileDialog1; 34 | private ToolStripMenuItem copyTileToolStripMenuItem; 35 | private System.Windows.Forms.ToolStripMenuItem editToolStripMenuItem; 36 | private System.Windows.Forms.ToolStripMenuItem colorTableToolStripMenuItem; 37 | 38 | /// 39 | /// Disposes resources used by the form. 40 | /// 41 | /// true if managed resources should be disposed; otherwise, false. 42 | protected override void Dispose(bool disposing) 43 | { 44 | if (disposing) { 45 | if (components != null) 46 | components.Dispose(); 47 | 48 | inputWnd.Dispose(); 49 | spriteWnd.Dispose(); 50 | } 51 | 52 | base.Dispose(disposing); 53 | } 54 | 55 | /// 56 | /// This method is required for Windows Forms designer support. 57 | /// Do not change the method contents inside the source code editor. The Forms designer might 58 | /// not be able to load this method if it was changed manually. 59 | private void InitializeComponent() 60 | { 61 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); 62 | this.menuStrip1 = new System.Windows.Forms.MenuStrip(); 63 | this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 64 | this.openBinaryToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 65 | this.openNESFileStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 66 | this.openSNESFileStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 67 | this.openMDFileStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 68 | this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); 69 | this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 70 | this.quitToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 71 | this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 72 | this.colorTableToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 73 | this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); 74 | this.copyTileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 75 | this.menuStrip1.SuspendLayout(); 76 | this.SuspendLayout(); 77 | // 78 | // menuStrip1 79 | // 80 | this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 81 | this.fileToolStripMenuItem, 82 | this.editToolStripMenuItem}); 83 | this.menuStrip1.Location = new System.Drawing.Point(0, 0); 84 | this.menuStrip1.Name = "menuStrip1"; 85 | this.menuStrip1.Size = new System.Drawing.Size(704, 24); 86 | // 87 | // fileToolStripMenuItem 88 | // 89 | this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 90 | this.openBinaryToolStripMenuItem, 91 | this.openNESFileStripMenuItem, 92 | this.openSNESFileStripMenuItem, 93 | this.openMDFileStripMenuItem, 94 | this.toolStripSeparator1, 95 | this.closeToolStripMenuItem, 96 | this.quitToolStripMenuItem}); 97 | this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 98 | this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); 99 | this.fileToolStripMenuItem.Text = "File"; 100 | // 101 | // openBinaryToolStripMenuItem 102 | // 103 | this.openBinaryToolStripMenuItem.Name = "openBinaryToolStripMenuItem"; 104 | this.openBinaryToolStripMenuItem.ShortcutKeyDisplayString = "Ctrl+O"; 105 | this.openBinaryToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.O))); 106 | this.openBinaryToolStripMenuItem.Size = new System.Drawing.Size(209, 22); 107 | this.openBinaryToolStripMenuItem.Text = "Open Binary"; 108 | this.openBinaryToolStripMenuItem.Click += (s, e) => openFileDialog1.ShowDialog(); 109 | // 110 | // openNESFileStripMenuItem 111 | // 112 | this.openNESFileStripMenuItem.Name = "openNESFileStripMenuItem"; 113 | this.openNESFileStripMenuItem.Size = new System.Drawing.Size(209, 22); 114 | this.openNESFileStripMenuItem.Text = "Open NES ROM"; 115 | this.openNESFileStripMenuItem.Click += (s, e) => openRom(FormatKind.NES); 116 | // 117 | // openSNESFileStripMenuItem 118 | // 119 | this.openSNESFileStripMenuItem.Name = "openSNESFileStripMenuItem"; 120 | this.openSNESFileStripMenuItem.Size = new System.Drawing.Size(209, 22); 121 | this.openSNESFileStripMenuItem.Text = "Open SNES ROM"; 122 | this.openSNESFileStripMenuItem.Click += (s, e) => openRom(FormatKind.SNES); 123 | // 124 | // openMDFileStripMenuItem 125 | // 126 | this.openMDFileStripMenuItem.Name = "openMDFileStripMenuItem"; 127 | this.openMDFileStripMenuItem.Size = new System.Drawing.Size(209, 22); 128 | this.openMDFileStripMenuItem.Text = "Open Genesis ROM"; 129 | this.openMDFileStripMenuItem.Click += (s, e) => openRom(FormatKind.MD); 130 | // 131 | // toolStripSeparator1 132 | // 133 | this.toolStripSeparator1.Name = "toolStripSeparator1"; 134 | this.toolStripSeparator1.Size = new System.Drawing.Size(206, 6); 135 | // 136 | // closeToolStripMenuItem 137 | // 138 | this.closeToolStripMenuItem.Name = "closeToolStripMenuItem"; 139 | this.closeToolStripMenuItem.ShortcutKeyDisplayString = "Ctrl+W"; 140 | this.closeToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.W))); 141 | this.closeToolStripMenuItem.Size = new System.Drawing.Size(209, 22); 142 | this.closeToolStripMenuItem.Text = "Close Workspace"; 143 | this.closeToolStripMenuItem.Click += new System.EventHandler(this.closeWorkspace); 144 | // 145 | // quitToolStripMenuItem 146 | // 147 | this.quitToolStripMenuItem.Name = "quitToolStripMenuItem"; 148 | this.quitToolStripMenuItem.Size = new System.Drawing.Size(209, 22); 149 | this.quitToolStripMenuItem.Text = "Quit"; 150 | this.quitToolStripMenuItem.Click += new System.EventHandler(this.quit); 151 | // 152 | // editToolStripMenuItem 153 | // 154 | this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 155 | this.colorTableToolStripMenuItem}); 156 | this.editToolStripMenuItem.Name = "editToolStripMenuItem"; 157 | this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 20); 158 | this.editToolStripMenuItem.Text = "Edit"; 159 | // 160 | // colorTableToolStripMenuItem 161 | // 162 | this.colorTableToolStripMenuItem.Enabled = false; 163 | this.colorTableToolStripMenuItem.Name = "colorTableToolStripMenuItem"; 164 | this.colorTableToolStripMenuItem.Size = new System.Drawing.Size(141, 22); 165 | this.colorTableToolStripMenuItem.Text = "Color Table"; 166 | this.colorTableToolStripMenuItem.Click += new System.EventHandler(this.editColorTable); 167 | // 168 | // openFileDialog1 169 | // 170 | this.openFileDialog1.Title = "Open tiles file"; 171 | this.openFileDialog1.FileOk += new System.ComponentModel.CancelEventHandler(this.openFileDialog1FileOk); 172 | // 173 | // copyTileToolStripMenuItem 174 | // 175 | this.copyTileToolStripMenuItem.Name = "copyTileToolStripMenuItem"; 176 | this.copyTileToolStripMenuItem.Size = new System.Drawing.Size(32, 19); 177 | // 178 | // MainForm 179 | // 180 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 181 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 182 | this.ClientSize = new System.Drawing.Size(704, 601); 183 | this.Controls.Add(this.menuStrip1); 184 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 185 | this.MainMenuStrip = this.menuStrip1; 186 | this.MinimumSize = new System.Drawing.Size(200, 400); 187 | this.Name = "MainForm"; 188 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 189 | this.Text = "SpriteWave"; 190 | this.menuStrip1.ResumeLayout(false); 191 | this.menuStrip1.PerformLayout(); 192 | this.ResumeLayout(false); 193 | this.PerformLayout(); 194 | 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /Source/Forms/MainForm.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 17, 17 122 | 123 | 124 | True 125 | 126 | 127 | 150, 16 128 | 129 | 130 | True 131 | 132 | 133 | 54 134 | 135 | 136 | 137 | 138 | AAABAAEAQEAQAAEABABoCgAAFgAAACgAAABAAAAAgAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 139 | AAAbHhwA4CgJAEwvIwCoC4cAix1vACs8MAB1PSMAp0ETABxMQgAAFvwAMUZ4AFdfHgAhNM0AG2dWAAih 140 | EwAAAAAAAAAAACAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAA 141 | AAAAAAAAAAIAAAAAIAAAAAAAAAIAIAAAAAAAAAACAAAAACAAIAAAAAAAAAAAAAAAAgAAAAAAACACAAAA 142 | AAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAACqWqqqqqCqAAAAAAAAIAAKAAAAAAAAAAACAAAAAA 143 | AKoKqqqqrMnKqqqgAAAgAAAA+goKAAAAAgAAAAAABaqqqqqqzMmZmZnMyqqqCqAAAACqCqqqCgqqqqqq 144 | qqqqqqrMyZmZmZmZmZmZnMqqpaqqAPqqqqr6qqqqqqqqqqzMmZmZmZmZmZnJmZmZmZzKqqqqmZnJzMyc 145 | zMyqqszJmZmZmZmZmcygAAzJmZmZmZmczKqZmZmZmZmZmZmZmZmZmZmZzMogAAAAAAAKzJnJmZmZnJmZ 146 | mZmZmZmZmZmZmZnJygAAAAAAAgAAAAAACsmZmZmZAAzMycnJnJmZmZnMoAACAAAAAAAAAAAAAAAgAKrJ 147 | mZkAAAAAAAAAoAAAACAAAAAAAAAAAAAAAAAAAAAAAKAKyQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 148 | AAAAAKAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAACICIgIAAAAAAAAAAAAiIiIiIgAAAAAAIAAgAAAiIi 149 | IiYgImYiIiIAAAAAACIiJEQiZiIgAAAAAAAAAAIiIiYmQzMzRCYiYiIgAAIiIiQzMzRCIiIAAAAAAAAA 150 | AAAiJDMzMzMzMzQ0YmIAJiJkMzMzMzM0IiIgAAAAAAAiJkMzMzMzMzMzMzMzNCJiQzMzMzMzMzMzQiIi 151 | IiACIiQzMzMzNEQzMzMzMzMzJkQzMzNEJDMzMzMzM0JmIiJkMzMzMzQAAAAAJDMzMzMzMzMzRAAgACRD 152 | MzMzMzRmQzMzMzRCAAAAAAAAAAAkQzMzM0IAAAAAAAREMzMzMzMzMzNEIAAAAgAAAAAAAAAAMzRAAAAA 153 | AAAAAAACQzMzMzMzRAACACAAAAAAIAACAABEIAAAIAAAAAAAAAAAJEMzM0AAAgAAAAAAIAAAAAAAAAIg 154 | AAAAAAAAAAAAAAAAAERAAAAAAAAAAAAAAAAAAAAAAAAAAAACACACAAAAACACAAAAIAAAAAAAAAACAAAg 155 | AAAAAAACAAAAAAAAACAAAAAAAAAAAAAAAAAgW1VVtVVQAAAAAAAAAAAAAAACAAAAAAAAIAAAIAUCtbW7 156 | u7u1u7u7AgACAAAAAAAAAAAAAAAAAAAAAAAFsrW7u7u7J3ERFytbUAAAAAC1VQAAAAAAIAAgAAAAALW7 157 | u7uydxEREREREbtbCwu7u7u7u7AAAAAAAAAABVu1u7t3EREREREREREREXETdiWwu7u7u7AAAAAAAAC7 158 | sncRERERERERERERERERERERERERERe7sLAAAAAAK7sRERERERERERRAAAAAZxERERERERERERYrtSAC 159 | AAC1thERERERc3QAIAAAAAAAERERERERERERERFlu7W7tbuxERERRAAgAAAAAAAAIAAAAgAAIGQUERER 160 | ERFru7u7u3ERdQAAAAAAAAAAAgAAAAAAAAAAAgAABxERERFru7u3ERcAAAAAAAAAAAAAAAAAAAAAAAAA 161 | AAAgBHERERESu2EREAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEREREXEREgiord3djYUAAAAAAAAAAA 162 | AAAAAAAAAAAAAEERERERFFjd3u7d3d3diIAAAAAAVQAAAAAAAAAAAAAAAGcRERF43d3u7u7u3d3YiIAA 163 | AACIigAAAAAAAAAAAAAAAAcREYjd3u7u7u7u7d3YiFAAAN3diIgKBQAAAAAAAAAAAAc23d7u7u7u7u7u 164 | 7u3d3YVY7u3d3ajdhQAAAAAAAAAABV3d7u7u3Y3u7u7u7u7d2Nju7u7t3d3ailhQAAAAAAUF3e7u7tWI 165 | AAje7u7u7u7u3e7u7u7u7d3d3ailUAAAWIje7u7oAIUAAAUF3u7u7u7u3u7u7u7u7u3d3d3agAWN3u7u 166 | 7QAAAAACAAAACO7u7u4ACO7u7u7u7u7d3d3d3d7u7u2AAAAAAAAAACAAAF3u7gAAAAju7u7u7u7u3d3d 167 | 7u7ugAACAAACAAAAAAAABYiOACAAAABd7u7u7u7u7u7u7tAAAAAAAAAAAAAAAAAAUAAAAAIAAAAAXe7u 168 | 7u7u7u7oAAAAAAAAAAAAAAAAACAAAAAAAAAAIAAAIF3u7u7u7VgAAAIAAAIAAAIAAgAAAAAAAAAAAAAA 169 | AAAAAABY7u5QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 170 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 171 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAgAgAgAgAgAgAgAgAgAgAgAgAgAgAgAg 172 | AgAgAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 173 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 174 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 175 | AACAAAAAAAAAAAAAAAAAAAAAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 176 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 177 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 178 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 179 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 180 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 181 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 182 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA== 183 | 184 | 185 | -------------------------------------------------------------------------------- /Source/GFX/Collage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | 5 | namespace SpriteWave 6 | { 7 | public class Collage : IPalette 8 | { 9 | private const int defCols = 16; 10 | 11 | private bool _readOnly; 12 | public bool ReadOnly { get { return _readOnly; } } 13 | 14 | private List _tiles; 15 | public int nTiles { get { return _tiles.Count; } } 16 | 17 | private FileFormat _fmt; 18 | public FileFormat Format { get { return _fmt; } } 19 | 20 | private Tile _templ; 21 | public int TileW { get { return _templ.Width; } } 22 | public int TileH { get { return _templ.Height; } } 23 | 24 | private int _nCols; 25 | public int Columns { get { return _nCols; } } 26 | public int Rows 27 | { 28 | get 29 | { 30 | int rows = _tiles.Count / _nCols; 31 | if (_tiles.Count % _nCols != 0) 32 | rows++; 33 | 34 | return rows; 35 | } 36 | } 37 | 38 | public int Width { get { return Columns * TileW; } } 39 | public int Height { get { return Rows * TileH; } } 40 | 41 | private Bitmap _canvas; 42 | public Bitmap Bitmap { get { return _canvas; } } 43 | 44 | private Pen _gridPen; 45 | public Pen GridPen { get { return _gridPen; } } 46 | 47 | private readonly ColorTable _tbl; 48 | //public uint HighestColor { get { return _tbl.LastColor; } } 49 | 50 | private uint[] _nativeClrs; 51 | public uint[] NativeColors { get { return _nativeClrs; } } 52 | public int ColorCount { get { return _nativeClrs.Length; } } 53 | 54 | public uint this[int idx] 55 | { 56 | get { return Utils.RedBlueSwap(_tbl.NativeToRGBA(_nativeClrs[idx])); } 57 | set { 58 | _nativeClrs[idx] = _tbl.RGBAToNative(Utils.RedBlueSwap(value)); 59 | UpdateGridPen(); 60 | } 61 | } 62 | 63 | public uint[] GetList() 64 | { 65 | int len = _nativeClrs.Length; 66 | var clrs = new uint[len]; 67 | for (int i = 0; i < len; i++) 68 | clrs[i] = this[i]; 69 | 70 | return clrs; 71 | } 72 | 73 | public Collage(FileFormat fmt, int nCols = defCols, bool readOnly = true) 74 | { 75 | _fmt = fmt; 76 | _templ = _fmt.NewTile(); 77 | 78 | _tiles = new List(); 79 | _nCols = nCols; 80 | _readOnly = readOnly; 81 | 82 | _tbl = _fmt.ColorTable; 83 | uint[] defs = _tbl.Defaults; 84 | _nativeClrs = new uint[defs.Length]; 85 | Buffer.BlockCopy(defs, 0, _nativeClrs, 0, defs.Length * Utils.cLen); 86 | 87 | UpdateGridPen(); 88 | } 89 | 90 | public byte[] BGRAPalette() 91 | { 92 | var clrs = new byte[_nativeClrs.Length * Utils.cLen]; 93 | int idx = 0; 94 | for (int i = 0; i < _nativeClrs.Length; i++) 95 | { 96 | uint c = this[i]; 97 | clrs[idx++] = (byte)(c >> 24); 98 | clrs[idx++] = (byte)((c >> 16) & 0xff); 99 | clrs[idx++] = (byte)((c >> 8) & 0xff); 100 | clrs[idx++] = (byte)(c & 0xff); 101 | } 102 | 103 | return clrs; 104 | } 105 | 106 | public void UpdateGridPen() 107 | { 108 | double red = 0, green = 0, blue = 0, alpha = 0; 109 | for (int i = 0; i < _nativeClrs.Length; i++) 110 | { 111 | uint clr = this[i]; 112 | blue += (double)((clr >> 24) & 0xff); 113 | green += (double)((clr >> 16) & 0xff); 114 | red += (double)((clr >> 8) & 0xff); 115 | alpha += (double)(clr & 0xff); 116 | } 117 | 118 | double n = (double)_nativeClrs.Length; 119 | uint rgba = 120 | ((uint)(red / n) & 0xff) << 24 | 121 | ((uint)(green / n) & 0xff) << 16 | 122 | ((uint)(blue / n) & 0xff) << 8 | 123 | (uint)(alpha / n) & 0xff 124 | ; 125 | 126 | _gridPen = new Pen(Utils.FromRGB(rgba ^ 0xFFFFFF00)); 127 | } 128 | 129 | public void AddTile(Tile t) 130 | { 131 | _tiles.Add(t); 132 | } 133 | 134 | public Tile TileAt(int idx) 135 | { 136 | return _tiles[idx]; 137 | } 138 | public Tile TileAt(Position p) 139 | { 140 | return TileAt(p.row * _nCols + p.col); 141 | } 142 | 143 | public void SetTile(int idx, Tile t) 144 | { 145 | if (t == null) 146 | throw new ArgumentNullException(); 147 | 148 | _tiles[idx] = t; 149 | } 150 | public void SetTile(Position p, Tile t) 151 | { 152 | SetTile(p.row * _nCols + p.col, t); 153 | } 154 | 155 | public void AddBlankTiles(int count) 156 | { 157 | for (int i = 0; i < count; i++) 158 | _tiles.Add(_fmt.NewTile()); 159 | } 160 | 161 | // Each pixel is stored as four bytes, ordered B, G, R, then A 162 | public Bitmap RenderTile(Tile t) 163 | { 164 | return t.ToBitmap(BGRAPalette()); 165 | } 166 | 167 | public bool LoadTiles(byte[] file, int offset) 168 | { 169 | if (file == null || offset < 0) 170 | return false; 171 | 172 | int idx = 0; 173 | while (offset < file.Length) 174 | { 175 | if (idx == _tiles.Count) 176 | _tiles.Add(_fmt.NewTile()); 177 | 178 | int off = _tiles[idx].Import(file, offset); 179 | 180 | idx++; 181 | if (off <= offset) 182 | break; 183 | 184 | offset = off; 185 | } 186 | 187 | // 'idx' now represents the last tile index + 1, aka the true number of tiles 188 | if (_tiles.Count > idx) 189 | _tiles.RemoveRange(idx, _tiles.Count - idx); 190 | 191 | return true; 192 | } 193 | 194 | public void Render() 195 | { 196 | const int cLen = 4; 197 | 198 | int rows = Rows; 199 | int height = rows * TileH; 200 | int width = _nCols * TileW; 201 | byte[] collage = new byte[height * width * cLen]; 202 | 203 | byte[] palBGRA = BGRAPalette(); 204 | int idx = 0; 205 | foreach (Tile t in _tiles) 206 | { 207 | int tCol = idx % _nCols; 208 | int tRow = idx / _nCols; 209 | 210 | int imgCol = tCol * TileW; 211 | int imgRow = tRow * TileH; 212 | 213 | int offset = (imgRow * _nCols * TileW) + imgCol; 214 | offset *= cLen; 215 | 216 | // Each pixel is stored as four bytes, ordered B, G, R, then A 217 | t.ApplyTo(collage, offset, width, palBGRA); 218 | idx++; 219 | } 220 | 221 | _canvas = Utils.BitmapFrom(collage, width, height); 222 | } 223 | 224 | public int InsertColumn(int idx) 225 | { 226 | if (idx < 0 || idx > _nCols) 227 | return 0; 228 | 229 | _nCols++; 230 | for (int i = 0; i < Rows; i++) 231 | _tiles.Insert(i * _nCols + idx, _fmt.NewTile()); 232 | 233 | return 1; 234 | } 235 | 236 | public int InsertRow(int idx) 237 | { 238 | int rows = Rows; 239 | if (idx < 0 || idx > rows) 240 | return 0; 241 | 242 | for (int i = 0; i < _nCols; i++) 243 | _tiles.Insert(idx * _nCols + i, _fmt.NewTile()); 244 | 245 | return 1; 246 | } 247 | 248 | public int DeleteColumn(int idx) 249 | { 250 | if (idx < 0 || idx >= _nCols || _nCols <= 1) 251 | return 0; 252 | 253 | for (int i = Rows - 1; i >= 0; i--) 254 | _tiles.RemoveAt(i * _nCols + idx); 255 | 256 | _nCols--; 257 | return -1; 258 | } 259 | 260 | public int DeleteRow(int idx) 261 | { 262 | int rows = Rows; 263 | if (idx < 0 || idx >= rows || rows <= 1) 264 | return 0; 265 | 266 | for (int i = _nCols - 1; i >= 0; i--) 267 | _tiles.RemoveAt(idx * _nCols + i); 268 | 269 | return -1; 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /Source/GFX/ColorList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public class ColorList : ColorTable, IPalette 6 | { 7 | // Stored as red, green, blue, alpha 8 | private uint[] _clrs; 9 | 10 | public uint this[int idx] 11 | { 12 | get { return Utils.RedBlueSwap(_clrs[idx]); } 13 | set { _clrs[idx] = Utils.RedBlueSwap(value); } 14 | } 15 | 16 | public int ColorCount { get { return _clrs.Length; } } 17 | 18 | public uint[] GetList() 19 | { 20 | var list = new uint[_clrs.Length]; 21 | for (int i = 0; i < _clrs.Length; i++) 22 | list[i] = this[i]; 23 | 24 | return list; 25 | } 26 | 27 | public void Render() {} 28 | 29 | public ColorList(uint[] clrList, uint[] defSel) 30 | { 31 | _clrs = clrList; 32 | _defSel = defSel; 33 | } 34 | 35 | public override uint NativeToRGBA(uint idx) 36 | { 37 | uint c = 0; 38 | if (idx >= 0 && idx < _clrs.Length) 39 | c = _clrs[idx]; 40 | 41 | return c; 42 | } 43 | 44 | // Only checks red, green and blue 45 | public override uint RGBAToNative(uint rgba) 46 | { 47 | double inRed = Utils.ColorAtF(rgba, 24); 48 | double inGreen = Utils.ColorAtF(rgba, 16); 49 | double inBlue = Utils.ColorAtF(rgba, 8); 50 | 51 | double lowest = Double.PositiveInfinity; 52 | int idx = 0; 53 | for (int i = 0; i < _clrs.Length; i++) 54 | { 55 | uint c = _clrs[idx]; 56 | double clrRed = Utils.ColorAtF(c, 24); 57 | double clrGreen = Utils.ColorAtF(c, 16); 58 | double clrBlue = Utils.ColorAtF(c, 8); 59 | 60 | // Fun fact: this is the formula for working out the distance between two points in 3D space 61 | double delta = Math.Sqrt( 62 | Math.Pow(clrRed - inRed, 2) + 63 | Math.Pow(clrGreen - inGreen, 2) + 64 | Math.Pow(clrBlue - inBlue, 2) 65 | ); 66 | 67 | if (delta < lowest) 68 | { 69 | idx = i; 70 | lowest = delta; 71 | } 72 | } 73 | 74 | return (uint)idx; 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Source/GFX/ColorPattern.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public class ColorPattern : ColorTable 6 | { 7 | // RGBA pattern 8 | private byte[] _rgbaOrder, _rgbaOrderInv; 9 | private byte[] _rgbaDepth; 10 | 11 | /* 12 | RGBA Order And Depth: A formula descriptor for how to arrange different formats of RGBA. 13 | The input parameter as passed as the integer 0xrgbaRGBA where: 14 | r = position of red channel (0-3) 15 | g = position of green channel (0-3) 16 | b = position of blue channel (0-3) 17 | a = position of alpha channel (0-3) 18 | R = depth (number of bits) for the red channel (0-8) 19 | G = depth for the green channel (0-8) 20 | B = depth for the blue channel (0-8) 21 | A = depth for the alpha channel (0-8) 22 | Each digit takes up 4 bits in the input parameter. 23 | */ 24 | public ColorPattern(uint rgbaOrderAndDepth, uint[] defSel) 25 | { 26 | _rgbaOrder = new byte[4]; 27 | _rgbaOrderInv = new byte[4]; 28 | _rgbaDepth = new byte[4]; 29 | 30 | uint cCfg = rgbaOrderAndDepth; 31 | 32 | // Here we take each of the 8 hex digits inside 'cCfg' and place them in their appropriate elements 33 | for (int i = 0; i < 4; i++) 34 | { 35 | int shift = (4 + (3 - i)) * 4; 36 | uint order = Utils.GetBits(cCfg, 4, shift); 37 | if (order > 3) 38 | throw new ArgumentException("RGBA Order Error:\nInvalid order index " + order + " for the " + Utils.RGBANames[i] + " channel (" + i + ")"); 39 | 40 | uint depth = Utils.GetBits(cCfg, 4, shift - 16); 41 | if (depth > 8) 42 | throw new ArgumentException("RGBA Depth Error:\nInvalid bit depth " + depth + " for the " + Utils.RGBANames[i] + " channel (" + i + ")"); 43 | 44 | _rgbaOrder[i] = (byte)order; 45 | _rgbaDepth[i] = (byte)depth; 46 | } 47 | 48 | // We make sure that _rgbaOrder is bijective (1:1 correspondence) to the set {0, 1, 2, 3} 49 | // This means _rgbaOrder will be bijective with its inverted set (_rgbaOrderInv), a fact we will rely upon later 50 | for (int i = 0; i < 4; i++) 51 | { 52 | int chn = -1, freq = 0; 53 | for (int j = 0; j < 4; j++) 54 | { 55 | if (_rgbaOrder[j] == i) 56 | { 57 | chn = j; 58 | freq++; 59 | } 60 | } 61 | 62 | if (chn < 0) 63 | throw new ArgumentException("RGBA Order Error:\nNo channel for position " + i + " was given"); 64 | if (freq != 1) 65 | throw new ArgumentException("RGBA Order Error:\nMultiple channels were attempted to placed in the same position"); 66 | 67 | _rgbaOrderInv[i] = (byte)chn; 68 | } 69 | 70 | _defSel = defSel; 71 | } 72 | 73 | public override uint NativeToRGBA(uint idx) 74 | { 75 | /* 76 | We turn our 'index' into an RGBA color using our 'rgbaOrderAndDepth' formula descriptor. 77 | This is something I came up with, so a bit of explanation is always nice. 78 | I've noticed there are two common attributes of RGBA color that are 79 | often tailored to particular pixel formats: order of channels and bits per channel. 80 | '_rgbaOrder' tells us the order of channels as they appear in 'idx', and 81 | '_rgbaDepth' tells us how many bits are used for the R, G, B and A channels respectively. 82 | */ 83 | 84 | // This for loop takes the channel values out of 'idx' and places them in RGBA order in the process 85 | int[] chVals = new int[4]; 86 | for (int i = 3; i >= 0; i--) 87 | { 88 | int which = _rgbaOrderInv[i]; 89 | int depth = _rgbaDepth[which]; 90 | chVals[which] = (int)idx & ((1 << depth) - 1); 91 | idx >>= depth; 92 | } 93 | 94 | /* 95 | This loop takes each channel value from above and pads it out as evenly as possible, 96 | while placing it in the final RGBA value we return. 97 | */ 98 | uint rgba = 0; 99 | for (int i = 0; i < 4; i++) 100 | { 101 | int depth = _rgbaDepth[i]; 102 | if (depth == 0) 103 | { 104 | if (i != 3) 105 | rgba <<= 8; 106 | 107 | continue; 108 | } 109 | 110 | /* 111 | If we continuously pad the remaining bits with the existing bits, it produces the smoothest pattern. 112 | Eg. 113 | 00 -> 00000000, 01 -> 01000000, 10 -> 10000000, 11 -> 11000000 114 | is not as smooth/even as 115 | 00 -> 00000000, 01 -> 01010101, 10 -> 10101010, 11 -> 11111111 116 | , the latter of which this while loop achieves. 117 | */ 118 | int val = 0; 119 | int left = 8; 120 | while (left > 0) 121 | { 122 | int pad = chVals[i]; 123 | if (left < depth) 124 | { 125 | pad >>= (depth - left); 126 | depth = left; 127 | } 128 | 129 | val <<= depth; 130 | val |= pad; 131 | 132 | left -= depth; 133 | } 134 | val &= 0xff; 135 | 136 | // Place the new channel value in the final output 137 | rgba |= (uint)val; 138 | if (i != 3) 139 | rgba <<= 8; 140 | } 141 | 142 | return rgba; 143 | } 144 | 145 | public override uint RGBAToNative(uint rgba) 146 | { 147 | uint native = 0; 148 | for (int i = 0; i < 4; i++) 149 | { 150 | int which = _rgbaOrderInv[i]; 151 | int depth = _rgbaDepth[which]; 152 | 153 | uint channel = Utils.ColorAt(rgba, _rgbaOrder[3 - i] * 8); 154 | uint val = channel >> (8 - depth); 155 | 156 | native <<= depth; 157 | native |= val; 158 | } 159 | 160 | return native; 161 | } 162 | } 163 | } -------------------------------------------------------------------------------- /Source/GFX/ColorTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | /* 6 | ColorTable: A way of keeping track of which colours the system (NES, SNES, etc.) has access to. 7 | There are two different ways a ColorTable is created: with a pre-defined set of colours, or with an RGBA pattern. 8 | The RGBA pattern is interpreted as a RGBA Order And Depth formula. More detail on that one is provided below. 9 | */ 10 | public abstract class ColorTable 11 | { 12 | // Default selection of colours, in the native format. Useful if a palette has not yet been decided. 13 | protected uint[] _defSel; 14 | public uint[] Defaults { get { return _defSel; } } 15 | 16 | public abstract uint NativeToRGBA(uint idx); 17 | 18 | public abstract uint RGBAToNative(uint rgba); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/GFX/Tile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace SpriteWave 5 | { 6 | public enum Translation 7 | { 8 | Left, Right, Horizontal, Vertical 9 | } 10 | 11 | public abstract class Tile : IPiece 12 | { 13 | // Implements IPiece.EdgeKind 14 | public virtual EdgeKind EdgeKind { get { return EdgeKind.None; } } 15 | 16 | protected byte[] _data = {}; 17 | public byte[] Data { get { return _data; } } 18 | 19 | public abstract int Width { get; } 20 | public abstract int Height { get; } 21 | public abstract int BytesPP { get; } 22 | 23 | // If the return value is less than or equal to the offset value, it means there was an error 24 | public abstract int Import(byte[] input, int offset); 25 | 26 | public abstract void ExtractRow(byte[] line, int offset, int y, byte[] palBGRA); 27 | 28 | public Tile Clone() 29 | { 30 | // Uses funky business since 'Tile' is an abstract class (need the derived type, eg. NESTile) 31 | Tile t = Activator.CreateInstance(this.GetType()) as Tile; 32 | Buffer.BlockCopy(_data, 0, t.Data, 0, Width * Height * BytesPP); 33 | return t; 34 | } 35 | 36 | public void ApplyTo(byte[] canvas, int offset, int width, byte[] palBGRA) 37 | { 38 | for (int i = 0; i < Height; i++) 39 | { 40 | ExtractRow(canvas, offset, i, palBGRA); 41 | offset += width * Utils.cLen; 42 | } 43 | } 44 | 45 | public Bitmap ToBitmap(byte[] activeColors) 46 | { 47 | byte[] pixels = new byte[Width * Height * Utils.cLen]; 48 | 49 | ApplyTo(pixels, 0, Width, activeColors); 50 | return Utils.BitmapFrom(pixels, Width, Height); 51 | } 52 | 53 | public void Translate(Translation t) 54 | { 55 | var image = new byte[Width * Height * BytesPP]; 56 | int idx = 0; 57 | for (int i = 0; i < Height; i++) 58 | { 59 | for (int j = 0; j < Width; j++) 60 | { 61 | int y, x; 62 | switch (t) 63 | { 64 | case Translation.Left: 65 | y = Width-j-1; 66 | x = i; 67 | break; 68 | case Translation.Right: 69 | y = j; 70 | x = Height-i-1; 71 | break; 72 | case Translation.Horizontal: 73 | y = i; 74 | x = Width-j-1; 75 | break; 76 | case Translation.Vertical: 77 | y = Height-i-1; 78 | x = j; 79 | break; 80 | default: 81 | throw new ArgumentException(); 82 | } 83 | 84 | int off = (y * Width + x) * BytesPP; 85 | for (int k = 0; k < BytesPP; k++) 86 | { 87 | image[off + k] = _data[idx + k]; 88 | } 89 | 90 | idx += BytesPP; 91 | } 92 | } 93 | 94 | _data = image; 95 | } 96 | 97 | public bool IsBlank() 98 | { 99 | int idx = 0; 100 | for (int i = 0; i < Height; i++) 101 | { 102 | for (int j = 0; j < Width; j++) 103 | { 104 | for (int k = 0; k < BytesPP; k++) 105 | { 106 | if (_data[idx] != 0) 107 | return false; 108 | 109 | idx++; 110 | } 111 | } 112 | } 113 | 114 | return true; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /Source/PaletteUI/ColorBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using System.Windows.Forms; 6 | 7 | namespace SpriteWave 8 | { 9 | public class ColorBox : PictureBox 10 | { 11 | public const int Border = 2; 12 | public byte[] Buffer; 13 | 14 | private Size _oldSize; 15 | private BitmapData _data; 16 | 17 | public ColorBox() 18 | { 19 | _oldSize = new Size(0, 0); 20 | } 21 | 22 | public void Lock() 23 | { 24 | if (_oldSize != this.Size || this.Image == null) 25 | { 26 | this.Image = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb); 27 | Buffer = new byte[this.Width * this.Height * 4]; 28 | } 29 | 30 | _data = (this.Image as Bitmap).LockBits( 31 | this.DisplayRectangle, 32 | ImageLockMode.ReadWrite, 33 | PixelFormat.Format32bppArgb 34 | ); 35 | } 36 | 37 | public void Unlock() 38 | { 39 | if (_data == null) 40 | return; 41 | 42 | Marshal.Copy(Buffer, 0, _data.Scan0, Buffer.Length); 43 | (this.Image as Bitmap).UnlockBits(_data); 44 | this.Invalidate(); 45 | 46 | _oldSize = this.Size; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/PaletteUI/DigitImages.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Drawing; 4 | using System.Drawing.Text; 5 | using System.Drawing.Imaging; 6 | 7 | namespace SpriteWave 8 | { 9 | public static class DigitImages 10 | { 11 | public const int digitW = 8; 12 | public const int digitH = 16; 13 | public const float fontSize = 12; 14 | 15 | // List of pixel maps of numbers 0-9 (black then white) 16 | private static byte[][] _numbersPix; 17 | private const int numBufSize = digitW * digitH * Utils.cLen; 18 | 19 | private static void CreateDigitMaps() 20 | { 21 | Font font; 22 | try { 23 | font = new Font(FontFamily.GenericMonospace, fontSize, FontStyle.Bold, GraphicsUnit.Pixel); 24 | } 25 | catch (ArgumentException) { 26 | font = SystemFonts.DefaultFont; 27 | } 28 | 29 | Brush[] clrs = {new SolidBrush(Color.Black), new SolidBrush(Color.White)}; 30 | 31 | // For positioning digit characters inside each image 32 | const float numKernX = -2; 33 | const float numKernY = 1; 34 | var bounds = new RectangleF( 35 | numKernX, 36 | numKernY, 37 | digitW - numKernX, 38 | digitH - numKernY 39 | ); 40 | var sizeRect = new Rectangle( 41 | 0, 0, digitW, digitH 42 | ); 43 | 44 | _numbersPix = new byte[10 * 2][]; 45 | int idx = 0; 46 | for (int i = 0; i < 2; i++) // 2 colours 47 | { 48 | for (int j = 0; j < 10; j++) // 10 digits 49 | { 50 | // Render text to a bitmap image 51 | var bmp = new Bitmap(digitW, digitH, PixelFormat.Format32bppArgb); 52 | using (var g = Graphics.FromImage(bmp)) 53 | { 54 | g.TextRenderingHint = TextRenderingHint.AntiAlias; 55 | g.DrawString(j.ToString(), font, clrs[i], bounds); 56 | } 57 | 58 | // Extract the pixel data out of the bitmap image 59 | _numbersPix[idx] = new byte[numBufSize]; 60 | var data = bmp.LockBits( 61 | sizeRect, 62 | ImageLockMode.ReadWrite, 63 | PixelFormat.Format32bppArgb 64 | ); 65 | 66 | // Copy the bitmap data directly to our pixel array. 67 | // Each pixel is stored as four bytes - B, G, R, then A. 68 | Marshal.Copy(data.Scan0, _numbersPix[idx], 0, numBufSize); 69 | bmp.UnlockBits(data); 70 | 71 | idx++; 72 | } 73 | } 74 | } 75 | 76 | public static byte[] Generate(int count) 77 | { 78 | if (count < 2) 79 | return null; 80 | 81 | if (_numbersPix == null) 82 | CreateDigitMaps(); 83 | 84 | // Minus one because maximum value is the last index, not the size 85 | int nDigits = Utils.DigitCount(count - 1); 86 | int[] digits = new int[nDigits]; 87 | 88 | // x2 is for black and white versions 89 | var numArrayPix = new byte[numBufSize * nDigits * count * 2]; 90 | 91 | int stride = nDigits * digitW * Utils.cLen; 92 | for (int i = 0; i < count; i++) 93 | { 94 | int dc = Utils.DigitCount(i); 95 | int n = i; 96 | int j; 97 | // Format the digits list such that the number starts from the left side of the row, eg. 98 | // 7 (out of 8) = |7|, 6 (out of 16) = |6 |, 14 (out of 256) = |14 | 99 | for (j = 0; j < nDigits; j++) 100 | { 101 | if (j < dc) 102 | digits[dc-j-1] = n % 10; // placed in reverse, as digit significance goes left->right 103 | else 104 | digits[j] = -1; // -1 means a blank space 105 | 106 | n /= 10; 107 | } 108 | 109 | for (j = 0; j < digitH; j++) 110 | { 111 | int dstX = 0; 112 | for (int k = 0; k < nDigits; k++) 113 | { 114 | int srcOff = j * digitW * Utils.cLen; 115 | int dstY = i * 2 * digitH + j; 116 | 117 | if (digits[k] >= 0) 118 | { 119 | // Copy row from black digit 120 | Buffer.BlockCopy( 121 | _numbersPix[digits[k]], 122 | srcOff, 123 | numArrayPix, 124 | dstY * stride + dstX, 125 | digitW * Utils.cLen 126 | ); 127 | // Copy row from white digit 128 | Buffer.BlockCopy( 129 | _numbersPix[10 + digits[k]], 130 | srcOff, 131 | numArrayPix, 132 | (dstY + digitH) * stride + dstX, 133 | digitW * Utils.cLen 134 | ); 135 | } 136 | 137 | dstX += digitW * Utils.cLen; 138 | } 139 | } 140 | } 141 | 142 | return numArrayPix; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Source/PaletteUI/IPalette.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public interface IPalette 6 | { 7 | uint this[int idx] { get; set; } 8 | int ColorCount { get; } 9 | uint[] GetList(); 10 | } 11 | 12 | public interface IPalettePicker 13 | { 14 | void SelectFromTable(PalettePanel panel, int cellIdx); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Source/PaletteUI/PalettePanel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public class PalettePanel : Panel 8 | { 9 | private readonly int scrollW = SystemInformation.VerticalScrollBarWidth; 10 | private const int pixPerScroll = 20; 11 | 12 | private IPalettePicker _uiParent; 13 | 14 | private ColorBox _box; 15 | private VScrollBar _scroll; 16 | 17 | private int _nCols; 18 | private int _nRows; 19 | private int _maxVisRows; 20 | 21 | public int CurrentCell = -1; 22 | private int _hoveredCell = -1; 23 | 24 | private int _palLen = 0; 25 | private int[] _palPolarLum = null; 26 | private byte[] _palNumbers = null; 27 | 28 | private IPalette _pal; 29 | public IPalette Palette 30 | { 31 | set { 32 | _pal = value; 33 | if (_pal == null) 34 | return; 35 | 36 | int len = _pal.ColorCount; 37 | if (_palLen != len) 38 | { 39 | _palLen = len; 40 | _palNumbers = DigitImages.Generate(_palLen); 41 | } 42 | 43 | //CalcBrightness(); 44 | } 45 | get { 46 | return _pal; 47 | } 48 | } 49 | 50 | private int FirstVisibleCell 51 | { 52 | get { return _scroll.Enabled ? _scroll.Value * _nCols : 0; } 53 | } 54 | private int VisibleRows 55 | { 56 | get { return Math.Min(_nRows, _maxVisRows); } 57 | } 58 | 59 | public PalettePanel(IPalettePicker uiParent, IPalette pal, int maxVisRows = 2) 60 | { 61 | _uiParent = uiParent; 62 | this.Palette = pal; 63 | _maxVisRows = maxVisRows; 64 | 65 | this.Name = "paletteBox"; 66 | this.Location = new Point(0, 0); 67 | Size size = new Size(80, 20); 68 | this.Size = size; 69 | 70 | _box = new ColorBox(); 71 | _box.MouseDown += this.boxClickHandler; 72 | _box.MouseWheel += this.boxScrollHandler; 73 | _box.MouseMove += this.cursorHandler; 74 | _box.MouseLeave += (s, e) => { _hoveredCell = -1; Draw(); }; 75 | 76 | _scroll = new VScrollBar(); 77 | _scroll.Scroll += (s, e) => Draw(); 78 | 79 | int scW = SystemInformation.VerticalScrollBarWidth; 80 | _scroll.Location = new Point(size.Width - scW, 0); 81 | _scroll.Size = new Size(scW, size.Height); 82 | 83 | _scroll.Minimum = 0; 84 | _scroll.Value = 0; 85 | _scroll.LargeChange = 1; 86 | _scroll.SmallChange = 1; 87 | 88 | this.Controls.Add(_box); 89 | this.Controls.Add(_scroll); 90 | } 91 | 92 | public void AdjustContents(ToolBoxOrientation layout) 93 | { 94 | _nCols = 1; 95 | _nRows = 1; 96 | if (_pal != null) 97 | { 98 | if (this.Width >= 400) 99 | _nCols = 16; 100 | else if (this.Width >= 200) 101 | _nCols = 8; 102 | else 103 | _nCols = 4; 104 | 105 | _nCols = Math.Min(_nCols, _palLen); 106 | _nRows = (int)Math.Ceiling((float)_palLen / (float)_nCols); 107 | } 108 | 109 | bool scVis = _nRows > _maxVisRows; 110 | if (scVis) 111 | { 112 | if (!_scroll.Enabled) 113 | _scroll.Enabled = true; 114 | 115 | _scroll.Maximum = _nRows - _maxVisRows; 116 | } 117 | else if (_scroll.Enabled) 118 | _scroll.Enabled = false; 119 | 120 | int scX = layout == ToolBoxOrientation.Left ? 0 : this.Width - scrollW; 121 | _scroll.Location = new Point(scX, 0); 122 | _scroll.Size = new Size(scrollW, this.Height); 123 | 124 | int boxX = layout == ToolBoxOrientation.Left ? scrollW : 0; 125 | _box.Location = new Point(boxX, 0); 126 | 127 | int visRows = VisibleRows; 128 | 129 | if (this.Width > scrollW && this.Height > 0) 130 | { 131 | Size s = new Size(this.Width - scrollW, this.Height); 132 | //_box.Image = new Bitmap(s.Width, s.Height); 133 | _box.Size = s; 134 | } 135 | } 136 | 137 | private static void BlendPixel(byte[] pixbuf, int idx, float b, float g, float r, float alpha) 138 | { 139 | pixbuf[idx] = (byte)((float)pixbuf[idx] * (1f - alpha) + b * alpha); 140 | pixbuf[idx+1] = (byte)((float)pixbuf[idx+1] * (1f - alpha) + g * alpha); 141 | pixbuf[idx+2] = (byte)((float)pixbuf[idx+2] * (1f - alpha) + r * alpha); 142 | } 143 | 144 | public void Draw() 145 | { 146 | if (_pal == null || this.Height <= 0) 147 | return; 148 | 149 | float[] shades = { Utils.AlphaShades[0] * 255f, Utils.AlphaShades[1] * 255f }; 150 | 151 | // Calculate the brightness of each color to determine its opposite luminance pole (black or white) 152 | uint[] colors = _pal.GetList(); 153 | _palPolarLum = new int[_palLen]; 154 | 155 | const int white = 0, black = 1; 156 | for (int i = 0; i < _palLen; i++) 157 | { 158 | uint clr = colors[i]; 159 | double a = (double)(clr & 0xff) / 255f; 160 | double r = (double)((clr >> 8) & 0xff) / 255f; 161 | double g = (double)((clr >> 16) & 0xff) / 255f; 162 | double b = (double)(clr >> 24) / 255f; 163 | 164 | /* 165 | // If a cell is selected and it's not this cell, increase the brightness 166 | if (CurrentCell >= 0 && i != CurrentCell) 167 | { 168 | r = 1 - ((1 - r) / 2); 169 | g = 1 - ((1 - g) / 2); 170 | b = 1 - ((1 - b) / 2); 171 | } 172 | */ 173 | 174 | double lum = 0.2126*r + 0.7152*g + 0.0722*b; 175 | lum += (1f - lum) * (1f - a); 176 | _palPolarLum[i] = lum >= 0.5f ? black : white; 177 | 178 | // Reverse pixel channel order 179 | colors[i] = Utils.ComposeBGRA(a, r, g, b); 180 | } 181 | 182 | _box.Lock(); 183 | 184 | var buf = _box.Buffer; 185 | int width = _box.Image.Size.Width; 186 | int height = _box.Image.Size.Height; 187 | 188 | float visRowsF = (float)VisibleRows; 189 | float cellW = (float)width / (float)_nCols; 190 | float cellH = (float)height / visRowsF; 191 | 192 | // Minus one because maximum value is the last index, not the size 193 | int nDigits = Utils.DigitCount(_palLen - 1); 194 | float numW = (float)(DigitImages.digitW * nDigits); 195 | float numH = (float)DigitImages.digitH; 196 | 197 | const float border = 1; 198 | 199 | int firstCellIdx = FirstVisibleCell; 200 | int idx = 0; 201 | for (int i = 0; i < height; i++) 202 | { 203 | int y = (int)((float)i / cellH); 204 | float subY = (float)i % cellH; 205 | int cellY = firstCellIdx + y * _nCols; 206 | 207 | for (int j = 0; j < width; j++) 208 | { 209 | int x = (int)((float)j / cellW); 210 | float subX = (float)j % cellW; 211 | int cell = cellY + x; 212 | 213 | if ((cell == CurrentCell || cell == _hoveredCell) && 214 | (subX < border || subX >= cellW - border || 215 | subY < border || subY >= cellH - border)) 216 | { 217 | if (_palPolarLum[cell] == white) 218 | { 219 | buf[idx++] = 0xff; buf[idx++] = 0xff; buf[idx++] = 0xff; 220 | } 221 | else { 222 | buf[idx++] = 0; buf[idx++] = 0; buf[idx++] = 0; 223 | } 224 | 225 | buf[idx++] = 0xff; 226 | continue; 227 | } 228 | 229 | // In most cases, no pixel blending occurs. 230 | // Therefore, we just use the quickest method (I think) to copy a pixel to a pixel buffer in C#. 231 | // Note that pixels are laid out as b.g.r.a in memory and x86 is little-endian, 232 | // meaning that we need to make sure colors[cell] (the input) is written as 0xaarrggbb before copying. 233 | Buffer.BlockCopy(colors, cell * 4, buf, idx, 4); 234 | 235 | // If the colour isn't fully opaque 236 | if (buf[idx+3] != 0xff) 237 | { 238 | int tileX = ((j % 16) < 8) ? 1 : 0; 239 | int tileY = ((i % 16) < 8) ? 1 : 0; 240 | float sh = shades[tileX ^ tileY]; 241 | BlendPixel(buf, idx, sh, sh, sh, (float)(255 - buf[idx+3]) / 255f); 242 | buf[idx+3] = 0xff; 243 | } 244 | 245 | // If this pixel is in the top-left corner, blend it with its corresponding pixel inside the appropriate digit bitmap. 246 | // Plain English: this is the part where we draw the numbers. 247 | if (subY < numH && subX < numW) 248 | { 249 | // XOR 1 because we want the text colour to oppose the brightness level of the cell 250 | int clrVersion = _palPolarLum[cell] ^ 1; 251 | 252 | int numLine = (cell * 2 + clrVersion) * (int)numH; 253 | int numPixIdx = ((numLine + (int)subY) * (int)numW + (int)subX) * Utils.cLen; 254 | 255 | // If the current pixel inside the digit bitmap isn't fully transparent 256 | if (_palNumbers[numPixIdx + 3] != 0) 257 | { 258 | float dgAlpha = (float)_palNumbers[numPixIdx + 3] / 255f; 259 | BlendPixel(buf, idx, (float)_palNumbers[numPixIdx], (float)_palNumbers[numPixIdx+1], (float)_palNumbers[numPixIdx+2], dgAlpha); 260 | } 261 | } 262 | 263 | idx += 4; 264 | } 265 | } 266 | 267 | _box.Unlock(); 268 | } 269 | 270 | private void boxClickHandler(object sender, MouseEventArgs e) 271 | { 272 | float cellW = (float)_box.Image.Size.Width / (float)_nCols; 273 | float cellH = (float)_box.Image.Size.Height / (float)VisibleRows; 274 | 275 | int col = (int)((float)e.X / cellW); 276 | int row = (int)((float)e.Y / cellH) + (FirstVisibleCell / _nCols); 277 | int idx = row * _nCols + col; 278 | 279 | _uiParent.SelectFromTable(this, idx); 280 | } 281 | 282 | private void boxScrollHandler(object sender, MouseEventArgs e) 283 | { 284 | if (!_scroll.Enabled) 285 | return; 286 | 287 | int delta = e.Delta != 0 ? e.Delta / Math.Abs(e.Delta) : 0; 288 | int oldValue = _scroll.Value; 289 | int newValue = oldValue - delta; 290 | 291 | if (newValue < _scroll.Minimum) 292 | newValue = _scroll.Minimum; 293 | else if (newValue > _scroll.Maximum) 294 | newValue = _scroll.Maximum; 295 | 296 | _scroll.Value = newValue; 297 | 298 | if (newValue != oldValue) 299 | Draw(); 300 | } 301 | 302 | private void cursorHandler(object sender, MouseEventArgs e) 303 | { 304 | float x = (float)e.X / (float)_box.Width; 305 | float y = (float)e.Y / (float)_box.Height; 306 | 307 | int col = (int)(x * (float)_nCols); 308 | int row = (int)(y * (float)VisibleRows); 309 | 310 | int idx = FirstVisibleCell + (row * _nCols) + col; 311 | 312 | if (idx != _hoveredCell) 313 | { 314 | _hoveredCell = idx; 315 | Draw(); 316 | } 317 | } 318 | } 319 | } 320 | -------------------------------------------------------------------------------- /Source/Program.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Created by SharpDevelop. 3 | * User: 101111482 4 | * Date: 28/04/2019 5 | * Time: 9:48 AM 6 | * 7 | * To change this template use Tools | Options | Coding | Edit Standard Headers. 8 | */ 9 | using System; 10 | using System.Windows.Forms; 11 | 12 | namespace SpriteWave 13 | { 14 | /// 15 | /// Class with program entry point. 16 | /// 17 | internal sealed class Program 18 | { 19 | /// 20 | /// Program entry point. 21 | /// 22 | [STAThread] 23 | private static void Main(string[] args) 24 | { 25 | Application.EnableVisualStyles(); 26 | Application.SetCompatibleTextRenderingDefault(false); 27 | Application.Run(new MainForm()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Selection/Drag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public class DragObject 8 | { 9 | private bool _escaped = false; 10 | public bool Started { get { return _escaped; } } 11 | 12 | private const float curScale = 4f; 13 | private const float curAlpha = 0.6f; 14 | private Cursor _cur; 15 | 16 | private Position _orgPos; 17 | private TileWindow _orgWnd; 18 | 19 | private int _lastX, _lastY; 20 | private TileWindow _lastWnd; 21 | public TileWindow Window { get { return _lastWnd; } } 22 | 23 | private IPiece _selObj; 24 | public bool IsEdge { get { return _selObj is Edge; } } 25 | 26 | public Selection Current() 27 | { 28 | if (_lastWnd == null) 29 | return null; 30 | 31 | bool wasOob; 32 | Position pos = _lastWnd.GetPosition(_lastX, _lastY, out wasOob); 33 | 34 | Edge edge = _selObj as Edge; 35 | if (edge == null) 36 | { 37 | _lastWnd.Selected = !wasOob; 38 | return wasOob ? null : new Selection(_selObj, _lastWnd, pos); 39 | } 40 | 41 | if (_lastWnd == _orgWnd) 42 | edge.Distance = new Position( 43 | pos.col - _orgPos.col, 44 | pos.row - _orgPos.row 45 | ); 46 | else 47 | edge.Distance = new Position(0, 0); 48 | 49 | return new Selection(_selObj, _lastWnd, pos); 50 | } 51 | 52 | public DragObject(TileWindow wnd, int x, int y) 53 | { 54 | _lastX = x; 55 | _lastY = y; 56 | 57 | _lastWnd = wnd; 58 | _orgWnd = wnd; 59 | 60 | bool wasOob; 61 | _orgPos = _orgWnd.GetPosition(x, y, out wasOob); 62 | if (wasOob) 63 | throw new ArgumentOutOfRangeException(); 64 | 65 | _selObj = _orgWnd.PieceAt(_orgPos); 66 | if (_selObj is Tile) 67 | { 68 | Bitmap img = _orgWnd.TileBitmap(_selObj as Tile); 69 | img = img.SetAlpha(curAlpha).Scale(curScale); 70 | _cur = new Cursor(img.GetHicon()); 71 | } 72 | 73 | _orgWnd.Cursor = new Selection(_selObj, _orgWnd, _orgPos); 74 | _orgWnd.AdoptCursor(); 75 | Transfer.Source = _orgWnd.Cursor; 76 | } 77 | 78 | public void End() 79 | { 80 | if (_lastWnd == null) 81 | return; 82 | 83 | _lastWnd.AdoptCursor(); 84 | _lastWnd.Cursor = null; 85 | } 86 | 87 | public Selection Cancel() 88 | { 89 | // maybe clear inputcontrolstab sample? 90 | _orgWnd.Cursor = null; 91 | _orgWnd.Selected = true; 92 | return _orgWnd.CurrentSelection(); 93 | } 94 | 95 | private void Escape() 96 | { 97 | _escaped = true; 98 | if (_cur != null) 99 | Cursor.Current = _cur; 100 | 101 | Transfer.Start(); 102 | } 103 | 104 | private bool HasLeft(TileWindow wnd) 105 | { 106 | if (wnd == null || wnd != _orgWnd) 107 | return true; 108 | 109 | if (_selObj is Edge) 110 | { 111 | var dist = ((Edge)_selObj).Distance; 112 | return (dist.col != 0 || dist.row != 0); 113 | } 114 | 115 | bool wasOob; 116 | Position loc = wnd.GetPosition(_lastX, _lastY, out wasOob); 117 | return !wasOob && (loc.col != _orgPos.col || loc.row != _orgPos.row); 118 | } 119 | 120 | public Selection Update(TileWindow wnd, int x, int y) 121 | { 122 | _lastX = x; 123 | _lastY = y; 124 | 125 | if (!_escaped && this.HasLeft(wnd)) 126 | Escape(); 127 | 128 | if (_selObj is Edge) 129 | wnd = _orgWnd; 130 | 131 | if (wnd != _lastWnd && _lastWnd != null) 132 | { 133 | _lastWnd.Cursor = null; 134 | _lastWnd.Selected = false; 135 | _lastWnd.Draw(); 136 | } 137 | 138 | _lastWnd = wnd; 139 | 140 | Selection sel = this.Current(); 141 | if (_lastWnd != null) 142 | _lastWnd.Cursor = sel; 143 | 144 | return sel; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Source/Selection/Edge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace SpriteWave 5 | { 6 | public enum EdgeKind 7 | { 8 | TL, 9 | Top, 10 | TR, 11 | Left, 12 | None, 13 | Right, 14 | BL, 15 | Bottom, 16 | BR 17 | } 18 | 19 | public class Edge : IPiece 20 | { 21 | private EdgeKind _kind; 22 | public EdgeKind EdgeKind { get { return _kind; } } 23 | 24 | private PointF[] _shape; 25 | public PointF[] Shape { get { return _shape; } } 26 | 27 | private Position _dist; 28 | public Position Distance 29 | { 30 | get { 31 | return _dist; 32 | } 33 | set { 34 | _dist = value; 35 | 36 | int x, y; 37 | GetCoords(_kind, out x, out y); 38 | if (x == 0) 39 | _dist.col = 0; 40 | if (y == 0) 41 | _dist.row = 0; 42 | } 43 | } 44 | 45 | public Edge(EdgeKind kind) 46 | { 47 | _kind = kind; 48 | _dist = new Position(0, 0); 49 | } 50 | 51 | public static EdgeKind Direction(int x, int y) 52 | { 53 | if (x < -1 || x > 1 || y < -1 || y > 1) 54 | return EdgeKind.None; 55 | 56 | return (EdgeKind)((y + 1) * 3 + (x + 1)); 57 | } 58 | 59 | public static void GetCoords(EdgeKind e, out int x, out int y) 60 | { 61 | x = ((int)e % 3) - 1; 62 | y = ((int)e / 3) - 1; 63 | } 64 | 65 | private PointF[] ReflectTriangle(PointF[] input, Collage cl, bool rfX, bool rfY) 66 | { 67 | int clW = cl.Width; 68 | int clH = cl.Height; 69 | int offW = _dist.col * cl.TileW; 70 | int offH = _dist.row * cl.TileH; 71 | 72 | var output = new PointF[3]; 73 | for (int i = 0; i < 3; i++) 74 | { 75 | if (rfX) 76 | output[i].X = offW + clW - input[i].X; 77 | else 78 | output[i].X = offW + input[i].X; 79 | 80 | if (rfY) 81 | output[i].Y = offH + clH - input[i].Y; 82 | else 83 | output[i].Y = offH + input[i].Y; 84 | } 85 | 86 | return output; 87 | } 88 | 89 | public void Render(Collage cl) 90 | { 91 | if (_kind == EdgeKind.None) 92 | return; 93 | 94 | float clWidth = cl.Width; 95 | float clHeight = cl.Height; 96 | 97 | float taperW = (float)cl.TileW / 2f; 98 | float taperH = (float)cl.TileH / 2f; 99 | float gapW = taperW / 2f; 100 | float gapH = taperH / 2f; 101 | float fitW = gapW / 2f; 102 | float fitH = gapH / 2f; 103 | 104 | int x, y; 105 | GetCoords(_kind, out x, out y); 106 | 107 | // Corner Edge 108 | if (x != 0 && y != 0) 109 | { 110 | _shape = new[] { 111 | new PointF(-taperW, fitH), 112 | new PointF(fitW, -taperH), 113 | new PointF(-taperW, -taperH) 114 | }; 115 | } 116 | // Vertical Edge 117 | else if (x != 0) 118 | { 119 | _shape = new[] { 120 | new PointF(-gapW, taperH), 121 | new PointF(-gapW, clHeight - taperH), 122 | new PointF(-gapW - taperW, clHeight / 2f) 123 | }; 124 | } 125 | // Horizontal Edge 126 | else 127 | { 128 | _shape = new[] { 129 | new PointF(taperW, -gapH), 130 | new PointF(clWidth - taperW, -gapH), 131 | new PointF(clWidth / 2f, -taperH - gapH) 132 | }; 133 | } 134 | 135 | _shape = ReflectTriangle(_shape, cl, x == 1, y == 1); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /Source/Selection/Selection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public interface IPiece 6 | { 7 | EdgeKind EdgeKind { get; } 8 | } 9 | 10 | public class Selection : IEquatable 11 | { 12 | protected TileWindow _wnd; 13 | public TileWindow Window { get { return _wnd; } } 14 | 15 | protected Position _loc; 16 | public Position Location { get { return _loc; } } 17 | 18 | protected IPiece _piece; 19 | public IPiece Piece { get { return _piece; } } 20 | 21 | public Selection(IPiece selObj, TileWindow wnd, Position loc) 22 | { 23 | _piece = selObj; 24 | _wnd = wnd; 25 | _loc = loc; 26 | } 27 | 28 | public bool Equals(Selection obj) 29 | { 30 | var sel = obj as Selection; 31 | if (sel == null) 32 | return false; 33 | 34 | return 35 | sel.Window == _wnd && 36 | sel.Location.col == _loc.col && 37 | sel.Location.row == _loc.row 38 | ; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Source/Selection/Transfer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public static class Transfer 6 | { 7 | private static Selection _src, _dst; 8 | public static Selection Source { get { return _src; } set { _src = value; } } 9 | public static Selection Dest { get { return _dst; } set { _dst = value; } } 10 | 11 | private static IPiece _obj; 12 | public static bool HasPiece { get { return _obj != null; } } 13 | 14 | private static bool _completed; 15 | public static bool Completed { get { return _completed; } } 16 | 17 | public static void Start() 18 | { 19 | _completed = false; 20 | 21 | if (_src == null) 22 | return; 23 | 24 | Tile t = _src.Piece as Tile; 25 | if (t != null) 26 | _obj = t.Clone(); 27 | else 28 | _obj = _src.Piece; 29 | } 30 | 31 | public static void Clear() 32 | { 33 | _src = null; 34 | _dst = null; 35 | _obj = null; 36 | } 37 | 38 | public static void Paste() 39 | { 40 | _completed = false; 41 | 42 | if (_obj == null || _src == null || _dst == null || _dst.Window == null) 43 | return; 44 | 45 | TileWindow wnd = _dst.Window; 46 | Edge e = _obj as Edge; 47 | if (e != null) 48 | _dst.Window.ResizeCollage(e); 49 | else 50 | { 51 | _dst.Window.ReceiveTile(_obj as Tile); 52 | _completed = true; 53 | } 54 | } 55 | 56 | public static void Swap() 57 | { 58 | _completed = false; 59 | 60 | Tile cur = _obj as Tile; 61 | if (cur == null || _src == null || _dst == null) 62 | return; 63 | 64 | Tile other = _dst.Piece as Tile; 65 | if (other == null) 66 | return; 67 | 68 | TileWindow recver = _dst.Window; 69 | TileWindow sender = _src.Window; 70 | if (recver == null || sender == null) 71 | return; 72 | 73 | sender.ReceiveTile(other.Clone() as Tile, _src.Location); 74 | recver.ReceiveTile(cur, _dst.Location); 75 | 76 | _completed = true; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Source/Suffix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SpriteWave 6 | { 7 | public class Suffix 8 | { 9 | private bool _insert = false; 10 | 11 | private string _preIns = null; 12 | private string _postIns = null; 13 | 14 | private int _fmtBase = 0; 15 | private int _nDigits = 0; 16 | private bool _upperCase = false; 17 | 18 | public bool HasInsert { get { return _insert; } } 19 | public int Base { get { return _fmtBase; } } 20 | public int Digits { get { return _nDigits; } } 21 | 22 | public Suffix(string fmt) 23 | { 24 | var subs = new[] { new { start = 0, len = 0} }.ToList(); 25 | string ins = null; 26 | 27 | int insOff = -1; 28 | int level = 0; 29 | int idx = 0; 30 | foreach (char c in fmt) 31 | { 32 | if (c == '{') 33 | { 34 | if (level == 0) 35 | insOff = idx; 36 | 37 | level++; 38 | } 39 | if (c == '}') 40 | { 41 | if (level == 1 && insOff >= 0 && idx - insOff > 1) 42 | { 43 | if (insOff > 0) 44 | _preIns = fmt.Substring(0, insOff); 45 | 46 | ins = fmt.Substring(insOff + 1, idx - (insOff + 1)); 47 | 48 | int post = idx + 1; 49 | if (post < fmt.Length - 1) 50 | _postIns = fmt.Substring(post); 51 | 52 | _insert = true; 53 | } 54 | 55 | level--; 56 | } 57 | 58 | idx++; 59 | if (_insert) 60 | break; 61 | } 62 | 63 | if (level != 0) 64 | throw new ArgumentException("Trailing curly-braces in suffix format"); 65 | 66 | if (!_insert) 67 | { 68 | _preIns = fmt; 69 | return; 70 | } 71 | 72 | if (ins.Length <= 1) 73 | throw new ArgumentException("Suffix format is too short (must include a base type and a length, eg. d3)"); 74 | 75 | switch (Char.ToLower(ins[0])) 76 | { 77 | case 'b': 78 | _fmtBase = 2; 79 | break; 80 | case 'o': 81 | _fmtBase = 8; 82 | break; 83 | case 'd': 84 | _fmtBase = 10; 85 | break; 86 | case 'x': 87 | _fmtBase = 16; 88 | break; 89 | default: 90 | throw new ArgumentException("Invalid base type '" + ins[0] + "' in suffix format \"" + ins + "\""); 91 | } 92 | 93 | _upperCase = Char.IsUpper(ins[0]); 94 | 95 | _nDigits = Convert.ToInt32(ins.Substring(1)); 96 | if (_nDigits < 1) 97 | throw new ArgumentException("The suffix number must take at least one digit"); 98 | } 99 | 100 | public string Generate(int num) 101 | { 102 | if (!_insert) 103 | return _preIns; 104 | 105 | if (num < 0) 106 | throw new ArgumentException("Negative numbers are not permitted in the suffix"); 107 | 108 | int maxNum = (int)Math.Pow(_fmtBase, _nDigits) - 1; 109 | if (num > maxNum) 110 | throw new ArgumentException("The given number " + num + " exceeds the number limit of the given format (" + maxNum + ")"); 111 | 112 | int n = num; 113 | string str = ""; 114 | for (int i = 0; i < _nDigits; i++) 115 | { 116 | int d = n % _fmtBase; 117 | n /= _fmtBase; 118 | 119 | char c; 120 | if (d >= 10) 121 | { 122 | int a = _upperCase ? (int)'A' : (int)'a'; 123 | c = Convert.ToChar(a - 10 + d); 124 | } 125 | else 126 | c = Convert.ToChar((int)'0' + d); 127 | 128 | str = c + str; 129 | } 130 | 131 | return _preIns + str + _postIns; 132 | } 133 | 134 | public int ValueOf(string str) 135 | { 136 | if (!_insert) 137 | throw new InvalidOperationException("This suffix does not contain a number insert (eg. {d3})"); 138 | 139 | int preLen = _preIns != null ? _preIns.Length : 0; 140 | int postLen = _postIns != null ? _postIns.Length : 0; 141 | 142 | int len = preLen + _nDigits + postLen; 143 | if (str.Length != len) 144 | throw new ArgumentException( 145 | "The given string is the wrong size to be of this suffix\n" + 146 | "Expected: " + len + ", actual: " + str.Length 147 | ); 148 | 149 | string pre = preLen > 0 ? str.Substring(0, preLen) : null; 150 | string post = postLen > 0 ? str.Substring(str.Length - postLen) : null; 151 | 152 | if (pre != _preIns || post != _postIns) 153 | { 154 | throw new ArgumentException( 155 | "The given string does not have the surrounding components of the suffix\n" + 156 | "(\"" + pre + "\" & \"" + post + "\" != \"" + _preIns + "\" & \"" + _postIns + "\")" 157 | ); 158 | } 159 | 160 | int num = 0; 161 | for (int i = 0; i < _nDigits; i++) 162 | { 163 | num *= _fmtBase; 164 | 165 | char c = str[preLen + i]; 166 | int d; 167 | if (c >= '0' && c <= '9') 168 | d = c - '0'; 169 | else if (c >= 'A' && c <= 'Z') 170 | d = c - 'A' + 10; 171 | else if (c >= 'a' && c <= 'z') 172 | d = c - 'a' + 10; 173 | else 174 | throw new ArgumentOutOfRangeException("Character '" + c + "' at offset " + i + " is not an acceptable digit"); 175 | 176 | if (d >= _fmtBase) 177 | throw new ArgumentOutOfRangeException("The number " + d + " inside the given string does not fit within this base (base-" + _fmtBase + ")"); 178 | 179 | num += d; 180 | } 181 | 182 | return num; 183 | } 184 | 185 | public int[] ListOfValues(string[] strList) 186 | { 187 | if (strList == null) 188 | return null; 189 | 190 | var values = new List(); 191 | foreach (string str in strList) 192 | { 193 | int n; 194 | try { 195 | n = this.ValueOf(str); 196 | values.Add(n); 197 | } 198 | catch (Exception ex) { 199 | if (ex is InvalidOperationException) 200 | return null; 201 | 202 | if (ex is ArgumentNullException || 203 | ex is ArgumentException || 204 | ex is ArgumentOutOfRangeException) 205 | { 206 | continue; 207 | } 208 | } 209 | } 210 | 211 | if (values.Count < 1) 212 | return null; 213 | 214 | values.Sort(); 215 | return values.ToArray(); 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /Source/Tiles/MDTile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public class MDTile : Tile 6 | { 7 | public const int W = 8; 8 | public const int H = 8; 9 | public const int ByPP = 1; // bytes per pixel 10 | public const int TileSize = 32; 11 | 12 | public override int Width { get { return W; } } 13 | public override int Height { get { return H; } } 14 | public override int BytesPP { get { return ByPP; } } 15 | 16 | public MDTile() 17 | { 18 | _data = new byte[W * H]; 19 | } 20 | 21 | public override int Import(byte[] tile, int offset) 22 | { 23 | if (tile.Length - offset < TileSize) 24 | return offset; 25 | 26 | int i, idx = 0; 27 | for (i = offset; i < offset + TileSize; i++) 28 | { 29 | _data[idx++] = (byte)((int)tile[i] >> 4); 30 | _data[idx++] = (byte)((int)tile[i] & 0xf); 31 | } 32 | 33 | return offset + TileSize; 34 | } 35 | 36 | public override void ExtractRow(byte[] line, int offset, int y, byte[] palBGRA) 37 | { 38 | const int cLen = 4; 39 | int row = y % H; 40 | int idx = row * W; 41 | for (int i = 0, s = 0; i < W; i++, s += cLen) 42 | { 43 | // Copies a color from inside 'palBGRA', indexed by 'palIdx', to 'line' at 'offset + s' 44 | int palIdx = _data[idx] * cLen; 45 | Buffer.BlockCopy(palBGRA, palIdx, line, offset + s, cLen); 46 | idx++; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/Tiles/NESTile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public class NESTile : Tile 6 | { 7 | public const int W = 8; 8 | public const int H = 8; 9 | public const int ByPP = 1; // bytes per pixel 10 | public const int CHRSize = 16; 11 | 12 | public override int Width { get { return W; } } 13 | public override int Height { get { return H; } } 14 | public override int BytesPP { get { return ByPP; } } 15 | 16 | public NESTile() 17 | { 18 | _data = new byte[W * H]; 19 | } 20 | 21 | public override int Import(byte[] chr, int offset) 22 | { 23 | if (chr.Length - offset < CHRSize) 24 | return offset; 25 | 26 | int idx = 0; 27 | int i, j, bit; 28 | for (i = 0; i < H; i++) 29 | { 30 | for (j = 0, bit = 7; j < W; j++, bit--) 31 | { 32 | int mask = 1 << bit; 33 | int c = ((int)chr[offset + i] & mask) >> bit; 34 | c |= (((int)chr[offset + i+8] & mask) >> bit) << 1; 35 | _data[idx] = (byte)c; 36 | idx++; 37 | } 38 | } 39 | 40 | return offset + CHRSize; 41 | } 42 | 43 | public override void ExtractRow(byte[] line, int offset, int y, byte[] palBGRA) 44 | { 45 | const int cLen = 4; 46 | int row = y % H; 47 | int idx = row * W; 48 | for (int i = 0, s = 0; i < W; i++, s += cLen) 49 | { 50 | // Copies a color from inside 'palBGRA', indexed by 'palIdx', to 'line' at 'offset + s' 51 | int palIdx = _data[idx] * cLen; 52 | Buffer.BlockCopy(palBGRA, palIdx, line, offset + s, cLen); 53 | idx++; 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Source/Tiles/SNESTile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public class SNESTile : Tile 6 | { 7 | public const int W = 8; 8 | public const int H = 8; 9 | public const int ByPP = 1; // bytes per pixel 10 | public const int TileSize = 32; 11 | 12 | public override int Width { get { return W; } } 13 | public override int Height { get { return H; } } 14 | public override int BytesPP { get { return ByPP; } } 15 | 16 | public SNESTile() 17 | { 18 | _data = new byte[W * H]; 19 | } 20 | 21 | public override int Import(byte[] tile, int offset) 22 | { 23 | if (tile.Length - offset < TileSize) 24 | return offset; 25 | 26 | // byte position for current row, bit to access current column 27 | int rpos, bit; 28 | 29 | int i, j, idx = 0; 30 | for (i = 0, rpos = 0; i < H; i++, rpos += 2) 31 | { 32 | // First column is most significant bit, last column is least significant bit 33 | for (j = 0, bit = 7; j < W; j++, bit--) 34 | { 35 | int c, mask = 1 << bit; 36 | 37 | c = ((int)tile[offset + rpos] & mask) >> bit; 38 | c |= (((int)tile[offset + rpos + 1] & mask) >> bit) << 1; 39 | c |= (((int)tile[offset + rpos + 16] & mask) >> bit) << 2; 40 | c |= (((int)tile[offset + rpos + 16+1] & mask) >> bit) << 3; 41 | 42 | _data[idx] = (byte)c; 43 | idx++; 44 | } 45 | } 46 | 47 | return offset + TileSize; 48 | } 49 | 50 | public override void ExtractRow(byte[] line, int offset, int y, byte[] palBGRA) 51 | { 52 | const int cLen = 4; 53 | int row = y % H; 54 | int idx = row * W; 55 | for (int i = 0, s = 0; i < W; i++, s += cLen) 56 | { 57 | // Copies a color from inside 'palBGRA', indexed by 'palIdx', to 'line' at 'offset + s' 58 | int palIdx = _data[idx] * cLen; 59 | Buffer.BlockCopy(palBGRA, palIdx, line, offset + s, cLen); 60 | idx++; 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Source/ToolBox/ITab.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public interface ITab 8 | { 9 | string Name { get; } 10 | string ID { get; } 11 | Button TabButton { get; } 12 | Panel Panel { get; } 13 | TileWindow Window { get; set; } 14 | Size Minimum { get; } 15 | int X { set; } 16 | 17 | bool HandleEscapeKey(MainForm main); 18 | void AdjustContents(Size size, ToolBoxOrientation layout); 19 | void Destruct(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Source/ToolBox/ITabCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public interface ITabCollection 6 | { 7 | ITab this[int idx] { get; } 8 | ITab this[string name] { get; } 9 | int TabCount { get; } 10 | 11 | int TabIndex(ITab t); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/ToolBox/InputControlsTab.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public class InputControlsTab : ITab 8 | { 9 | private string _name; 10 | private string _id; 11 | private Button _tabButton; 12 | private Panel _panel; 13 | private readonly InputWindow _wnd; 14 | 15 | private Label _offsetLabel; 16 | private TextBox _offsetBox; 17 | private Label _sizeLabel; 18 | private Button _sendTile; 19 | private PictureBox _tileSample; 20 | 21 | private Bitmap _sampleBmp; 22 | 23 | public string Name { get { return _name; } } 24 | public string ID { get { return _id; } } 25 | public Button TabButton { get { return _tabButton; } } 26 | public Panel Panel { get { return _panel; } } 27 | public TileWindow Window { get { return _wnd as TileWindow; } set {} } 28 | 29 | public Size Minimum 30 | { 31 | get { 32 | int h = 70; 33 | if (_panel.Visible && _sendTile.Location.X <= _sizeLabel.Location.X + _sizeLabel.Width) 34 | h += 50; 35 | 36 | return new Size(200, h); 37 | } 38 | } 39 | 40 | public int X { set { _panel.Location = new Point(value, _panel.Location.Y); } } 41 | 42 | public Bitmap Sample 43 | { 44 | set { 45 | _sampleBmp = value; 46 | bool state = _sampleBmp != null; 47 | 48 | _sendTile.Enabled = state; 49 | _tileSample.Enabled = state; 50 | 51 | //AdjustContents(); 52 | } 53 | } 54 | 55 | public bool IsSampleVisible { get { return this.Panel.Visible && _sampleBmp != null; } } 56 | 57 | public EventHandler SendTileAction { set { _sendTile.Click += value; } } 58 | 59 | public int SizeText { set { _sizeLabel.Text = "/ 0x" + value.ToString("X"); } } 60 | 61 | public InputControlsTab(InputWindow wnd) 62 | { 63 | _wnd = wnd; 64 | _id = "inputControlsTab"; 65 | _name = "Controls"; 66 | 67 | _tabButton = new ToolBoxButton(_name); 68 | _tabButton.Tag = this; 69 | 70 | _panel = new Panel(); 71 | _panel.Name = "inputControlsPanel"; 72 | //_panel.UseVisualStyleBackColor = true; 73 | 74 | _offsetLabel = new Label(); 75 | _offsetLabel.Location = new System.Drawing.Point(5, 18); 76 | _offsetLabel.Name = "inputOffsetLabel"; 77 | _offsetLabel.Size = new System.Drawing.Size(53, 15); 78 | _offsetLabel.Text = "Offset: 0x"; 79 | 80 | _offsetBox = new TextBox(); 81 | _offsetBox.Location = new System.Drawing.Point(60, 15); 82 | _offsetBox.Name = "inputOffset"; 83 | _offsetBox.Size = new System.Drawing.Size(60, 20); 84 | _offsetBox.Text = "0"; 85 | _offsetBox.TextChanged += this.editOffsetBox; 86 | 87 | _sizeLabel = new Label(); 88 | _sizeLabel.Location = new System.Drawing.Point(122, 18); 89 | _sizeLabel.Name = "inputSizeLabel"; 90 | _sizeLabel.AutoSize = true; 91 | _sizeLabel.Text = "/"; 92 | 93 | _sendTile = new Button(); 94 | _sendTile.Location = new System.Drawing.Point(200, 12); 95 | _sendTile.Name = "inputSend"; 96 | _sendTile.Size = new System.Drawing.Size(90, 24); 97 | _sendTile.Text = "Send To Sprite"; 98 | _sendTile.UseVisualStyleBackColor = true; 99 | 100 | _tileSample = new PictureBox(); 101 | _tileSample.BackColor = System.Drawing.SystemColors.ControlLight; 102 | _tileSample.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; 103 | _tileSample.Location = new System.Drawing.Point(300, 4); 104 | _tileSample.Name = "inputSample"; 105 | _tileSample.Size = new System.Drawing.Size(40, 40); 106 | _tileSample.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; 107 | _tileSample.Paint += this.paintSample; 108 | 109 | _panel.Controls.Add(_offsetLabel); 110 | _panel.Controls.Add(_offsetBox); 111 | _panel.Controls.Add(_sizeLabel); 112 | _panel.Controls.Add(_sendTile); 113 | _panel.Controls.Add(_tileSample); 114 | } 115 | 116 | public bool HandleEscapeKey(MainForm main) 117 | { 118 | bool textFocus = main.ActiveControl is TextBox; 119 | if (textFocus) 120 | _wnd.Focus(main); 121 | 122 | return textFocus; 123 | } 124 | 125 | public void AdjustContents(Size size, ToolBoxOrientation layout) 126 | { 127 | int w = size.Width; 128 | int h = this.Minimum.Height; 129 | 130 | _panel.Size = new Size(w, h); 131 | 132 | _offsetLabel.Location = new Point(_offsetLabel.Location.X, h - 45); 133 | _offsetBox.Location = new Point(_offsetBox.Location.X, h - 48); 134 | _sizeLabel.Location = new Point(_sizeLabel.Location.X, h - 45); 135 | 136 | _sendTile.Location = new Point(w - 150, 19); 137 | _tileSample.Location = new Point(w - 50, 11); 138 | } 139 | 140 | public void Destruct() {} 141 | 142 | private void editOffsetBox(object sender, EventArgs e) 143 | { 144 | try 145 | { 146 | string text = _offsetBox.Text; 147 | int offset = 0; 148 | if (text.Length > 0) 149 | offset = Convert.ToInt32(text, 16); 150 | 151 | _wnd.Load(offset); 152 | } 153 | catch (Exception ex) 154 | { 155 | if (ex is ArgumentOutOfRangeException || 156 | ex is FormatException || 157 | ex is OverflowException 158 | ) 159 | return; 160 | 161 | throw; 162 | } 163 | } 164 | 165 | private void paintSample(object sender, PaintEventArgs e) 166 | { 167 | if (_sampleBmp == null) 168 | return; 169 | 170 | e.Graphics.ToggleSmoothing(false); 171 | e.Graphics.DrawImage(_sampleBmp, 0, 0, _tileSample.Width - 1, _tileSample.Height - 1); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Source/ToolBox/PaletteTab.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public class PaletteTab : ITab, IPalettePicker 8 | { 9 | private const int DividerH = 9; 10 | private const int PrimaryH = 80; 11 | 12 | private string _name; 13 | private string _id; 14 | private Button _tabButton; 15 | private Panel _panel; 16 | private TileWindow _wnd; 17 | private MainForm _main; 18 | 19 | private PalettePanel _primary; 20 | private PalettePanel _second; 21 | private int _pmIdx; 22 | 23 | public string Name { get { return _name; } } 24 | public string ID { get { return _id; } } 25 | public Button TabButton { get { return _tabButton; } } 26 | public Panel Panel { get { return _panel; } } 27 | 28 | public Size Minimum 29 | { 30 | get { 31 | return new Size(150, _second != null ? 250 : PrimaryH); 32 | } 33 | } 34 | 35 | public TileWindow Window 36 | { 37 | get { return _wnd; } 38 | set { 39 | if (value is SpriteWindow) 40 | { 41 | _wnd = value; 42 | _primary.Palette = _wnd.Collage; 43 | } 44 | } 45 | } 46 | 47 | public int X { set { _panel.Location = new Point(value, _panel.Location.Y); } } 48 | 49 | public PaletteTab(MainForm main, SpriteWindow wnd) 50 | { 51 | _main = main; 52 | _wnd = wnd; 53 | _id = "paletteTab"; 54 | _name = "Palette"; 55 | 56 | _tabButton = new ToolBoxButton(_name); 57 | _tabButton.Tag = this; 58 | 59 | _panel = new Panel(); 60 | _panel.Name = "palettePanel"; 61 | 62 | _primary = new PalettePanel(this, _wnd.Collage); 63 | _primary.Name = "primaryBox"; 64 | 65 | _panel.Controls.Add(_primary); 66 | } 67 | 68 | public void SelectFromTable(PalettePanel panel, int cellIdx) 69 | { 70 | if (panel == _primary) 71 | { 72 | panel.CurrentCell = cellIdx; 73 | panel.Draw(); 74 | } 75 | 76 | var table = _wnd.Collage.Format.ColorTable; 77 | if (!(table is ColorList)) 78 | { 79 | _main.OpenColorPicker(panel.Palette, cellIdx); 80 | return; 81 | } 82 | 83 | if (panel == _second && _second != null) 84 | { 85 | var cl = _wnd.Collage; 86 | cl.NativeColors[_pmIdx] = (uint)cellIdx; 87 | cl.UpdateGridPen(); 88 | cl.Render(); 89 | _main.PerformLayout(); 90 | return; 91 | } 92 | 93 | _pmIdx = cellIdx; 94 | if (_second == null) 95 | { 96 | _second = new PalettePanel(this, table as ColorList, maxVisRows: 4); 97 | _second.Name = "secondaryBox"; 98 | _panel.Controls.Add(_second); 99 | _main.PerformLayout(); 100 | } 101 | } 102 | 103 | public bool HandleEscapeKey(MainForm main) 104 | { 105 | _primary.CurrentCell = -1; 106 | 107 | bool shrink = _second != null; 108 | if (shrink) 109 | { 110 | _panel.Controls.Remove(_second); 111 | _second = null; 112 | main.PerformLayout(); 113 | } 114 | else 115 | _primary.Draw(); 116 | 117 | return shrink; 118 | } 119 | 120 | public void AdjustContents(Size size, ToolBoxOrientation layout) 121 | { 122 | if (size.Height < Minimum.Height) 123 | return; 124 | 125 | _panel.Size = size; 126 | 127 | _primary.Location = new Point(0, size.Height - PrimaryH); 128 | _primary.Size = new Size(size.Width, PrimaryH); 129 | 130 | if (_second != null) 131 | { 132 | _second.Size = new Size(size.Width, size.Height - (PrimaryH + DividerH)); 133 | _second.AdjustContents(layout); 134 | } 135 | 136 | _primary.AdjustContents(layout); 137 | 138 | _primary.Draw(); 139 | if (_second != null) 140 | _second.Draw(); 141 | } 142 | 143 | public void Destruct() {} 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /Source/ToolBox/SpriteControlsTab.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | 123 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 124 | IwAALiMBeKU/dgAAAAd0SU1FB+MHCQEsLncJXEAAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 125 | TVBXgQ4XAAABI0lEQVRIS8WWyw3CQAxEucM93dAAvdADdEANSNwogD4QohFOCC0zCCPLHgIbseHwpOTF 126 | HpOw+UxKKaMiZUukbImULZFS0XXdAuxACdAtVI9CSg/DXPgnHoNt3+cYSXjQtLbmCla2HfNIEgYa1LAt 127 | mIPpE27TxboHMZMkQVAcL+MJzACPKXjsCHwPonJ2EiQ0clgcEPH1L2IuSQKF8ez6zszw9Z6UnwSK/NLn 128 | /xPDq4j5SaDI/0IuihRSQ8xPAkV+IFdiCqkh5ieBor8OHOWSjr5ohtwWb4n5SRAU+oHf3PjGHlzAkvsq 129 | W0oUD3m0ncENsP4KZLaUBA1DH94cdlCZREoDjUNeTxuVZUjpQUD1C7gPKRUMA+0/MX6NlC2RsiVStqNM 130 | 7i7mKYmJcy1OAAAAAElFTkSuQmCC 131 | 132 | 133 | 134 | 135 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 136 | IwAALiMBeKU/dgAAAAd0SU1FB+MHCQEyCQZC1vQAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 137 | TVBXgQ4XAAABA0lEQVRIS93WzQ2CQBAFYO56pxsboBd70A6swYSbBdiHMTbiyZj1vWTXzA6PCCHDwcOX 138 | wGNnJiA/NimlVckwkgwjyTCSDJW2bTvoITnMOlWjyLA0y9sc5IeM+TlYhqbBwWxPdVQ9CxmKJsUZdrDJ 139 | uM3MrxsdKkPR4AZb4DGFx+5ga+TlHQTkCi0/yKuG+r40CHKhHWLZ5grP1K4fnGW1Q1i0lP1Ne9+/2qFc 140 | tARvpO9Z+v7VDuWiJXj3/vfA1S/pqjfN/MeCsJD28IRL3p9i/oNPufiVC9/wgLhXG6HgCmVoEfPyLlB4 141 | co2mmP95stAg/gOssBnE/MWIJMNIMowkwzip+QC4fimJDEQu2AAAAABJRU5ErkJggg== 142 | 143 | 144 | 145 | 146 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 147 | IwAALiMBeKU/dgAAAAd0SU1FB+MHCQICAkyQh9YAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 148 | TVBXgQ4XAAAAmElEQVRIS8XNQQ6AMAhEUW/R+58UnUQSSn90oaOLt/BTZIuIT2F0wuiE0QmjE0YnjE4Y 149 | nTA6YZQxRhxwdkU72u09YTyPPToofSZLyMenZX5HO/UffT591Idvqjfsx9L/B4UevqHemA5Ke7zM72in 150 | /qPPp49UFnB+RTu532eyhPT0YO8JoxNGJ4xOGJ0wOmF0wuiE0QmjT2w7nXM+1Y+uWdwAAAAASUVORK5C 151 | YII= 152 | 153 | 154 | 155 | 156 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAu 157 | IwAALiMBeKU/dgAAAAd0SU1FB+MHCQIDCltQPqUAAAAZdEVYdENvbW1lbnQAQ3JlYXRlZCB3aXRoIEdJ 158 | TVBXgQ4XAAAAm0lEQVRIS+3NSw6DMBAEUW7h+590wChEk1a5JwI5i8iLtyl/eouIn8I4E8ZKay067d/A 159 | 6Fxjd0cxjujYRe85GAkNZXp/BKOiAaLvCEZ1fJbp0PuM3iqMTho66XkFo7MGKxidNVjB6KzBCkbnPweP 160 | j7OPwVc70VuFUaXPLX1HMBIayPT+CMYRGur0noPReTLWYazcHeswzoRxnth2nXM+1XXtO9cAAAAASUVO 161 | RK5CYII= 162 | 163 | 164 | 165 | 166 | iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 167 | YQUAAAAJcEhZcwAALiMAAC4jAXilP3YAAAAHdElNRQfjBwkEECd77V/wAAAAGXRFWHRDb21tZW50AENy 168 | ZWF0ZWQgd2l0aCBHSU1QV4EOFwAAAOlJREFUSEu90DEOgzAUg2Hu0IE79v57GkdN9fzjskRh+CQcYUdw 169 | tNYeFQ93ioc7xcOdLgfneUoDns9MtTNw34KkUsfBmal2Bu5bkFTqODgz1c7AfQuSSh0HZ6baGbhvQVKp 170 | 4+DMVDsD9y1IKnUc/ufS5b4FSaUV3LcgqQTv7vWl5/TOD/ctSCoVumD+vvnu7aXctyCpVOir6jt61ll9 171 | x3DfgqRS8fiF/KWy9ZeKLtBXye1lwn0LkkoruG9BUmkF9y1IKq3gvgVJpRXctyCptIL7FiSVVnDfgqTS 172 | Cu5beEI83Cke7hQP92nHB2URLFEIqgwLAAAAAElFTkSuQmCC 173 | 174 | 175 | -------------------------------------------------------------------------------- /Source/ToolBox/ToolBox.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace SpriteWave 7 | { 8 | public enum ToolBoxOrientation 9 | { 10 | Left, Right, None 11 | } 12 | 13 | public class ToolBox : ITabCollection 14 | { 15 | private const int MaxHeight = 300; 16 | private const int MinMinWidth = 100; 17 | 18 | private readonly Color TabPanelColor = Color.FromArgb(250, 250, 250); 19 | private readonly Color TabButtonColor = Color.FromArgb(210, 210, 210); 20 | 21 | private bool _isActive; 22 | private bool _isOpen; 23 | private bool _canSwitch; 24 | 25 | private TileWindow _wnd; 26 | 27 | private ITab _curTab; 28 | 29 | private Panel _ui; 30 | private ToolBoxButton _switch; 31 | private ToolBoxButton _minimise; 32 | private Panel _tabButtons; 33 | 34 | private Action _refresh; 35 | private Utils.ControlFunc _configure; 36 | 37 | public bool IsOpen { get { return _isOpen && _wnd != null && _wnd.IsActive; } } 38 | 39 | public bool IsActive 40 | { 41 | get { 42 | return _isActive; 43 | } 44 | set { 45 | _isActive = value; 46 | _minimise.Visible = value; 47 | 48 | if (!value) 49 | _switch.Visible = false; 50 | 51 | _canSwitch = false; 52 | } 53 | } 54 | 55 | public bool Switch 56 | { 57 | set { 58 | _canSwitch = value; 59 | UpdateSwitch(); 60 | Refresh(); 61 | } 62 | } 63 | 64 | public TileWindow CurrentWindow 65 | { 66 | get { 67 | return _wnd; 68 | } 69 | set { 70 | _wnd.RescindTabButtons(this); 71 | 72 | int idx = _wnd.TabIndex(_curTab); 73 | idx = idx >= 0 ? idx : 0; 74 | 75 | _wnd = value; 76 | Select(this[idx], reconfig: false); 77 | 78 | _wnd.ProvideTabButtons(this); 79 | 80 | Utils.ApplyRecursiveControlFunc(_ui, _configure); 81 | Refresh(); 82 | } 83 | } 84 | 85 | public ITab this[int idx] 86 | { 87 | get { 88 | if (idx < 0) 89 | return null; 90 | 91 | if (_wnd == null || idx >= _wnd.TabCount) 92 | return null; 93 | 94 | return _wnd[idx]; 95 | } 96 | } 97 | 98 | public ITab this[string name] 99 | { 100 | get { 101 | return _wnd != null ? _wnd[name] : null; 102 | } 103 | } 104 | 105 | public int TabCount { get { return _wnd.TabCount; } } 106 | 107 | public Size Minimum 108 | { 109 | get { 110 | Size s = new Size(MinMinWidth, 0); 111 | 112 | if (this.IsOpen && _curTab != null) 113 | s = _curTab.Minimum; 114 | 115 | s.Width += _switch.Width; 116 | s.Height += _minimise.Height; 117 | return s; 118 | } 119 | } 120 | 121 | public ToolBox(MainForm main, TileWindow initialWnd) 122 | { 123 | _wnd = initialWnd; 124 | _refresh = main.PerformLayout; 125 | _configure = main.ConfigureControls; 126 | 127 | _switch = new ToolBoxButton(ToolBoxShapes.Switch, new Size(20, 140)); 128 | _switch.Name = "toolBoxSwitchWindow"; 129 | _switch.Click += (s, e) => main.SwitchToolBoxWindow(); 130 | 131 | _minimise = new ToolBoxButton(ToolBoxShapes.Minimise, new Size(40, 21), 1); 132 | _minimise.Name = "toolBoxMinimise"; 133 | _minimise.Click += (s, e) => { Minimise(); Refresh(); }; 134 | 135 | _ui = new Panel(); 136 | _ui.Controls.Add(_switch); 137 | _ui.Controls.Add(_minimise); 138 | 139 | _tabButtons = new Panel(); 140 | _tabButtons.Size = new Size(0, 0); 141 | 142 | //_wnd.ProvideTabButtons(this); 143 | 144 | _ui.Controls.Add(_tabButtons); 145 | main.Controls.Add(_ui); 146 | 147 | _switch.Visible = false; 148 | 149 | IsActive = false; 150 | _isOpen = true; 151 | } 152 | 153 | public void Refresh() 154 | { 155 | if (_refresh != null) 156 | _refresh(); 157 | } 158 | 159 | public void Activate(TileWindow wnd) 160 | { 161 | this.IsActive = true; 162 | _wnd = wnd; 163 | Select(0); 164 | if (!_isOpen) 165 | Minimise(); 166 | 167 | Refresh(); 168 | } 169 | 170 | public void RefreshTab() 171 | { 172 | if (!_isOpen) 173 | Minimise(); 174 | else 175 | Refresh(); 176 | } 177 | 178 | public bool HandleEscapeKey(MainForm main) 179 | { 180 | if (_curTab == null) 181 | return false; 182 | 183 | return _curTab.HandleEscapeKey(main); 184 | } 185 | 186 | // Implements ITabCollection.TabIndex 187 | public int TabIndex(ITab t) 188 | { 189 | return _wnd.TabIndex(t); 190 | } 191 | 192 | private void Select(ITab t, bool reconfig = true) 193 | { 194 | if (t == null) 195 | t = _wnd[0]; 196 | 197 | t.Window = _wnd; 198 | 199 | if (t == _curTab) 200 | return; 201 | 202 | if (_curTab != null) 203 | { 204 | _curTab.Panel.Visible = false; 205 | _curTab.TabButton.BackColor = Button.DefaultBackColor; 206 | } 207 | 208 | _curTab = t; 209 | 210 | t.Panel.Visible = true; 211 | t.TabButton.BackColor = TabButtonColor; 212 | 213 | foreach (Control c in _ui.Controls) 214 | { 215 | if (c != _tabButtons && c is Panel) 216 | _ui.Controls.Remove(c); 217 | } 218 | 219 | t.Panel.BackColor = TabPanelColor; 220 | _ui.Controls.Add(t.Panel); 221 | 222 | if (reconfig) 223 | Utils.ApplyRecursiveControlFunc(_ui, _configure); 224 | } 225 | public void Select(int idx) 226 | { 227 | Select(this[idx]); 228 | } 229 | public void Select(string name) 230 | { 231 | Select(this[name]); 232 | } 233 | 234 | public void Cycle(int dir) 235 | { 236 | int nTabs = this.TabCount; 237 | Select((this.TabIndex(_curTab) + nTabs + dir) % nTabs); 238 | } 239 | 240 | public Control GetControl(string name) 241 | { 242 | if (!this.IsOpen) 243 | return null; 244 | 245 | Control c = Utils.FindControl(_curTab.Panel, name); 246 | if (c != null) 247 | c.Visible = true; 248 | 249 | return c; 250 | } 251 | 252 | private void UpdateSwitch() 253 | { 254 | if (_canSwitch) 255 | _switch.Visible = _isOpen; 256 | else 257 | _switch.Visible = false; 258 | } 259 | 260 | public void Minimise() 261 | { 262 | _isOpen = !_isOpen; 263 | _minimise.State = _isOpen ? 1 : 0; 264 | 265 | _curTab.Panel.Visible = _isOpen; 266 | 267 | UpdateSwitch(); 268 | } 269 | 270 | // a lil' bit hacky 271 | private void tabButtonClick(object sender, EventArgs e) 272 | { 273 | ITab old = _curTab; 274 | Select((sender as ToolBoxButton).Tag as ITab); 275 | 276 | bool needsRefresh = _curTab != old; 277 | if (!this.IsOpen) 278 | { 279 | Minimise(); 280 | needsRefresh = true; 281 | } 282 | if (needsRefresh) 283 | { 284 | Refresh(); 285 | Refresh(); // just a tad 286 | } 287 | } 288 | 289 | public void AddTabButton(Button btn) 290 | { 291 | btn.Visible = true; 292 | 293 | if (_tabButtons.Controls.Contains(btn)) 294 | return; 295 | 296 | btn.Click += this.tabButtonClick; 297 | _tabButtons.Controls.Add(btn); 298 | } 299 | 300 | public void RemoveTabButton(Button btn) 301 | { 302 | if (_tabButtons.Controls.Contains(btn)) 303 | btn.Visible = false; 304 | } 305 | 306 | public void AdjustTabButtons(Point loc) 307 | { 308 | int x = 0; 309 | int h = 0; 310 | foreach (Control c in _tabButtons.Controls) 311 | { 312 | if (!c.Visible) 313 | continue; 314 | 315 | int cH = c.Height; 316 | if (cH > h) 317 | h = cH; 318 | 319 | c.Location = new Point(x, 0 /*c.Location.Y*/); 320 | x += c.Width; 321 | } 322 | 323 | _tabButtons.Size = new Size(x, h); 324 | _tabButtons.Location = loc; 325 | } 326 | 327 | public void AdjustCurrentTab(ToolBoxOrientation layout) 328 | { 329 | _curTab.AdjustContents( 330 | new Size( 331 | _ui.Width - _switch.Width, 332 | _ui.Height - _minimise.Height 333 | ), 334 | layout 335 | ); 336 | } 337 | 338 | public void UpdateLayout(ToolBoxOrientation layout, Size clientSize) 339 | { 340 | if (!IsActive) 341 | return; 342 | 343 | int tileWndX = _wnd.CanvasPos.X; 344 | 345 | _ui.Size = new Size(_wnd.CanvasSize.Width, this.Minimum.Height); 346 | _ui.Location = new Point(_wnd.CanvasPos.X, clientSize.Height - _ui.Height); 347 | 348 | int swX = 0; 349 | int minX = 0; 350 | int tabX = 0; 351 | int btnsX = _switch.Width; 352 | 353 | if (layout == ToolBoxOrientation.Left) 354 | { 355 | swX = _ui.Width - _switch.Width; 356 | btnsX = swX - _tabButtons.Width; 357 | _switch.State = 0; 358 | } 359 | else 360 | { 361 | minX = _ui.Width - _minimise.Width; 362 | tabX = _switch.Width; 363 | _switch.State = 1; 364 | } 365 | 366 | _minimise.Location = new Point(minX, _ui.Height - _minimise.Height); 367 | 368 | _switch.Location = new Point(swX, 0); 369 | _switch.Size = new Size(20, _ui.Height - 1); 370 | 371 | if (_curTab != null) 372 | { 373 | _curTab.X = tabX; 374 | AdjustCurrentTab(layout); 375 | AdjustTabButtons(new Point(btnsX, _ui.Height - _minimise.Height)); 376 | } 377 | } 378 | } 379 | } -------------------------------------------------------------------------------- /Source/ToolBox/ToolBoxButton.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using System.Drawing.Drawing2D; 5 | using System.Windows.Forms; 6 | 7 | namespace SpriteWave 8 | { 9 | public class ToolBoxButton : Button 10 | { 11 | private readonly Pen _pen = new Pen(Color.Black); 12 | 13 | private PointF[][] _shapes; 14 | private Bitmap[] _imgs; 15 | 16 | private int _idx; 17 | public int State 18 | { 19 | set { 20 | _idx = value; 21 | this.Image = _imgs[_idx]; 22 | } 23 | } 24 | 25 | public ToolBoxButton(PointF[][] fracPoints, Size size, int startIdx = 0) 26 | { 27 | _shapes = fracPoints; 28 | 29 | _imgs = new Bitmap[_shapes.Length]; 30 | _idx = startIdx; 31 | 32 | this.BackColor = SystemColors.ControlLight; 33 | Initialise(size); 34 | } 35 | 36 | public ToolBoxButton(Point[][] pixPoints, Size size, int startIdx = 0) 37 | { 38 | _shapes = new PointF[pixPoints.Length][]; 39 | Size client = Interior(size); 40 | 41 | int i = 0, j = 0; 42 | foreach (Point[] shp in pixPoints) 43 | { 44 | _shapes[i] = new PointF[shp.Length]; 45 | foreach (Point p in shp) 46 | _shapes[i][j++] = new PointF( 47 | (float)p.X / (float)client.Width, 48 | (float)p.Y / (float)client.Height 49 | ); 50 | 51 | j = 0; 52 | i++; 53 | } 54 | 55 | _imgs = new Bitmap[_shapes.Length]; 56 | _idx = startIdx; 57 | 58 | this.BackColor = SystemColors.ControlLight; 59 | Initialise(size); 60 | } 61 | 62 | public ToolBoxButton(string text) 63 | { 64 | _shapes = null; 65 | 66 | this.Text = text; 67 | this.Location = new Point(0, 0); 68 | 69 | float width = (int)TextRenderer.MeasureText(text, this.Font).Width; 70 | Initialise(new Size((int)width + 10, 20)); 71 | } 72 | 73 | private void Initialise(Size size) 74 | { 75 | this.FlatAppearance.BorderSize = 0; 76 | this.FlatStyle = FlatStyle.Flat; 77 | this.UseVisualStyleBackColor = false; 78 | this.SetStyle(ControlStyles.Selectable, false); 79 | 80 | this.Size = size; // Calls Render() 81 | } 82 | 83 | public void Render() 84 | { 85 | if (_shapes == null) 86 | return; 87 | 88 | Size client = Interior(this.Size); 89 | 90 | if (client.Width < 1 || client.Height < 1) 91 | return; 92 | 93 | for (int i = 0; i < _shapes.Length; i++) 94 | _imgs[i] = CreateShape(_shapes[i], client); 95 | 96 | this.Image = _imgs[_idx]; 97 | } 98 | 99 | private static Size Interior(Size s) 100 | { 101 | return new Size( 102 | s.Width - 4, 103 | s.Height - 5 104 | ); 105 | } 106 | 107 | private Bitmap CreateShape(PointF[] poly, Size area) 108 | { 109 | int w = area.Width; 110 | int h = area.Height; 111 | 112 | Point[] scaled = new Point[poly.Length]; 113 | int i = 0; 114 | foreach (PointF p in poly) 115 | scaled[i++] = new Point( 116 | (int)(p.X * (float)w), 117 | (int)(p.Y * (float)h) 118 | ); 119 | 120 | Bitmap canvas = new Bitmap(area.Width, area.Height, PixelFormat.Format32bppArgb); 121 | using (var g = Graphics.FromImage(canvas)) 122 | { 123 | g.SmoothingMode = SmoothingMode.AntiAlias; 124 | g.DrawLines(_pen, scaled); 125 | } 126 | 127 | return canvas; 128 | } 129 | 130 | protected override void OnLayout(LayoutEventArgs e) 131 | { 132 | base.OnLayout(e); 133 | Render(); 134 | } 135 | } 136 | 137 | public static class ToolBoxShapes 138 | { 139 | public static Point[][] Minimise = { 140 | new[] { 141 | new Point(12, 2), 142 | new Point(22, 2), 143 | new Point(22, 11), 144 | new Point(12, 11), 145 | new Point(12, 2) 146 | }, 147 | new[] { 148 | new Point(12, 7), 149 | new Point(22, 7) 150 | } 151 | }; 152 | 153 | public static PointF[][] Switch = { 154 | new[] { 155 | new PointF(0.05f, 0.25f), 156 | new PointF(0.85f, 0.50f), 157 | new PointF(0.05f, 0.75f) 158 | }, 159 | new[] { 160 | new PointF(0.85f, 0.25f), 161 | new PointF(0.05f, 0.50f), 162 | new PointF(0.85f, 0.75f) 163 | } 164 | }; 165 | } 166 | } -------------------------------------------------------------------------------- /Source/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | using System.Drawing; 5 | using System.Drawing.Drawing2D; 6 | using System.Drawing.Imaging; 7 | using System.Windows.Forms; 8 | 9 | namespace SpriteWave 10 | { 11 | public struct Position 12 | { 13 | public int row, col; 14 | 15 | public Position(int c, int r) 16 | { 17 | col = c; 18 | row = r; 19 | } 20 | } 21 | 22 | public struct EventPair 23 | { 24 | public string name; 25 | public EventHandler handler; 26 | 27 | public EventPair(string n, EventHandler h) 28 | { 29 | name = n; 30 | handler = h; 31 | } 32 | } 33 | 34 | public static class Utils 35 | { 36 | public const int cLen = 4; 37 | 38 | //public static MainForm mainForm; 39 | 40 | public static Type TileType(string name) 41 | { 42 | return Type.GetType("SpriteWave." + name); 43 | } 44 | 45 | public static Control FindControl(Control container, string name) 46 | { 47 | Control[] list = container.Controls.Find(name, false); 48 | if (list == null || list.Length != 1) 49 | return null; 50 | 51 | return list[0]; 52 | } 53 | 54 | /* 55 | Thanks again, StackOverflow! 56 | https://stackoverflow.com/a/439606 57 | */ 58 | public static Control FindActiveControl(Control c) 59 | { 60 | var container = c as IContainerControl; 61 | while (container != null) 62 | { 63 | c = container.ActiveControl; 64 | container = c as IContainerControl; 65 | } 66 | 67 | return c; 68 | } 69 | 70 | public delegate object ControlFunc(Control ctrl, object result); 71 | 72 | // Sounds (at least) 10x scarier than it really is 73 | public static object ApplyRecursiveControlFunc(Control ctrl, ControlFunc ctrlFunc, object result = null) 74 | { 75 | result = ctrlFunc(ctrl, result); 76 | 77 | foreach (Control c in ctrl.Controls) 78 | result = ApplyRecursiveControlFunc(c, ctrlFunc, result); 79 | 80 | return result; 81 | } 82 | 83 | public static void ApplyEvents(Control ctrl, EventPair[] events) 84 | { 85 | var list = ctrl.GetType().GetEvents(); 86 | int count = 0; 87 | foreach (EventInfo ev in list) 88 | { 89 | foreach (EventPair pair in events) 90 | { 91 | if (ev.Name == pair.name) 92 | { 93 | ev.AddEventHandler(ctrl, pair.handler); 94 | count++; 95 | break; 96 | } 97 | } 98 | if (count == events.Length) 99 | return; 100 | } 101 | } 102 | 103 | /* 104 | https://stackoverflow.com/a/26808856 105 | Honestly, where would I be without this site 106 | */ 107 | public static bool HasMouse(this Control c) 108 | { 109 | return c.ClientRectangle.Contains(c.PointToClient(Cursor.Position)); 110 | } 111 | 112 | /* 113 | Check to see if a control needs to be initialised. 114 | If the control has already been set, set its visibility depending on whether the new value is null. 115 | Note that in the case that both the given control and its intended new value are null, 116 | this will return false, as there is no initialisation that can be done. 117 | */ 118 | public static bool NeedsInit(this Control c, Control val) 119 | { 120 | if (c != null) 121 | c.Visible = val != null; 122 | 123 | return c == null && val != null; 124 | } 125 | 126 | public static void Reset(this ScrollBar bar) 127 | { 128 | bar.Minimum = 0; 129 | bar.Value = 0; 130 | } 131 | 132 | public static void Inform(this ScrollBar bar, int value, int large, int min, int max) 133 | { 134 | bar.LargeChange = large; 135 | bar.Minimum = min; 136 | bar.Maximum = max; 137 | if (bar.Visible && value >= bar.Minimum && value <= bar.Maximum) 138 | bar.Value = value; 139 | } 140 | 141 | public static void ToggleSmoothing(this Graphics g, bool smooth) 142 | { 143 | if (smooth) 144 | { 145 | g.InterpolationMode = InterpolationMode.Bilinear; 146 | g.PixelOffsetMode = PixelOffsetMode.Default; 147 | } 148 | else 149 | { 150 | g.InterpolationMode = InterpolationMode.NearestNeighbor; 151 | g.PixelOffsetMode = PixelOffsetMode.Half; 152 | } 153 | } 154 | 155 | public static Bitmap Scale(this Bitmap bmp, float scX, float scY = default(float), bool smooth = false) 156 | { 157 | if (object.Equals(scY, default(float))) 158 | scY = scX; 159 | 160 | int width = (int)((float)bmp.Width * scX); 161 | int height = (int)((float)bmp.Height * scY); 162 | 163 | Bitmap scaled = new Bitmap(width, height, bmp.PixelFormat); 164 | using (var g = Graphics.FromImage(scaled)) 165 | { 166 | g.ToggleSmoothing(smooth); 167 | g.DrawImage(bmp, 0, 0, width, height); 168 | } 169 | 170 | return scaled; 171 | } 172 | 173 | /* 174 | Combination of: 175 | - https://www.c-sharpcorner.com/article/drawing-transparent-images-and-shapes-using-alpha-blending/ 176 | - https://stackoverflow.com/a/38852476 177 | - the function above 178 | */ 179 | public static Bitmap SetAlpha(this Bitmap bmp, float alpha) 180 | { 181 | float[][] ptsArray = { 182 | new[] {1f, 0, 0, 0, 0}, 183 | new[] {0f, 1, 0, 0, 0}, 184 | new[] {0f, 0, 1, 0, 0}, 185 | new[] {0f, 0, 0, alpha, 0}, 186 | new[] {0f, 0, 0, 0, 1} 187 | }; 188 | 189 | Bitmap faded = new Bitmap(bmp.Width, bmp.Height, bmp.PixelFormat); 190 | using (var imgAttrs = new ImageAttributes()) 191 | { 192 | imgAttrs.SetColorMatrix(new ColorMatrix(ptsArray)); 193 | using (var g = Graphics.FromImage(faded)) 194 | { 195 | g.DrawImage( 196 | bmp, 197 | new Rectangle(0, 0, bmp.Width, bmp.Height), 198 | 0, 0, bmp.Width, bmp.Height, 199 | GraphicsUnit.Pixel, imgAttrs 200 | ); 201 | } 202 | } 203 | 204 | return faded; 205 | } 206 | 207 | public unsafe static void ClearBitmap(Bitmap img, Color clr, Rectangle area) 208 | { 209 | uint pix = 0xff000000 | (uint)clr.R << 16 | (uint)clr.G << 8 | (uint)clr.B; 210 | 211 | var data = img.LockBits( 212 | area, 213 | ImageLockMode.ReadWrite, 214 | PixelFormat.Format32bppArgb 215 | ); 216 | 217 | uint *fb = (uint*)data.Scan0.ToPointer(); 218 | int size = area.Width * area.Height; 219 | 220 | for (int i = 0; i < size; i++) 221 | *fb++ = pix; 222 | 223 | img.UnlockBits(data); 224 | } 225 | 226 | public static void Clear(this PictureBox box) 227 | { 228 | ClearBitmap(box.Image as Bitmap, box.BackColor, box.DisplayRectangle); 229 | box.Invalidate(); 230 | } 231 | 232 | public unsafe static void ApplyCheckerboard(Bitmap img, int chkSize, Color clr1, Color clr2) 233 | { 234 | uint[] shades = { 235 | 0xff000000 | (uint)clr1.R << 16 | (uint)clr1.G << 8 | (uint)clr1.B, 236 | 0xff000000 | (uint)clr2.R << 16 | (uint)clr2.G << 8 | (uint)clr2.B 237 | }; 238 | 239 | int width = img.Width; 240 | int height = img.Height; 241 | 242 | var data = img.LockBits( 243 | new Rectangle(0, 0, width, height), 244 | ImageLockMode.ReadWrite, 245 | PixelFormat.Format32bppArgb 246 | ); 247 | uint *fb = (uint*)data.Scan0.ToPointer(); 248 | 249 | for (int i = 0; i < width * height; i++) 250 | { 251 | int tileX = ((i % (chkSize * 2)) < chkSize) ? 1 : 0; 252 | int tileY = (((i / width) % (chkSize * 2)) < chkSize) ? 1 : 0; 253 | *fb++ = shades[tileX ^ tileY]; 254 | } 255 | 256 | img.UnlockBits(data); 257 | } 258 | 259 | public static int Between(this int n, int min, int max) 260 | { 261 | return Math.Max(Math.Min(n, max), min); 262 | } 263 | 264 | // Doesn't handle negative numbers, only for base-10 265 | public static int DigitCount(int n) 266 | { 267 | return n > 1 ? (int)Math.Log10(n) + 1 : 1; 268 | } 269 | 270 | public static uint GetBits(uint val, int len, int shift) 271 | { 272 | uint mask = (1u << len) - 1; 273 | return (val & (mask << shift)) >> shift; 274 | } 275 | 276 | public static uint ColorAt(uint clr, int rshift) 277 | { 278 | return (clr >> rshift) & 0xff; 279 | } 280 | public static double ColorAtF(uint clr, int rshift) 281 | { 282 | return (double)((clr >> rshift) & 0xff) / 255.0; 283 | } 284 | 285 | // Used to convert RGBA to BGRA and back again 286 | public static uint RedBlueSwap(uint rgba) 287 | { 288 | return 289 | (rgba & 0x00FF00FF) | 290 | ((rgba & 0xFF000000) >> 16) | 291 | ((rgba & 0x0000FF00) << 16) 292 | ; 293 | } 294 | 295 | public static uint ComposeBGRA(double b, double g, double r, double a) 296 | { 297 | return 298 | (((uint)(b * 255f) & 255) << 24) | 299 | (((uint)(g * 255f) & 255) << 16) | 300 | (((uint)(r * 255f) & 255) << 8) | 301 | ((uint)(a * 255f) & 255) 302 | ; 303 | } 304 | 305 | // Ignores alpha 306 | public static Color FromRGB(uint clr) 307 | { 308 | int rgb = (int)(clr >> 8); 309 | return Color.FromArgb(255, Color.FromArgb(rgb)); 310 | } 311 | 312 | public static uint InvertRGB(uint clr) 313 | { 314 | return (clr ^ 0xFFFFFF00) | 0xFF; 315 | } 316 | /* 317 | This function assumes 'input' is stored as red.green.blue.alpha 318 | so that it can be transformed into {blue, green, red, alpha}. 319 | The reason the order is shuffled is so that the pixel 320 | can be easily understood by the Windows graphics API. 321 | */ 322 | /* 323 | public static void EmbedPixel(byte[] output, uint input, int offset = 0) 324 | { 325 | output[offset] = (byte)((input >> 8) & 0xff); // blue 326 | output[offset+1] = (byte)((input >> 16) & 0xff); // green 327 | output[offset+2] = (byte)((input >> 24) & 0xff); // red 328 | output[offset+3] = (byte)(input & 0xff); // alpha 329 | } 330 | public static void EmbedPixel(uint[] output, uint input, int offset = 0) 331 | { 332 | output[offset] = 333 | (((input >> 8) & 0xff) << 24) | 334 | (((input >> 16) & 0xff) << 16) | 335 | (((input >> 24) & 0xff) << 8) | 336 | (input & 0xff); 337 | } 338 | */ 339 | 340 | public unsafe static Bitmap BitmapFrom(byte[] pixbuf, int width, int height) 341 | { 342 | // Allocate some memory that the GDI will have direct access to 343 | IntPtr mem = Marshal.AllocHGlobal(pixbuf.Length); 344 | // Copy our pixel buffer over 345 | Marshal.Copy(pixbuf, 0, mem, pixbuf.Length); 346 | 347 | // Create a bitmap to wrap around our new memory buffer, then create a copy of that bitmap. 348 | Bitmap bmp = new Bitmap( 349 | new Bitmap(width, height, width * 4, PixelFormat.Format32bppArgb, mem) 350 | ); 351 | 352 | // This means we can free that memory buffer now and not have to worry about it later. Inefficient but I don't care 353 | Marshal.FreeHGlobal(mem); 354 | return bmp; 355 | } 356 | 357 | public static readonly float[] AlphaShades = 358 | { 359 | 0.6f, 0.8f 360 | }; 361 | 362 | public static readonly string[] RGBANames = 363 | { 364 | "Red", "Green", "Blue", "Alpha" 365 | }; 366 | 367 | // Stored as RGBA 368 | // Palette created using Bisqwit's NES palette generator (https://bisqwit.iki.fi/utils/nespalette.php) with saturation set to 1.5 369 | public static readonly uint[] NESPalette = 370 | { 371 | 0x525252FF, 0x001C72FF, 0x0C0B92FF, 0x2A008FFF, 372 | 0x470068FF, 0x57002EFF, 0x550400FF, 0x411200FF, 373 | 0x232400FF, 0x063400FF, 0x003D00FF, 0x003A04FF, 374 | 0x002E39FF, 0x000000FF, 0x000000FF, 0x00000000, // Set colour 0xf to transparent. Non-standard but conventional. 375 | 0xA0A0A0FF, 0x0E4DCEFF, 0x3230FFFF, 0x621BF9FF, 376 | 0x8E12BFFF, 0xA71468FF, 0xA42315FF, 0x863C00FF, 377 | 0x575900FF, 0x287300FF, 0x087F00FF, 0x007C24FF, 378 | 0x00697AFF, 0x000000FF, 0x000000FF, 0x000000FF, 379 | 0xFEFFFFFF, 0x53A1FFFF, 0x827FFFFF, 0xBA65FFFF, 380 | 0xEB59FFFF, 0xFF5DC0FF, 0xFF6F5EFF, 0xE18E18FF, 381 | 0xADB000FF, 0x76CC00FF, 0x4BDA23FF, 0x36D670FF, 382 | 0x39C1D5FF, 0x3C3C3CFF, 0x000000FF, 0x000000FF, 383 | 0xFEFFFFFF, 0xB4D7FFFF, 0xC9C8FFFF, 0xE2BCFFFF, 384 | 0xF6B7FFFF, 0xFFB8E5FF, 0xFFC1B9FF, 0xF2CF96FF, 385 | 0xDCDD83FF, 0xC4EA85FF, 0xB0F09CFF, 0xA6EEC1FF, 386 | 0xA7E5EDFF, 0xA9A9A9FF, 0x000000FF, 0x000000FF 387 | }; 388 | 389 | // A hand-picked selection from the table above 390 | public static readonly uint[] NESDefSel = 391 | { 392 | // dark blue, green, bright yellow, white 393 | 0x0C, 0x1A, 0x37, 0x30 394 | }; 395 | 396 | public const uint SNESRGBAOrderAndDepth = 0x12305551; 397 | 398 | // A hand-picked selection of SNES-compatible RGB colors 399 | public static readonly uint[] SNESDefSel = 400 | { 401 | 0x8000, 0x8008, 0x804C, 0x808E, // black -> dark blue 402 | 0x818C, 0x9188, 0x9208, 0x9284, // dark blue green -> green 403 | 0xB284, 0xD2C4, 0xEB48, 0xFF48, // green -> bright yellow 404 | 0xFFA8, 0xFFD0, 0xFFF8, 0xFFFF // bright yellow -> white 405 | }; 406 | 407 | public const uint MDRGBAOrderAndDepth = 0x12303331; 408 | 409 | public static readonly uint[] MDDefSel = 410 | { 411 | 0x0200, 0x0280, 0x02C0, 0x02C8, // black -> dark red 412 | 0x02D8, 0x0299, 0x02A1, 0x0269, // dark orange -> green 413 | 0x026B, 0x026D, 0x02B6, 0x02B7, // green -> bright cyan 414 | 0x02BF, 0x033F, 0x03BF, 0x03FF // bright cyan -> white 415 | }; 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /Source/Windows/InputWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public partial class InputWindow 8 | { 9 | public override void Activate(ToolBox toolBox) 10 | { 11 | _vis.col = _cl.Columns; 12 | AdjustWindow(); 13 | base.Activate(toolBox); 14 | } 15 | 16 | public override void Clear() 17 | { 18 | base.Clear(); 19 | this.Prompt = "Open a file containing tiles to begin!"; 20 | } 21 | 22 | protected override void SetupWindowUI() 23 | { 24 | _window.Name = "inputBox"; 25 | _scrollX.Name = "inputScrollX"; 26 | _scrollY.Name = "inputScrollY"; 27 | _menu.Name = "inputMenu"; 28 | 29 | _window.MouseLeave += (s, e) => Draw(); 30 | _scrollX.Enabled = false; 31 | } 32 | 33 | protected override void InitializeTabs() 34 | { 35 | _tabs = new List(); 36 | _tabs.Add(new InputControlsTab(this)); 37 | } 38 | 39 | protected override void InitializeRightClickMenu(MainForm.TileAction copyTile, MainForm.TileAction pasteTile = null) 40 | { 41 | _menu.Items.Add(new ToolStripMenuItem("Copy Tile", null, (s, e) => copyTile(this), "copyTile")); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Source/Windows/InputWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public partial class InputWindow : TileWindow 8 | { 9 | private byte[] _contents; 10 | 11 | // camera offset, number of visible tiles 12 | int _row; 13 | protected Position _vis; 14 | 15 | public override SizeF TileDimensions 16 | { 17 | get { 18 | return new SizeF( 19 | (float)_window.Width / (float)_vis.col, 20 | (float)_window.Height / (float)_vis.row 21 | ); 22 | } 23 | } 24 | 25 | public override Rectangle VisibleCollageBounds 26 | { 27 | get 28 | { 29 | return new Rectangle( 30 | 0, 31 | _row * _cl.TileH, 32 | _vis.col * _cl.TileW, 33 | _vis.row * _cl.TileH 34 | ); 35 | } 36 | } 37 | 38 | public InputControlsTab ControlsTab { get { return this["Controls"] as InputControlsTab; } } 39 | 40 | public Tile TileSample 41 | { 42 | set { 43 | var input = ControlsTab; 44 | if (input != null) 45 | input.Sample = TileBitmap(value); 46 | } 47 | } 48 | 49 | public InputWindow(MainForm main) 50 | : base(main) 51 | { 52 | _row = 0; 53 | _vis = new Position(0, 0); 54 | DeleteFrame(); 55 | } 56 | 57 | public void Load(MainForm main, FileFormat fmt, byte[] file, int offset = 0) 58 | { 59 | _selPos = new Position(0, 0); 60 | Selected = false; 61 | 62 | _contents = file; 63 | _cl = new Collage(fmt); 64 | _cl.LoadTiles(_contents, offset); 65 | Render(); 66 | 67 | Activate(main.toolBox); 68 | ControlsTab.SizeText = file.Length; 69 | TileSample = null; 70 | 71 | ResetScroll(); 72 | } 73 | 74 | public void Load(int offset) 75 | { 76 | if (offset < 0 || offset >= _contents.Length) 77 | return; 78 | 79 | _selPos = new Position(0, 0); 80 | Selected = false; 81 | TileSample = null; 82 | 83 | if (_cl != null && _contents != null) 84 | { 85 | _cl.LoadTiles(_contents, offset); 86 | Render(); 87 | } 88 | 89 | ResetScroll(); 90 | Draw(); 91 | } 92 | 93 | public override void Scroll(float dx, float dy) 94 | { 95 | if (_cl == null) 96 | return; 97 | 98 | int lastRow = _cl.Rows - _vis.row; 99 | _row += (int)dy; 100 | _row = _row.Between(0, lastRow); 101 | 102 | Draw(); 103 | } 104 | public override void ScrollTo(float x, float y) 105 | { 106 | Scroll(0, (int)y - _row); 107 | } 108 | 109 | public void ResetScroll() 110 | { 111 | AdjustWindow(); 112 | _scrollY.Reset(); 113 | ScrollTo(0, 0); 114 | } 115 | 116 | public override void UpdateBars() 117 | { 118 | if (_cl != null) 119 | _scrollY.Inform(_row, _vis.row, 0, _cl.Rows); 120 | } 121 | 122 | public override Position GetPosition(int x, int y, out bool wasOob) 123 | { 124 | wasOob = false; 125 | 126 | SizeF tileSc = TileDimensions; 127 | 128 | var pos = new Position( 129 | (int)((float)x / tileSc.Width), 130 | (int)((float)y / tileSc.Height) 131 | ); 132 | 133 | pos.row += _row; 134 | return pos; 135 | } 136 | 137 | public override void MoveSelection(int dCol, int dRow) 138 | { 139 | base.MoveSelection(dCol, dRow); 140 | 141 | if (_selPos.row >= _row + _vis.row) 142 | { 143 | _row = _selPos.row - _vis.row + 1; 144 | UpdateBars(); 145 | } 146 | else if (_selPos.row < _row) 147 | { 148 | _row = _selPos.row; 149 | UpdateBars(); 150 | } 151 | } 152 | 153 | public override RectangleF PieceHitbox(Position p) 154 | { 155 | int col = p.col; 156 | int row = p.row - _row; 157 | SizeF tileSc = TileDimensions; 158 | 159 | return new RectangleF( 160 | (float)col * tileSc.Width, 161 | (float)row * tileSc.Height, 162 | (int)tileSc.Width + 1, 163 | (int)tileSc.Height + 1 164 | ); 165 | } 166 | 167 | public override void AdjustWindow(int width = 0, int height = 0) 168 | { 169 | if (_cl == null || _window == null) 170 | return; 171 | 172 | int wndW = width > 0 ? width : _window.Width; 173 | int wndH = height > 0 ? height : _window.Height; 174 | 175 | if (wndW <= 0 || wndH <= 0) 176 | return; 177 | 178 | float thF = (float)_cl.TileH; 179 | float scaleX = (float)wndW / (float)_cl.Width; 180 | 181 | // The number of visible rows is the foundation from which all InputWindow sizing is based 182 | float visRowsF = (float)wndH / (scaleX * thF); 183 | visRowsF = (float)Math.Round(visRowsF); 184 | _vis.row = (int)visRowsF; 185 | 186 | int rows = _cl.Rows; 187 | bool scroll = true; 188 | if (_vis.row > rows) 189 | { 190 | scroll = false; 191 | _vis.row = rows; 192 | wndH = (int)((float)_vis.row * thF * scaleX); 193 | } 194 | 195 | if (_row + _vis.row > rows) 196 | _row = Math.Max(rows - _vis.row, 0); 197 | 198 | _window.Width = wndW; 199 | if (wndH <= _window.Height) 200 | _window.Height = wndH; 201 | 202 | _scrollY.Visible = scroll; 203 | } 204 | 205 | public override void DrawGrid(Graphics g) 206 | { 207 | int wndW = _window.Width; 208 | int wndH = _window.Height; 209 | Pen p = _cl.GridPen; 210 | 211 | // Draw vertical margins 212 | float tlW = (float)wndW / (float)_vis.col; 213 | for (int i = 0; i < _vis.col - 1; i++) 214 | { 215 | float x = (float)(i + 1) * tlW; 216 | g.DrawLine(p, x, 0, x, wndH); 217 | } 218 | 219 | // Draw horizontal margins 220 | float tlH = (float)wndH / (float)_vis.row; 221 | for (int i = 0; i < _vis.row - 1; i++) 222 | { 223 | float y = (float)(i + 1) * tlH; 224 | g.DrawLine(p, 0, y, wndW, y); 225 | } 226 | } 227 | 228 | protected override void windowScrollAction(object sender, MouseEventArgs e) 229 | { 230 | Scroll(0, -e.Delta / 120); 231 | UpdateBars(); 232 | } 233 | 234 | protected override void yScrollAction(object sender, ScrollEventArgs e) 235 | { 236 | ScrollTo(0, e.NewValue); 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Source/Windows/SpriteWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | public partial class SpriteWindow 8 | { 9 | public override void Activate(ToolBox toolBox) 10 | { 11 | _zoom = InitialZoom; 12 | UpdateBars(); 13 | Centre(); 14 | base.Activate(toolBox); 15 | } 16 | 17 | protected override void SetupWindowUI() 18 | { 19 | _window.Name = "spriteBox"; 20 | _scrollX.Name = "spriteScrollX"; 21 | _scrollY.Name = "spriteScrollY"; 22 | _menu.Name = "spriteMenu"; 23 | 24 | _window.MouseLeave += (s, e) => { _hlEdge = null; Draw(); }; 25 | } 26 | 27 | protected override void InitializeTabs() 28 | { 29 | _tabs = new List(); 30 | _tabs.Add(new PaletteTab(_main, this)); 31 | _tabs.Add(new SpriteControlsTab(this)); 32 | } 33 | 34 | protected override void InitializeRightClickMenu(MainForm.TileAction copyTile, MainForm.TileAction pasteTile = null) 35 | { 36 | _menu.Items.Add(new ToolStripMenuItem("Copy Tile", null, (s, e) => copyTile(this), "copyTile")); 37 | _menu.Items.Add(new ToolStripMenuItem("Paste Tile", null, (s, e) => pasteTile(this), "pasteTile")); 38 | 39 | _menu.Items.Add(new ToolStripMenuItem("Erase Tile", null, (s, e) => EraseTile(), "eraseTile")); 40 | _menu.Items.Add(new ToolStripSeparator()); 41 | 42 | _menu.Items.Add( 43 | new ToolStripMenuItem( 44 | "Rotate Tile", null, new[] { 45 | new ToolStripMenuItem("Left", null, (s, e) => FlipTile(Translation.Left), "rotateLeft"), 46 | new ToolStripMenuItem("Right", null, (s, e) => FlipTile(Translation.Right), "rotateRight") 47 | } 48 | ) 49 | ); 50 | _menu.Items.Add( 51 | new ToolStripMenuItem( 52 | "Mirror Tile", null, new[] { 53 | new ToolStripMenuItem("Horizontally", null, (s, e) => FlipTile(Translation.Horizontal), "mirrorHori"), 54 | new ToolStripMenuItem("Vertically", null, (s, e) => FlipTile(Translation.Vertical), "mirrorVert") 55 | } 56 | ) 57 | ); 58 | _menu.Items.Add(new ToolStripSeparator()); 59 | 60 | _menu.Items.Add( 61 | new ToolStripMenuItem( 62 | "Insert", null, new[] { 63 | new ToolStripMenuItem("Column Left", null, (s, e) => InsertCollageColumn(_selPos.col), "insertLeft"), 64 | new ToolStripMenuItem("Column Right", null, (s, e) => InsertCollageColumn(_selPos.col+1), "insertRight"), 65 | new ToolStripMenuItem("Row Above", null, (s, e) => InsertCollageRow(_selPos.row), "insertAbove"), 66 | new ToolStripMenuItem("Row Below", null, (s, e) => InsertCollageRow(_selPos.row+1), "insertBelow") 67 | } 68 | ) 69 | ); 70 | 71 | _menu.Items.Add( 72 | new ToolStripMenuItem( 73 | "Delete", null, new[] { 74 | new ToolStripMenuItem("Column", null, (s, e) => DeleteCollageColumn(_selPos.col), "deleteCol"), 75 | new ToolStripMenuItem("Row", null, (s, e) => DeleteCollageRow(_selPos.row), "deleteRow") 76 | } 77 | ) 78 | ); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Source/Windows/TileWindow.Designer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace SpriteWave 7 | { 8 | abstract partial class TileWindow 9 | { 10 | protected readonly Color _emptyBackColor = Color.FromArgb(200, 200, 200); 11 | 12 | protected PictureBox _window; 13 | protected HScrollBar _scrollX; 14 | protected VScrollBar _scrollY; 15 | protected ContextMenuStrip _menu; 16 | 17 | protected Label _prompt; 18 | public string Prompt 19 | { 20 | set { 21 | bool state = !String.IsNullOrEmpty(value); 22 | 23 | _prompt.Text = value; 24 | _prompt.Enabled = state; 25 | _prompt.Visible = state; 26 | AdjustPrompt(); 27 | } 28 | } 29 | 30 | protected List _tabs; 31 | 32 | public int TabCount { get { return _tabs.Count; } } 33 | 34 | public ITab this[int idx] 35 | { 36 | get { 37 | if (_tabs == null || idx < 0 || idx >= _tabs.Count) 38 | return null; 39 | 40 | return _tabs[idx]; 41 | } 42 | } 43 | public ITab this[string name] 44 | { 45 | get { 46 | if (_tabs == null) 47 | return null; 48 | 49 | foreach (ITab t in _tabs) 50 | { 51 | if (t.Name == name) 52 | return t; 53 | } 54 | 55 | return null; 56 | } 57 | } 58 | 59 | public Point CanvasPos { get { return _window.Location; } } 60 | public Size CanvasSize { get { return _window.Size; } } 61 | 62 | public int ScrollYWidth { get { return _scrollY.Width; } } 63 | public int ScrollXHeight { get { return _scrollX.Height; } } 64 | 65 | protected abstract void SetupWindowUI(); 66 | protected abstract void InitializeRightClickMenu(MainForm.TileAction copyTile, MainForm.TileAction pasteTile = null); 67 | protected abstract void InitializeTabs(); 68 | 69 | protected virtual void DisposeTabs() {} 70 | 71 | public void Dispose() 72 | { 73 | _window.Dispose(); 74 | _scrollX.Dispose(); 75 | _scrollY.Dispose(); 76 | _menu.Dispose(); 77 | DisposeTabs(); 78 | } 79 | 80 | protected void InitializeUI(MainForm main) 81 | { 82 | _window = new PictureBox(); 83 | _scrollX = new HScrollBar(); 84 | _scrollY = new VScrollBar(); 85 | _menu = new ContextMenuStrip(); 86 | _prompt = new Label(); 87 | 88 | ((System.ComponentModel.ISupportInitialize)(_window)).BeginInit(); 89 | 90 | _window.Resize += this.adjustWindowSize; 91 | _window.MouseWheel += this.windowScrollAction; 92 | 93 | _scrollX.Size = new Size(331, 17); 94 | _scrollX.Scroll += xScrollAction; 95 | 96 | _scrollY.Size = new Size(17, 518); 97 | _scrollY.Scroll += yScrollAction; 98 | 99 | _menu.Size = new Size(61, 4); 100 | InitializeRightClickMenu(main.CopyTile, main.PasteTile); 101 | ToggleMenu(false); 102 | 103 | _prompt.Parent = _window; 104 | _prompt.AutoSize = true; 105 | _prompt.ForeColor = Color.Black; 106 | _prompt.BackColor = Color.Transparent; 107 | this.Prompt = ""; 108 | 109 | SetupWindowUI(); 110 | 111 | Clear(); 112 | 113 | main.Controls.Add(_window); 114 | main.Controls.Add(_scrollX); 115 | main.Controls.Add(_scrollY); 116 | 117 | ((System.ComponentModel.ISupportInitialize)(_window)).EndInit(); 118 | } 119 | 120 | public virtual void Activate(ToolBox toolBox) 121 | { 122 | this.Prompt = ""; 123 | 124 | _window.BackColor = SystemColors.ControlDark; 125 | 126 | _scrollX.Visible = true; 127 | _scrollY.Visible = true; 128 | 129 | ToggleMenu(true); 130 | 131 | InitializeTabs(); 132 | ProvideTabButtons(toolBox); 133 | } 134 | 135 | public virtual void Clear() 136 | { 137 | ToggleMenu(false); 138 | if (_tabs != null) 139 | ToggleTabsContents(false); 140 | 141 | _cl = null; 142 | DeleteFrame(); 143 | 144 | _scrollX.Visible = false; 145 | _scrollY.Visible = false; 146 | 147 | _window.BackColor = _emptyBackColor; 148 | } 149 | public virtual void Close(MainForm main) 150 | { 151 | Clear(); 152 | 153 | if (_tabs == null) 154 | return; 155 | 156 | for (int i = 0; i < _tabs.Count; i++) 157 | { 158 | if (_tabs[i] == null) 159 | continue; 160 | 161 | main.toolBox.RemoveTabButton(_tabs[i].TabButton); 162 | _tabs[i] = null; 163 | } 164 | } 165 | 166 | public void UpdateLayout(int x, int w, int totalH, int menuH) 167 | { 168 | w -= _scrollY.Width; 169 | 170 | //_window.SuspendLayout(); 171 | _window.Location = new Point(x, menuH); 172 | _window.Size = new Size(w, totalH - (menuH + _scrollX.Height)); 173 | //_window.ResumeLayout(); 174 | 175 | _scrollX.Location = new Point(x, menuH + _window.Height); 176 | _scrollX.Size = new Size(w, _scrollX.Height); 177 | 178 | _scrollY.Location = new Point(x + w, menuH); 179 | _scrollY.Size = new Size(_scrollY.Width, _window.Height); 180 | 181 | AdjustPrompt(); 182 | } 183 | 184 | private void AdjustPrompt() 185 | { 186 | if (!_prompt.Visible) 187 | return; 188 | 189 | _prompt.Location = new Point( 190 | (_window.Width - _prompt.Width) / 2, 191 | (_window.Height - _prompt.Height) / 2 192 | ); 193 | } 194 | 195 | public void ReduceWindowTo(int maxH) 196 | { 197 | if (_window.Height <= maxH) 198 | return; 199 | 200 | _window.Size = new Size( 201 | _window.Width, 202 | maxH 203 | ); 204 | _scrollX.Location = new Point( 205 | _window.Location.X, 206 | _window.Location.Y + maxH 207 | ); 208 | _scrollY.Size = new Size( 209 | _scrollY.Width, 210 | maxH 211 | ); 212 | } 213 | 214 | // Implements ITabCollection.ConfigureTabs 215 | public int TabIndex(ITab t) 216 | { 217 | return _tabs.IndexOf(t); 218 | } 219 | 220 | public void ToggleTabsContents(bool state) 221 | { 222 | foreach (ITab t in _tabs) 223 | { 224 | if (t == null) 225 | continue; 226 | 227 | foreach (Control c in t.Panel.Controls) 228 | c.Visible = state; 229 | } 230 | } 231 | 232 | public void ToggleMenu(bool state) 233 | { 234 | foreach (ToolStripItem item in _menu.Items) 235 | item.Visible = state; 236 | } 237 | 238 | public void EnablePaste() 239 | { 240 | int idx = _menu.Items.IndexOfKey("pasteTile"); 241 | if (idx >= 0) 242 | _menu.Items[idx].Visible = true; 243 | } 244 | 245 | public void ShowMenu(int x, int y) 246 | { 247 | if ( _window == null) 248 | return; 249 | 250 | // If a piece has been copied to our clipboard, enable the "Paste Tile" option (if there is one) 251 | int pasteIdx = _menu.Items.IndexOfKey("pasteTile"); 252 | if (pasteIdx >= 0) 253 | _menu.Items[pasteIdx].Enabled = Transfer.HasPiece; 254 | 255 | _menu.Show(_window, new Point(x, y)); 256 | } 257 | 258 | public void Focus(MainForm main) 259 | { 260 | main.ActiveControl = _window; 261 | } 262 | public bool WindowIs(Control c) 263 | { 264 | return c == _window || c == _prompt; 265 | } 266 | 267 | public void ProvideTabButtons(ToolBox box) 268 | { 269 | foreach (ITab t in _tabs) 270 | box.AddTabButton(t.TabButton); 271 | } 272 | 273 | public void RescindTabButtons(ToolBox box) 274 | { 275 | foreach (ITab t in _tabs) 276 | box.RemoveTabButton(t.TabButton); 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /Source/Windows/TileWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Drawing.Imaging; 4 | using System.Windows.Forms; 5 | 6 | namespace SpriteWave 7 | { 8 | public abstract partial class TileWindow : ITabCollection 9 | { 10 | protected Collage _cl; 11 | 12 | public abstract SizeF TileDimensions { get; } 13 | public abstract Rectangle VisibleCollageBounds { get; } 14 | 15 | protected Brush _defHl, _cursorHl; 16 | protected Brush _selHl; 17 | 18 | protected Selection _cursor; 19 | protected Position _selPos; 20 | protected bool _isSel = false; 21 | 22 | protected Rectangle _bounds; 23 | 24 | public Collage Collage { get { return _cl; } } 25 | 26 | public bool IsActive { get { return _cl != null; } } 27 | 28 | public virtual bool Selected { get { return _isSel; } set { _isSel = value; } } 29 | 30 | public Position Position 31 | { 32 | get { 33 | Selected = true; 34 | return _selPos; 35 | } 36 | set { 37 | _selPos = value; 38 | Selected = true; 39 | } 40 | } 41 | 42 | public Selection Cursor 43 | { 44 | set { 45 | _cursor = value; 46 | if (_cursor == null) 47 | _selHl = _defHl; 48 | else 49 | { 50 | _selHl = _cursorHl; 51 | Selected = true; 52 | } 53 | } 54 | get { 55 | return _cursor; 56 | } 57 | } 58 | 59 | public Selection CurrentSelection() 60 | { 61 | if (!_isSel) 62 | return null; 63 | 64 | return _cursor ?? new Selection(this.PieceAt(_selPos), this, _selPos); 65 | } 66 | 67 | public void AdoptCursor() 68 | { 69 | if (_cursor != null) 70 | this.Position = _cursor.Location; 71 | } 72 | 73 | public virtual void ResizeCollage(Edge msg) {} 74 | public virtual void ReceiveTile(Tile t) {} 75 | public virtual void ReceiveTile(Tile t, Position loc) {} 76 | public virtual void DeleteSelection() {} 77 | 78 | public virtual void AdjustWindow(int width = 0, int height = 0) {} 79 | 80 | protected virtual void xScrollAction(object sender, ScrollEventArgs e) {} 81 | protected abstract void yScrollAction(object sender, ScrollEventArgs e); 82 | 83 | protected abstract void windowScrollAction(object sender, MouseEventArgs e); 84 | //protected abstract void adjustWindowSize(object sender, EventArgs e); 85 | 86 | public abstract void Scroll(float dx, float dy); 87 | public abstract void ScrollTo(float x, float y); 88 | 89 | public abstract void UpdateBars(); 90 | 91 | public abstract Position GetPosition(int x, int y, out bool wasOob); 92 | public virtual Position GetPosition(int x, int y) 93 | { 94 | bool _; 95 | return GetPosition(x, y, out _); 96 | } 97 | 98 | public abstract RectangleF PieceHitbox(Position p); 99 | 100 | public abstract void DrawGrid(Graphics g); 101 | 102 | protected TileWindow(MainForm main) 103 | { 104 | _selPos = new Position(0, 0); 105 | _defHl = new SolidBrush(Color.FromArgb(96, 0, 64, 255)); 106 | _cursorHl = new SolidBrush(Color.FromArgb(96, 0, 255, 64)); 107 | _selHl = _defHl; 108 | 109 | InitializeUI(main); 110 | } 111 | 112 | public virtual void Render() 113 | { 114 | _cl.Render(); 115 | } 116 | 117 | public virtual IPiece PieceAt(Position loc) 118 | { 119 | if (_cl == null) 120 | return null; 121 | 122 | return _cl.TileAt(loc); 123 | } 124 | 125 | public void DeleteFrame() 126 | { 127 | if (_window.Image != null) 128 | { 129 | _window.Image.Dispose(); 130 | _window.Image = null; 131 | } 132 | } 133 | 134 | public virtual void MoveSelection(int dCol, int dRow) 135 | { 136 | if (_cl == null) 137 | return; 138 | 139 | int nCols = _cl.Columns; 140 | int idx = (_selPos.row + dRow) * nCols + _selPos.col + dCol; 141 | 142 | if (idx < 0) 143 | idx = 0; 144 | else if (idx >= _cl.nTiles) 145 | idx = _cl.nTiles - 1; 146 | 147 | this.Position = new Position(idx % nCols, idx / nCols); 148 | } 149 | 150 | public Bitmap TileBitmap(Tile t) 151 | { 152 | if (t == null || _cl == null) 153 | return null; 154 | 155 | return _cl.RenderTile(t); 156 | } 157 | 158 | public virtual EdgeKind EdgeOf(Position p) { return EdgeKind.None; } 159 | public virtual PointF[] ShapeEdge(Edge edge) { return null; } 160 | 161 | public virtual void DrawCanvas(Graphics g) 162 | { 163 | Rectangle clBounds = VisibleCollageBounds; 164 | if (clBounds.Width <= 0 || clBounds.Height <= 0) 165 | return; 166 | 167 | // Select the subset of the window's collage to render 168 | using (Bitmap canvas = _cl.Bitmap.Clone(clBounds, PixelFormat.Format32bppArgb)) 169 | { 170 | // Draw the visible section of the collage. This method automatically resizes our provided bitmap before drawing it. 171 | g.DrawImage(canvas, 0, 0, _window.Width, _window.Height); 172 | } 173 | } 174 | 175 | public void DrawSelection(Graphics g) 176 | { 177 | if (!_isSel || _selHl == null) 178 | return; 179 | 180 | Position loc; 181 | IPiece obj; 182 | if (_cursor != null) 183 | { 184 | loc = _cursor.Location; 185 | obj = _cursor.Piece; 186 | } 187 | else 188 | { 189 | loc = _selPos; 190 | obj = PieceAt(_selPos); 191 | } 192 | 193 | if (obj != null && obj.EdgeKind != EdgeKind.None) 194 | g.FillPolygon(_selHl, ShapeEdge(obj as Edge)); 195 | else 196 | g.FillRectangle(_selHl, PieceHitbox(loc)); 197 | } 198 | 199 | public virtual void DrawEdges(Graphics g) {} 200 | 201 | public virtual void Draw() 202 | { 203 | if (_cl == null || _window == null) 204 | return; 205 | 206 | int wndW = _window.Width; 207 | int wndH = _window.Height; 208 | if (wndW <= 0 || wndH <= 0) 209 | return; 210 | 211 | // Make sure the window's collage has something for us to draw 212 | if (_cl.Bitmap == null) 213 | Render(); 214 | 215 | if (_window.Image == null) 216 | { 217 | _window.Image = new Bitmap(wndW, wndH, PixelFormat.Format32bppArgb); 218 | } 219 | else if (_window.Image.Width != wndW || _window.Image.Height != wndH) 220 | { 221 | DeleteFrame(); 222 | _window.Image = new Bitmap(wndW, wndH, PixelFormat.Format32bppArgb); 223 | } 224 | else 225 | _window.Clear(); 226 | 227 | using (var g = Graphics.FromImage(_window.Image)) 228 | { 229 | /* 230 | Here, we first disable pixel interpolation (blurring). This is because the section of the collage we just selected 231 | is almost certainly going to be smaller or larger than the space we want to render it to. 232 | The aim is to retain the blocky, "8-bit" look, while maximising the available space. 233 | */ 234 | g.ToggleSmoothing(false); 235 | 236 | // Draw the tiles 237 | DrawCanvas(g); 238 | 239 | // Highlight the current tile, if one is currently selected 240 | DrawSelection(g); 241 | 242 | // Draw some borders to indicate that the window's collage can be resized 243 | // Only implemented in SpriteWindow 244 | DrawEdges(g); 245 | 246 | // In order to more easily discern between tiles on the screen, we draw margins around each tile. 247 | DrawGrid(g); 248 | } 249 | } 250 | 251 | protected virtual void adjustWindowSize(object sender, EventArgs e) 252 | { 253 | AdjustWindow(_window.Width, _window.Height); 254 | //Draw(); 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /SpriteWave.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {E44CB3DF-76C7-46B8-886A-C2479FE03B79} 5 | {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 6 | Debug 7 | AnyCPU 8 | WinExe 9 | SpriteWave 10 | SpriteWave 11 | v4.0 12 | Properties 13 | False 14 | True 15 | False 16 | False 17 | obj\$(Configuration)\ 18 | 4 19 | 20 | 21 | x86 22 | 4194304 23 | False 24 | Auto 25 | 4096 26 | 27 | 28 | bin\Debug\ 29 | True 30 | Full 31 | False 32 | True 33 | DEBUG;TRACE 34 | obj\ 35 | 36 | 37 | bin\Release\ 38 | False 39 | None 40 | True 41 | False 42 | TRACE 43 | 44 | 45 | 46 | 4.0 47 | 48 | 49 | 50 | 3.5 51 | 52 | 53 | 54 | 3.5 55 | 56 | 57 | 58 | 59 | 60 | 3.5 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | MainForm.cs 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | InputWindow.cs 83 | 84 | 85 | SpriteWindow.cs 86 | 87 | 88 | TileWindow.cs 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | ColorPicker.cs 126 | 127 | 128 | MainForm.cs 129 | 130 | 131 | SpriteControlsTab.cs 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /SpriteWave.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | # SharpDevelop 5.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SpriteWave", "SpriteWave.csproj", "{E44CB3DF-76C7-46B8-886A-C2479FE03B79}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {E44CB3DF-76C7-46B8-886A-C2479FE03B79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {E44CB3DF-76C7-46B8-886A-C2479FE03B79}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {E44CB3DF-76C7-46B8-886A-C2479FE03B79}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {E44CB3DF-76C7-46B8-886A-C2479FE03B79}.Release|Any CPU.Build.0 = Release|Any CPU 17 | EndGlobalSection 18 | EndGlobal 19 | -------------------------------------------------------------------------------- /Tests/0_CompileTest.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set CSC="%CD:~0,3%Tools\Mono\bin\csc.bat" 4 | 5 | if "%~1"=="" ( 6 | echo No input file was given 7 | echo Try typing "%0 " 8 | goto exit 9 | ) 10 | 11 | call %CSC% -unsafe "%~1" ..\Source\Utils.cs ..\Source\Suffix.cs ..\Source\GFX\ColorTable.cs ..\Source\GFX\ColorPattern.cs 12 | 13 | :exit 14 | echo. 15 | pause 16 | exit /b 0 -------------------------------------------------------------------------------- /Tests/1_RunTest.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set MONO="%CD:~0,3%Tools\Mono\bin\mono.exe" 4 | 5 | if "%~1"=="" ( 6 | echo No input file was given 7 | echo Try typing "%0 " 8 | goto exit 9 | ) 10 | 11 | call %MONO% "%~1" 12 | pause -------------------------------------------------------------------------------- /Tests/SuffixChars.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Drawing; 4 | using System.Drawing.Drawing2D; 5 | 6 | namespace SpriteWave 7 | { 8 | class Characters 9 | { 10 | static byte[] tileset = 11 | { 12 | 0x38, 0x4C, 0xC6, 0xC6, 0xC6, 0x64, 0x38, 0x00, // 0 13 | 0x18, 0x38, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, // 1 14 | 0x7C, 0xC6, 0x0E, 0x3C, 0x78, 0xE0, 0xFE, 0x00, // 2 15 | 0x7E, 0x0C, 0x18, 0x3C, 0x06, 0xC6, 0x7C, 0x00, // 3 16 | 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x00, // 4 17 | 0xFC, 0xC0, 0xFC, 0x06, 0x06, 0xC6, 0x7C, 0x00, // 5 18 | 0x3C, 0x60, 0xC0, 0xFC, 0xC6, 0xC6, 0x7C, 0x00, // 6 19 | 0xFE, 0xC6, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x00, // 7 20 | 0x7C, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0x7C, 0x00, // 8 21 | 0x7C, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0x78, 0x00, // 9 22 | 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00, // A 23 | 0xFC, 0xC6, 0xC6, 0xFC, 0xC6, 0xC6, 0xFC, 0x00, // B 24 | 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, // C 25 | 0xF8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0xF8, 0x00, // D 26 | 0xFE, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xFE, 0x00, // E 27 | 0xFE, 0xC0, 0xC0, 0xFC, 0xC0, 0xC0, 0xC0, 0x00, // F 28 | 0x3E, 0x60, 0xC0, 0xCE, 0xC6, 0x66, 0x3E, 0x00, // G 29 | 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, // H 30 | 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, // I 31 | 0x1E, 0x06, 0x06, 0x06, 0xC6, 0xC6, 0x7C, 0x00, // J 32 | 0xC6, 0xCC, 0xD8, 0xF0, 0xF8, 0xDC, 0xCE, 0x00, // K 33 | 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x7E, 0x00, // L 34 | 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, // M 35 | 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0x00, // N 36 | 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, // O 37 | 0xFC, 0xC6, 0xC6, 0xC6, 0xFC, 0xC0, 0xC0, 0x00, // P 38 | 0x7C, 0xC6, 0xC6, 0xC6, 0xDE, 0xCC, 0x7A, 0x00, // Q 39 | 0xFC, 0xC6, 0xC6, 0xCE, 0xF8, 0xDC, 0xCE, 0x00, // R 40 | 0x78, 0xCC, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x00, // S 41 | 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, // T 42 | 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, // U 43 | 0xC6, 0xC6, 0xC6, 0xEE, 0x7C, 0x38, 0x10, 0x00, // V 44 | 0xC6, 0xC6, 0xD6, 0xFE, 0xFE, 0xEE, 0xC6, 0x00, // W 45 | 0xC6, 0xEE, 0x7C, 0x38, 0x7C, 0xEE, 0xC6, 0x00, // X 46 | 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x00, // Y 47 | 0xFE, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE, 0x00 // Z 48 | }; 49 | 50 | static byte[] bmpHeader = new byte[] 51 | { 52 | (byte)'B', (byte)'M', 53 | 0, 0, 0, 0, // file size 54 | 0, 0, 0, 0, 55 | 0x3E, 0, 0, 0, // data offset 56 | 0x28, 0, 0, 0, // dib header size 57 | 0, 0, 0, 0, // width 58 | 8, 0, 0, 0, // height 59 | 1, 0, 60 | 1, 0, // bits per pixel 61 | 0, 0, 0, 0, 62 | 0, 0, 0, 0, // data size 63 | 0, 0, 0, 0, 64 | 0, 0, 0, 0, 65 | 0, 0, 0, 0, 66 | 0, 0, 0, 0, 67 | 0, 0, 0, 0xFF, // palette - black 68 | 0xFF, 0xFF, 0xFF, 0xFF // white 69 | }; 70 | 71 | const int Scale = 10; 72 | 73 | void Embed(byte[] array, int n, int offset) 74 | { 75 | uint val = (uint)n; 76 | // not sure how effective loop rolling is in .NET but let's give it a go 77 | array[offset+3] = (byte)((val >> 24) & 0xff); 78 | array[offset+2] = (byte)((val >> 16) & 0xff); 79 | array[offset+1] = (byte)((val >> 8) & 0xff); 80 | array[offset] = (byte)(val & 0xff); 81 | } 82 | 83 | void ProduceImage(Graphics g, Suffix suff, int num) 84 | { 85 | var tiles = new int[suff.Digits]; 86 | int n = num; 87 | for (int i = 0; i < suff.Digits; i++) 88 | { 89 | tiles[suff.Digits-i-1] = n % suff.Base; 90 | n /= suff.Base; 91 | } 92 | 93 | int rowLen = suff.Digits; 94 | rowLen += (4 - (suff.Digits % 4)) % 4; 95 | int pixSize = rowLen * 8; 96 | 97 | var bmp = new byte[bmpHeader.Length + pixSize]; 98 | Buffer.BlockCopy(bmpHeader, 0, bmp, 0, bmpHeader.Length); 99 | 100 | Embed(bmp, bmp.Length, 2); 101 | Embed(bmp, suff.Digits * 8, 18); 102 | Embed(bmp, pixSize, 34); 103 | 104 | for (int i = 7; i >= 0; i--) 105 | { 106 | int idxBmp = bmpHeader.Length + i * rowLen; 107 | for (int j = 0; j < suff.Digits; j++) 108 | bmp[idxBmp + j] = Characters.tileset[tiles[j] * 8 + 7-i]; 109 | } 110 | 111 | using (var ms = new MemoryStream(bmp)) 112 | { 113 | using (var img = new Bitmap(ms)) 114 | g.DrawImage(img, 0, 0, suff.Digits * 8 * Scale, 8 * Scale); 115 | } 116 | } 117 | 118 | public void Generate(Suffix suff, string path, int count) 119 | { 120 | var img = new Bitmap(suff.Digits * 8 * Scale, 8 * Scale); 121 | using (var g = Graphics.FromImage(img)) 122 | { 123 | g.InterpolationMode = InterpolationMode.NearestNeighbor; 124 | g.PixelOffsetMode = PixelOffsetMode.Half; 125 | 126 | for (int i = 0; i < count; i++) 127 | { 128 | string name = suff.Generate(i); 129 | ProduceImage(g, suff, i); 130 | img.Save(path + name); 131 | } 132 | } 133 | } 134 | } 135 | 136 | class MainClass 137 | { 138 | static void Main() 139 | { 140 | Console.Write("Number Tile Generator\nSuffix Format:\n> "); 141 | 142 | Suffix suffix; 143 | try { 144 | string sufStr = Console.ReadLine(); 145 | suffix = new Suffix(sufStr); 146 | if (!suffix.HasInsert) 147 | throw new ArgumentException("\"" + sufStr + "\" does not contain an insert"); 148 | } 149 | catch (Exception ex) { 150 | Console.WriteLine(ex.Message); 151 | Console.ReadLine(); 152 | return; 153 | } 154 | 155 | Console.Write("Number of tiles:\n> "); 156 | int nTiles = 0; 157 | try { 158 | nTiles = Convert.ToInt32(Console.ReadLine()); 159 | if (nTiles <= 0) 160 | throw new ArgumentException("The number of tiles must be positive"); 161 | } 162 | catch (Exception ex) { 163 | Console.WriteLine(ex.Message); 164 | Console.ReadLine(); 165 | return; 166 | } 167 | 168 | Console.Write("Output folder:\n> "); 169 | string dir = Console.ReadLine(); 170 | if (dir[dir.Length - 1] != '\\') 171 | dir += "\\"; 172 | 173 | new Characters().Generate(suffix, dir, nTiles); 174 | } 175 | } 176 | } -------------------------------------------------------------------------------- /Tests/Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SpriteWave 4 | { 5 | public class Tests 6 | { 7 | int nTests = 0; 8 | int nSuccesses = 0; 9 | const uint OrderAndDepth = 0x12305551; 10 | 11 | public void ViewResults(string testName, bool testRes) 12 | { 13 | nTests++; 14 | if (testRes) 15 | nSuccesses++; 16 | 17 | string status = testRes ? "Success!" : "Fail :("; 18 | Console.WriteLine(testName + ": " + status + "\n"); 19 | } 20 | 21 | public void PrintResultsUInt(uint expected, uint actual) 22 | { 23 | Console.WriteLine("Expected: " + expected.ToString("X8") + ", Actual: " + actual.ToString("X8")); 24 | } 25 | 26 | public bool TestNativeToRGBA(uint expected, uint c) 27 | { 28 | uint actual = new ColorPattern(OrderAndDepth, null).NativeToRGBA(c); 29 | PrintResultsUInt(expected, actual); 30 | return actual == expected; 31 | } 32 | 33 | public bool TestRGBAToNative(uint expected, uint rgba) 34 | { 35 | uint actual = new ColorPattern(OrderAndDepth, null).RGBAToNative(rgba); 36 | PrintResultsUInt(expected, actual); 37 | return actual == expected; 38 | } 39 | 40 | public bool TestException(string exType, Action action) 41 | { 42 | string msg = null; 43 | bool success = false; 44 | 45 | try { 46 | action(); 47 | } 48 | catch (Exception ex) { 49 | msg = ex.Message; 50 | if (ex.GetType() == Type.GetType("System." + exType)) 51 | success = true; 52 | else 53 | throw; 54 | } 55 | 56 | Console.WriteLine(msg != null ? msg : "No exception"); 57 | return success; 58 | } 59 | 60 | public bool TestSuffixGenerate(string expected, string fmt, int num) 61 | { 62 | string output = new Suffix(fmt).Generate(num); 63 | Console.WriteLine("Expected: " + expected + ", Actual: " + output); 64 | return output == expected; 65 | } 66 | 67 | public bool TestSuffixValueOf(int expected, string fmt, string str) 68 | { 69 | int value = new Suffix(fmt).ValueOf(str); 70 | Console.WriteLine("Expected: " + expected + ", Actual: " + value); 71 | return value == expected; 72 | } 73 | 74 | public void Run() 75 | { 76 | ViewResults("NativeToRGBA", TestNativeToRGBA(0xFF00FFFF, 0xFC1F)); 77 | ViewResults("RGBAToNative", TestRGBAToNative(0x83FF, 0x00FFFFFF)); 78 | ViewResults("RGBAToNative", TestRGBAToNative(0xFC1F, 0xFF00FFFF)); 79 | ViewResults("RGBAToNative", TestRGBAToNative(0xFFE0, 0xFFFF00FF)); 80 | ViewResults("RGBAToNative", TestRGBAToNative(0x7FFF, 0xFFFFFF00)); 81 | 82 | ViewResults("CreateSuffix (trailing '{')", TestException("ArgumentException", () => {var suff = new Suffix("_{{d2");})); 83 | ViewResults("CreateSuffix (too short)", TestException("ArgumentException", () => {var suff = new Suffix("-{d}");})); 84 | ViewResults("CreateSuffix (base type)", TestException("ArgumentException", () => {var suff = new Suffix("-{l}");})); 85 | ViewResults("CreateSuffix (nDigits < 1)", TestException("ArgumentException", () => {var suff = new Suffix("_{d0}");})); 86 | 87 | ViewResults("SuffixGenerate (num is -ve)", TestException("ArgumentException", () => {var str = new Suffix("_{d2}").Generate(-10);})); 88 | ViewResults("SuffixGenerate (num exceeds limit)", TestException("ArgumentException", () => {var str = new Suffix("_{b3}").Generate(10);})); 89 | 90 | ViewResults("SuffixGenerate", TestSuffixGenerate("number 090 here", "number {d3} here", 90)); 91 | ViewResults("SuffixGenerate", TestSuffixGenerate("_0f4", "_{x3}", 244)); 92 | ViewResults("SuffixGenerate", TestSuffixGenerate("_0F4", "_{X3}", 244)); 93 | 94 | ViewResults("SuffixValueOf (no insert)", TestException("InvalidOperationException", () => {var num = new Suffix("nothing here").ValueOf("nothing here");})); 95 | ViewResults("SuffixValueOf (incorrect size 1)", TestException("ArgumentException", () => {var num = new Suffix("{d2} cc").ValueOf("not correct");})); 96 | ViewResults("SuffixValueOf (incorrect size 2)", TestException("ArgumentException", () => {var num = new Suffix("aa {d2} cc").ValueOf("not correct");})); 97 | ViewResults("SuffixValueOf (incorrect size 3)", TestException("ArgumentException", () => {var num = new Suffix("aa {d2}").ValueOf("not correct");})); 98 | ViewResults("SuffixValueOf (invalid input 1)", TestException("ArgumentException", () => {var num = new Suffix("{d2} cc").ValueOf("00 cd");})); 99 | ViewResults("SuffixValueOf (invalid input 2)", TestException("ArgumentException", () => {var num = new Suffix("aa {d2} cc").ValueOf("aa 00 cd");})); 100 | ViewResults("SuffixValueOf (invalid input 3)", TestException("ArgumentException", () => {var num = new Suffix("aa {d2} cc").ValueOf("ab 00 cc");})); 101 | ViewResults("SuffixValueOf (invalid input 4)", TestException("ArgumentException", () => {var num = new Suffix("aa {d2}").ValueOf("ab 00");})); 102 | ViewResults("SuffixValueOf (invalid input 4)", TestException("ArgumentOutOfRangeException", () => {var num = new Suffix("aa {d2}").ValueOf("aa 0-");})); 103 | ViewResults("SuffixValueOf (oob digit)", TestException("ArgumentOutOfRangeException", () => {var num = new Suffix("aa {o2}").ValueOf("aa 09");})); 104 | 105 | ViewResults("SuffixValueOf", TestSuffixValueOf(345, "the number is: {b10}", "the number is: 0101011001")); 106 | ViewResults("SuffixValueOf", TestSuffixValueOf(987, "{o4} is the number", "1733 is the number")); 107 | ViewResults("SuffixValueOf", TestSuffixValueOf(90, "number {d3} here", "number 090 here")); 108 | ViewResults("SuffixValueOf", TestSuffixValueOf(244, "_{x3}", "_0f4")); 109 | ViewResults("SuffixValueOf", TestSuffixValueOf(244, "_{X3}", "_0F4")); 110 | 111 | Console.WriteLine("\nTests Passed: " + nSuccesses + "/" + nTests); 112 | } 113 | } 114 | 115 | public class TestsMain 116 | { 117 | public static void Main() 118 | { 119 | new Tests().Run(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Tools/0_CompileGui.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set CSC="%CD:~0,3%Tools\Mono\bin\csc.bat" 4 | 5 | if "%~1"=="" ( 6 | echo No input file was given 7 | echo Try typing "%0 " 8 | goto exit 9 | ) 10 | 11 | call %CSC% -unsafe /t:winexe "%~1" ..\Source\Utils.cs ..\Source\Suffix.cs ..\Source\GFX\ColorTable.cs ..\Source\GFX\ColorPattern.cs 12 | 13 | :exit 14 | echo. 15 | pause 16 | exit /b 0 -------------------------------------------------------------------------------- /Tools/OrderAndDepthGUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SpriteWave 6 | { 7 | class InputModule 8 | { 9 | private string _name; 10 | 11 | private uint _value; 12 | public uint Value 13 | { 14 | get { 15 | return _value; 16 | } 17 | set { 18 | _value = value; 19 | string lbl = _name + ": 0x" + _value.ToString("X8"); 20 | SetText(lbl, "", null); 21 | } 22 | } 23 | 24 | private Label _label; 25 | private TextBox _box; 26 | private Button _submit; 27 | 28 | private Action _action; 29 | public Action OnSubmit { set { _action = value; } } 30 | 31 | public uint Input 32 | { 33 | get { 34 | uint res; 35 | 36 | string text = _box.Text; 37 | if (text == null || text.Length < 1) 38 | res = this.Value; 39 | else 40 | res = Convert.ToUInt32(text, 16); 41 | 42 | return res; 43 | } 44 | } 45 | 46 | public bool Visible 47 | { 48 | set { 49 | _label.Visible = value; 50 | _box.Visible = value; 51 | _submit.Visible = value; 52 | } 53 | } 54 | 55 | public InputModule(OrderAndDepthGUI mainForm, string name, int x, int y) 56 | { 57 | _name = name; 58 | _value = 0; 59 | 60 | _label = new Label(); 61 | _label.Location = new Point(x - 5, y); 62 | _label.BackColor = Color.White; 63 | _label.TextAlign = ContentAlignment.MiddleCenter; 64 | _label.Visible = false; 65 | 66 | _box = new TextBox(); 67 | _box.Location = new Point(x, y + 30); 68 | _box.BackColor = Color.White; 69 | _box.KeyDown += new KeyEventHandler(EditSpecBox); 70 | _box.Visible = false; 71 | 72 | _submit = new Button(); 73 | _submit.Location = new Point(x + 12, y + 60); 74 | _submit.BackColor = Color.White; 75 | _submit.Click += new EventHandler(ClickSpecButton); 76 | _submit.Text = "Submit"; 77 | _submit.Visible = false; 78 | 79 | _label.Size = new Size(_box.Width + 10, _label.Height); 80 | 81 | mainForm.Controls.Add(_label); 82 | mainForm.Controls.Add(_box); 83 | mainForm.Controls.Add(_submit); 84 | } 85 | 86 | public void SetText(string lblText, string boxText, string btnText) 87 | { 88 | if (lblText != null) 89 | _label.Text = lblText; 90 | if (boxText != null) 91 | _box.Text = boxText; 92 | if (btnText != null) 93 | _submit.Text = btnText; 94 | } 95 | 96 | private void Submit() 97 | { 98 | if (_action == null) 99 | return; 100 | 101 | try { 102 | _action(this); 103 | } 104 | catch (Exception ex) { 105 | MessageBox.Show(ex.Message, "Uh Oh"); 106 | } 107 | } 108 | 109 | private void EditSpecBox(object sender, KeyEventArgs e) 110 | { 111 | if (e.KeyCode == Keys.Enter) 112 | Submit(); 113 | } 114 | 115 | private void ClickSpecButton(object sender, EventArgs e) 116 | { 117 | Submit(); 118 | } 119 | } 120 | 121 | class OrderAndDepthGUI : Form 122 | { 123 | ColorPattern _table; 124 | InputModule _spec, _rgba, _native; 125 | 126 | public OrderAndDepthGUI() 127 | { 128 | this.Text = "Order And Depth GUI"; 129 | 130 | _spec = new InputModule(this, "Spec", 95, 25); 131 | _spec.SetText("No spec given", "01238888", "New Spec"); 132 | _spec.OnSubmit = NewSpec; 133 | _spec.Visible = true; 134 | 135 | _rgba = new InputModule(this, "RGBA", 30, 145); 136 | _rgba.SetText("RGBA Color Field", null, "--->"); 137 | _rgba.OnSubmit = RGBAToNative; 138 | 139 | _native = new InputModule(this, "Native", 160, 145); 140 | _native.SetText("Native Color Field", null, "<---"); 141 | _native.OnSubmit = NativeToRGBA; 142 | } 143 | 144 | public void SetColor(uint rgba) 145 | { 146 | int rgb = (int)(rgba >> 8); 147 | this.BackColor = Color.FromArgb(255, Color.FromArgb(rgb)); 148 | } 149 | 150 | public void NewSpec(InputModule mod) 151 | { 152 | uint seed = mod.Input; 153 | _table = new ColorPattern(seed, null); 154 | 155 | mod.Value = seed; 156 | 157 | _rgba.SetText(null, "", null); 158 | _rgba.Visible = true; 159 | 160 | _native.SetText(null, "", null); 161 | _native.Visible = true; 162 | } 163 | 164 | public void RGBAToNative(InputModule mod) 165 | { 166 | uint rgba = mod.Input; 167 | uint nat = _table.RGBAToNative(rgba); 168 | 169 | _rgba.Value = rgba; 170 | _native.Value = nat; 171 | 172 | SetColor(rgba); 173 | } 174 | 175 | public void NativeToRGBA(InputModule mod) 176 | { 177 | uint nat = mod.Input; 178 | uint rgba = _table.NativeToRGBA(nat); 179 | 180 | _native.Value = nat; 181 | _rgba.Value = rgba; 182 | 183 | SetColor(rgba); 184 | } 185 | } 186 | 187 | class OrderAndDepthMain 188 | { 189 | static void Main() 190 | { 191 | Application.Run(new OrderAndDepthGUI()); 192 | } 193 | } 194 | } -------------------------------------------------------------------------------- /Tools/SuffixTool.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Drawing; 4 | using System.Windows.Forms; 5 | 6 | namespace SpriteWave 7 | { 8 | public interface ISuffixPanel 9 | { 10 | int MinimumHeight { get; } 11 | void AdjustContents(); 12 | } 13 | 14 | public class TextSubmit 15 | { 16 | TextBox _text; 17 | Button _submit; 18 | 19 | public string Text { get { return _text.Text; } set { _text.Text = value; } } 20 | 21 | public bool Enabled 22 | { 23 | get { 24 | return _text.Enabled || _submit.Enabled; 25 | } 26 | set { 27 | _text.Enabled = value; 28 | _submit.Enabled = value; 29 | } 30 | } 31 | 32 | public int X 33 | { 34 | set { 35 | _text.Location = new Point(value - (_text.Size.Width / 2), _text.Location.Y); 36 | _submit.Location = new Point(value - (_submit.Size.Width / 2) - 1, _submit.Location.Y); 37 | } 38 | } 39 | 40 | public TextSubmit(Panel p, string name, int y, string btnText, EventHandler action) 41 | { 42 | _text = new TextBox(); 43 | _text.Name = name + "Box"; 44 | _text.Text = ""; 45 | _text.Location = new Point(0, y); 46 | _text.Enabled = false; 47 | 48 | _submit = new Button(); 49 | _submit.Name = name + "Button"; 50 | _submit.Text = btnText; 51 | _submit.Location = new Point(0, y + 30); 52 | _submit.Click += action; 53 | _submit.Enabled = false; 54 | 55 | p.Controls.Add(_text); 56 | p.Controls.Add(_submit); 57 | } 58 | } 59 | 60 | public class TestSuffix : Panel, ISuffixPanel 61 | { 62 | Suffix _suff; 63 | 64 | Label _desc; 65 | TextSubmit _main; 66 | TextSubmit _value; 67 | TextSubmit _string; 68 | 69 | public int MinimumHeight { get { return 200; } } 70 | 71 | public TestSuffix() 72 | { 73 | this.Location = new Point(0, 0); 74 | 75 | _desc = new Label(); 76 | _desc.Text = "Testing Area"; 77 | _desc.Font = new Font(Label.DefaultFont, FontStyle.Bold); 78 | _desc.Location = new Point(15, 15); 79 | 80 | _main = new TextSubmit(this, "main", 40, "New Suffix", this.newSuffix); 81 | _main.Text = "example_{d2}"; 82 | _main.Enabled = true; 83 | 84 | _value = new TextSubmit(this, "value", 120, "--->", this.generate); 85 | _value.Text = "8"; 86 | 87 | _string = new TextSubmit(this, "string", 120, "<---", this.valueOf); 88 | 89 | this.Controls.Add(_desc); 90 | } 91 | 92 | public void AdjustContents() 93 | { 94 | int mid = this.Size.Width / 2; 95 | _main.X = mid; 96 | _value.X = mid - 70; 97 | _string.X = mid + 70; 98 | } 99 | 100 | void newSuffix(object sender, EventArgs e) 101 | { 102 | bool success = true; 103 | try { 104 | _suff = new Suffix(_main.Text); 105 | } 106 | catch (Exception ex) { 107 | MessageBox.Show(ex.Message); 108 | success = false; 109 | } 110 | 111 | _value.Enabled = success; 112 | _string.Enabled = success; 113 | } 114 | 115 | void generate(object sender, EventArgs e) 116 | { 117 | try { 118 | _string.Text = _suff.Generate(Convert.ToInt32(_value.Text)); 119 | _value.Text = _suff.ValueOf(_string.Text).ToString(); 120 | } 121 | catch (Exception ex) { 122 | MessageBox.Show(ex.Message); 123 | _string.Text = ""; 124 | } 125 | } 126 | 127 | void valueOf(object sender, EventArgs e) 128 | { 129 | try { 130 | _value.Text = _suff.ValueOf(_string.Text).ToString(); 131 | _string.Text = _suff.Generate(Convert.ToInt32(_value.Text)); 132 | } 133 | catch (Exception ex) { 134 | MessageBox.Show(ex.Message); 135 | _value.Text = ""; 136 | } 137 | } 138 | } 139 | 140 | public class InputField 141 | { 142 | Label _desc; 143 | TextBox _input; 144 | 145 | public string Text 146 | { 147 | get { 148 | return _input.Text; 149 | } 150 | } 151 | 152 | public InputField(Panel p, string name, int y, string text, Action textChanged) 153 | { 154 | _desc = new Label(); 155 | _desc.Name = name + "Label"; 156 | _desc.Text = text; 157 | _desc.AutoSize = true; 158 | _desc.Location = new Point(0, y); 159 | 160 | _input = new TextBox(); 161 | _input.Name = name + "Box"; 162 | _input.Text = ""; 163 | _input.AutoSize = true; 164 | _input.Location = new Point(0, y); 165 | _input.TextChanged += (s, e) => textChanged(); 166 | 167 | p.Controls.Add(_desc); 168 | p.Controls.Add(_input); 169 | } 170 | 171 | public void SetPosition(int labelX, int boxX, int y) 172 | { 173 | _desc.Location = new Point( 174 | labelX, 175 | y - (_desc.Size.Height / 2) 176 | ); 177 | _input.Location = new Point( 178 | boxX, 179 | y - (_input.Size.Height / 2) 180 | ); 181 | } 182 | } 183 | 184 | public class RenamePanel : Panel 185 | { 186 | Label _desc; 187 | InputField _srcField; 188 | InputField _dstField; 189 | 190 | Label _folderDesc; 191 | Button _folderBtn; 192 | Label _folderText; 193 | 194 | Label _resultText; 195 | Button _submit; 196 | 197 | FolderBrowserDialog _browse; 198 | 199 | public int MinimumHeight { get { return 230; } } 200 | 201 | string Output 202 | { 203 | set { 204 | _resultText.Text = value; 205 | _resultText.Location = new Point( 206 | (this.Size.Width - _resultText.Size.Width) / 2, 207 | _resultText.Location.Y 208 | ); 209 | } 210 | } 211 | 212 | public RenamePanel() 213 | { 214 | _browse = new FolderBrowserDialog(); 215 | 216 | _desc = new Label(); 217 | _desc.Text = "Batch Rename"; 218 | _desc.Font = new Font(Label.DefaultFont, FontStyle.Bold); 219 | _desc.AutoSize = true; 220 | _desc.Location = new Point(15, 15); 221 | 222 | _srcField = new InputField(this, "source", 40, "Suffix To Match", this.UpdateUI); 223 | 224 | _folderDesc = new Label(); 225 | _folderDesc.Text = "Folder To Process"; 226 | _folderDesc.AutoSize = true; 227 | 228 | _folderBtn = new Button(); 229 | _folderBtn.Text = "..."; 230 | _folderBtn.Size = new Size(40, 20); 231 | _folderBtn.Click += this.browseFolderHandler; 232 | 233 | _folderText = new Label(); 234 | _folderText.Text = ""; 235 | _folderText.AutoEllipsis = true; 236 | _folderText.Font = new Font(Label.DefaultFont, FontStyle.Italic); 237 | //_folderText.Size = new Size(60, 17); 238 | 239 | _dstField = new InputField(this, "dest", 120, "Replacement Suffix", this.UpdateUI); 240 | 241 | _resultText = new Label(); 242 | _resultText.Text = ""; 243 | _resultText.ForeColor = Color.Navy; 244 | _resultText.AutoSize = true; 245 | 246 | _submit = new Button(); 247 | _submit.Text = "Process"; 248 | _submit.Enabled = false; 249 | _submit.Click += this.process; 250 | 251 | this.Controls.Add(_desc); 252 | this.Controls.Add(_folderDesc); 253 | this.Controls.Add(_folderBtn); 254 | this.Controls.Add(_folderText); 255 | this.Controls.Add(_resultText); 256 | this.Controls.Add(_submit); 257 | } 258 | 259 | public void AdjustContents() 260 | { 261 | const int labelX = -115; 262 | const int boxX = 15; 263 | 264 | int midX = this.Size.Width / 2; 265 | int midY = this.Size.Height / 2; 266 | Action centre = (ctrl, x, y) => 267 | { 268 | ctrl.Location = new Point( 269 | midX + x, 270 | midY + y 271 | ); 272 | }; 273 | 274 | _srcField.SetPosition(midX + labelX, midX + boxX, midY - 50); 275 | //_srcField.Position = new Point(mid, 50); 276 | 277 | centre(_folderDesc, labelX, -17); 278 | centre(_folderBtn, boxX, -20); 279 | centre(_folderText, 65, -17); 280 | 281 | _folderText.Size = new Size(this.Size.Width - _folderText.Location.X - 10, 17); 282 | 283 | //_dstField.Position = new Point(mid, 130); 284 | _dstField.SetPosition(midX + labelX, midX + boxX, midY + 30); 285 | 286 | centre(_resultText, _resultText.Size.Width / -2, 55); 287 | centre(_submit, _submit.Size.Width / -2, 75); 288 | } 289 | 290 | void UpdateUI() 291 | { 292 | _submit.Enabled = 293 | _srcField.Text.Length > 0 && 294 | _folderText.Text.Length > 0 && 295 | _dstField.Text.Length > 0 296 | ; 297 | } 298 | 299 | void browseFolderHandler(object sender, EventArgs e) 300 | { 301 | var result = _browse.ShowDialog(); 302 | if (result != DialogResult.OK) 303 | return; 304 | 305 | _folderText.Text = _browse.SelectedPath; 306 | UpdateUI(); 307 | } 308 | 309 | void process(object sender, EventArgs e) 310 | { 311 | this.Output = ""; 312 | 313 | string path = _folderText.Text + "\\"; 314 | string[] fileList = Directory.GetFiles(path); 315 | string msg = fileList[0] + "\n"; 316 | 317 | int nFiles = 0; 318 | string plural = ""; 319 | 320 | for (int i = 0; i < fileList.Length; i++) 321 | fileList[i] = Path.GetFileName(fileList[i]); 322 | 323 | try { 324 | Suffix src = new Suffix(_srcField.Text); 325 | Suffix dst = new Suffix(_dstField.Text); 326 | if (!src.HasInsert || !dst.HasInsert) 327 | throw new ArgumentException("Both suffix inputs must contain an insert (eg. {d2})"); 328 | 329 | int[] values = src.ListOfValues(fileList); 330 | if (values == null) 331 | throw new ArgumentException("No files matching \"" + _srcField.Text + "\" were found"); 332 | 333 | nFiles = values.Length; 334 | plural = nFiles != 1 ? "s" : ""; 335 | 336 | Action rename = (idx) => 337 | { 338 | string outName = dst.Generate(values[idx]); 339 | string srcFile = path + fileList[idx]; 340 | string dstFile = path + outName; 341 | 342 | if (!File.Exists(srcFile)) 343 | return; 344 | 345 | if (File.Exists(dstFile)) 346 | { 347 | if (outName.ToLower() != src.Generate(values[idx]).ToLower()) 348 | throw new InvalidOperationException("i cant code :)"); 349 | else 350 | return; 351 | } 352 | 353 | File.Move(srcFile, dstFile); 354 | }; 355 | 356 | for (int i = 0; i < nFiles; i++) 357 | { 358 | // This ensures that there aren't any rename conflicts, 359 | // where the destination file has the same name as the source file, 360 | // yet does not refer to the same number 361 | int idx = src.Base > dst.Base ? nFiles-i-1 : i; 362 | rename(idx); 363 | } 364 | } 365 | catch (Exception ex) { 366 | MessageBox.Show(ex.Message); 367 | return; 368 | } 369 | 370 | this.Output = "Successfully renamed " + nFiles + " file" + plural; 371 | } 372 | } 373 | 374 | class SuffixTool : Form 375 | { 376 | Pen _divPen; 377 | int _divY; 378 | const float dividerGap = 0.0625f; 379 | 380 | TestSuffix _testSuffix; 381 | RenamePanel _renamePanel; 382 | 383 | public SuffixTool() 384 | { 385 | this.Text = "Suffix Tool"; 386 | this.Size = new Size(350, 420); 387 | this.ResizeRedraw = true; 388 | this.Layout += this.layoutHandler; 389 | 390 | _divPen = new Pen(Color.Silver); 391 | 392 | _testSuffix = new TestSuffix(); 393 | _testSuffix.Name = "testSuffix"; 394 | 395 | _renamePanel = new RenamePanel(); 396 | _renamePanel.Name = "renamePanel"; 397 | 398 | this.MinimumSize = new Size(300, 20 + _testSuffix.MinimumHeight + _renamePanel.MinimumHeight); 399 | 400 | this.Controls.Add(_testSuffix); 401 | this.Controls.Add(_renamePanel); 402 | } 403 | 404 | void layoutHandler(object sender, LayoutEventArgs e) 405 | { 406 | _divY = _testSuffix.MinimumHeight; 407 | 408 | _testSuffix.Size = new Size(this.ClientSize.Width, _divY - 1); 409 | _testSuffix.AdjustContents(); 410 | 411 | _renamePanel.Location = new Point(0, _divY + 2); 412 | _renamePanel.Size = new Size(this.ClientSize.Width, this.ClientSize.Height - _renamePanel.Location.Y); 413 | _renamePanel.AdjustContents(); 414 | } 415 | 416 | protected override void OnPaint(PaintEventArgs e) 417 | { 418 | base.OnPaint(e); 419 | 420 | float w = this.ClientSize.Width; 421 | float h = this.ClientSize.Height; 422 | float gap = w * dividerGap; 423 | 424 | e.Graphics.DrawLine( 425 | _divPen, 426 | gap, 427 | _divY, 428 | w - gap, 429 | _divY 430 | ); 431 | } 432 | } 433 | 434 | class SuffixToolMain 435 | { 436 | [STAThread] 437 | static void Main() 438 | { 439 | Application.Run(new SuffixTool()); 440 | } 441 | } 442 | } -------------------------------------------------------------------------------- /Tools/colour_gen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 57 | 190 | 191 | 192 | Colour Generator 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | Random 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | Generate 212 | 213 | 214 | 215 | 216 | 217 | 218 | R: 219 | 220 | 221 | 222 | G: 223 | 224 | 225 | 226 | B: 227 | 228 | 229 | 230 | Hex: 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | Input 241 | 242 | 243 | Hue 244 | 245 | 246 | 247 | Saturation 248 | 249 | 250 | 251 | Lightness 252 | 253 | 254 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /Tools/palette_tab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 84 | 391 | 392 | 393 | 394 | Palette Tab Mockup 395 | Number of colours: 396 | Generate 397 | 398 | 399 | 400 | 401 | Index -> Palette 402 | 403 | 404 | 405 | Index -> Real 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /Tools/palette_tab_v2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 61 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /images/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbendtsen/SpriteWave/9e0df60fea5bda11b1193f1b77f29eaf190e9388/images/screenshot4.png -------------------------------------------------------------------------------- /images/screenshot5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbendtsen/SpriteWave/9e0df60fea5bda11b1193f1b77f29eaf190e9388/images/screenshot5.png -------------------------------------------------------------------------------- /images/screenshot6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jbendtsen/SpriteWave/9e0df60fea5bda11b1193f1b77f29eaf190e9388/images/screenshot6.png --------------------------------------------------------------------------------