昨天想基於一張圖片做個手機鎖屏來著,原圖如下:
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 else
26 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 else
51 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);
成品圖如下:
最後想說的是,巴薩梅球王求輕虐十個以內啊!