昨天想基于一张图片做个手机锁屏来着,原图如下:主要是嫌白底太丑了,一开始是想画图工具直接油漆桶伺候,然而一浇上去就发现问题了,变成了这样:
看来得手工处理一下把底色统一了,原图分辨率挺高的,SetPixel显然会太慢,所以只能LockBits咯。LockBits的使用方法和参数什么的都可以百度和MSDN,不多说,直接贴一个BitmapWrapper先:
1 unsafe class BitmapWrapper 2 { 3 PRivate readonly Bitmap bmp; 4 private readonly BitmapData bmpData; 5 6 private readonly byte* scan0; 7 private readonly int byteCount; 8 9 public BitmapWrapper(Bitmap bitmap)10 {11 bmp = bitmap;12 bmpData = bmp.LockBits(13 new Rectangle(0, 0, bmp.Width, bmp.Height),14 ImageLockMode.ReadWrite,15 bmp.PixelFormat);16 17 scan0 = (byte*) bmpData.Scan0;18 // byteCount = bmpData.Stride / bmpData.Width;19 byteCount = bmpData.PixelFormat.ToString().IndexOf("32") > 0 ? 4 : 3;20 }21 public Bitmap UnWrapper()22 {23 bmp.UnlockBits(bmpData);24 return bmp;25 }26 public void SetPixel(Point point, Color color)27 {28 int offset = (point.X - 1) * byteCount + (point.Y - 1) * bmpData.Stride;29 scan0[offset] = color.B;30 scan0[offset + 1] = color.G;31 scan0[offset + 2] = color.R;32 if (byteCount == 4)33 scan0[offset + 3] = color.A;34 }35 public Color GetPixel(Point point)36 {37 int offset = (point.X - 1) * byteCount + (point.Y - 1) * bmpData.Stride;38 Color color = Color.FromArgb(39 scan0[offset + 2],40 scan0[offset + 1],41 scan0[offset]42 );43 if (byteCount == 4)44 color = Color.FromArgb(scan0[offset + 3], color);45 return color;46 }47 }
注意代码里头有一句注掉了,那里是我出现第一个问题的地方。。。本来是想计算每一像素占的字节数,那就拿每行的字节数除每一行的像素数咯,于是就错了。。。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 + 1] = color.G;scan0[offset + 2] = color.R;
而不是这样:
scan0[offset] = color.R;scan0[offset + 1] = color.G;scan0[offset + 2] = color.B;
第三个问题发生在保存图片的时候。。。本来是这么写的:
bmp.Save("Juven.bmp");
打开图片再用油漆桶,发现还是和原来差不多,底色里面仍然参杂了高度接近纯白的灰色斑点。因为Save不管你文件扩展名是什么的啊!通通默认Jpeg啊!一压缩就前功尽弃了!所以应该改成这样:
bmp.Save(@"Juven.bmp", ImageFormat.Bmp);
这样就对了,油漆桶后的效果如下(上传前转回jpg了,所以这张图的底色其实还是不纯的):既然都走到这一步了,就干脆走得远一点,直接代码做成品了:
1 Bitmap bmp = new Bitmap(src); 2 BitmapWrapper wrapper = new BitmapWrapper(bmp); 3 4 byte r, g, b; 5 for (int y = 1; y <= bmp.Height; y++) 6 { 7 for (int x = 1; x <= bmp.Width; x++) 8 { 9 Point point = new Point(x, y);10 Color cr = wrapper.GetPixel(point);11 if (cr.R + cr.G + cr.B >= 30)12 {13 if (x < 200)14 {15 r = 34;16 g = 177;17 b = 76;18 }19 else if (x > 395)20 {21 r = 237;22 g = 28;23 b = 36;24 }25 else26 r = g = b = 255;27 wrapper.SetPixel(point, Color.FromArgb(r, g, b));28 }29 else break;30 }31 32 for (int x = bmp.Width; x > 0; x--)33 {34 Point point = new Point(x, y);35 Color cr = wrapper.GetPixel(point);36 if (cr.R + cr.G + cr.B >= 30)37 {38 if (x < 200)39 {40 r = 34;41 g = 177;42 b = 76;43 }44 else if (x > 400)45 {46 r = 237;47 g = 28;48 b = 36;49 }50 else51 r = g = b = 255;52 wrapper.SetPixel(point, Color.FromArgb(r, g, b));53 }54 else break;55 }56 }57 wrapper.UnWrapper();58 bmp.Save(target);
成品图如下:最后想说的是,巴萨梅球王求轻虐十个以内啊!
新闻热点
疑难解答