Alpha masking in c# System.Drawing? - c#

I'm trying to draw an image, with a source Bitmap and an alpha mask Bitmap, using the System.Drawing.Graphics object.
At the moment I loop X and Y and use GetPixel and SetPixel to write the source color and mask alpha to a third Bitmap, and then render that.
However this is very inefficient and I am wondering if there is an faster way to achieve this?
The effect I'm after looks like this:
The grid pattern represents transparency; you probably knew that.

Yes, the faster way to do this is to use Bitmap.LockBits and use pointer arithmetic to retrieve the values instead of GetPixel and SetPixel. The downside, of course, is that you have to use unsafe code; if you make a mistake, you can cause some really bad crashes in your program. But if you keep it simple and self-contained, it should be fine (hey, if I can do, you can do it too).
For example, you could do something like this (not tested, use at your own risk):
Bitmap mask = ...;
Bitmap input = ...;
Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
var rect = new Rectangle(0, 0, input.Width, input.Height);
var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
for (int y = 0; y < input.Height; y++)
{
byte* ptrMask = (byte*) bitsMask.Scan0 + y * bitsMask.Stride;
byte* ptrInput = (byte*) bitsInput.Scan0 + y * bitsInput.Stride;
byte* ptrOutput = (byte*) bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < input.Width; x++)
{
ptrOutput[4 * x] = ptrInput[4 * x]; // blue
ptrOutput[4 * x + 1] = ptrInput[4 * x + 1]; // green
ptrOutput[4 * x + 2] = ptrInput[4 * x + 2]; // red
ptrOutput[4 * x + 3] = ptrMask[4 * x]; // alpha
}
}
}
mask.UnlockBits(bitsMask);
input.UnlockBits(bitsInput);
output.UnlockBits(bitsOutput);
output.Save(...);
This example derives the alpha channel in the output from the blue channel in the mask image. I’m sure you can change it to use the mask’s red or alpha channel if required.

Depending on your requirements this may be much easier:
Invert your mask so that the circle is transparent and the rest is
from a color not used in your input bitmap (like red in your example)
Draw your mask on top of your image using
Graphics.FromImage(image).DrawImage(mask)...
Set the mask color as transparent on your image
(image.MakeTransparent(Color.Red))
The only drawback of this method is that it needs you to make sure the mask color is not used in your image.
I don't know if this is slower or faster than the manual way.

Related

How can I merge bitmap (Alpha Bitmap and Main Bitmap) in C#?

```
if (alpha != null && input != null)
{
Bitmap output = new Bitmap(input.Width, input.Height, PixelFormat.Format32bppArgb);
var rect = new Rectangle(0, 0, input.Width, input.Height);
var bitsAlpha = alpha.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsInput = input.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
for (int y = 0; y < input.Height; y++)
{
byte* ptrAlpha = (byte*)bitsAlpha.Scan0 + y * bitsAlpha.Stride;
byte* ptrInput = (byte*)bitsInput.Scan0 + y * bitsInput.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < input.Width; x++)
{
ptrOutput[4 * x] = ptrInput[4 * x]; // blue
ptrOutput[4 * x + 1] = ptrInput[4 * x + 1]; // green
ptrOutput[4 * x + 2] = ptrInput[4 * x + 2]; // red
ptrOutput[4 * x + 3] = ptrAlpha[4 * x]; // alpha
}
}
}
alpha.UnlockBits(bitsAlpha);
input.UnlockBits(bitsInput);
output.UnlockBits(bitsOutput);
return output;
}
```
I changed the PixelFormat to Format8bppIndexed.I set the pixel format to Format8bppIndexed and came to this conclusion image . Please help me
From what I can see, you're trying to use an 8-bit grayscale image as alpha for another picture.
This does not mean the final output will be 8-bit. It doesn't even mean the input image is 8-bit. In fact, the output of this should still be 32-bit, since 8-bit only supports palette-based transparency, meaning you set alpha to specific colours (affecting all pixels on the image that use that colour), rather than to specific pixels on the image.
The only things you need to change are these:
Since the alpha image is apparently 8-bit, lock that one as 8-bit. But to be sure, you should add a specific check in advance to test if its pixel format is indeed Format8bppIndexed.
Since that image is now locked as 8-bit, its single pixels are not grouped per 4 bytes but per 1 byte. So in the code that retrieves the alpha from it, remove the * 4 part.
The changed lines:
var bitsAlpha = alpha.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
and
ptrOutput[4 * x + 3] = ptrAlpha[x]; // alpha
Besides this, the code should be kept as it is.
Red, Green, Blue and Alpha is for 32bit images (each of these is stored as a byte which 8 bits, 4 x 8 = 32), indexed images doesn't work this way.
1 . if your image is a 32bit image, then your loop steps should be 4:
for (int x = 0; x < input.Width; x+=4) // x+=3 for 24bit images (without alpha like jpg images)
instead of
for (int x = 0; x < input.Width; x++)
for 8bit indexed images it does not work that way and the colors are stored in a pallet (have a look at this)

