您现在的位置是:首页 >学无止境 >关于图像抖动算法的应用-附加C#源码网站首页学无止境
关于图像抖动算法的应用-附加C#源码
简介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实现:
打工人必备文档转换工具:
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。