今天一朋友问我怎么实现PS中对指定区域进行填充的效果(这家伙在用C#做一个需要众多图形处理的项目)

据我所知,Graphics有个属性Clip :获取或设置 Region,该对象限定此 Graphics 的绘图区域。

好吧,这里我们不用这个方式,而是用另外一个位图(mask)来做有效区域标记。

这种做法在很多图形库中都是存在的(比如openCV)。

 

效果

纹理贴图 和 mask:

   

效果图:

左图是纹理贴图,右图是纯填充。

 

实现

首先需要准备一个mask

究竟mask是个什么东西呢?这里的mask 是一个深度为8的位图且只有两个颜色索引的(0x00 , 0xff)黑白图像,用黑色表示非作用域。这里我用PS随便画了一个

色阶如下:

 

代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;

namespace BitmapTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Size size = Properties.Resources.mask.Size;
            Bitmap bmp = new Bitmap(size.Width, size.Height);
            Fill(bmp, Color.Red, Properties.Resources.mask);
            pictureBox1.Image = bmp;

            Bitmap bmp2 = new Bitmap(size.Width, size.Height);
            DrawBitmap(bmp2, Properties.Resources.tex, Properties.Resources.mask);
            pictureBox2.Image = bmp2;
        }

        private void Fill(Bitmap bmp, Color clr, Bitmap mask)
        {
            if (!bmp.Size.Equals(mask.Size))
            {
                throw new Exception("mask大小与bmp大小不一致");
            }
            int w = bmp.Width;
            int h = bmp.Height;
            Rectangle rect = new Rectangle(0, 0, w, h);
            BitmapData srcBmData = bmp.LockBits(rect,
                ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            BitmapData maskData = mask.LockBits(rect,
                ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
            
            unsafe
            {
                byte* pSrc = (byte*)srcBmData.Scan0;
                byte* pMsk = (byte*)maskData.Scan0;
                int nClr = clr.ToArgb();
                int i = 0, j = 0;
                for (; i < h; i++)//一行
                {
                    for (j = 0; j < w; j++)//一点
                    {
                        if (pMsk[i * maskData.Stride + j] == 00)
                        {
                            continue;
                        }
                        else
                        {
                            *((int*)(pSrc + (i * w + j) * 4)) = nClr;
                        }
                    }
                }
            }
            bmp.UnlockBits(srcBmData);
            mask.UnlockBits(maskData);
        }

private void DrawBitmap(Bitmap bmp, Bitmap tex, Bitmap mask) { if (!bmp.Size.Equals(mask.Size)) { throw new Exception("mask大小与bmp大小不一致"); } int w = bmp.Width; int h = bmp.Height; Rectangle rect = new Rectangle(0, 0, w, h); BitmapData srcBmData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); BitmapData texBmData = tex.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); BitmapData maskData = mask.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); unsafe { byte* pSrc = (byte*)srcBmData.Scan0; byte* pTex = (byte*)texBmData.Scan0; byte* pMsk = (byte*)maskData.Scan0; int i = 0, j = 0; for (; i < h; i++)//一行 { for (j = 0; j < w; j++)//一点 { if (pMsk[i * maskData.Stride + j] == 00) { continue; } else { *((int*)(pSrc + (i * w + j) * 4)) = *((int*)(pTex + (i * w + j) * 4)); } } } } bmp.UnlockBits(srcBmData); mask.UnlockBits(maskData); } } }


这里用了C++的处理方式来实现(没办法,比较习惯这种方式 - -)。

其实实现起来非常简单,就是逐个扫描像素点,判断mask的值并确定是否改变这个像素点里的值……

 

值得注意的一点是,BitmapData的Stride属性。

因为系统考虑到效率问题,将每个扫描行的数据量与4字节对齐,Stride就是对齐后一行的字节数。

举个例子:

有一个99*99 深度为8的图片,那么在理论上一个扫描行的字节数只需要99byte就够了。你可能以为第二行的第一个像素点的数据是第100个字节的值。

但是事实上并不是这样,在对齐到4以后,每一行的字节数被对齐至100,这样一来第二行的第一个像素点则是第101个字节…

另外,这个问题在深度为32的图片中是不需要考虑的,因为32位的图片字节数肯定会对齐到4…

posted on 2012-08-17 18:43  zcmmwbd  阅读(1849)  评论(0编辑  收藏  举报