Adding transparency to a SKBitmap image results in black background - c#

I'm currently facing a problem showing a transparent image in a Xamarin.Forms Image view.
The image is retrieved from the gallery, and converted to PNG format.
Pixels are iterated and with some of them, their alpha value is adjusted.
Bitmap is converted to SKBitmapImageSource and shown in an Image view.
Result (top), and original (bottom), taken on Android:
Screenshot
The goal is to show the Image with a transparent background, but I can't get it to work. It keeps showing with a black background. Loading a transparent PNG file from internet works, so something in the process of conversion or image processing must go wrong.
Image retrieval and conversion:
SKBitmap source = SKBitmap.Decode(file.GetStream());
SKData data = SKImage.FromBitmap(source).Encode(SKEncodedImageFormat.Png, 100);
SKBitmap converted = SKBitmap.Decode(data);
SKBitmap result = ImageProcessor.AddTransparency(converted, 0.7f);
Transparency added:
public static SKBitmap AddTransparency(SKBitmap bitmapSource, float treshold)
{
if (bitmapSource == null)
{
throw new ArgumentNullException(nameof(bitmapSource), $"{nameof(bitmapSource)} is null.");
}
var bitmapTarget = bitmapSource.Copy();
// Calculate the treshold as a number between 0 and 255
int value = (int)(255 * treshold);
// loop trough every pixel
int width = bitmapTarget.Width;
int height = bitmapTarget.Height;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
var color = bitmapTarget.GetPixel(col, row);
if (color.Red > value && color.Green > value && color.Blue > value)
{
bitmapTarget.SetPixel(col, row, color.WithAlpha(0x00));
}
}
}
return bitmapTarget;
}
Conversion to imagesource:
return SKBitmapImageSource.FromStream(SKImage.FromBitmap((SKBitmap)value).Encode().AsStream);

The problem is the AlphaType being set incorrectly. For the way you're doing the alpha conversion, the AlphaType should be set to AlphaType.Premul
Since it's a readonly property, copy the bitmap to a new one and set the correct alpha type

Related

Check if Clipboard.GetImage() is transparent in C#

I think I have a simple problem that seems very hard for me to figure out - how to check if an image I get from Clipboard.GetImage() uses transparency. If it does then I will show it in a PictureBox with the transparent background.
I copy the picture directly from the application - e.g. the browser or one of the Windows image viewers. Pasting the picture to e.g. Word will get the transparent background.
I am using this code:
// Check if the picture in clipboard has any transparency (alpha channel != 255)
Bitmap img = new Bitmap(Clipboard.GetImage());
for (int y = 0; y < img.Height; ++y)
{
for (int x = 0; x < img.Width; ++x)
{
if (img.GetPixel(x, y).A != 255)
{
Debug.WriteLine("Picture is transparent - set breakpoint here");
}
}
}
...
// Show the picture with transparent background - this works fine
img.MakeTransparent(img.GetPixel(0,0));
myPictureBox.Image = (Image)img;
I am trying with various pictures found on the net and I can copy/paste those pictures with the transparent background so I know they are transparent but no pictures will trigger the Debug.WriteLine and all values equals 255?
Though I recognize this has been asked before then I must be doing something wrong since this simple example does not work? Also they are old so maybe there is a new and better way? I have tried to find other solutions besides these:
Detecting if a PNG image file is a Transparent image?
Check to see if image is transparent
.. and more also not from StackOverflow. I have seen both really simple solutions and horrofying complex ones - but still none of them seems to work.
is this because the clipboard object cannot see the transparency or .. ?
I ended up this solution, based on the comment from #Jeff, CopyTransparentImages. This will get the (real?) image from the clipboard (which will also include the alpha channel) and then I will check if the image contains any transparency afterwards. If it does then I will make the image background color transparent, according to my original question, before I show it in a PictureBox.
// Get the image formats from clipboard and check if it is transparent
Image imgCopy = GetImageFromClipboard();
bool isClipboardImageTransparent = IsImageTransparent(imgCopy);
if (isClipboardImageTransparent)
{
...
}
// Get the real image from clipboard (this supports the alpha channel)
private Image GetImageFromClipboard()
{
if (Clipboard.GetDataObject() == null) return null;
if (Clipboard.GetDataObject().GetDataPresent(DataFormats.Dib))
{
// Sometimes getting the image data fails and results in a "System.NullReferenceException" error - probably because clipboard handling also can be messy and complex
byte[] dib;
try
{
dib = ((System.IO.MemoryStream)Clipboard.GetData(DataFormats.Dib)).ToArray();
}
catch (Exception ex)
{
return Clipboard.ContainsImage() ? Clipboard.GetImage() : null;
}
var width = BitConverter.ToInt32(dib, 4);
var height = BitConverter.ToInt32(dib, 8);
var bpp = BitConverter.ToInt16(dib, 14);
if (bpp == 32)
{
var gch = GCHandle.Alloc(dib, GCHandleType.Pinned);
Bitmap bmp = null;
try
{
var ptr = new IntPtr((long)gch.AddrOfPinnedObject() + 40);
bmp = new Bitmap(width, height, width * 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ptr);
return new Bitmap(bmp);
}
finally
{
gch.Free();
if (bmp != null) bmp.Dispose();
}
}
}
return Clipboard.ContainsImage() ? Clipboard.GetImage() : null;
}
// Check if the image contains any transparency
private static bool IsImageTransparent(Image image)
{
Bitmap img = new Bitmap(image);
for (int y = 0; y < img.Height; ++y)
{
for (int x = 0; x < img.Width; ++x)
{
if (img.GetPixel(x, y).A != 255)
{
return true;
}
}
}
return false;
}
At least this is working for me :-)