C# How do I convert my get GetPixel / SetPixel color processing to Lockbits?

EDIT: I deeply appreciate the replies. What I need more than anything here is sample code for what I do with the few lines of code in the nested loop, since that's what works right in GetPixel/SetPixel, but also what I can't get to work right using Lockbits. Thank you
I'm trying to convert my image processing filters from GetPixel / SetPixel to Lockbits, to improve processing time. I have seen Lockbits tutorials here on Stack Overflow, MSDN, and other sites as well, but I'm doing something wrong. I'm starting with an exceedingly simple filter, which simply reduces green to create a red and purple effect. Here's my code:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = bmpMain.GetPixel(x, y);
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
So that GetPixel / SetPixel code works fine, but it's slow. So I tried this:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
Rectangle rect = new Rectangle(Point.Empty, bmpMain.Size);
BitmapData bmpData = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = new Color();
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
bmpMain.UnlockBits(bmpData);
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
Which throws the error "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Drawing.dll Additional information: Bitmap region is already locked" when it reaches the first line of the nested loop.
I realize this has to be a beginner's error, I'd appreciate if someone could demonstrate the correct way to convert this very simple filter to Lockbits. Thank you very much
The array returned by scan0 is in this format BGRA BGRA BGRA BGRA ... and so on,
where B = Blue, G = Green, R = Red, A = Alpha.
Example of a very small bitmap 4 pixels wide and 3 pixels height.
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
stride = width*bytesPerPixel = 4*4 = 16 bytes
height = 3
maxLenght = stride*height= 16*3 = 48 bytes
To reach a certain pixel in the image (x, y) use this formula
int certainPixel = bytesPerPixel*x + stride * y;
B = scan0[certainPixel + 0];
G = scan0[certainPixel + 1];
R = scan0[certainPixel + 2];
A = scan0[certainPixel + 3];
public unsafe void Test(Bitmap bmp)
{
int width = bmp.Width;
int height = bmp.Height;
//TODO determine bytes per pixel
int bytesPerPixel = 4; // we assume that image is Format32bppArgb
int maxPointerLenght = width * height * bytesPerPixel;
int stride = width * bytesPerPixel;
byte R, G, B, A;
BitmapData bData = bmp.LockBits(
new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadWrite, bmp.PixelFormat);
byte* scan0 = (byte*)bData.Scan0.ToPointer();
for (int i = 0; i < maxPointerLenght; i += 4)
{
B = scan0[i + 0];
G = scan0[i + 1];
R = scan0[i + 2];
A = scan0[i + 3];
// do anything with the colors
// Set the green component to 0
G = 0;
// do something with red
R = R < 54 ? (byte)(R + 127) : R;
R = R > 255 ? 255 : R;
}
bmp.UnlockBits(bData);
}
You can test is yourself. Create a very small bitmap ( few pixels wide/height) in paint or any other program and put a breakpoint at the begining of the method.
Additional information: Bitmap region is already locked"
You now know why GetPixel() is slow, it also uses Un/LockBits under the hood. But does so for each individual pixel, the overhead steals cpu cycles. A bitmap can be locked only once, that's why you got the exception. Also the basic reason that you can't access a bitmap in multiple threads simultaneously.
The point of LockBits is that you can access the memory occupied by the bitmap pixels directly. The BitmapData.Scan0 member gives you the memory address. Directly addressing the memory is very fast. You'll however have to work with an IntPtr, the type of Scan0, that requires using a pointer or Marshal.Copy(). Using a pointer is the optimal way, there are many existing examples on how to do this, I won't repeat it here.
... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
The last argument you pass is very, very important. It selects the pixel format of the data and that affects the code you write. Using bmpMain.PixelFormat is the fastest way to lock but it is also very inconvenient. Since that now requires you to adapt your code to the specific pixel format. There are many, take a good look at the PixelFormat enum. They differ in the number of bytes taken for each pixel and how the colors are encoded in the bits.
The only convenient pixel format is Format32bppArgb, every pixel takes 4 bytes, the color/alpha is encoded in a single byte and you can very easily and quickly address the pixels with an uint*. You can still deal with Format24bppRgb but you now need a byte*, that's a lot slower. The ones that have a P in the name are pre-multiplied formats, very fast to display but exceedingly awkward to deal with. You may thus be well ahead by taking the perf hit of forcing LockBits() to convert the pixel format. Paying attention to the pixel format up front is important to avoid this kind of lossage.

