You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SkiaSharp中如何实现自定义灰度颜色重映射?

SkiaSharp中是否有类似GDI+ ColorMap的自定义颜色重映射机制?

我正在开发一款热力图生成程序,原本基于GDI+编写相关代码,现因多种原因决定迁移至SkiaSharp。当前我有一个灰度图块,其中白色代表最大值,纯黑色为透明。在GDI+/C#中,我可以通过ColorMapSetRemapTable快速实现自定义颜色重映射。我了解过SkiaSharp的ColorFilter,但它采用矩阵变换,无法满足我的自定义重映射需求。请问SkiaSharp是否有类似GDI+的颜色重映射机制?还是我需要手动遍历每个像素进行重新着色?

原GDI+代码:

ImageAttributes imageAttributes = new ImageAttributes();
ColorMap[] remapTable = new ColorMap[256];
Color[] scale = IncandescentHeatScale();
for (int i = 0; i < 256; i++) {
    remapTable[i] = new ColorMap() { OldColor = Color.FromArgb(i, i, i), NewColor = scale[i] };
}
imageAttributes.SetRemapTable(remapTable);
var outTile = new Bitmap(TileSize, TileSize);
using (var g = Graphics.FromImage(outTile)) {
    g.DrawImage(tile, new Rectangle(0, 0, TileSize, TileSize), padding, padding, TileSize, TileSize, GraphicsUnit.Pixel, imageAttributes);
}

参考的颜色刻度生成函数:

static Color[] GetHeatScale(float[] points, Color[] colors) {
    var bm = new Bitmap(256, 1);
    using (Graphics g = Graphics.FromImage(bm)) {
        LinearGradientBrush brush = new LinearGradientBrush(new Point(0, 0), new Point(256, 0), colors[0], colors[colors.Length - 1]);
        var cb = new ColorBlend();
        cb.Colors = colors;
        cb.Positions = points;
        brush.InterpolationColors = cb;
        g.FillRectangle(brush, 0, 0, 256, 1);
    }
    return Enumerable.Range(0, 256).Select(x => bm.GetPixel(x, 0)).ToArray();
}

static Color[] IncandescentHeatScale() {
    float[] points = new float[] { 0.0f, 0.333f, 0.6666f, 1f };
    Color[] colors = new Color[] { Color.Black, Color.DarkRed, Color.Yellow, Color.White };
    return GetHeatScale(points, colors);
}

回答

好消息是,SkiaSharp确实有和GDI+ ColorMap功能几乎一致的颜色重映射方案——SKColorFilter.CreateTable(),它基于查找表(LUT)实现,完美适配你256级灰度图的自定义着色需求,完全不用手动遍历每个像素。

核心原理

SKColorFilter.CreateTable()允许你为红、绿、蓝、alpha四个通道分别提供一个长度为256的字节数组,输入像素的每个通道值(0-255)会直接被替换成对应数组中的值。对于你的灰度图场景,因为输入的R、G、B通道值完全相同,你可以复用同一个热力颜色刻度表到RGB三个通道,alpha通道则可以保持原样(或者根据你的透明需求调整)。

迁移后的代码实现

第一步:将GDI+颜色刻度转为SkiaSharp格式

你可以直接复用原来的IncandescentHeatScale()函数,只需要把生成的Color数组转换为SkiaSharp的SKColor数组:

// 复用原GDI+的颜色刻度生成逻辑
Color[] gdiHeatScale = IncandescentHeatScale();
// 转换为SKColor数组
SKColor[] skHeatScale = gdiHeatScale.Select(c => new SKColor(c.R, c.G, c.B, c.A)).ToArray();

第二步:构建颜色查找表并创建滤镜

// 为RGB三个通道构建查找表(因为输入是灰度图,三个通道值相同)
byte[] redTable = skHeatScale.Select(c => c.Red).ToArray();
byte[] greenTable = skHeatScale.Select(c => c.Green).ToArray();
byte[] blueTable = skHeatScale.Select(c => c.Blue).ToArray();
// alpha通道保持原样(如果原灰度图的黑色需要透明,也可以在这里自定义)
byte[] alphaTable = Enumerable.Range(0, 256).Select(i => (byte)i).ToArray();

// 创建颜色重映射滤镜
using var colorFilter = SKColorFilter.CreateTable(redTable, greenTable, blueTable, alphaTable);

第三步:应用滤镜绘制图像

SKCanvas绘制时,只需要把滤镜赋值给SKPaint,就能实现和GDI+ DrawImageImageAttributes完全一致的效果:

// 假设tile是你的输入SKBitmap
using var outTile = new SKBitmap(TileSize, TileSize);
using var canvas = new SKCanvas(outTile);
using var paint = new SKPaint { ColorFilter = colorFilter };

// 对应原GDI+的DrawImage裁剪绘制逻辑
canvas.DrawBitmap(
    tile, 
    new SKRect(padding, padding, padding + TileSize, padding + TileSize), // 源区域
    new SKRect(0, 0, TileSize, TileSize), // 目标区域
    paint
);

可选:完全基于SkiaSharp生成热力颜色刻度

如果你想彻底摆脱GDI+依赖,也可以用SkiaSharp的SKShader实现线性渐变来生成颜色刻度:

static SKColor[] SkiaIncandescentHeatScale() {
    float[] points = new float[] { 0.0f, 0.333f, 0.6666f, 1f };
    SKColor[] colors = new SKColor[] { SKColors.Black, SKColors.DarkRed, SKColors.Yellow, SKColors.White };
    
    // 创建多色线性渐变着色器
    using var shader = SKShader.CreateLinearGradient(
        new SKPoint(0, 0), 
        new SKPoint(256, 0), 
        colors, 
        points, 
        SKShaderTileMode.Clamp
    );
    
    // 绘制渐变到临时Bitmap
    using var tempBmp = new SKBitmap(256, 1);
    using var canvas = new SKCanvas(tempBmp);
    using var paint = new SKPaint { Shader = shader };
    canvas.DrawRect(new SKRect(0, 0, 256, 1), paint);
    
    // 提取每个位置的颜色
    SKColor[] scale = new SKColor[256];
    for (int i = 0; i < 256; i++) {
        scale[i] = tempBmp.GetPixel(i, 0);
    }
    return scale;
}

总结

SKColorFilter.CreateTable()完全替代了GDI+ ColorMap的重映射功能,性能上和原生实现相当,而且不需要手动操作像素,是最适合你的迁移方案。

内容的提问来源于stack exchange,提问作者jklemmack

火山引擎 最新活动