您现在的位置是:首页 >学无止境 >关于图像抖动算法的应用-附加C#源码网站首页学无止境

关于图像抖动算法的应用-附加C#源码

weixin_42616441 2025-03-26 00:01:02
简介c#图像抖动

在上一篇文章中,已经大概说了下图片抖动算法的实现,并且贴出了js代码。

今天,我们再来聊聊这个话题。

为什么要用抖动处理图片,在哪些场景需要用到?是不是必须的?

首先,我们先谈谈应用场景问题,上篇文章也大概说了,类似于墨水屏这种设备,就需要用到这种图像处理方式。

那么,是不是必须要抖动处理才行呢?不是的。也可以不处理直接显示图像,只是效果就没有那么好。为了直观一点感受一下,我们来上一组图:

原图:

抖动处理后:

未抖动处理:

从上面的三张图片,我们就很直观的看到效果了,没有抖动处理的图片显示出来后会失去部分渐变的细节,看起来很呆板,而抖动处理后的效果虽然会损失一部分细节,但是整体图像完整性效果保留较好。

为什么会这样呢?

因为类似电子纸的这种显示屏,并不支持过渡色,只能显示纯黑色、白色或者红色等,所以,我们才会使用到抖动算法,来处理图像,让大部分保留的细节都处理成纯色,这样就可以更好的在屏幕上面显示。

下面上C#版本的实现源码:

using SkiaSharp;
using System.Runtime.InteropServices;

public class DitheringHelper
{
    private int srcW, srcH;
    private int dstW, dstH;

    private SKColor[] curPal;

    private SKBitmap srcBmp;

    public DitheringHelper(SKBitmap srcBmp, SKColor[] pallete)
    {
        this.curPal = pallete;
        this.srcBmp = srcBmp;
    }

    private double GetErr(double r, double g, double b, SKColor stdCol)
    {
        r -= stdCol.Red;
        g -= stdCol.Green;
        b -= stdCol.Blue;

        return r * r + g * g + b * b;
    }

    private int GetNear(double r, double g, double b)
    {
        int ind = 0;
        double err = GetErr(r, g, b, curPal[0]);

        for (int i = 1; i < curPal.Length; i++)
        {
            double cur = GetErr(r, g, b, curPal[i]);
            if (cur < err) { err = cur; ind = i; }
        }

        return ind;
    }

    private int GetNear(SKColor clr)
    {
        return GetNear(clr.Red, clr.Green, clr.Blue);
    }

    private void AddVal(double[] e, int i, double r, double g, double b, double k)
    {
        int index = i * 3;
        e[index] = (r * k) / 16 + e[index];
        e[index + 1] = (g * k) / 16 + e[index + 1];
        e[index + 2] = (b * k) / 16 + e[index + 2];
    }

    private SKColor NearColor(int x, int y)
    {
        if ((x >= srcW) || (y >= srcH)) return curPal[(x + y) % 2 == 0 ? 1 : 0];
        return curPal[GetNear(srcBmp.GetPixel(x, y))];
    }

