How do I set the background colour of a transparent PNG (WPF)? - wpf

I'm loading up some images (jpg, bmp, png etc.), doing some manipulation, and saving them back out again as jpg images. When I save PNG images with a transparent background, they are saved with a black background, and I'd really prefer it to be white. Is there a way to do this?
The important thing is that this is a 'UI-less' routine in a product, so I've not got much leeway when it comes to radically changing the way things are done. Surely there must be a 'set background = white' or something?
A short snippet of my code...
// Load the image
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.None;
image.UriSource = "SomeImage.png";
image.EndInit();
// Some manipulation of the image here...
image.Shake().Twist().ThrowItAllAbout();
// Save it back out, in a different format
using (FileStream stream = new FileStream("SomeOtherFile.jpg", FileMode.Create))
{
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create((BitmapSource)image));
encoder.Save(stream);
}

I've tried to replace transparent pixels directly (with WriteableBitmap CopyPixels/WritePixels), but this wont work with semi-transparent pixels (for example, shadows will produce black outline artifacts).
So I decided to draw my image on top of white rectangle before saving.
It works just fine:
public static BitmapSource ReplaceTransparency(this BitmapSource bitmap, Color color)
{
var rect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
var visual = new DrawingVisual();
var context = visual.RenderOpen();
context.DrawRectangle(new SolidColorBrush(color), null, rect);
context.DrawImage(bitmap, rect);
context.Close();
var render = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
96, 96, PixelFormats.Pbgra32);
render.Render(visual);
return render;
}
Usage:
var white = bitmap.ReplaceTransparency(Colors.White);
var enc = new JpegBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(white));
using (var fs = File.Create(filename))
enc.Save(fs);

There is no simple method to this, further you make some false assumptions when saying that transparent PNGs will be saved with a black background, that is not the case for all PNGs and it is solely dependent on the program that was used for saving the PNG.
If you make a pixel completely transparent that does not necessarily purge all its color from it, the pixel still has its three colour channels, e.g. pictures of the IPU certainly contain colour information, if you use your code to convert that image you'll see it.
One way to set all completely transparent pixels to white would be to get all the pixels as byte array, check the alpha channel of every pixel and if it is 0 you set every other channel of that pixel to 255.

Related

when to use WriteableBitmap and BitmapImage in silverlight

I am trying to display image using BitmapImage for some time and it worked.I have changed the image and it stopped working.
For Bitmapimage I was using this code:
`ms.Seek(0, SeekOrigin.Begin); // ms is memory stream
BitmapImage b = new BitmapImage();
b.SetSource(ms);
image.ImageSource = b;`
I have ran into piece of code where it was checking if the length of the bytes[] ==14400
if(bytes.length == 14400)
{
var bmp = new WriteableBitmap(width, height);
Buffer.BlockCopy(buffer, 0, bmp.Pixels, 0, buffer.Length);
}
I want to know when to use WriteableBitmap and BitmapImage .
From iProgrammer:
Bitmaps are generally implemented as immutable objects. What this means is that once you create a bitmap you can't make any changes to it. You can manipulate bitmaps by creating new versions, which then immediately become immutable....
The WriteableBitmap, as its name suggests, isn't immutable and you can get at its individual pixels and manipulate them as much as you want. This is the ideal way to work when you need dynamic bitmaps.
iProgrammer - WriteableBitmap
From MSDN:
"Use the WriteableBitmap class to update and render a bitmap on a per-frame basis..." MSDN - WriteableBitmap Class
The Examples section of the MSDN article also shows how to update a WritableBitmap image when responding to mouse events. The code in the example erases pixels of the image by setting the pixel's ARGB values to 0 when the mouse's right button is down. The code also shows how to update individual pixels in the image where the mouse's left button is down. Essentially the code shows a rudimentary pixel image editor.
The point, however, is that you can't change image data when using regular bitmap - you have to use WritableBitmap instead. You can, however, render both if you wish.

Drawing to a bitmap from a WPF canvas

I have a canvas that contains an Image in which I dislay an existing BMP. I draw rectangles on the canvas and add these to the Children colllection. When I click save, I want to update the underlying BMP file.
The following code works, but the rectangle that gets drawn to the BMP is way smaller than what I drew. I guess there's some difference in the co-ordinates? Maybe I shouldn't be using System.Drawing?
using (Graphics g = Graphics.FromImage(image))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
foreach (var child in canvas.Children)
{
if (child is System.Windows.Shapes.Rectangle)
{
var oldRect = child as System.Windows.Shapes.Rectangle;
// need to do something here to make the new rect bigger as the scale is clearly different
var rect = new Rectangle((int)Canvas.GetLeft(oldRect), (int)Canvas.GetTop(oldRect), (int)oldRect.Width, (int)oldRect.Height);
g.FillRectangle(Brushes.Black, rect);
}
}
... code to save bmp
All suggestions welcome!
Thanks
Try using the System.Windows.Media.Imaging.RenderTargetBitmap Class (an example here).
Wpf uses Device Independent Graphics so you have to compensate for the DPI :
RenderTargetBitmap bmp = new RenderTargetBitmap((int)Canvas1.Width, (int)Canvas1.Height, 96, 96, PixelFormats.Default);
bmp.Render(Canvas1);
From Third Link:
There are two system factors that determine the size of text and graphics on your screen: resolution and DPI. Resolution describes the number of pixels that appear on the screen. As the resolution gets higher, pixels get smaller, causing graphics and text to appear smaller. A graphic displayed on a monitor set to 1024 x 768 will appear much smaller when the resolution is changed to 1600 x 1200.

Saving multiple canvas controls as an image in silverlight/wpf

