王朝网络
分享
 
 
 

C#图像处理之-给你的图片打上LOGO

王朝c#·作者佚名  2008-12-13
宽屏版  字体: |||超大  

做图片处理软件,肯定会遇到这样的需求:如何把一张图片放在另一张图片上,比如在大图片中打上公司或个人的Logo等等。

其实原理很简单。假设我们有一张 800 * 600 的大图片(Bitmap b0),想在(10,10)的位置上打上Logo(Bitmap b1),Logo图片大小是 150 * 30。很简单,我们以大图片的(10,10)位置作为起始位置,做一个150 * 30 的矩形,把Logo图片整个替换这个矩形。

老规矩,还是用BitmapData来帮助我们实现这个功能。view plaincopy to clipboardprint?

// 取以大图(X,Y)为起点,w * h 的矩形区域

BitmapData srcData = b0.LockBits(new Rectangle(X,Y,w,h),ImageLockMode.WriteOnly,PixelFormat.Format24bppRgb);

// 取整个Logo

BitmapData dstData = b1.LockBits(new Rectangle(0,0,w,h),ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

unsafe

{

byte* pIn = (byte*)srcData.Scan0.ToPointer();

byte* pLogo = (byte*)dstData.Scan0.ToPointer();

for (int y = 0; y < h; y++)

{

for (int x = 0; x < w; x++)

{

// 复制Logo图的像素

pIn[0] = (byte)pLogo[0];

pIn[1] = (byte)pLogo[1];

pIn[2] = (byte)pLogo[2];

pIn += 3;

pLogo += 3;

}

pIn += srcData.Stride - w * 3;

pLogo += dstData.Stride - w * 3;

}

b0.UnlockBits(srcData);

b1.UnlockBits(dstData);

}

// 取以大图(X,Y)为起点,w * h 的矩形区域

BitmapData srcData = b0.LockBits(new Rectangle(X,Y,w,h),ImageLockMode.WriteOnly,PixelFormat.Format24bppRgb);

// 取整个Logo

BitmapData dstData = b1.LockBits(new Rectangle(0,0,w,h),ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

unsafe

{

byte* pIn = (byte*)srcData.Scan0.ToPointer();

byte* pLogo = (byte*)dstData.Scan0.ToPointer();

for (int y = 0; y < h; y++)

{

for (int x = 0; x < w; x++)

{

// 复制Logo图的像素

pIn[0] = (byte)pLogo[0];

pIn[1] = (byte)pLogo[1];

pIn[2] = (byte)pLogo[2];

pIn += 3;

pLogo += 3;

}

pIn += srcData.Stride - w * 3;

pLogo += dstData.Stride - w * 3;

}

b0.UnlockBits(srcData);

b1.UnlockBits(dstData);

}这样,为图片打Logo的功能就实现了。当然,还可以有别的方法,比如用BitBlt这个API函数,或者使用GDI+的DrawImage函数。

但这样有个小问题,Logo区域永远是个矩形。假如我的Logo是个圆形,只希望在大图上出现圆形的Logo而不是整块矩形,那又该如何实现呢?

这个问题其实可以这样理解:我们让Logo的背景变透明。而所谓透明,就是两张图片叠加时,上层图片“透明”区域中的像素,仍然使用下层图的相应像素来代替。但在RGB模式下,并没有“透明”这个概念(我们的讨论都在用PixelFormat.Format24bppRgb,PixelFormat.Format32bppArgb中的Alpha分量支持“透明度”),因此我们需要自己规定一种颜色,凡是这个颜色的,都作为“透明”处理。

比如,我们的Logo是黑底的,那么就指定黑色作为透明色。

修改算法也很简单,凡是遇到Logo图中像素颜色是黑色(当然这是个参数,可以自己改)的,就用大图相应位置的颜色来代替,否则还是使用Logo的像素色。下面是算法:view plaincopy to clipboardprint?

// 因为有可能要回写,所以原图的 ImageLockMode 这次是 ReadWrite

BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

unsafe

{

byte* pIn = (byte*)srcData.Scan0.ToPointer();

byte* pLogo = (byte*)dstData.Scan0.ToPointer();

for (int y = 0; y < h; y++)

{

for (int x = 0; x < w; x++)

{

// 判断当前点是否透明色

// 取得当前色

Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);

if (!ColorIsSimilar(c, TransColor, delta))

{

// 不是透明色,b0当前位置的颜色用b1对应位置的来代替

pIn[0] = (byte)pLogo[0];

pIn[1] = (byte)pLogo[1];

pIn[2] = (byte)pLogo[2];

}

pIn += 3;

pLogo += 3;

}

pIn += srcData.Stride - w * 3;

pLogo += dstData.Stride - w * 3;

}

b0.UnlockBits(srcData);

b1.UnlockBits(dstData);

}

// 因为有可能要回写,所以原图的 ImageLockMode 这次是 ReadWrite

BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

unsafe

{

byte* pIn = (byte*)srcData.Scan0.ToPointer();

byte* pLogo = (byte*)dstData.Scan0.ToPointer();

for (int y = 0; y < h; y++)

{

for (int x = 0; x < w; x++)

{

// 判断当前点是否透明色

// 取得当前色

Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);

if (!ColorIsSimilar(c, TransColor, delta))

{

// 不是透明色,b0当前位置的颜色用b1对应位置的来代替

pIn[0] = (byte)pLogo[0];

pIn[1] = (byte)pLogo[1];

pIn[2] = (byte)pLogo[2];

}

pIn += 3;

pLogo += 3;

}

pIn += srcData.Stride - w * 3;

pLogo += dstData.Stride - w * 3;

}

b0.UnlockBits(srcData);

b1.UnlockBits(dstData);

}我们在上面的算法中出现了另一个小算法,判断颜色C0,C1是否相等。当然如果两个颜色的RGB分量都相等那就肯定是相等了,但考虑到容差方面的因素,我们需要一个更灵活的算法。

