NBTExplorer/SubstrateCS/Source/Data/MapConverter.cs
2012-02-06 02:01:41 -05:00

464 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
namespace Substrate.Data
{
/// <summary>
/// Represents a range of color index values that pertains to an individual group of blocks.
/// </summary>
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,
}
/// <summary>
/// A utility class for converting <see cref="Map"/> colors and data.
/// </summary>
public class MapConverter
{
private static Color[] _defaultColorIndex;
private Color[] _colorIndex;
private ColorGroup[] _blockIndex;
private int _groupSize = 4;
private Vector3[] _labIndex;
/// <summary>
/// Creates a new <see cref="MapConverter"/> with a Minecraft-default color-index and block-index.
/// </summary>
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;
}
/// <summary>
/// Gets or sets the number of color levels within each color group. The Minecraft default is 4.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the property is assigned a non-positive value.</exception>
public int ColorGroupSize
{
get { return _groupSize; }
set
{
if (value <= 0) {
throw new ArgumentOutOfRangeException("The ColorGroupSize property must be a positive number.");
}
_groupSize = value;
}
}
/// <summary>
/// Gets the color index table, used to translate map color index values to RGB color values.
/// </summary>
public Color[] ColorIndex
{
get { return _colorIndex; }
}
/// <summary>
/// Gets the block index table, used to translate block IDs to <see cref="ColorGroup"/>s that represent them.
/// </summary>
public ColorGroup[] BlockIndex
{
get { return _blockIndex; }
}
/// <summary>
/// Returns the baseline color index associated with the <see cref="ColorGroup"/> currently representing the given block ID.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <returns>A color index value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="blockId"/> is out of its normal range.</exception>
public int BlockToColorIndex (int blockId)
{
return BlockToColorIndex(blockId, 0);
}
/// <summary>
/// Returns a color index associated with the <see cref="ColorGroup"/> currently representing the given block ID and based on the given level.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <param name="level">The color level to select from within the derived <see cref="ColorGroup"/>.</param>
/// <returns>A color index value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when either <paramref name="blockId"/> or <paramref name="level"/> are out of their normal ranges.</exception>
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;
}
/// <summary>
/// Returns a <see cref="Color"/> value assocated with the <see cref="ColorGroup"/> currently representing the given block ID.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <returns>A <see cref="Color"/> value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="blockId"/> is out of its normal range.</exception>
/// <exception cref="InvalidOperationException">Thrown when <paramref name="blockId"/> maps to an invalid color index.</exception>
public Color BlockToColor (int blockId)
{
return BlockToColor(blockId, 0);
}
/// <summary>
/// Returns a <see cref="Color"/> value associated with the <see cref="ColorGroup"/> currently representing the given block ID and based on the given level.
/// </summary>
/// <param name="blockId">The ID of a block.</param>
/// <param name="level">The color level to select from within the derived <see cref="ColorGroup"/>.</param>
/// <returns>A <see cref="Color"/> value.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when either <paramref name="blockId"/> or <paramref name="level"/> are out of their normal ranges.</exception>
/// <exception cref="InvalidOperationException">Thrown when <paramref name="blockId"/> maps to an invalid color index.</exception>
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];
}
/// <summary>
/// Returns the <see cref="ColorGroup"/> that a particular color index is part of.
/// </summary>
/// <param name="colorIndex">A color index value.</param>
/// <returns>A <see cref="ColorGroup"/> value.</returns>
public ColorGroup ColorIndexToGroup (int colorIndex)
{
int group = colorIndex / _groupSize;
try {
return (ColorGroup)group;
}
catch {
return ColorGroup.Other;
}
}
/// <summary>
/// Returns the baseline color index within a particular <see cref="ColorGroup"/>.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <returns>The baseline (level = 0) color index value for the given <see cref="ColorGroup"/>.</returns>
public int GroupToColorIndex (ColorGroup group)
{
return GroupToColorIndex(group, 0);
}
/// <summary>
/// Returns the color index for a given <see cref="ColorGroup"/> and group level.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <param name="level">A level value within the <see cref="ColorGroup"/>.</param>
/// <returns>The color index value for the given <see cref="ColorGroup"/> and group level.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="level"/> is out of range with respect to the current <see cref="ColorGroupSize"/> parameter.</exception>
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;
}
/// <summary>
/// Returns the baseline <see cref="Color"/> within a particular <see cref="ColorGroup"/>.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <returns>The baseline (level = 0) <see cref="Color"/> for the given <see cref="ColorGroup"/>.</returns>
public Color GroupToColor (ColorGroup group)
{
return GroupToColor(group, 0);
}
/// <summary>
/// Returns the <see cref="Color"/> for a given <see cref="ColorGroup"/> and group level.
/// </summary>
/// <param name="group">A <see cref="ColorGroup"/> value.</param>
/// <param name="level">A level value within the <see cref="ColorGroup"/> and group level.</param>
/// <returns>The <see cref="Color"/> for the given <see cref="ColorGroup"/> and group level.</returns>
/// <exception cref="ArgumentOutOfRangeException">Thrown when the <paramref name="level"/> is out of range with respect to the current <see cref="ColorGroupSize"/> parameter.</exception>
/// <exception cref="InvalidOperationException">Thrown when the <paramref name="group"/> and <paramref name="level"/> map to an invalid color index.</exception>
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];
}
/// <summary>
/// Rebuilds the internal color conversion tables. Should be called after modifying the <see cref="ColorIndex"/> table.
/// </summary>
public void RefreshColorCache ()
{
for (int i = 0; i < _colorIndex.Length; i++) {
_labIndex[i] = RgbToLab(_colorIndex[i]);
}
}
/// <summary>
/// Given a <see cref="Color"/>, returns the index of the closest matching color from the color index table.
/// </summary>
/// <param name="color">The source <see cref="Color"/>.</param>
/// <returns>The closest matching color index value.</returns>
/// <remarks>This method performs color comparisons in the CIELAB color space, to find the best match according to human perception.</remarks>
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;
}
/// <summary>
/// Given a <see cref="Color"/>, returns the cloest matching <see cref="Color"/> from the color index table.
/// </summary>
/// <param name="color">The source <see cref="Color"/>.</param>
/// <returns>The closest matching <see cref="Color"/>.</returns>
/// <remarks>This method performs color comparisons in the CIELAB color space, to find the best match according to human perception.</remarks>
public Color NearestColor (Color color)
{
return _colorIndex[NearestColorIndex(color)];
}
/// <summary>
/// Fills a <see cref="Map"/>'s color data using nearest-matching colors from a source <see cref="Bitmap"/>.
/// </summary>
/// <param name="map">The <see cref="Map"/> to modify.</param>
/// <param name="bmp">The source <see cref="Bitmap"/>.</param>
/// <exception cref="InvalidOperationException">Thrown when the <paramref name="map"/> and <paramref name="bmp"/> objects have different dimensions.</exception>
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);
}
}
}
/// <summary>
/// Creates a 32bpp <see cref="Bitmap"/> from a <see cref="Map"/>.
/// </summary>
/// <param name="map">The source <see cref="Map"/> object.</param>
/// <returns>A 32bpp <see cref="Bitmap"/> with the same dimensions and pixel data as the source <see cref="Map"/>.</returns>
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),
};
}
}
}