/// <summary> /// Automatically generates a TSA-compliant image from a 256(or fewer)-color image /// </summary> /// <param name="width">Width of the TSA (in tiles)</param> /// <param name="height">Height of the TSA (in tiles)</param> /// <param name="image">The bitmap to convert</param> /// <param name="paletteAmount">the maximum amount of 16-color palettes</param> /// <param name="checkRedundantTiles">whether or not to add all tiles found to the tileset</param> public TSA_Image( int width, int height, GBA.Bitmap image, int paletteAmount, bool checkRedundantTiles) { Width = width * 8; Height = height * 8; if (image.Width != Width || image.Height != Height) { throw new Exception("Image given has invalid dimensions.\n" + "It must be " + Width + "x" + Height + " pixels."); } if (image.Colors.Count <= 16) { // No need to run TSA-ifier code Palettes = Palette.Split(image.Colors, paletteAmount); Graphics = new Tileset(new Image(image)); Tiling = TSA_Array.GetBasicTSA(width, height); } else { using (FormLoading loading = new FormLoading()) { // Run the image TSA-ifier loading.Show(); int tileAmount = width * height; byte[] bytes = image.ToBytes(); int[] colorTotals = new int[image.Colors.Count]; int[,] colorAmounts = new int[image.Colors.Count, tileAmount]; loading.SetLoading("Checking colors...", 2); int index; int tile = 0; for (int tileY = 0; tileY < height; tileY++) { for (int tileX = 0; tileX < width; tileX++) { index = tileX * 8 + tileY * 8 * Width; for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { colorTotals[bytes[index]]++; colorAmounts[bytes[index], tile]++; } index += Width - 8; } tile++; } // first we take a look at which colors are most present (in all, and per tile) } loading.SetPercent(7); List <int> colors = new List <int>(); for (int i = 0; i < colorTotals.Length; i++) { index = 0; while (index < colors.Count && colorTotals[i] > colors[index]) { index++; } if (index < colors.Count) { colors.Insert(index, i); } else { colors.Add(i); } if (colors.Count > paletteAmount * 15) { colors.RemoveAt(0); } } // then we create a list of indices to colors with only the most used colors loading.SetPercent(9); Palettes = new Palette[paletteAmount]; for (int i = 0; i < paletteAmount; i++) { Palettes[i] = new GBA.Palette(16); Palettes[i].Add(new GBA.Color(0x0000)); } // we create our palettes, forcing the 1st color to be black on each palette loading.SetLoading("Asserting tile palettes...", 10); byte[] tilePalettes = new byte[tileAmount]; int[] certainty = new int[tileAmount]; GBA.Color color; int amount; for (int i = colors.Count - 1; i >= 0; i--) { color = image.Colors[colors[i]]; tile = 0; while (tile < tilePalettes.Length) { amount = colorAmounts[colors[i], tile]; if (Palettes[tilePalettes[tile]].Contains(color)) { certainty[tile] += amount; } else { if (Palettes[tilePalettes[tile]].IsFull) { if (certainty[tile] < amount) { tilePalettes[tile]++; tilePalettes[tile] %= (byte)Palettes.Length; certainty[tile] = 0; i++; break; } else { certainty[tile] -= amount; } } else if (amount != 0) { Palettes[tilePalettes[tile]].Add(color); certainty[tile] += amount; } } tile++; } loading.SetPercent(10 + (50 - 50 * ((float)i / (float)(colors.Count)))); } // and we do a loop going from most used color to least used, setting tilePalettes and filling said palettes amount = 0; for (int p = 0; p < Palettes.Length; p++) { Palettes[p].Sort(delegate(GBA.Color first, GBA.Color second) { return((first.GetValueR() + first.GetValueG() + first.GetValueB()) - (second.GetValueR() + second.GetValueG() + second.GetValueB())); }); for (int i = Palettes[p].Count; i < 16; i++) { Palettes[p].Add(new Color(0x0000)); } } loading.SetMessage("Creating TSA information..."); Graphics = new Tileset(width * height); Tiling = new TSA_Array(width, height); int pixel; tile = 0; byte HI_nibble; byte LO_nibble; Tile currentTile; Tuple <int, bool, bool> current; for (int tileY = 0; tileY < height; tileY++) { for (int tileX = 0; tileX < width; tileX++) { index = (Palettes[tilePalettes[tile]].IsFull) ? tilePalettes[tile] : 0; bytes = new byte[GBA.Tile.LENGTH]; for (int y = 0; y < 8; y++) { for (int x = 0; x < 4; x++) { color = image.GetColor(tileX * 8 + x * 2, tileY * 8 + y); pixel = GBA.Color.GetNearest(Palettes[index], color); LO_nibble = (pixel == -1) ? (byte)0x00 : (byte)(pixel); color = image.GetColor(tileX * 8 + x * 2 + 1, tileY * 8 + y); pixel = GBA.Color.GetNearest(Palettes[index], color); HI_nibble = (pixel == -1) ? (byte)0x00 : (byte)(pixel); bytes[x + y * 4] = (byte)((HI_nibble << 4) | LO_nibble); } } currentTile = new Tile(bytes); if (checkRedundantTiles) { if (currentTile.IsEmpty()) { current = Tuple.Create(0, false, false); } else { current = Graphics.FindMatch(currentTile); if (current == null) { current = Tuple.Create(Graphics.Count, false, false); Graphics.Add(currentTile); } } } else { current = Tuple.Create(Graphics.Count, false, false); Graphics.Add(currentTile); } // try { Tiling[tileX, tileY] = new TSA( (UInt16)current.Item1, tilePalettes[tile], current.Item2, current.Item3); // } catch { } loading.SetPercent(60 + 40 * ((float)tile / (float)tileAmount)); tile++; } } } } }
/// <summary> /// Creates a Palette from a palette file (if its an image file, must be 16 pixels in width) /// </summary> public Palette(string filepath, int maximum = MAX) { Colors = new List <Color>(maximum); if (filepath.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || filepath.EndsWith(".gif", StringComparison.OrdinalIgnoreCase) || filepath.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase)) { Bitmap source; try { source = new GBA.Bitmap(filepath); } catch { throw new Exception("Could not open palette file:\n" + filepath); } bool addEveryPixel = (source.Width == 16); int index = 0; Color color; for (int y = 0; y < source.Height; y++) { for (int x = 0; x < source.Width; x++) { color = source.GetColor(x, y); if (!addEveryPixel && this.Contains(color)) { continue; } else { if (this.IsFull) { throw new Exception("This palette cannot hold more than " + maximum + " colors."); } else { this.Colors.Add(color); index++; } } } } } else if (filepath.EndsWith(".pal", StringComparison.OrdinalIgnoreCase)) { byte[] palette = File.ReadAllBytes(filepath); if (palette[0] == 0x43 && palette[1] == 0x4C && palette[2] == 0x52 && palette[3] == 0x58) // 'CLRX' is the header for usenti .pal files { string[] file = File.ReadAllLines(filepath); int channelBits; int colorAmount; int parse = 5; int length = 0; while (parse + length < file[0].Length && file[0][parse + length] != ' ') { length++; } channelBits = int.Parse(file[0].Substring(parse, length)); parse += length + 1; length = 0; while (parse + length < file[0].Length && file[0][parse + length] != ' ') { length++; } colorAmount = int.Parse(file[0].Substring(parse, length)); uint color = 0x00000000; Palette colors = new Palette(colorAmount); for (int i = 0; i < (colorAmount / 4); i++) { parse = 0; length = 0; for (int j = 0; j < 4; j++) { while (parse + length < file[1 + i].Length && file[1 + i][parse + length] != ' ') { length++; } color = Util.HexToInt(file[1 + i].Substring(parse, length)); colors.Add(new Color(color)); parse += length + 1; length = 0; } } palette = colors.ToBytes(false); } else if (palette.Length != maximum * 2) { throw new Exception( "Cannot load palette, the file given is of invalid length.\n" + "It should be " + maximum * 2 + " bytes long."); } byte[] buffer = new byte[2]; for (int i = 0; i < palette.Length / 2; i++) { buffer[0] = palette[i * 2 + 1]; buffer[1] = palette[i * 2]; Colors.Add(new Color(buffer)); } } else { throw new Exception("Invalid filetype given to load palette."); } }