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

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.

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.

How do I set the background colour of a transparent PNG (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.

WPF Background setting failed

Here is a code.
var image = new BitmapImage(new Uri(#"pack://application:,,,/Images/background.png",
UriKind.RelativeOrAbsolute));
var backgroundBrush = new ImageBrush()
{
ImageSource = image,
Viewport = new Rect(0, 0, image.PixelWidth / ActualWidth,
image.PixelHeight / ActualHeight),
TileMode = TileMode.Tile,
Stretch = Stretch.None,
};
// Set it for the main window.
Background = backgroundBrush;
It works just fine on my PC with XPSP3 and .Net 4.0. But when I run the same sample on Eee PC T91MT with Windows 7 Home Premium it fails. No exceptions, but nothing is drawn (solid color brushes ARE drawn if used instead, though). I thought it could be the result of limited resources, but on Viliv S5, that has about the same specs it works fine too.
Any ideas?
Thanks!
UPDATE
The root of the problem is Viewport's rect. Since the bitmap has twice window's size by X, the rect is (0, 0, 2, 1). So, on power computer with XPSP3, the left half of the image is drawn. But on Eee PC it causes a problem with visualization.
The answer is just normalizing Viewport rectangle. E.g. instead of (0,0,2,1) I had to set it as (0,0,1,0.5).
I'm not sure, but it looks like WPF just transmit rect values (after some transformation) into a D3D driver, which is (or is not) able to handle it the right way. So, non-normalized rect Viewport works on GeForce-based machine but does not on Eee PC with it's integrated video's driver.

Resizing images in Silverlight 3 using WriteableBitmap

This is my first day with Silverlight. I’m trying to prototype an application which (among other functions) should be able to resize user supplied images. It should be able to handle and display several resized images at once. The most obviously approaches I've tried seem to "leak" memory in the sense that the original bitmaps are still being referenced in some way which causes Silverlight to allocate hundreds of megabytes of memory after a while. I just want to be able to load the images one by one, resize them and keep the small versions.
To be precise, I've tried the following:
Creating a list of System.Windows.Controls.Image's (and scaling them). I'm not surprised that this has not worked.
Creating a list of rectangles filled by image brushes. I'm not surprised either.
Rendering the bitmaps into System.Windows.Media.Imaging.WriteableBitmap. I expected this to perform well; I assumed that the bitmaps are really just drawn directly and not referenced in any way. However, the memory consumption indicated otherwise.
Here is a snippet of the relevant piece of code:
// create image source
Stream stream = file.OpenRead();
BitmapImage bmpImg = new BitmapImage();
bmpImg.SetSource(stream);
stream.Close();
// create temporary image from it
Image tmpImg = new Image();
tmpImg.Source = bmpImg;
// this is required by WriteableBitmap
tmpImg.Measure(new Size(100, 100));
tmpImg.Arrange(new Rect(0, 0, 100, 100));
// prepare scaling to 100x100
ScaleTransform scaleTrans = new ScaleTransform();
double scale = (double)100 / (double)Math.Max(bmpImg.PixelHeight, bmpImg.PixelWidth);
scaleTrans.CenterX = 0;
scaleTrans.CenterY = 0;
scaleTrans.ScaleX = scale;
scaleTrans.ScaleY = scale;
// render
WriteableBitmap writeableBitmap = new WriteableBitmap(100, 100);
writeableBitmap.Render(tmpImg, scaleTrans);
writeableBitmap.Invalidate();
// final image
Image img = new Image();
img.Source = writeableBitmap;
I hope I'm not missing anything silly, but it looks to OK to me and does the right thing (except the memory problem). Please also bear in mind that code quality is not supposed to be a production quality; it’s just a quick and dirty prototype.
I noticed that I’m not alone; I found questions related to image manipulation in Silverlight. I’m also aware of the fact that I could use some third party library, do processing on the server or write something even myself, but I’m surprised that Silverlight does not offer any basic image manipulation functions. It does not seem to be an uncommon requirement given where Silverlight positions itself.
Have you looked at the WriteableBitmapEx project? It's an open source project with a tonne of extension methods for the WriteableBitmap class. Here's how you resize:
BitmapImage image = new BitmapImage();
image.SetSource(dialog.File.OpenRead());
WriteableBitmap bitmap = new WriteableBitmap(image);
WriteableBitmap resizedBitmap = bitmap.Resize(500, 500, WriteableBitmapExtensions.Interpolation.Bilinear);
// For uploading
byte[] data = resizedBitmap.ToByteArray();
I don't know about the specifics here, but if you're leaking resources - you might look at which of your objects implements the IDisposable interface. I would guess that the Stream and Image classes implement this interface. And if they do - calling Dispose() on them (or wrapping their usage in a "Using" statement) will cause them to free their resources immediately rather than waiting for the garbage collector to kick in eventually.

Resources