I have a situation where I have a canvas with an image and I lay multiple canvas controls over it and each canvas may have multiple controls drawn on them (Ellipses, Lines, etc). What is the best/most efficient way to save all these canvas controls toone image? I assume WritableBitmap, but how will this work with multiple canvas controls? One given is that each canvas will be the same size.
EDIT:
This is what I tried. I also tried with the dpi of the original image, which gives me the tiny result.
BitmapSource bms = (BitmapSource)this.ctlImage.Source;
Matrix m = PresentationSource.FromVisual(this.ctlImgCanvas).CompositionTarget.TransformToDevice;
RenderTargetBitmap rtb = new RenderTargetBitmap(bms.PixelWidth, bms.PixelHeight, m.M11 * 96.0, m.M22 * 96.0, PixelFormats.Pbgra32);
rtb.Render(this.ctlImgCanvas);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(memStream);
This will take care of everything including children of the canvas. You don't need to know the size of the canvas in advance.
Matrix m =
PresentationSource.FromVisual(canvas).CompositionTarget.TransformToDevice;
BitmapSource panelBitmap = new RenderTargetBitmap(
(int)Math.Floor(canvas.ActualWidth),
(int)Math.Floor(canvas.ActualHeight),
m.M11 * 96.0, // For DPI. This hardcoded value is correct. Check WPF DPI
m.M22 * 96.0,
PixelFormats.Default);
panelBitmap.Render(canvas);
using (FileStream fs = File.Open([filename], FileModel.Create, FileAccess.Write))
{
BitmapEncoder encoder = GetBitmapEncoderForFileExtension([filename]); // See documentation for which encoder to use when.
encoder.Frames.Add(BitmapFrame.Create(panelBitmap ));
encoder.Save(fs);
}
Save the container of the Canvas's to a bitmap, it'll pick up all the children.

WPF/GDI interop: how do I get correct alpha?

I've created a semi-transparent test image, filled with 128-alpha black. I draw it while alternating between two methods: WPF native and GDI interop. They come out completely different; the WPF one is done correctly, but the GDI appears to be blended against white first, before being drawn:
If drawn against a photo, the GDI part lightens what's below it, even though a 0,0,0,128 color should darken whatever is below it:
The code goes like this:
if (iteration % 2 == 0)
context.DrawImage(new BitmapImage(new System.Uri("I:/test.png")),
new Rect(0, 0, 80, 24));
else
{
var bmp = (D.Bitmap) D.Bitmap.FromFile("I:/test.png");
var handle = bmp.GetHbitmap();
var bmpSource = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero,
Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
context.DrawImage(bmpSource, new Rect(0, 0, 80, 24));
WinAPI.DeleteObject(handle);
GC.KeepAlive(bmp);
}
For the second image, I changed the Rect size for the WPF one to show that they don't overlap.
How do I draw a semi-transparent GDI image correctly?
(yes, it really has to be a GDI image for complex reasons - in reality it doesn't come from a file, but from some external code, but the problem is reproduceable easily with a file)
I've settled on avoiding this call now, and just copying the raw bitmap data over. The code:
var writable = new WriteableBitmap(bmp.Width, bmp.Height,
bmp.HorizontalResolution, bmp.VerticalResolution,
PixelFormats.Bgra32, null);
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
writable.WritePixels(new Int32Rect(0, 0, bmp.Width, bmp.Height),
data.Scan0, data.Stride * bmp.Height, data.Stride);
GC.KeepAlive(bmp);
context.DrawImage(writable, new Rect(0, 0, 80, 24));
(this might need a using or two)
Also, if the writable bitmap is used directly as a source for something like Image then a call to Freeze helps prevent flicker on update, though I don't see why it would be necessary.
How do I draw a semi-transparent GDI image correctly?
Like this:
Bitmap targetBitmap = ...
Bitmap bitmap = new Bitmap(filename);
using (var g = Graphics.FromImage(targetBitmap))
g.DrawImage(...);
In other words, don’t bother with the GDI-to-WPF interop (it’s clearly not working). Save the intermediate bitmaps to files and use WPF-only when you need WPF and GDI-only when you need GDI.

Render a "not visible" WPF controls to an bitmap image

Render a WPF control to a bitmap image is not a trivial task as I found out today. As I know now dealing with the parent control margin is a problem, as Rick Strahl wrote in his blog
http://www.west-wind.com/weblog/posts/2007/Sep/10/Rendering-a-WPF-Container-to-Bitmap
So far I am able to create bitmaps of any control that is visible inside a window but what I really need to do is create bitmaps of invisible controls. I just create them in code - simples shapes like rectangle and ellipse - and want to save them as bitmaps to disk.
For me this turn out to be a personal nightmare. Since my ActualHeight and ActualWidth is always 0 I use Height and Width instead. But all I get is a empty image in the size of my control.
How can I create a bitmap of any control without adding it to a visible window?
New elements haven't had their layout performed. You need to call Measure and Arrange on the control before you render it.
Canvas c = new Canvas();
Rectangle r = new Rectangle
{
Fill = Brushes.Orange,
Width = 200,
Height = 100
};
Ellipse e = new Ellipse
{
Fill = Brushes.DodgerBlue,
Width = 100,
Height = 100
};
Canvas.SetLeft(e, 150);
c.Children.Add(r);
c.Children.Add(e);
var size = new Size(250,100);
c.Measure(size);
c.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(250, 100, 96, 96, PixelFormats.Pbgra32);
bmp.Render(c);
PngBitmapEncoder enc = new PngBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bmp));
using(var s = File.OpenWrite("image.png"))
enc.Save(s);

Resources