8

For those who aren't familiar with the Gimp's "Color to Alpha" feature yet, here is the page on it from the Gimp's documentation: Color to Alpha. It does a really good job, and I wonder much how exactly Gimp does it in terms of color manipulation, whichever color space the colors might be in. Thanks a bunch for any hints.

EDIT 1: Generating transparency information for a pixel based on its similarity to the key color (the one you select in the "Color to Alpha" dialog), like some folk suggested before removing his answer for some reason, would sound like a good insight, but I suppose it's more intricate than that. Let's assume we estimate color similarity in the unit range from 0.0 to 1.0 and we've got a pixel whose color is, for example, 0.4 similar to, say, the color of white (like you would have selected white in the "Color to Alpha" dialog) and therefore the pixel gets an alpha value of 0.6, then how would you alter the pixel's actual color to compensate the loose of brightness/luminosity/saturation when the resulting pixel is displayed against a white background with the alpha of 0.6?

EDIT 2: Actually an update: The sub-question related to the first edit has been answered in How to change the alpha of a pixel without changing the resulting color? but it's probably not the full story because what is going on in the Gimp's source for the "Color to Alpha" feature is not that simple and seems to be based on a specific algorithm rather than a formula.

4

5 回答 5

10

我看了一下源代码,它的核心是 colortoalpha 函数。参数 *a1 到 *a4 分别是输入/输出红色、绿色、蓝色和 alpha,c1 到 c3 是制作 alpha 的颜色。

当您将两种颜色 c1 和 c2 与特定的 alpha a (0 ≤ a ≤ 1) 组合时,结果是

y = a * c1 + (1-a) * c2

这里我们做相反的操作:我们知道最终结果 y 和背景颜色 c2,并想计算出 c1 和 a。由于这是一个未充分说明的方程,因此存在无限量的解。但是,范围 0 ≤ c1 ≤ 255 和 0 ≤ a ≤ 1 为解增加了界限。

Gimp 插件的工作方式是,对于每个像素,它都会最小化 alpha 值(即最大化透明度)。相反,这意味着对于每个不完全透明的结果像素(即不完全是背景颜色),RGB 分量之一是 0 或 255。

这会生成一个图像,当叠加在指定颜色的顶部时将生成原始图像(没有舍入误差),并且每个像素都具有最大的透明度。

值得注意的是,整个过程是在 RGB 颜色空间中完成的,但也可以在其他颜色空间中执行,只要在同一颜色空间中完成组合操作即可。

于 2013-02-16T21:39:16.507 回答
2

我在我的编辑器www.Photopea.com 中实现了这个过滤器,在 Filter - Other - Color to Alpha 下。结果与 GIMP 100% 相同。算法极其简单

想法:使用透明度值 A 将背景颜色 B 与前景色 F 组合,以获得新颜色 N:

N = A * F  +  (1 - A) * B;

你知道N(图像中的实际颜色)和B(滤镜的参数),你想恢复前景色F和它的透明度A。

像这样做:

A = max( abs(N.r - B.r), abs(N.g - B.g), abs(N.b - B.b)  )  

现在,您知道 N、B、A。只需使用上面的公式来计算 F。

于 2020-06-11T21:56:08.403 回答
1

你需要想出一种机制来比较颜色的相似性。您可以在多种色彩空间中执行此操作。RGB 通常不是这类事情的最佳选择。但是您可以使用 HSV、YCbCr 或其他一些亮度/色度空间。通常,其中一个空间中的距离会比 RGB 中的欧几里德距离为您提供更好的答案。一旦有了距离,就可以将其除以最大距离以获得百分比。作为一种可能性,该百分比将是您想要使用的 alpha 的倒数。

如果你想知道 GIMP 是怎么做的,可以看源码。例如,这是该插件最近的一项代码更改。

于 2012-02-16T20:52:59.217 回答
1

所以我查看了GIMP 源代码……嗯!我使它通用且可读。不过还是蛮快的。有关数学解释,请参阅Sampo 的答案。这是 C# 实现(易于转换为 C / C++):

static class PixelShaders {

    /// <summary>
    /// Generic color space color to alpha.
    /// </summary>
    /// <param name="pA">Pixel alpha.</param>
    /// <param name="p1">Pixel 1st channel.</param>
    /// <param name="p2">Pixel 2nd channel.</param>
    /// <param name="p3">Pixel 3rd channel.</param>
    /// <param name="r1">Reference 1st channel.</param>
    /// <param name="r2">Reference 2nd channel.</param>
    /// <param name="r3">Reference 3rd channel.</param>
    /// <param name="mA">Maximum alpha value.</param>
    /// <param name="mX">Maximum channel value.</param>
    static void GColorToAlpha(ref double pA, ref double p1, ref double p2, ref double p3, double r1, double r2, double r3, double mA = 1.0, double mX = 1.0) {
        double aA, a1, a2, a3;
        // a1 calculation: minimal alpha giving r1 from p1
        if (p1 > r1) a1 = mA * (p1 - r1) / (mX - r1);
        else if (p1 < r1) a1 = mA * (r1 - p1) / r1;
        else a1 = 0.0;
        // a2 calculation: minimal alpha giving r2 from p2
        if (p2 > r2) a2 = mA * (p2 - r2) / (mX - r2);
        else if (p2 < r2) a2 = mA * (r2 - p2) / r2;
        else a2 = 0.0;
        // a3 calculation: minimal alpha giving r3 from p3
        if (p3 > r3) a3 = mA * (p3 - r3) / (mX - r3);
        else if (p3 < r3) a3 = mA * (r3 - p3) / r3;
        else a3 = 0.0;
        // aA calculation: max(a1, a2, a3)
        aA = a1;
        if (a2 > aA) aA = a2;
        if (a3 > aA) aA = a3;
        // apply aA to pixel:
        if (aA >= mA / mX) {
            pA = aA * pA / mA;
            p1 = mA * (p1 - r1) / aA + r1;
            p2 = mA * (p2 - r2) / aA + r2;
            p3 = mA * (p3 - r3) / aA + r3;
        } else {
            pA = 0;
            p1 = 0;
            p2 = 0;
            p3 = 0;
        }
    }

}

GIMP 的实现(此处)使用 RGB 颜色空间,使用 alpha 值,float范围为 0 到 1,R、G、B 范围为float0 到 255。

当图像具有 JPEG 伪影时,RGB 实现会失败,因为它们意味着可感知的颜色偏差微不足道,但绝对 R、G、B 偏差非常显着。使用 LAB 颜色空间应该可以解决这个问题。

如果您只是想从图像中删除纯色背景,则颜色到 Alpha 算法不是最佳选择。当使用 LAB 颜色空间计算每个像素的颜色空间距离时,我得到了很好的结果。然后将计算出的距离应用于原始图像的 Alpha 通道。这和颜色到 alpha 之间的主要区别是像素的色调不会改变。背景移除只是将 alpha(不透明度)设置为色彩空间差异。如果前景图像中没有出现背景颜色,则效果很好。如果确实如此,则无法删除背景,或者必须使用 BFS 算法仅遍历外部像素(类似于在 GIMP 中使用魔棒选择,然后删除选择)。

如果前景图像具有与背景颜色相似的颜色的孔和像素,则无法移除背景。此类图像需要一些手动处理。

于 2016-11-29T09:49:53.243 回答
0

我尽我所能将 colortoalpha 方法从 gimp 翻译成 C#。问题是 RGBA 值被视为ImageSharp等库中每个通道的字节。一些转换在转换过程中会丢失数据,但我尽力保留尽可能多的数据。这使用 ImageSharp 进行图像突变。ImageSharp 是完全托管的,因此它可以跨平台工作。它也很快。整个方法运行时间约为 10 毫秒(小于 10 毫秒)。

以下是 C# 实现的代码:

public static unsafe void ColorToAlpha(this Image<Rgba32> image, Rgba32 color)
    {
        double alpha1, alpha2, alpha3, alpha4;
        double* a1, a2, a3, a4;

        a1 = &alpha1;
        a2 = &alpha2;
        a3 = &alpha3;
        a4 = &alpha4;

        for (int j = 0; j < image.Height; j++)
        {
            var span = image.GetPixelRowSpan(j);

            for (int i = 0; i < span.Length; i++)
            {
                ref Rgba32 pixel = ref span[i];

                // Don't know what this is for
                // *a4 = pixel.A;

                if (pixel.R > color.R)
                    *a1 = (pixel.R - color.R) / (255.0 - color.R);
                else if (pixel.R < color.R)
                    *a1 = (color.R - pixel.R) / color.R;
                else
                    *a1 = 0.0;

                if (pixel.G > color.G)
                    *a2 = (pixel.G - color.G) / (255.0 - color.G);
                else if (pixel.G < color.G)
                    *a2 = (color.G - pixel.G) / color.G;
                else
                    *a2 = 0.0;

                if (pixel.B > color.B)
                    *a3 = (pixel.B - color.B) / (255.0 - color.B);
                else if (pixel.B < color.B)
                    *a3 = (color.B - pixel.B) / color.B;
                else
                    *a3 = 0.0;

                if (*a1 > *a2)
                    *a4 = *a1 > *a3 ? *a1 * 255.0 : *a3 * 255.0;
                else
                    *a4 = *a2 > *a3 ? *a2 * 255.0 : *a3 * 255.0;

                if (*a4 < 1.0)
                    return;

                pixel.R = (byte)Math.Truncate((255.0 * (*a1 - color.R) / *a4 + color.R));
                pixel.G = (byte)Math.Truncate((255.0 * (*a2 - color.G) / *a4 + color.G));
                pixel.B = (byte)Math.Truncate((255.0 * (*a3 - color.B) / *a4 + color.B));

                pixel.A = (byte)Math.Truncate(*a4);
            }
        }
    }
于 2018-09-04T15:54:00.897 回答