using System; using System.Collections.Generic; using System.Text; using System.Drawing; using System.Drawing.Imaging; namespace Substrate.Data { /// /// Represents a range of color index values that pertains to an individual group of blocks. /// public enum ColorGroup { Unexplored = 0, Grass = 1, Sand = 2, Other = 3, Lava = 4, Ice = 5, Iron = 6, Leaves = 7, Snow = 8, Clay = 9, Dirt = 10, Stone = 11, Water = 12, Wood = 13, } /// /// A utility class for converting colors and data. /// public class MapConverter { private static Color[] _defaultColorIndex; private Color[] _colorIndex; private ColorGroup[] _blockIndex; private int _groupSize = 4; private Vector3[] _labIndex; /// /// Creates a new with a Minecraft-default color-index and block-index. /// public MapConverter () { _colorIndex = new Color[256]; _labIndex = new Vector3[256]; _defaultColorIndex.CopyTo(_colorIndex, 0); RefreshColorCache(); // Setup default block index _blockIndex = new ColorGroup[256]; for (int i = 0; i < 256; i++) { _blockIndex[i] = ColorGroup.Other; } _blockIndex[BlockInfo.Grass.ID] = ColorGroup.Grass; _blockIndex[BlockInfo.TallGrass.ID] = ColorGroup.Grass; _blockIndex[BlockInfo.Sand.ID] = ColorGroup.Sand; _blockIndex[BlockInfo.Gravel.ID] = ColorGroup.Sand; _blockIndex[BlockInfo.Sandstone.ID] = ColorGroup.Sand; _blockIndex[BlockInfo.Lava.ID] = ColorGroup.Lava; _blockIndex[BlockInfo.StationaryLava.ID] = ColorGroup.Lava; _blockIndex[BlockInfo.Ice.ID] = ColorGroup.Ice; _blockIndex[BlockInfo.Leaves.ID] = ColorGroup.Leaves; _blockIndex[BlockInfo.YellowFlower.ID] = ColorGroup.Leaves; _blockIndex[BlockInfo.RedRose.ID] = ColorGroup.Leaves; _blockIndex[BlockInfo.Snow.ID] = ColorGroup.Snow; _blockIndex[BlockInfo.SnowBlock.ID] = ColorGroup.Snow; _blockIndex[BlockInfo.ClayBlock.ID] = ColorGroup.Clay; _blockIndex[BlockInfo.Dirt.ID] = ColorGroup.Dirt; _blockIndex[BlockInfo.Stone.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.Cobblestone.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.CoalOre.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.IronOre.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.GoldOre.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.DiamondOre.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.RedstoneOre.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.LapisOre.ID] = ColorGroup.Stone; _blockIndex[BlockInfo.Water.ID] = ColorGroup.Water; _blockIndex[BlockInfo.StationaryWater.ID] = ColorGroup.Water; _blockIndex[BlockInfo.Wood.ID] = ColorGroup.Wood; } /// /// Gets or sets the number of color levels within each color group. The Minecraft default is 4. /// /// Thrown when the property is assigned a non-positive value. public int ColorGroupSize { get { return _groupSize; } set { if (value <= 0) { throw new ArgumentOutOfRangeException("The ColorGroupSize property must be a positive number."); } _groupSize = value; } } /// /// Gets the color index table, used to translate map color index values to RGB color values. /// public Color[] ColorIndex { get { return _colorIndex; } } /// /// Gets the block index table, used to translate block IDs to s that represent them. /// public ColorGroup[] BlockIndex { get { return _blockIndex; } } /// /// Returns the baseline color index associated with the currently representing the given block ID. /// /// The ID of a block. /// A color index value. /// Thrown when is out of its normal range. public int BlockToColorIndex (int blockId) { return BlockToColorIndex(blockId, 0); } /// /// Returns a color index associated with the currently representing the given block ID and based on the given level. /// /// The ID of a block. /// The color level to select from within the derived . /// A color index value. /// Thrown when either or are out of their normal ranges. public int BlockToColorIndex (int blockId, int level) { if (level < 0 || level >= _groupSize) { throw new ArgumentOutOfRangeException("level", level, "Argument 'level' must be in range [0, " + (_groupSize - 1) + "]"); } if (blockId < 0 || blockId >= 256) { throw new ArgumentOutOfRangeException("blockId"); } return (int)_blockIndex[blockId] * _groupSize + level; } /// /// Returns a value assocated with the currently representing the given block ID. /// /// The ID of a block. /// A value. /// Thrown when is out of its normal range. /// Thrown when maps to an invalid color index. public Color BlockToColor (int blockId) { return BlockToColor(blockId, 0); } /// /// Returns a value associated with the currently representing the given block ID and based on the given level. /// /// The ID of a block. /// The color level to select from within the derived . /// A value. /// Thrown when either or are out of their normal ranges. /// Thrown when maps to an invalid color index. public Color BlockToColor (int blockId, int level) { int ci = BlockToColorIndex(blockId, level); if (ci < 0 || ci >= 256) { throw new InvalidOperationException("The specified Block ID mapped to an invalid color index."); } return _colorIndex[ci]; } /// /// Returns the that a particular color index is part of. /// /// A color index value. /// A value. public ColorGroup ColorIndexToGroup (int colorIndex) { int group = colorIndex / _groupSize; try { return (ColorGroup)group; } catch { return ColorGroup.Other; } } /// /// Returns the baseline color index within a particular . /// /// A value. /// The baseline (level = 0) color index value for the given . public int GroupToColorIndex (ColorGroup group) { return GroupToColorIndex(group, 0); } /// /// Returns the color index for a given and group level. /// /// A value. /// A level value within the . /// The color index value for the given and group level. /// Thrown when the is out of range with respect to the current parameter. public int GroupToColorIndex (ColorGroup group, int level) { if (level < 0 || level >= _groupSize) { throw new ArgumentOutOfRangeException("level", level, "Argument 'level' must be in range [0, " + (_groupSize - 1) + "]"); } return (int)group * _groupSize + level; } /// /// Returns the baseline within a particular . /// /// A value. /// The baseline (level = 0) for the given . public Color GroupToColor (ColorGroup group) { return GroupToColor(group, 0); } /// /// Returns the for a given and group level. /// /// A value. /// A level value within the and group level. /// The for the given and group level. /// Thrown when the is out of range with respect to the current parameter. /// Thrown when the and map to an invalid color index. public Color GroupToColor (ColorGroup group, int level) { int ci = GroupToColorIndex(group, level); if (ci < 0 || ci >= 256) { throw new InvalidOperationException("The specified group mapped to an invalid color index."); } return _colorIndex[ci]; } /// /// Rebuilds the internal color conversion tables. Should be called after modifying the table. /// public void RefreshColorCache () { for (int i = 0; i < _colorIndex.Length; i++) { _labIndex[i] = RgbToLab(_colorIndex[i]); } } /// /// Given a , returns the index of the closest matching color from the color index table. /// /// The source . /// The closest matching color index value. /// This method performs color comparisons in the CIELAB color space, to find the best match according to human perception. public int NearestColorIndex (Color color) { double min = double.MaxValue; int minIndex = 0; Vector3 cr = RgbToLab(color); for (int i = 0; i < _colorIndex.Length; i++) { if (_colorIndex[i].A == 0) { continue; } double x = cr.X - _labIndex[i].X; double y = cr.Y - _labIndex[i].Y; double z = cr.Z - _labIndex[i].Z; double err = x * x + y * y + z * z; if (err < min) { min = err; minIndex = i; } } return minIndex; } /// /// Given a , returns the cloest matching from the color index table. /// /// The source . /// The closest matching . /// This method performs color comparisons in the CIELAB color space, to find the best match according to human perception. public Color NearestColor (Color color) { return _colorIndex[NearestColorIndex(color)]; } /// /// Fills a 's color data using nearest-matching colors from a source . /// /// The to modify. /// The source . /// Thrown when the and objects have different dimensions. public void BitmapToMap (Map map, Bitmap bmp) { if (map.Width != bmp.Width || map.Height != bmp.Height) { throw new InvalidOperationException("The source map and bitmap must have the same dimensions."); } for (int x = 0; x < map.Width; x++) { for (int z = 0; z < map.Height; z++) { Color c = bmp.GetPixel(x, z); map[x, z] = (byte)NearestColorIndex(c); } } } /// /// Creates a 32bpp from a . /// /// The source object. /// A 32bpp with the same dimensions and pixel data as the source . public Bitmap MapToBitmap (Map map) { Bitmap bmp = new Bitmap(map.Width, map.Height, PixelFormat.Format32bppArgb); for (int x = 0; x < map.Width; x++) { for (int z = 0; z < map.Height; z++) { Color c = _colorIndex[map[x, z]]; bmp.SetPixel(x, z, c); } } return bmp; } private Vector3 RgbToXyz (Color color) { double r = color.R / 255.0; double g = color.G / 255.0; double b = color.B / 255.0; r = (r > 0.04045) ? Math.Pow((r + 0.055) / 1.055, 2.4) : r / 12.92; g = (g > 0.04045) ? Math.Pow((g + 0.055) / 1.055, 2.4) : g / 12.92; b = (b > 0.04045) ? Math.Pow((b + 0.055) / 1.055, 2.4) : b / 12.92; r *= 100; g *= 100; b *= 100; Vector3 xyz = new Vector3(); xyz.X = r * 0.4124 + g * 0.3576 + b * 0.1805; xyz.Y = r * 0.2126 + g * 0.7152 + b * 0.0722; xyz.Z = r * 0.0193 + g * 0.1192 + b * 0.9505; return xyz; } private Vector3 XyzToLab (Vector3 xyz) { double x = xyz.X / 95.047; double y = xyz.Y / 100.0; double z = xyz.Z / 108.883; x = (x > 0.008856) ? Math.Pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0); y = (y > 0.008856) ? Math.Pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0); z = (z > 0.008856) ? Math.Pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0); Vector3 lab = new Vector3(); lab.X = (116 * y) - 16; lab.Y = 500 * (x - y); lab.Z = 200 * (y - z); return lab; } private Vector3 RgbToLab (Color rgb) { return XyzToLab(RgbToXyz(rgb)); } static MapConverter () { _defaultColorIndex = new Color[] { Color.FromArgb(0, 0, 0, 0), // Unexplored Color.FromArgb(0, 0, 0, 0), Color.FromArgb(0, 0, 0, 0), Color.FromArgb(0, 0, 0, 0), Color.FromArgb(89, 125, 39), // Grass Color.FromArgb(109, 153, 48), Color.FromArgb(127, 178, 56), Color.FromArgb(109, 153, 48), Color.FromArgb(174, 164, 115), // Sand/Gravel Color.FromArgb(213, 201, 140), Color.FromArgb(247, 233, 163), Color.FromArgb(213, 201, 140), Color.FromArgb(117, 117, 117), // Other Color.FromArgb(144, 144, 144), Color.FromArgb(167, 167, 167), Color.FromArgb(144, 144, 144), Color.FromArgb(180, 0, 0), // Lava Color.FromArgb(220, 0, 0), Color.FromArgb(255, 0, 0), Color.FromArgb(220, 0, 0), Color.FromArgb(112, 112, 180), // Ice Color.FromArgb(138, 138, 220), Color.FromArgb(160, 160, 255), Color.FromArgb(138, 138, 220), Color.FromArgb(117, 117, 117), // Iron Color.FromArgb(144, 144, 144), Color.FromArgb(167, 167, 167), Color.FromArgb(144, 144, 144), Color.FromArgb(0, 87, 0), // Leaves/Flowers Color.FromArgb(0, 106, 0), Color.FromArgb(0, 124, 0), Color.FromArgb(0, 106, 0), Color.FromArgb(180, 180, 180), // Snow Color.FromArgb(220, 220, 220), Color.FromArgb(255, 255, 255), Color.FromArgb(220, 220, 220), Color.FromArgb(115, 118, 129), // Clay Color.FromArgb(141, 144, 158), Color.FromArgb(164, 168, 184), Color.FromArgb(141, 144, 158), Color.FromArgb(129, 74, 33), // Dirt Color.FromArgb(157, 91, 40), Color.FromArgb(183, 106, 47), Color.FromArgb(157, 91, 40), Color.FromArgb(79, 79, 79), // Stone/Cobblestone/Ore Color.FromArgb(96, 96, 96), Color.FromArgb(112, 112, 112), Color.FromArgb(96, 96, 96), Color.FromArgb(45, 45, 180), // Water Color.FromArgb(55, 55, 220), Color.FromArgb(64, 64, 255), Color.FromArgb(55, 55, 220), Color.FromArgb(73, 58, 35), // Log/Tree/Wood Color.FromArgb(89, 71, 43), Color.FromArgb(104, 83, 50), Color.FromArgb(89, 71, 43), }; } } }