我们知道,一个颜色由RGB三个分量组成,每个分量取值[0,255]的整数。这样,任意一个颜色都是这个有限空间中的一个点。两个颜色的“相似度”是否在阀值范围内的问题,最简单的思路也就可以认为求证空间两点的距离是否在阀值范围内。

空间两点距离公式:r2 = (x0-x1)2 + (y0-y1)2 + (z0-z1)2

下面是我的算法。view plaincopy to clipboardprint?

/// <summary>

/// 颜色是否近似

/// </summary>

/// <param name="c0">颜色0</param>

/// <param name="c1">颜色1</param>

/// <param name="delta">容差</param>

/// <returns>是/否</returns>

public static bool ColorIsSimilar(Color c0, Color c1, int delta)

{

int r0, r1, g0, g1, b0, b1;

r0 = c0.R;

r1 = c1.R;

g0 = c0.G;

g1 = c1.G;

b0 = c0.B;

b1 = c1.B;

if ((r0-r1)*(r0-r1) + (g0-g1) * (g0-g1) + (b0-b1) * (b0-b1) <= delta * delta)

{

return true;

}

else

{

return false;

}

}

/// <summary>

/// 颜色是否近似

/// </summary>

/// <param name="c0">颜色0</param>

/// <param name="c1">颜色1</param>

/// <param name="delta">容差</param>

/// <returns>是/否</returns>

public static bool ColorIsSimilar(Color c0, Color c1, int delta)

{

int r0, r1, g0, g1, b0, b1;

r0 = c0.R;

r1 = c1.R;

g0 = c0.G;

g1 = c1.G;

b0 = c0.B;

b1 = c1.B;

if ((r0-r1)*(r0-r1) + (g0-g1) * (g0-g1) + (b0-b1) * (b0-b1) <= delta * delta)

{

return true;

}

else

{

return false;

}

}最后再来看看一种情况。我们需要Logo半透明的呈现,貌似水印样的效果。有了上面两种处理图片的经验,不难认识到,大图b0的指定区域中需要混合原始像素和Logo图片b1像素后的新像素填充。

不难想象,新像素值应该结合了大图和Logo图两方面因素的。Logo图越透明,大图受的影响越小。所以这里我们可以采用加权平均的方法来得到混合后的新值。

新值 = (1 - 不透明度%)* 大图像素值 + 不透明度% * Logo图像素值

完整的函数如下:view plaincopy to clipboardprint?

/// <summary>

/// 合并两张图片,支持不透明度和透明色

/// </summary>

/// <param name="b0">图片一</param>

/// <param name="b1">图片二</param>

/// <param name="X">起始坐标X</param>

/// <param name="Y">起始坐标Y</param>

/// <param name="b1_alpha">图片二的不透明度</param>

/// <param name="TransColor">被作为透明色处理的颜色</param>

/// <param name="delta">透明色的容差</param>

/// <returns>合并后的图片</returns>

public static Bitmap KiMerge(Bitmap b0, Bitmap b1, int X, int Y, int b1_alpha,Color TransColor,int delta)

{

if (b0.Equals(null) || b1.Equals(null))

{

return null;

}

int w0 = b0.Width;

int h0 = b0.Height;

int w1 = b1.Width;

int h1 = b1.Height;

int w, h;

if (X + w1 > w0)

{

w = w0 - X;

}

else

{

w = w1;

}

if (Y + h1 > h0)

{

h = h0 - Y;

}

else

{

h = h1;

}

BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

unsafe

{

byte* pIn = (byte*)srcData.Scan0.ToPointer();

byte* pLogo = (byte*)dstData.Scan0.ToPointer();

for (int y = 0; y < h; y++)

{

for (int x = 0; x < w; x++)

{

// 判断透明色

Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);

if (!ColorIsSimilar(c, TransColor, delta))

{

float bili = (float)b1_alpha / (float)255; // 不是透明色,加权平均

float inbili = 1.0f - bili;

int r, g, b;

b = (int)(pIn[0] * inbili + pLogo[0] * bili);

g = (int)(pIn[1] * inbili + pLogo[1] * bili);

r = (int)(pIn[2] * inbili + pLogo[2] * bili);

pIn[0] = (byte)b;

pIn[1] = (byte)g;

pIn[2] = (byte)r;

}

pIn += 3;

pLogo += 3;

}

pIn += srcData.Stride - w * 3;

pLogo += dstData.Stride - w * 3;

}

b0.UnlockBits(srcData);

b1.UnlockBits(dstData);

}

return b0;

}

/// <summary>

/// 合并两张图片,支持不透明度和透明色

/// </summary>

/// <param name="b0">图片一</param>

/// <param name="b1">图片二</param>

/// <param name="X">起始坐标X</param>

/// <param name="Y">起始坐标Y</param>

/// <param name="b1_alpha">图片二的不透明度</param>

/// <param name="TransColor">被作为透明色处理的颜色</param>

/// <param name="delta">透明色的容差</param>

/// <returns>合并后的图片</returns>

public static Bitmap KiMerge(Bitmap b0, Bitmap b1, int X, int Y, int b1_alpha,Color TransColor,int delta)

{

if (b0.Equals(null) || b1.Equals(null))

{

return null;

}

int w0 = b0.Width;

int h0 = b0.Height;

int w1 = b1.Width;

int h1 = b1.Height;

int w, h;

if (X + w1 > w0)

{

w = w0 - X;

}

else

{

w = w1;

}

if (Y + h1 > h0)

{

h = h0 - Y;

}

else

{

h = h1;

}

BitmapData srcData = b0.LockBits(new Rectangle(X, Y, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

BitmapData dstData = b1.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

unsafe

{

byte* pIn = (byte*)srcData.Scan0.ToPointer();

byte* pLogo = (byte*)dstData.Scan0.ToPointer();

for (int y = 0; y < h; y++)

{

for (int x = 0; x < w; x++)

{

// 判断透明色

Color c = Color.FromArgb(pLogo[2], pLogo[1], pLogo[0]);

if (!ColorIsSimilar(c, TransColor, delta))

{

float bili = (float)b1_alpha / (float)255; // 不是透明色,加权平均

float inbili = 1.0f - bili;

int r, g, b;

b = (int)(pIn[0] * inbili + pLogo[0] * bili);

g = (int)(pIn[1] * inbili + pLogo[1] * bili);

r = (int)(pIn[2] * inbili + pLogo[2] * bili);

pIn[0] = (byte)b;

pIn[1] = (byte)g;

pIn[2] = (byte)r;

}

pIn += 3;

pLogo += 3;

}

pIn += srcData.Stride - w * 3;

pLogo += dstData.Stride - w * 3;

}

b0.UnlockBits(srcData);

b1.UnlockBits(dstData);

}

return b0;

}当然方法有很多,这里只是从原理的角度阐述了一下我的观点,谁有更好的方法请让我知道。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有