SkiaSharp 颜色筛选器

颜色筛选器可以将位图(或其他图像)中的颜色转换为其他颜色,以实现色调分离等效果:

颜色筛选器示例

若要使用颜色筛选器,请将 SKPaintColorFilter 属性设置为该类中某个静态方法创建的类型 SKColorFilter 的对象。 本文演示了:

颜色转换

颜色转换涉及使用矩阵修改颜色。 与大多数 2D 图形系统一样,SkiaSharp 主要使用矩阵来转换坐标点,如 SkiaSharp 中的矩阵转换一文中所述。 SKColorFilter 也支持矩阵转换,但矩阵转换 RGB 颜色。 熟悉一些矩阵的概念是了解这些颜色转换所必需的。

颜色转换矩阵的维度为四行和五列:

| M11 M12 M13 M14 M15 |
| M21 M22 M23 M24 M25 |
| M31 M32 M33 M34 M35 |
| M41 M42 M43 M44 M45 |

它将 RGB 源色(R、G、B、A)转换为目标色(R'、G'、B'、A')。

在矩阵乘法的准备中,源色将转换为 5×1 矩阵:

| R |
| G |
| B |
| A |
| 1 |

这些 R、G、B 和 A 值是介于 0 到 255 之间的原始字节。 它们未规范化为范围 0 到 1 中的浮点值。

转换因子需要额外的单元格。 这类似于使用 3×3 矩阵转换二维坐标点,如本文中关于使用矩阵转换坐标点的“使用 3×3 矩阵的原因”一节中所述。

4×5 矩阵乘以 5×1 矩阵,乘积为 4×1 矩阵,转换的颜色为:

| M11 M12 M13 M14 M15 |    | R |   | R' |
| M21 M22 M23 M24 M25 |    | G |   | G' |
| M31 M32 M33 M34 M35 |  × | B | = | B' |
| M41 M42 M43 M44 M45 |    | A |   | A' |
                           | 1 |

下面是 R'、G'、B' 和 A' 的单独公式:

R' = M11·R + M12·G + M13·B + M14·A + M15

G' = M21·R + M22·G + M23·B + M24·A + M25

B' = M31·R + M32·G + M33·B + M34·A + M35

A' = M41·R + M42·G + M43·B + M44·A + M45

大多数矩阵由通常介于 0 到 2 范围内的乘法因子组成。 但是,最后一列(M15 到 M45)包含公式中添加的值。 这些值通常范围为 0 到 255。 结果的值固定在 0 到 255 之间。

标识矩阵为:

| 1 0 0 0 0 |
| 0 1 0 0 0 |
| 0 0 1 0 0 |
| 0 0 0 1 0 |

这不会导致颜色更改。 转换公式为:

R' = R

G' = G

B' = B

A' = A

M44 单元格非常重要,因为它保留了不透明度。 通常,M41、M42 和 M43 均为零,因为你可能不希望不透明度基于红色、绿色和蓝色值。 但是,如果 M44 为零,则 A' 将为零,没有任何内容可见。

颜色矩阵的最常见用途之一是将颜色位图转换为灰度位图。 这涉及到对红色、绿色和蓝色值的加权平均值的公式。 对于使用 sRGB(“标准红绿蓝”)颜色空间的视频显示,此公式为:

gray-shade = 0.2126·R + 0.7152·G + 0.0722·B

若要将颜色位图转换为灰度位图,R'、G' 和 B' 结果必须全部等于同一值。 矩阵为:

| 0.21 0.72 0.07 0 0 |
| 0.21 0.72 0.07 0 0 |
| 0.21 0.72 0.07 0 0 |
| 0    0    0    1 0 |

没有与此矩阵对应的 SkiaSharp 数据类型。 相反,必须以行顺序将矩阵表示为 20 个 float 值的数组:第一行、第二行,以此类推。

静态 SKColorFilter.CreateColorMatrix 方法具有以下语法:

public static SKColorFilter CreateColorMatrix (float[] matrix);

其中 matrix 是 20 个 float 值的数组。 使用 C# 创建数组时,可以轻松设置数字的格式,使其类似于 4×5 矩阵。 这在示例的“灰度矩阵”页中进行了演示

public class GrayScaleMatrixPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(CenteredTilesPage),
                        "SkiaSharpFormsDemos.Media.Banana.jpg");

    public GrayScaleMatrixPage()
    {
        Title = "Gray-Scale Matrix";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.ColorFilter =
                SKColorFilter.CreateColorMatrix(new float[]
                {
                    0.21f, 0.72f, 0.07f, 0, 0,
                    0.21f, 0.72f, 0.07f, 0, 0,
                    0.21f, 0.72f, 0.07f, 0, 0,
                    0,     0,     0,     1, 0
                });

            canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform, paint: paint);
        }
    }
}

