位图 Alpha 层编辑器






4.96/5 (14投票s)
此程序允许为现有位图添加 alpha 图层。
引言
在我开发 XNA 游戏时,我发现 XNA 精灵使用 alpha 混合来定义精灵位图中的透明部分。由于我没有编辑位图 alpha 图层的工具,所以我用 C# 编写了这个小型实用程序。
背景
大多数图像编辑器(例如,Microsoft Paint)允许您编辑位图,从而为每个像素选择一种颜色。这种颜色由三个值定义:红色、绿色、蓝色。
示例
- 红色=255,绿色=0,蓝色=0 是纯红色像素,
- 红色=0,绿色=0,蓝色=0 是黑色像素,
等等。
对于使用 alpha 混合的应用程序,每个像素会增加第四个值。alpha 值定义了透明度
- Alpha=255 是不透明的
- Alpha=127 是部分透明的
- Alpha=0 是完全透明的
位图文件(BMP 或 PNG)可能包含 alpha 图层,但大多数情况下不包含。
使用应用程序
应用程序窗口显示三个位图
- 左上角的图像是从磁盘加载的原始位图
- 右上角的图像将用于定义 alpha 图层的蒙版(重要提示:此位图必须与原始位图具有相同的大小)
- 底部的图像是将蒙版应用于原始图像的结果
蒙版的暗部是透明的,亮部是不透明的。如果您的蒙版绘制方式相反,请使用“反转蒙版”复选框来反转蒙版图像。
如果您没有单独的蒙版文件,请使用“将加载的图像用作蒙版”复选框,程序将通过将加载的图像转换为灰度位图来自动创建蒙版。您可以通过保存蒙版(按“保存蒙版”按钮)、使用您喜欢的位图编辑器对其进行编辑,取消选中“将加载的图像用作蒙版”复选框,然后重新加载编辑后的蒙版来编辑此自动生成的蒙版。
大多数情况下,我不需要部分透明。因此,我取消选中“允许部分不透明”复选框。这会将蒙版强制转换为双色(黑白)图像,而不是灰度图像。定义黑白之间界限的阈值是可调的。
工作原理
写入位图的 alpha 图层
程序首先将原始图像复制到对象位图 maskedImage
中,然后逐像素地将蒙版(对象位图 maskImage)
的像素复制到 maskedImage
的 alpha 图层中。
为了访问像素,我们需要一个包含两个位图像素的托管字节向量。
BitmapData bmpData1 = maskedImage.LockBits(new Rectangle(0, 0, maskedImage.Width,
maskedImage.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
maskedImage.PixelFormat);
byte[] maskedImageRGBAData = new byte[bmpData1.Stride * bmpData1.Height];
System.Runtime.InteropServices.Marshal.Copy(bmpData1.Scan0,
maskedImageRGBAData,
0,
maskedImageRGBAData.Length);
这将创建字节向量 bmpData1
,其中包含位图 maskedImage
的像素数据。
BitmapData bmpData2 = maskImage.LockBits(new Rectangle(0, 0, maskImage.Width,
maskImage.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly,
maskImage.PixelFormat);
byte[] maskImageRGBAData = new byte[bmpData2.Stride * bmpData2.Height];
System.Runtime.InteropServices.Marshal.Copy(bmpData2.Scan0,
maskImageRGBAData,
0,
maskImageRGBAData.Length);
这将创建字节向量 bmpData2
,其中包含位图 maskImage
的像素数据。
重要提示:.NET Bitmap
对象在像素格式为 PixelFormat.Format32bppArgb
时使用的内部像素存储具有以下非标准顺序
byte 0: Blue value of pixel 1
Byte 1: Green value of pixel 1
Byte 2: Red value of pixel 1
Byte 3: Alpha value of pixel 1
byte 4: Blue value of pixel 2
Byte 5: Green value of pixel 2
Byte 6: Red value of pixel 2
Byte 7: Alpha value of pixel 2
...
一旦我们有了两个向量,我们就简单地将蒙版向量中的蓝色分量值复制到蒙版位图向量的 alpha 分量中。
//copy the mask to the Alpha layer
for (int i = 0; i + 2 < maskedImageRGBAData.Length; i += 4)
{
maskedImageRGBAData[i + 3] = maskImageRGBAData[i];
}
然后,我们将像素信息复制回蒙版图像,并且不要忘记解锁位图对象的非托管内部部分。
System.Runtime.InteropServices.Marshal.Copy(maskedImageRGBAData, 0,
bmpData1.Scan0, maskedImageRGBAData.Length);
this.maskedImage.UnlockBits(bmpData1);
this.maskImage.UnlockBits(bmpData2);
将任何位图转换为每像素 32 位 RGBA 格式
由于加载的位图可以具有任何像素格式,而程序需要 PixelFormat.Format32bppArgb
格式,因此我们必须转换位图格式。这是通过创建具有相同大小的新位图并将原始位图绘制到其中来完成的。
// declare the new image
Bitmap returnedImage = new Bitmap(tmpImage.Width,
tmpImage.Height,
PixelFormat.Format32bppArgb);
// create a graphics instance to draw the original image in the new one
Rectangle rect = new Rectangle(0, 0, tmpImage.Width, tmpImage.Height)
Graphics g = Graphics.FromImage(returnedImage);
// draw the original image
g.DrawImage(tmpImage, rect, 0, 0, tmpImage.Width, tmpImage.Height,GraphicsUnit.Pixel);
//Release the graphics object
g.Dispose();
注意:此函数有一个积极的副作用:出于优化原因,当您从文件创建位图实例时,.NET 框架会将文件保持打开状态,直到位图被使用。这意味着当您尝试写回位图文件时会出现异常。由于上面的函数创建了一个完全独立于从文件中加载的原始位图的新位图,.NET 框架会关闭文件,从而可以覆盖它。
从位图中提取蒙版
蒙版是从加载的图像创建的黑白或灰度图像。这是通过直接操作像素信息来实现的。这是如上文“写入位图的 alpha 图层”部分所述的,不同之处在于这里我们将红色、绿色、蓝色值设置为以下代码获得的 greyValue
。
for (int i = 0; i + 2 < maskImageRGBData.Length; i += 4)
{
//convert to gray scale R:0.30 G=0.59 B 0.11
greyLevel = (byte)(0.3 * maskImageRGBData[i + 2] + 0.59 *
maskImageRGBData[i + 1] + 0.11 * maskImageRGBData[i]);
if (opaque)
{
greyLevel = (greyLevel < OpacityThreshold) ? byte.MinValue : byte.MaxValue;
}
if (invertedMask)
{
greyLevel = (byte)(255 - (int)greyLevel);
}
maskImageRGBData[i] = greyLevel;
maskImageRGBData[i + 1] = greyLevel;
maskImageRGBData[i + 2] = greyLevel;
}
注意:此循环会执行无数次。它必须尽可能快地执行。这就是为什么我们使用变量 opaque
和 invertedMask
而不是在每次迭代中调用访问器 this.checkBoxAllowPartialOpacity.Checked
和 this.checkBoxInvertMask.Checked
来浪费宝贵的微秒。
强制 alpha 图层归零
当打开的位图文件已经定义了 alpha 图层时,我们需要将其重置为完全不透明。我们可以像上面解释的那样,通过将 alpha 图层的每个字节更改为 255 来完成,但是 .NET 框架提供了一种更方便的解决方案:ImageAttributes
类允许对每个像素执行以下操作。
- Red = M00*Red + M10*Green + M20*Blue + M30*Alpha + M40 *255
- Green = M01*Red + M11*Green + M21*Blue + M30*Alpha + M41 *255
- Blue = M02*Red + M12*Green + M22*Blue + M30*Alpha + M42 *255
- Alpha = M03*Red + M13*Green + M23*Blue + M30*Alpha + M43 *255
这意味着以下矩阵将保持颜色不变并强制 alpha 图层值为 255。
float[][] colorMatrixElements = {
new float[] {1,0,0,0,0},
new float[] {0,1,0,0,0},
new float[] {0,0,1,0,0},
new float[] {0,0,0,0,0},
new float[] {0,0,0,1,1}};
为了使用此矩阵,我们将其与 ColorMatrix
关联,然后将此矩阵与 ImageAttribute
关联。
ColorMatrix colorMatrix = new ColorMatrix(colorMatrixElements);
ImageAttributes imageAttributes = new ImageAttributes();
imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default,
ColorAdjustType.Bitmap);
然后,使用 imageAttribute
指定图像的绘制方式。
g.DrawImage(tmpImage, rect, 0, 0, tmpImage.Width, tmpImage.Height,
GraphicsUnit.Pixel, imageAttributes);