昨天想基于一张图片做个手机锁屏来着,原图如下:
主要是嫌白底太丑了,一开始是想画图工具直接油漆桶伺候,然而一浇上去就发现问题了,变成了这样:
看来得手工处理一下把底色统一了,原图分辨率挺高的,SetPixel显然会太慢,所以只能LockBits咯。
LockBits的使用方法和参数什么的都可以百度和MSDN,不多说,直接贴一个BitmapWrapper先:
unsafe class BitmapWrapper
{
private readonly Bitmap bmp;
private readonly BitmapData bmpData; private readonly byte* scan0;
private readonly int byteCount; public BitmapWrapper(Bitmap bitmap)
{
bmp = bitmap;
bmpData = bmp.LockBits(
new Rectangle(, , bmp.Width, bmp.Height),
ImageLockMode.ReadWrite,
bmp.PixelFormat); scan0 = (byte*) bmpData.Scan0;
// byteCount = bmpData.Stride / bmpData.Width;
byteCount = bmpData.PixelFormat.ToString().IndexOf("") > ? : ;
}
public Bitmap UnWrapper()
{
bmp.UnlockBits(bmpData);
return bmp;
}
public void SetPixel(Point point, Color color)
{
int offset = (point.X - ) * byteCount + (point.Y - ) * bmpData.Stride;
scan0[offset] = color.B;
scan0[offset + ] = color.G;
scan0[offset + ] = color.R;
if (byteCount == )
scan0[offset + ] = color.A;
}
public Color GetPixel(Point point)
{
int offset = (point.X - ) * byteCount + (point.Y - ) * bmpData.Stride;
Color color = Color.FromArgb(
scan0[offset + ],
scan0[offset + ],
scan0[offset]
);
if (byteCount == )
color = Color.FromArgb(scan0[offset + ], color);
return color;
}
}
注意代码里头有一句注掉了,那里是我出现第一个问题的地方。。。
本来是想计算每一像素占的字节数,那就拿每行的字节数除每一行的像素数咯,于是就错了。。。
MSDN查BitmapData.Stride可以看到备注里面的一句话:
跨距是单行像素(一个扫描行)的宽度,舍入为一个 4 字节的边界。
所以跨距其实应该是等于这样的:Stride = byteCount * Width + ((byteCount * Width) % 4) == 0 ? 0 : (4 - (byteCount * Width) % 4)
于是不知道该怎么反解byteCount,所以用了19行的那个方法,暂时忽略其他情况吧。。。
第二个问题是发生在存取RGB三个byte值的时候。
因为每个像素的RGB三个值是从高位到低位放置的,所以SetPixel里面应该是这样:
scan0[offset] = color.B;
scan0[offset + ] = color.G;
scan0[offset + ] = color.R;
而不是这样:
scan0[offset] = color.R;
scan0[offset + ] = color.G;
scan0[offset + ] = color.B;
第三个问题发生在保存图片的时候。。。本来是这么写的:
bmp.Save("Juven.bmp");
打开图片再用油漆桶,发现还是和原来差不多,底色里面仍然参杂了高度接近纯白的灰色斑点。
因为Save不管你文件扩展名是什么的啊!通通默认Jpeg啊!一压缩就前功尽弃了!所以应该改成这样:
bmp.Save(@"Juven.bmp", ImageFormat.Bmp);
这样就对了,油漆桶后的效果如下(上传前转回jpg了,所以这张图的底色其实还是不纯的):
既然都走到这一步了,就干脆走得远一点,直接代码做成品了:
Bitmap bmp = new Bitmap(src);
BitmapWrapper wrapper = new BitmapWrapper(bmp); byte r, g, b;
for (int y = ; y <= bmp.Height; y++)
{
for (int x = ; x <= bmp.Width; x++)
{
Point point = new Point(x, y);
Color cr = wrapper.GetPixel(point);
if (cr.R + cr.G + cr.B >= )
{
if (x < )
{
r = ;
g = ;
b = ;
}
else if (x > )
{
r = ;
g = ;
b = ;
}
else
r = g = b = ;
wrapper.SetPixel(point, Color.FromArgb(r, g, b));
}
else break;
} for (int x = bmp.Width; x > ; x--)
{
Point point = new Point(x, y);
Color cr = wrapper.GetPixel(point);
if (cr.R + cr.G + cr.B >= )
{
if (x < )
{
r = ;
g = ;
b = ;
}
else if (x > )
{
r = ;
g = ;
b = ;
}
else
r = g = b = ;
wrapper.SetPixel(point, Color.FromArgb(r, g, b));
}
else break;
}
}
57 wrapper.UnWrapper();
bmp.Save(target);
成品图如下:
最后想说的是,巴萨梅球王求轻虐十个以内啊!