How do I set a color other than black to image created with makeTransparent()

I am using the MakeTransparent() function call on the c# Bitmap object to convert images to transparent images. When this method is called, it will convert a background color to be transparent by setting the alpha channel, but it then converts the background color to black.
I need to find a fast way of converting this background color back to white or whatever the original color was, as occasionally I need to flatten the image to a non-alpha channel enabled format.
Make transparent doesn't seem to have any flags or overloads that allows you to tell it to leave the background color alone, and altering the image pixel by pixel is way to inefficient. Anyone have any suggestions or GDI tricks to solve this problem?
There doesn't seem to be a fast way to do this using the managed code interface. Using individual pixel manipulation, or using unmanaged code to update the pixels seem to be the only real options.
This is actually possible in managed code, by using Marshal.Copy to copy the backing byte array out of a bitmap object, then editing it, and then copying it back.
So basically, with that general method in mind, you just go over the pixels, line by line, detect which pixels have the colour you want replaced, and set their alpha byte to 0.
Note that "ARGB" refers to the order of the components inside the Int32 value of one read pixel. Since this value is little-endian, the actual order of the bytes at a given offset is the reverse; B = offset + 0, G = offset + 1, R = offset + 2, A = offset + 3.
/// <summary>
/// Clears the alpha value of all pixels matching the given colour.
/// </summary>
public static Bitmap MakeTransparentKeepColour(Bitmap image, Color clearColour)
{
Int32 width = image.Width;
Int32 height = image.Height;
// Paint on 32bppargb, so we're sure of the byte data format
Bitmap bm32 = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics gr = Graphics.FromImage(bm32))
gr.DrawImage(image, new Rectangle(0, 0, width, height));
BitmapData sourceData = bm32.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bm32.PixelFormat);
Int32 stride = sourceData.Stride;
// Copy the image data into a local array so we can use managed functions to manipulate it.
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
Byte colR = clearColour.R;
Byte colG = clearColour.G;
Byte colB = clearColour.B;
for (Int32 y = 0; y < height; y++)
{
Int32 inputOffs = y * stride;
for (Int32 x = 0; x < width; x++)
{
if (data[inputOffs + 2] == colR && data[inputOffs + 1] == colG && data[inputOffs] == colB)
data[inputOffs + 3] = 0;
inputOffs += 4;
}
}
// Copy the edited image data back.
Marshal.Copy(data, 0, sourceData.Scan0, data.Length);
bm32.UnlockBits(sourceData);
return bm32;
}
This can easily be enhanced with a tolerance level instead of an exact match, with something like Math.Abs(data[inputOffs + 2] - colR) < tolerance, or by actually converting the bytes to a colour object and doing some other kind of approximation (like hue/saturation/brightness).

Determine image overall lightness