How to draw what is inside a contour in an image but leave everything else blank?

I have a colour image of type Image<Hsv, Byte>, and another image of type Image<Gray, Byte> of the same size that is all black with some all-white shapes. From the black and white image, I found the contours of the shapes using findContours(). What I want is to create a new image or modify the original colour image I have to show only what corresponds to inside the contours, with everything else being transparent, without having to check pixel by pixel values of the two images (did this, it takes too long). Any possible way to do this?
For example, I have the original image, the black and white image, and the final product I need.
I'm completely new to emgucv, so, I'm not saying this is the best approach; but it seems to work.
Create a new draw surface
Draw the original image
Change the white pixels in the mask image to transparent pixels
Draw the transparent mask on top of the original image
The result image looks like your desired outcome.
void Main()
{
var path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
"images");
var original = new Image<Bgr, Byte>(Path.Combine(path, "vz7Oo1W.png"));
var mask = new Image<Bgr, Byte>(Path.Combine(path, "vIQUvUU.png"));
var bitmap = new Bitmap(original.Width, original.Height);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.DrawImage(original.Bitmap, 0, 0);
g.DrawImage(MakeTransparent(mask.Bitmap), 0, 0);
}
bitmap.Save(Path.Combine(path, "new.png"));
}
public static Bitmap MakeTransparent(Bitmap image)
{
Bitmap b = new Bitmap(image);
var tolerance = 10;
for (int i = b.Size.Width - 1; i >= 0; i--)
{
for (int j = b.Size.Height - 1; j >= 0; j--)
{
var col = b.GetPixel(i, j);
col.Dump();
if (255 - col.R < tolerance &&
255 - col.G < tolerance &&
255 - col.B < tolerance)
{
b.SetPixel(i, j, Color.Transparent);
}
}
}
return b;
}

How to detect where the image content ends?