    public SKBitmap Dither(int? brightness = null, int? contrastRatio = null, SKColor? resultColor = null, bool original = false, bool isLvl = false)
    {
        if (resultColor.HasValue)
        {
            curPal = new[] { SKColors.Black, SKColors.White };
        }

        double contrast = 0;

        var degree_con = contrastRatio.HasValue ? contrastRatio.Value : 0;
        var degree_bri = brightness.HasValue ? brightness.Value : 0;

        dstW = srcBmp.Width;
        dstH = srcBmp.Height;

        srcW = srcBmp.Width;
        srcH = srcBmp.Height;

        SKColor[] dstArr = new SKColor[dstW * dstH];

        int index = 0;

        if (degree_con != 0 || degree_bri != 0)
        {
            if (degree_con != 0)
            {
                if (degree_con < -100) degree_con = -100;
                if (degree_con > 100) degree_con = 100;
            }

            if (degree_bri != 0)
            {
                if (degree_bri < -255) degree_bri = -255;
                if (degree_bri > 255) degree_bri = 255;
            }

            contrast = (100.0 + degree_con) / 100.0;
            contrast *= contrast;
        }

        if (isLvl)
        {
            for (int y = 0; y < dstH; y++)
                for (int x = 0; x < dstW; x++)
                    dstArr[index++] = NearColor(x, y);
        }
        else
        {
            int aInd = 0;
            int bInd = 1;

            double[][] errArr = new double[2][];

            errArr[0] = new double[3 * dstW];
            errArr[1] = new double[3 * dstW];

            for (int i = 0; i < dstW; i++)
            {
                errArr[bInd][3 * i] = 0;
                errArr[bInd][3 * i + 1] = 0;
                errArr[bInd][3 * i + 2] = 0;
            }

            for (int j = 0; j < dstH; j++)
            {
                if (j >= srcH)
                {
                    for (int i = 0; i < dstW; i++, index++)
                        dstArr[index] = curPal[(i + j) % 2 == 0 ? 1 : 0];
                    continue;
                }

                aInd = ((bInd = aInd) + 1) & 1;

                for (int i = 0; i < dstW; i++)
                {
                    errArr[bInd][3 * i] = 0;
                    errArr[bInd][3 * i + 1] = 0;
                    errArr[bInd][3 * i + 2] = 0;
                }

                for (int i = 0; i < dstW; i++)
                {
                    if (i >= srcW)
                    {
                        dstArr[index++] = curPal[(i + j) % 2 == 0 ? 1 : 0];
                        continue;
                    }

                    SKColor srcPix = srcBmp.GetPixel(i, j);

                    if (degree_con != 0 || degree_bri != 0)
                    {
                        if (degree_con != 0)
                        {
                            var R = ((srcPix.Red / 255.0 - 0.5) * contrast + 0.5) * 255;
                            var G = ((srcPix.Green / 255.0 - 0.5) * contrast + 0.5) * 255;
                            var B = ((srcPix.Blue / 255.0 - 0.5) * contrast + 0.5) * 255;

                            if (R < 0) R = 0;
                            if (R > 255) R = 255;

                            if (G < 0) G = 0;
                            if (G > 255) G = 255;

                            if (B < 0) B = 0;
                            if (B > 255) B = 255;

                            srcPix = new SKColor((byte)R, (byte)G, (byte)B);
                        }

                        if (degree_bri != 0)
                        {
                            var R = srcPix.Red + degree_bri;
                            var G = srcPix.Green + degree_bri;
                            var B = srcPix.Blue + degree_bri;

                            if (R < 0) R = 0;
                            if (R > 255) R = 255;

                            if (G < 0) G = 0;
                            if (G > 255) G = 255;

                            if (B < 0) B = 0;
                            if (B > 255) B = 255;

                            srcPix = new SKColor((byte)R, (byte)G, (byte)B);
                        }
                    }

                    double r = srcPix.Red + errArr[aInd][3 * i];
                    double g = srcPix.Green + errArr[aInd][3 * i + 1];
                    double b = srcPix.Blue + errArr[aInd][3 * i + 2];

                    SKColor colVal = curPal[GetNear(r, g, b)];

                    if (original)
                    {
                        colVal = srcPix;
                    }

                    dstArr[index++] = colVal;

                    if (!original)
                    {
                        r -= colVal.Red;
                        g -= colVal.Green;
                        b -= colVal.Blue;

                        if (i == 0)
                        {
                            AddVal(errArr[bInd], (i), r, g, b, 7.0);
                            AddVal(errArr[bInd], (i + 1), r, g, b, 2.0);
                            AddVal(errArr[aInd], (i + 1), r, g, b, 7.0);
                        }
                        else if (i == dstW - 1)
                        {
                            AddVal(errArr[bInd], (i - 1), r, g, b, 7.0);
                            AddVal(errArr[bInd], (i), r, g, b, 9.0);
                        }
                        else
                        {
                            AddVal(errArr[bInd], (i - 1), r, g, b, 3.0);
                            AddVal(errArr[bInd], (i), r, g, b, 5.0);
                            AddVal(errArr[bInd], (i + 1), r, g, b, 1.0);
                            AddVal(errArr[aInd], (i + 1), r, g, b, 7.0);
                        }
                    }
                }
            }
        }

        int k = 0;

        var outputData = new byte[dstArr.Length * 4];
        for (int i = 0; i < outputData.Length; i += 4)
        {
            var tc = dstArr[k];

            if (resultColor.HasValue)
            {
                byte gray = (byte)(0.299 * tc.Red + 0.587 * tc.Green + 0.114 * tc.Blue);

                if (gray < 128) tc = resultColor.Value;
            }

            outputData[i] = tc.Blue;
            outputData[i + 1] = tc.Green;
            outputData[i + 2] = tc.Red;
            outputData[i + 3] = tc.Alpha;

            k++;
        }

        var gcHandle = GCHandle.Alloc(outputData, GCHandleType.Pinned);

        var info = new SKImageInfo(dstW, dstH, SKImageInfo.PlatformColorType, SKAlphaType.Unpremul);
        srcBmp.InstallPixels(info, gcHandle.AddrOfPinnedObject(), info.RowBytes, delegate { gcHandle.Free(); }, null);

        return srcBmp;
    }
}

代码中使用到了SkiaSharp,需要自行引入到项目中,使用方法比较简单,具体如下:

var ditheringHelper = new DitheringHelper(图片, [SKColors.Black, SKColors.White, SKColors.Red]);

var bitmap = ditheringHelper.Dither();

关于图像抖动处理问题今天就说到这里,希望对大家有所帮助,以后我还会写更多的关于墨水屏、电子纸方面一些开发经验,若有不足之处欢迎大家留言以便修正。

JS实现:

js图像抖动算法实现可用于电子标签墨水屏-CSDN博客

打工人必备文档转换工具:

PdfConvert免费PDF、WORD转换工具的使用-CSDN博客

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。