此代码中使用的 DrawBitmap 方法来自示例中包含的 BitmapExtension.cs 文件

下面是在 iOS、Android 和通用 Windows 平台上运行的结果:

灰度矩阵

注意第四行和第四列中的值。 这是乘以原始颜色的 A 值得到转换后颜色的 A' 值的关键因素。 如果该单元格为零,则不会显示任何内容,并且问题可能难以找到。

在试验颜色矩阵时,可以从源的角度或目标的角度处理转换。 源的红色像素对目标的红色、绿色和蓝色像素有什么影响? 这由矩阵第一中的值确定。 或者,目标红色像素应如何受到源的红色、绿色和蓝色像素的影响? 这由矩阵的第一确定。

有关如何使用颜色转换的一些想法,请参阅重新着色图像页。 该讨论是关于 Windows 窗体的,虽然矩阵是一种不同的格式,但概念是相同的。

粉彩矩阵通过衰减源红色像素并稍微强调红色和绿色像素来计算目标红色像素。 对于绿色和蓝色像素,此过程类似:

public class PastelMatrixPage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(PastelMatrixPage),
                        "SkiaSharpFormsDemos.Media.MountainClimbers.jpg");

    public PastelMatrixPage()
    {
        Title = "Pastel Matrix";

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.ColorFilter =
                SKColorFilter.CreateColorMatrix(new float[]
                {
                    0.75f, 0.25f, 0.25f, 0, 0,
                    0.25f, 0.75f, 0.25f, 0, 0,
                    0.25f, 0.25f, 0.75f, 0, 0,
                    0, 0, 0, 1, 0
                });

            canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform, paint: paint);
        }
    }
}

其结果是减弱了色彩的强度,如下所示:

粉彩矩阵

颜色表

静态 SKColorFilter.CreateTable 方法有两个版本:

public static SKColorFilter CreateTable (byte[] table);

public static SKColorFilter CreateTable (byte[] tableA, byte[] tableR, byte[] tableG, byte[] tableB);

数组始终包含 256 个条目。 在具有一个表的 CreateTable 方法中,同一个表用于红色、绿色和蓝色组件。 它是一个简单的查找表:如果源颜色为(R、G、B),并且目标颜色为(R'、B'、G'),则目标组件是通过使用源组件编制 table 的索引来获取的:

R' = table[R]

G' = table[G]

B' = table[B]

在第二种方法中,四种颜色组件中的每一个都可以具有单独的颜色表,或者同一颜色表可能在两个或更多个组件之间共享。

如果要将其中一个参数设置为颜色表的第二个 CreateTable 方法,而该颜色表包含序列中 0 到 255 的值,则可以改用 null。 通常情况下,CreateTable 调用的第一个参数是关于 alpha 通道的 null

在有关访问 SkiaSharp 位图像素位的文章中的色调分离部分中,你了解了如何修改位图的各个像素位以降低其颜色分辨率。 这是一种称为色调分离的技术。

还可以使用颜色表使位图色调分离。 “表格色调分离”页的构造函数会创建一个颜色表,该颜色表将其索引映射到底部 6 位设置为零的字节:

public class PosterizeTablePage : ContentPage
{
    SKBitmap bitmap = BitmapExtensions.LoadBitmapResource(
                        typeof(PosterizeTablePage),
                        "SkiaSharpFormsDemos.Media.MonkeyFace.png");

    byte[] colorTable = new byte[256];

    public PosterizeTablePage()
    {
        Title = "Posterize Table";

        // Create color table
        for (int i = 0; i < 256; i++)
        {
            colorTable[i] = (byte)(0xC0 & i);
        }

        SKCanvasView canvasView = new SKCanvasView();
        canvasView.PaintSurface += OnCanvasViewPaintSurface;
        Content = canvasView;
    }

    void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
    {
        SKImageInfo info = args.Info;
        SKSurface surface = args.Surface;
        SKCanvas canvas = surface.Canvas;

        canvas.Clear();

        using (SKPaint paint = new SKPaint())
        {
            paint.ColorFilter =
                SKColorFilter.CreateTable(null, null, colorTable, colorTable);

            canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform, paint: paint);
        }
    }
}

程序选择仅将此颜色表用于绿色和蓝色通道。 红色通道继续具有完整分辨率:

表格色调分离

可以将各种颜色表用于不同颜色通道以实现各种效果。