I receive images of the same size but with different amounts of information. Examples below (red borders are mine). The background is always white.
I am trying to detect where the information on the image ends - at what pixel height (and crop accordingly). In other words, find the first non-white pixel from the bottom.
Is there a better way to do this other than extract BitmapData out of Image object and loop through all the pixels?
Just to add a suggestion having looked over your images and your solution (below) and your method is fine but you may be able to improve efficiency.
The more you know about your image the better; you're confident the background is always white (according to your post, the code is a more generic utility but the following suggestion can still work); can you be confident on the furthest point in a non-white pixel will be found if the row is not empty?
For example; in your two pictures the furthest in non-white pixel on a row is about 60px in. If this is universally true for your data then you don't need to scan the whole line of the image, which would make your for loop:
for (int y = bitmap.Height - 1; y >= 0; y--) {
for (int x = 0; x < 60; x++) {
Color color = bitmap.GetPixel(x, y);
if (color.R != backColor.R || color.G != backColor.G || color.B != backColor.B) {
foundContentOnRow = y;
break;
}
}
}
(You could make it a parameter on the function so you can easily control it if needed).
Imagine for example that the first non-white row was 80px down. To find it currently you do 640 x 300 = 192,000 checks. If you could confidently say that you would know a row was blank within 100 pixels (an over-estimate based on the data presented) then this would be 100 * 300 = 30,000 checks per image.
If you always knew that the first 10 pixels of the image were always blank you could shave a little bit more off (say 3000 checks).
Musing on a setup where you knew that the first non-white pixel was between 10 and 60 pixels in (range of 50) you could find it at row 80 in 50 x 300 = 15,000 checks which is a good reduction.
Of course the downside about assumptions is that if things change your assumptions may not be valid, but if the data is going to remain fairly constant then it may be worthwhile, especially if you do this for a lot of images.
I've ended up using the following code to trim the image. Hopefully someone finds this useful.
class Program {
static void Main(string[] args) {
Image full = Image.FromFile("foo.png");
Image cropped = full.TrimOnBottom();
}
}
public static class ImageUtilities {
public static Image TrimOnBottom(this Image image, Color? backgroundColor = null, int margin = 30) {
var bitmap = (Bitmap)image;
int foundContentOnRow = -1;
// handle empty optional parameter
var backColor = backgroundColor ?? Color.White;
// scan the image from the bottom up, left to right
for (int y = bitmap.Height - 1; y >= 0; y--) {
for (int x = 0; x < bitmap.Width; x++) {
Color color = bitmap.GetPixel(x, y);
if (color.R != backColor.R || color.G != backColor.G || color.B != backColor.B) {
foundContentOnRow = y;
break;
}
}
// exit loop if content found
if (foundContentOnRow > -1) {
break;
}
}
if (foundContentOnRow > -1) {
int proposedHeight = foundContentOnRow + margin;
// only trim if proposed height smaller than existing image
if (proposedHeight < bitmap.Height) {
return CropImage(image, bitmap.Width, proposedHeight);
}
}
return image;
}
private static Image CropImage(Image image, int width, int height) {
Rectangle cropArea = new Rectangle(0, 0, width, height);
Bitmap bitmap = new Bitmap(image);
return bitmap.Clone(cropArea, bitmap.PixelFormat);
}
}

GetPixel always return 0 on png image

When i read image with GetPixel() color value is always black. My image is a png image.
I tried convert the image to bitmap before, but don't had success.
I believe which the problem isn't my code, because whether I open png image in Paint and only save it. The code read image correctly.
I load image like bellow
myImage = new Bitmap(fileName);
I need read image here
private void LoadImageMap(Bitmap value){
for (int col = 0; col < value.Width; col++)
{
for (int row = 0; row < value.Height; row++)
{
var color = value.GetPixel(col, row);
var color is black, always.
Sample of the image...
PNG image there is transparency and when pixel is full transparency GetPixel() result with zero value to RGB colors.
Then my code needed of the one if to validate this case.
the solution was like bellow:
var color = value.GetPixel(x, y);
if(color.A.Equals(0))
{
color = Color.White;
}
PNG use ARGB colors where A represent the alpha channel and whether alpha is zero this pixel has full transparence.
Since you passed in the bitmap to your code, you need to actually use that variable when calling GetPixel. Also, ref keyword is not needed in this case.
private void LoadImageMap(Bitmap value)
{
for (int col = 0; col < value.Width; col++)
{
for (int row = 0; row < value.Height; row++)
{
var color = value.GetPixel(col, row);
}
}
}

Crop an image while cropping area is not rectangle

How can I crop a specific area from an image while the chosen area is not a perfect rectangle? In this image, how can I crop the area other than red into different pieces?
To isolate non-rectangular areas of an image, and/or areas based upon color, a powerful method is to use the alpha/transparency property of 32 bit images. For example, the image on the left is an original, 24 bit image (no alpha), and on the right is the result of converting that image to 32 bit, and setting the alpha = 0 for all pixels that are white or red in the original image. Effectively, this can make your image non-rectangular.
The code for this is fairly simple. The method below takes an input bitmap (24 bit), generates a blank 32 bit image, and transfers all pixels from the source to destination, setting the alpha to 0 for all white or red pixels.
public Bitmap ModifyAlpha(Bitmap bmap)
{
Bitmap bmap32 = new Bitmap(bmap.Width, bmap.Height, PixelFormat.Format32bppArgb);
Color theColor = new Color();
Color newColor = new Color();
for (int i = 0; i < bmap.Width; i++)
{
for (int j = 0; j < bmap.Height; j++)
{
// Get the color of the pixel at (i,j)
theColor = bmap.GetPixel(i, j);
// Set the pixel color/range you want to make transparent
if ((theColor.R > 250 && theColor.G > 250 && theColor.B > 250) ||
(theColor.R > 250))
{
newColor = Color.FromArgb(0, theColor.R, theColor.G, theColor.B);
bmap32.SetPixel(i, j, newColor);
} else
{
bmap32.SetPixel(i, j, theColor);
}
}
}
return bmap32;
}

Categories