I need to overlay some texts on an image; this text should be lighter or darker based on the overall image lightness.
How to compute the overall (perceived) lightness of an image?
Found something interesting for single pixel:
Formula to determine brightness of RGB color
Solved by me:
public static double CalculateAverageLightness(Bitmap bm)
{
double lum = 0;
var tmpBmp = new Bitmap(bm);
var width = bm.Width;
var height = bm.Height;
var bppModifier = bm.PixelFormat == PixelFormat.Format24bppRgb ? 3 : 4;
var srcData = tmpBmp.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, bm.PixelFormat);
var stride = srcData.Stride;
var scan0 = srcData.Scan0;
//Luminance (standard, objective): (0.2126*R) + (0.7152*G) + (0.0722*B)
//Luminance (perceived option 1): (0.299*R + 0.587*G + 0.114*B)
//Luminance (perceived option 2, slower to calculate): sqrt( 0.299*R^2 + 0.587*G^2 + 0.114*B^2 )
unsafe
{
byte* p = (byte*)(void*)scan0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int idx = (y * stride) + x * bppModifier;
lum += (0.299*p[idx + 2] + 0.587*p[idx + 1] + 0.114*p[idx]);
}
}
}
tmpBmp.UnlockBits(srcData);
tmpBmp.Dispose();
var avgLum = lum / (width * height);
return avgLum/255.0;
}
I think all you can do is measure every pixel in the image and take an average. If thats too slow for your purposes then I would suggest taking an evenly distributed sample of pixels and using that to calculate an average. You could also limit the pixels to the area where you need to draw the text.
You can load the image as a Bitmap (http://msdn.microsoft.com/en-us/library/system.drawing.bitmap.aspx) and use the GetPixel method to actually get the colour values.
How you assess the brightness is entirely up to you. I would suggest a simpler approach (say just taking the highest colour value) may actually be better as some users will perceive colour differently to the human norm (colour-blindness etc).

Programmatically replace transparent regions in an image with white fill?

I've got a PNG image that I'm operating on via the System.Drawing API in .NET. It has large transparent regions, and I would like to replace the transparent regions with white fill--so that there are no transparent regions in the image. Easy enough in an image editing program... but so far I've had no success doing this in C#.
Can someone give me some pointers?
I'm not sure how to detect transparent pixel. I know if the Alpha is 0 it's completly transparent and if it's 255 it's opaque. I'm not sure if you should check for Alpha == 0 or Alpha != 255 ; if you can try it and give me a feedback that would be helpful.
From MSDN
The alpha component specifies the
transparency of the color: 0 is fully
transparent, and 255 is fully opaque.
Likewise, an A value of 255 represents
an opaque color. An A value from 1
through 254 represents a
semitransparent color. The color
becomes more opaque as A approaches
255.
void Foo(Bitmap image)
{
for (int y = 0; y < image.Height; ++y)
{
for (int x = 0; x < image.Width; ++x)
{
// not very sure about the condition.
if (image.GetPixel(x, y).A != 255)
{
image.SetPixel(x,y,Color.White);
}
}
}
}
My example:
public void FillPngWhite(Bitmap bmp)
{
if (bmp.PixelFormat != PixelFormat.Format32bppArgb)
throw new ApplicationException("Not supported PNG image!");
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbaValues = new byte[bytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbaValues, 0, bytes);
// array consists of values RGBARGBARGBA
for (int counter = 0; counter < rgbaValues.Length; counter += 4)
{
double t = rgbaValues[counter + 3]/255.0; // transparency of pixel between 0 .. 1 , easier to do math with this
double rt = 1 - t; // inverted value of transparency
// C = C * t + W * (1-t) // alpha transparency for your case C-color, W-white (255)
// same for each color
rgbaValues[counter] = (byte) (rgbaValues[counter]*t + 255*rt); // R color
rgbaValues[counter + 1] = (byte)(rgbaValues[counter + 1] * t + 255 * rt); // G color
rgbaValues[counter + 2] = (byte)(rgbaValues[counter + 2] * t + 255 * rt); // B color
rgbaValues[counter + 3] = 255; // A = 255 => no transparency
}
// Copy the RGB values back to the bitmap
System.Runtime.InteropServices.Marshal.Copy(rgbaValues, 0, ptr, bytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
}
This is different bacause:
I use LockBits instead GetPixel and SetPixel. It is much more faster, but little harder to understand. It's a little modified example from : MSDN
I'm taking real aplha value into consideration, as I said in the comment to your question. This will make black with 50% transparency (128) look like gray instead of black. Reason for this is by "replace alpha with white in graphics editor" I imagine creating new layer underneath you image filled with white and then flattening both layers together. This example will have same effect.
Once you have a handle to the bitmap object, just do something like:
Bitmap yourImage = HOWEVER YOU LOAD YOUR IMAGE;
int width = YOUR IMAGE WIDTH;
int height = YOUR IMAGE HEIGHT;
Color c;
Color white = new Color(255,255,255,255)
for(int w = 0; w < width; w++)
for(int h = 0; h < height; h++)
{
c = yourImage.GetPixel(w,h);
yourImage.SetPixel(w,h, ((((short)(c.A)) & 0x00FF) <= 0)? white:c); //replace 0 here with some higher tolerance if needed
}
This may be oversimplifying your problem, but if it's on a form or other readily available control, you could simply paint the background White before placing the Image on top.

Categories

Resources