我这样做的基本方法是将图像转换为灰度调色板格式,然后给它一个改变色调的调色板。我得到了所有这些工具集(事实上,我已经做到了)。我看看能不能找到相关代码。
要制作灰度调色图像,你应该可以get the original image's pixels out somehow,如果它们已经是灰色的,任何 R/G/B 组件都可以。将它们存储在字节数组中并从中烘焙调色板图像。最简单的方法是使用Image.LockBits 函数,它可以直接访问支持图像的数组。这是我的工具功能:
/// <summary>
/// Creates a bitmap based on data, width, height, stride and pixel format.
/// </summary>
/// <param name="sourceData">Byte array of raw source data</param>
/// <param name="width">Width of the image</param>
/// <param name="height">Height of the image</param>
/// <param name="stride">Scanline length inside the data</param>
/// <param name="pixelFormat">Pixel format</param>
/// <param name="palette">Color palette</param>
/// <param name="defaultColor">Default color to fill in on the palette if the given colors don't fully fill it.</param>
/// <returns>The new image</returns>
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat, Color[] palette, Color? defaultColor)
{
Bitmap newImage = new Bitmap(width, height, pixelFormat);
BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
// Compensate for possible negative stride on BMP format.
Boolean isFlipped = targetData.Stride < 0;
Int32 targetStride = Math.Abs(targetData.Stride);
Int64 scan0 = targetData.Scan0.ToInt64();
for (Int32 y = 0; y < height; y++)
Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
newImage.UnlockBits(targetData);
// Fix negative stride on BMP format.
if (isFlipped)
newImage.RotateFlip(RotateFlipType.Rotate180FlipX);
// For indexed images, set the palette.
if ((pixelFormat & PixelFormat.Indexed) != 0 && palette != null)
{
ColorPalette pal = newImage.Palette;
for (Int32 i = 0; i < pal.Entries.Length; i++)
{
if (i < palette.Length)
pal.Entries[i] = palette[i];
else if (defaultColor.HasValue)
pal.Entries[i] = defaultColor.Value;
else
break;
}
newImage.Palette = pal;
}
return newImage;
}
现在,要获得漂亮的颜色,您当然需要调色板。为此,我使用 Rich Newman 的 HSLColor 类:
https://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/
请注意,我对这段代码做了一个小的修改,并将私有 const “scale” 变成了 public static “SCALE”,以便在我的操作中轻松访问它。
/// <summary>
/// Generates a colour palette of the given bits per pixel containing a hue rotation of the given range.
/// </summary>
/// <param name="bpp">Bits per pixel of the image the palette is for.</param>
/// <param name="blackOnZero">Replace the first colour on the palette with black.</param>
/// <param name="addTransparentZero">Make the first colour on the palette transparent.</param>
/// <param name="reverseGenerated">Reverse the generated range. This happens after the generating, and before the operations on the first index/</param>
/// <param name="startHue">Start hue range. Value from 0 to 240.</param>
/// <param name="endHue">End hue range. Value from 0 to 240. Must be higher then startHue.</param>
/// <param name="inclusiveEnd">True to include the end hue in the palette. If you generate a full hue range, this can be set to False to avoid getting a duplicate red colour on it.</param>
/// <returns>The generated palette, as array of System.Drawing.Color objects.</returns>
public static Color[] GenerateRainbowPalette(Int32 bpp, Boolean blackOnZero, Boolean addTransparentZero, Boolean reverseGenerated, Int32 startHue, Int32 endHue, Boolean inclusiveEnd)
{
Int32 colors = 1 << bpp;
Color[] pal = new Color[colors];
Double step = (Double)(endHue - startHue) / (inclusiveEnd ? colors - 1 : colors);
Double start = startHue;
Double satValue = ColorHSL.SCALE;
Double lumValue = 0.5 * ColorHSL.SCALE;
for (Int32 i = 0; i < colors; i++)
{
Double curStep = start + step * i;
pal[i] = new ColorHSL(curStep, satValue, lumValue);
}
if (reverseGenerated)
{
Color[] entries = pal.Reverse().ToArray();
for (Int32 i = 0; i < pal.Length; i++)
pal[i] = entries[i];
}
if (blackOnZero)
pal[0] = Color.Black;
// make color 0 transparent
if (addTransparentZero)
pal[0] = Color.FromArgb(0, pal[0]);
return pal;
}
一个完整的色调旋转会从红色到黄色到青色到绿色到蓝色到紫色再回到红色。但是,为了对伪色产生良好的效果,您不希望最高值与最低值相似,因此最好从蓝色变为红色。这意味着,限制生成的范围,并反转最终的调色板,这就是我在函数中添加这些选项的原因。
蓝色到红色彩虹的最终调用是这样的:
Color[] heightmap = PaletteUtils.GenerateRainbowPalette(8, false, false, true, 0, 160, true);
应用在游戏地形高度图上的结果: