[PPTX解析] 图片效果算法篇:设置透明色

内容纲要

PPTX解析:设置透明色

在PPT中,我们可以通过图片格式选项中->颜色->设置透明色,将指定颜色设置为透明,以实现去除纯色背景的需求。

file

效果原理

PPT中设置透明色的逻辑十分简单清晰:

· 将颜色A替换为颜色B

当颜色B为透明色时,即可达成将某一种颜色设置为透明色的要求。

解析颜色替换节点(OpenXml)

首先,我们来观察一下PPT存储设置透明色的xml节点:

<p:pic>
    ......
    <p:blipFill>
        <a:blip ......>
            <a:clrChange>
                <a:clrFrom>
                    <a:srgbClr val="FFAAFF" >
                        <a:alpha val="100000" />
                    </a:srgbClr>
                </a:clrFrom>
                <a:clrTo>
                    <a:srgbClr val="FFAAFF">
                        <a:alpha val="0" />
                    </a:srgbClr>
                </a:clrTo>
            </a:clrChange>
        </a:blip>
        ......
    </p:blipFill>
    ......
</p:pic>

节点属性解析:

节点名称 含义 值含义
p:pic 图片 此元素指定文档中的图片对象的存在
p:blipFill 图片填充 此元素指定图片对象具有图片填充的类型
a:blip Blip 此元素指定的图像 (二进制大图像或图片) 存在并包含图像数据的引用
a:clrChange 变色效果 此元素指定颜色变化效果。 clrFrom 的实例替换为 clrTo 的实例。
a:clrFrom Change Color From 此元素指定在颜色更改效果中移除的颜色。它是“来自”或源输入颜色。
a:clrTo Change Color To 此元素指定替换 clrChange 效果中的 clrFrom 的颜色。这是颜色变化效果中的“目标”或“目标”颜色。

颜色的Alpha通道解析

节点<a:srgbClr />中存在属性值val,该属性表示的是颜色的RGB通道的十六进制表示。

子节点<a:alpha />中存在属性值val,该属性表示的是颜色的Alpha通道的千倍百分比(如:100000实际值代表100%)。

注:当节点<a:alpha />中的val值为100000时,该节点可以省略。即当节点<a:srgbClr />中不存在子节点<a:alpha />时,表示该颜色的Alpha通道的百分比为100%(255)。

在该案例中,我们解析出要将图片中的颜色#FFAAFF替换成颜色#00FFAAFF

效果实现(C#)

可以参考下面的代码,将指定Bitmap的颜色进行替换:

namespace DreamBlog.ImageRecolor
{
    public static class BitmapExtension
    {
        /// <summary>
        ///     将图片<paramref name="bitmap"/>上指定的颜色<paramref name="sourceColor"/>替换为颜色<paramref name="targetColor"/>
        /// </summary>
        /// <param name="bitmap">图片</param>
        /// <param name="sourceColor">原始颜色</param>
        /// <param name="targetColor">目标颜色</param>
        /// <param name="accuracyARgb">ARgb通道允许的误差</param>
        public static void ReplaceColor(this Bitmap bitmap, Color sourceColor, Color targetColor, int accuracyARgb = 1)
        {
            bitmap.PerPixelProcess((colorData, channels) =>
            {
                var bytesCount = colorData.Length;
                for (var i = 0; i < bytesCount; i += channels)
                {
                    if (ColorHelper.TryCreateColorFromData(colorData, i, channels, out var color)
                        && color.IsNearlyEquals(sourceColor, accuracyARgb))
                    {
                        ColorHelper.SaveToData(targetColor, colorData, i, channels);
                    }
                }
            });
        }

        /// <summary>
        ///     将图片<paramref name="bitmap"/>上指定的一部分颜色替换为指定的对应颜色
        /// </summary>
        /// <param name="bitmap">图片</param>
        /// <param name="colorInfos">存储替换信息的颜色组</param>
        /// <param name="accuracyARgb">ARgb通道允许的误差</param>
        public static void ReplaceColor(this Bitmap bitmap, Dictionary<Color, Color> colorInfos, int accuracyARgb = 1)
        {
            bitmap.PerPixelProcess((colorData, channels) =>
            {
                var bytesCount = colorData.Length;
                for (var i = 0; i < bytesCount; i += channels)
                {
                    if (!ColorHelper.TryCreateColorFromData(colorData, i, channels, out var color)) continue;

                    foreach (var colorInfo in colorInfos)
                    {
                        if (color.IsNearlyEquals(colorInfo.Key, accuracyARgb))
                        {
                            ColorHelper.SaveToData(colorInfo.Value, colorData, i, channels);
                        }
                    }
                }
            });
        }

        /// <summary>
        ///     对图像进行逐像素处理
        /// </summary>
        /// <param name="bitmap"></param>
        /// <param name="action"></param>
        public static void PerPixelProcess(this Bitmap bitmap, Action<byte[], int> action)
        {
            var pixelFormat = bitmap.PixelFormat;

            if (pixelFormat != PixelFormat.Format32bppArgb && pixelFormat != PixelFormat.Format24bppRgb)
            {
                throw new NotSupportedException($"Unsupported image pixel format {nameof(pixelFormat)} is used.");
            }

            var cols = bitmap.Width;
            var rows = bitmap.Height;
            var channels = Image.GetPixelFormatSize(bitmap.PixelFormat) / 8;
            var total = cols * rows * channels;

            //锁定图片并拷贝图片像素
            var rect = new Rectangle(0, 0, cols, rows);
            var bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
            var iPtr = bitmapData.Scan0;
            var data = new byte[total];
            Marshal.Copy(iPtr, data, 0, total);

            //逐像素处理
            action?.Invoke(data, channels);

            Marshal.Copy(data, 0, iPtr, total);
            bitmap.UnlockBits(bitmapData);
        }
    }

    internal static class ColorExtension
    {
        /// <summary>
        ///     是否是近似颜色
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="accuracyARgb">ARgb通道允许的误差</param>
        /// <returns></returns>
        public static bool IsNearlyEquals(this Color x, Color y, int accuracyARgb = 1)
        {
            return Math.Abs(x.A - y.A) <= accuracyARgb
                    && Math.Abs(x.R - y.R) <= accuracyARgb
                    && Math.Abs(x.G - y.G) <= accuracyARgb
                    && Math.Abs(x.B - y.B) <= accuracyARgb;
        }
    }

    internal static class ColorHelper
    {
        /// <summary>
        ///     尝试获取颜色
        /// </summary>
        /// <param name="colorData"></param>
        /// <param name="offset"></param>
        /// <param name="channels"></param>
        /// <param name="color"></param>
        /// <returns></returns>
        public static bool TryCreateColorFromData(byte[] colorData, int offset, int channels, out Color color)
        {
            //需要考虑大小端
            var isLittleEndian = BitConverter.IsLittleEndian;
            color = Color.Black;

            try
            {
                if (channels == 3)
                {
                    var r = isLittleEndian ? colorData[offset + 2] : colorData[offset];
                    var g = colorData[offset + 1];
                    var b = isLittleEndian ? colorData[offset] : colorData[offset + 2];
                    color = Color.FromArgb(byte.MaxValue, r, g, b);
                    return true;
                }

                if (channels == 4)
                {
                    var a = isLittleEndian ? colorData[offset + 3] : colorData[offset + 0];
                    var r = isLittleEndian ? colorData[offset + 2] : colorData[offset + 1];
                    var g = isLittleEndian ? colorData[offset + 1] : colorData[offset + 2];
                    var b = isLittleEndian ? colorData[offset + 0] : colorData[offset + 3];
                    color = Color.FromArgb(a, r, g, b);
                    return true;
                }
            }
            catch (Exception)
            {
                return false;
            }

            return false;
        }

        /// <summary>
        ///     保存颜色到数组
        /// </summary>
        /// <param name="color"></param>
        /// <param name="colorData"></param>
        /// <param name="offset"></param>
        /// <param name="channels"></param>
        /// <returns></returns>
        public static void SaveToData(Color color, byte[] colorData, int offset, int channels)
        {
            //需要考虑大小端
            var isLittleEndian = BitConverter.IsLittleEndian;

            if (channels == 3)
            {
                colorData[offset] = isLittleEndian ? color.B : color.R;
                colorData[offset + 1] = color.G;
                colorData[offset + 2] = isLittleEndian ? color.R : color.B;
            }

            if (channels == 4)
            {
                colorData[offset + 0] = isLittleEndian ? color.B : color.A;
                colorData[offset + 1] = isLittleEndian ? color.G : color.R;
                colorData[offset + 2] = isLittleEndian ? color.R : color.G;
                colorData[offset + 3] = isLittleEndian ? color.A : color.B;
            }
        }
    }
}

本文会经常更新,请阅读原文:https://imxcg.com/technology/dot-net/pptx-analysis/pptx-analysis-set-transparent-color,以避免陈旧错误知识的误导,同时有更好的阅读体验。

知识共享许可协议 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名 仙尘阁 (包含链接: https://imxcg.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系 (imxcg@foxmail.com)