forked from mirrors/NBTExplorer
464 lines
19 KiB
C#
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[4096];
|
|
for (int i = 0; i < _blockIndex.Length; 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 >= 4096) {
|
|
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),
|
|
};
|
|
}
|
|
}
|
|
}
|