BitmapImage from BitmapSource always Bgr32 - wpf

I'm loading a BitmapImage from a BitmapSource and i always find the BitmapImage's format is Bgr32 instead of Bgra32 which the BitmapSource is. There is a similar thread on this here:
BitmapImage from file PixelFormat is always bgr32
but using BitmapCreateOptions.PreservePixelFormat didn't work for me as was suggested in the thread. here's what i'm doing:
// I have verified that b is a valid BitmapSource and its format is Bgra32
// the following code produces a file (testbmp2.bmp) with an alpha channel as expected
// placing a breakpoint here and querying b.Format in the Immediate window also produces
// a format of Bgra32
BmpBitmapEncoder test = new BmpBitmapEncoder();
FileStream stest = new FileStream(#"c:\temp\testbmp2.bmp", FileMode.Create);
test.Frames.Add(BitmapFrame.Create(b));
test.Save(stest);
stest.Close();
// however, this following snippet results in bmp.Format being Bgr32
BitmapImage bmp = new BitmapImage();
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
MemoryStream stream = new MemoryStream();
encoder.Frames.Add(BitmapFrame.Create(b));
encoder.Save(stream);
bmp.BeginInit();
bmp.StreamSource = new MemoryStream(stream.ToArray());
bmp.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
bmp.CacheOption = BitmapCacheOption.None;
bmp.EndInit();
stream.Close();
Is there a way to create a BitmapImage from the BitmapSource and preserver the alpha channel?
Update:
I used the same code posted in the the other thread to load testbmp2.bmp from file and after loading, the Format property is Bgr32.
BitmapImage b1 = new BitmapImage();
b1.BeginInit();
b1.UriSource = new Uri(#"c:\temp\testbmp2.bmp");
b1.CreateOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreImageCache;
b1.CacheOption = BitmapCacheOption.OnLoad;
b1.EndInit();
so maybe i'm not saving the FileStream/MemoryStream correctly? That doesn't seem right since after saving the FileStream, I can open testbmp2.bmp in Photoshop and see the alpha channel.
Update 2:
I think I need to re-word the issue I'm having. I'm trying to display separate channels of a texture. I am displaying the bitmap via an Image control. I have a simple HLSL pre compiled shader assigned to the Image's Effect property that will mask out the channels based user input. Not being able to get the BitmapSource into a BitmapImage while retaining the alpha channel was only part of the problem. I realize now that since my original BitmapSource's Format was Bgra32 I could assign that directly to the Image's Source property. The problem appears to be that an Image object will only display texels with pre-multiplied alpha...? Here's my shader code:
sampler2D inputImage : register(s0);
float4 channelMasks : register(c0);
float4 main (float2 uv : TEXCOORD) : COLOR0
{
float4 outCol = tex2D(inputImage, uv);
if (!any(channelMasks.rgb - float3(1, 0, 0)))
{
outCol.rgb = float3(outCol.r, outCol.r, outCol.r);
}
else if (!any(channelMasks.rgb - float3(0, 1, 0)))
{
outCol.rgb = float3(outCol.g, outCol.g, outCol.g);
}
else if (!any(channelMasks.rgb - float3(0, 0, 1)))
{
outCol.rgb = float3(outCol.b, outCol.b, outCol.b);
}
else
{
outCol *= channelMasks;
}
if (channelMasks.a == 1.0)
{
outCol.r = outCol.a; // * 0.5 + 0.5;
outCol.g = outCol.a;
outCol.b = outCol.a;
}
outCol.a = 1;
return outCol;
}
I've tested it pretty extensively and I'm pretty sure that its setting the outCol value properly. When i pass in a channel mask value of (1.0, 1.0, 1.0, 0.0) the image displayed is the RGB channels with the areas of the alpha that are black drawing as black - as you would expect if WPF was pre-multiplying the alpha on to the RGB channels. Does anyone know of any way to dispaly a BitmapSource in an Image without pre multiplied alpha? Or more to the point, is there a way to have the effect receive the texture before the alpha pre-multiply happens? I'm not sure what order things are done in, but apparently the pre-multiply happens before the texture is written to the s0 register and the shader gets to work on it. I could try to do this with WriteableBitmap and copying the alpha out to another BitmapSource but that would all be using software rendering i think...?

I ran into a similar problem lately. The BmpBitmapEncoder does not care about the alpha channel and therefore does not preserve it. An easy fix is to use a PngBitmapEncoder instead.

Related

How do I set a WPF Image's Source to a bytearray in C# code behind?

I'm building a small app using C#/WPF.
The application receives (from an unmanaged C++ library) a byte array (byte[]) from a bitmap source
In my WPF window, I have an (System.windows.Controls.Image) image which I will use to display the bitmap.
In the code behind (C#) I need to able to take that byte array, create BitmapSource /ImageSource and assign the source for my image control.
// byte array source from unmanaged librariy
byte[] imageData;
// Image Control Definition
System.Windows.Controls.Image image = new Image() {width = 100, height = 100 };
// Assign the Image Source
image.Source = ConvertByteArrayToImageSource(imageData);
private BitmapSource ConvertByteArrayToImagesource(byte[] imageData)
{
??????????
}
I've been working on this for a bit here and haven't been able to figure this out. I've tried several solutions that I've found by goolging around. To date, I haven't been able to figure this out.
I've tried:
1) Creating a BitmapSource
var stride = ((width * PixelFormats.Bgr24 +31) ?32) *4);
var imageSrc = BitmapSource.Create(width, height, 96d, 96d, PixelFormats.Bgr24, null, imageData, stride);
That through a runtime exception saying buffer was too small
Buffer size is not sufficient
2) I tried using a memory stream:
BitmapImage bitmapImage = new BitmapImage();
using (var mem = new MemoryStream(imageData))
{
bitmapImage.BeginInit();
bitmapImage.CrateOptions = BitmapCreateOptions.PreservePixelFormat;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = mem;
bitmapImage.EndInit();
return bitmapImage;
}
This code through an exception on the EndInit() call.
"No imaging component suitableto complete this operation was found."
SOS! I've spent a couple of days on this one and am clearly stuck.
Any help/ideas/direction would be greatly appreciated.
Thanks,
JohnB
Your stride calculation is wrong. It is the number of full bytes per scan line, and should therefore be calculated like this:
var format = PixelFormats.Bgr24;
var stride = (width * format.BitsPerPixel + 7) / 8;
var imageSrc = BitmapSource.Create(
width, height, 96d, 96d, format, null, imageData, stride);
Of course you also have to make sure that you use the correct image size, i.e. that the width and height values actually correspond with the data in imageBuffer.

Faster way to convert BitmapSource to BitmapImage

We have some video software that converts BitmapSource into a BitmapImage which is then drawn to screen later using the following code. The problem is on slower machines this process seems to be a little slow, running the MS profiler it turns out that these Bitmap operations (namely the ToBitmapImage function) is in the top 4 most expensives calls we make. Is there anything I can do to improve the efficiency of this?
// Conversion
this.VideoImage = bitmapsource.ToBitmapImage();
// Drawing
drawingContext.DrawImage(this.VideoImage, new Rect(0, 0, imageWidth, imageHeight));
// Conversion code
internal static BitmapImage ToBitmapImage(this BitmapSource bitmapSource)
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder()
MemoryStream memorystream = new MemoryStream();
BitmapImage tmpImage = new BitmapImage();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(memorystream);
tmpImage.BeginInit();
tmpImage.StreamSource = new MemoryStream(memorystream.ToArray());
tmpImage.EndInit();
memorystream.Close();
return tmpImage;
}
The conversion isn't necessary at all. Change the type of your VideoImage property to ImageSource or BitmapSource. Now you directly pass it as parameter to DrawImage:
this.VideoImage = bitmapsource;
drawingContext.DrawImage(this.VideoImage, new Rect(0, 0, imageWidth, imageHeight));

Displaying RGBA images using Image class w/o pre-multiplied alpha

I'm trying to display the separate R, G, B and A channels of a texture based on user input. I'm using an Image class to display textures that have alpha channels. These textures are loaded in to BitmapSource objects and have a Format of Bgra32. The problem is that when I set the Image's Source to the BitmapSource, if I display any combination of the R, G or B channels, I always get pixels that are pre-multiplied by the alpha value. I wrote a really simple shader, pre-compiled it, and used a ShaderEffect class to assign to the Image's Effect property in order to separate and display the separate channels, but apparently, the shader is given the texture after WPF has pre-multiplied the alpha value onto the texture.
Here's the code snippet for setting the Image's Source:
BitmapSource b = MyClass.GetBitmapSource(filepath);
// just as a test, write out the bitmap to file to see if there's an alpha.
// see attached image1
BmpBitmapEncoder test = new BmpBitmapEncoder();
//test.Compression = TiffCompressOption.None;
FileStream stest = new FileStream(#"c:\temp\testbmp2.bmp", FileMode.Create);
test.Frames.Add(BitmapFrame.Create(b));
test.Save(stest);
stest.Close();
// effect is a class derived from ShaderEffect. The pixel shader associated with the
// effect displays the color channel of the texture loaded in the Image object
// depending on which member of the Point4D is set. In this case, we are showing
// the RGB channels but no alpha
effect.PixelMask = new System.Windows.Media.Media3D.Point4D(1.0f, 1.0f, 1.0f, 0.0f);
this.image1.Effect = effect;
this.image1.Source = b;
this.image1.UpdateLayout();
Here's the shader code (its pretty simple, but I figured I'd include it just for completeness):
sampler2D inputImage : register(s0);
float4 channelMasks : register(c0);
float4 main (float2 uv : TEXCOORD) : COLOR0
{
float4 outCol = tex2D(inputImage, uv);
if (!any(channelMasks.rgb - float3(1, 0, 0)))
{
outCol.rgb = float3(outCol.r, outCol.r, outCol.r);
}
else if (!any(channelMasks.rgb - float3(0, 1, 0)))
{
outCol.rgb = float3(outCol.g, outCol.g, outCol.g);
}
else if (!any(channelMasks.rgb - float3(0, 0, 1)))
{
outCol.rgb = float3(outCol.b, outCol.b, outCol.b);
}
else
{
outCol *= channelMasks;
}
if (channelMasks.a == 1.0)
{
outCol.r = outCol.a;
outCol.g = outCol.a;
outCol.b = outCol.a;
}
outCol.a = 1;
return outCol;
}
Here's the output from the code above:
(sorry, i don't have enough reputation points to post images or apparently more than 2 links)
The file save to disk (C:\temp\testbmp2.bmp) in photoshop:
http://screencast.com/t/eeEr5kGgPukz
Image as displayed in my WPF application (using image mask in code snippet above):
http://screencast.com/t/zkK0U5I7P7

Problems with rendering text as bitmaps using WPF

I am developing an application using WPF to dynamically render content, including text and images from WPF into jpg files. I am currently using the RenderTargetBitmap class to do the job. Everything works as expected but the quality of the rendered fonts is terrible. I understand that the RenderTargetBitmap doesn’t use the ClearType but a GrayScale antialias, which is kind of blury with small fonts. But I am using large fonts, larger than 30 pts, and the results are totally unacceptable. Is there some kind of workaround for this issue?
[Update]
The Code I am using is listed below. As expected it is called on each Rendering event of the CompositionTarget.
void CompositionTarget_Rendering(object sender, EventArgs e)
{
prefix = "";
if (counter < 10)
{
prefix = "000";
}
else if (counter < 100)
{
prefix = "00";
}
else if (counter < 1000)
{
prefix = "0";
}
Size size = new Size(MainCanvas.Width, MainCanvas.Height);
MainCanvas.Measure(size);
MainCanvas.Arrange(new Rect(size));
RenderTargetBitmap bmp = new RenderTargetBitmap(imgWidth, imgHeight, 96d, 96d, PixelFormats.Default);
bmp.Render(MainCanvas);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = 90;
encoder.Frames.Add(BitmapFrame.Create(bmp));
string file = basePath + prefix + counter.ToString() + "_testpic.jpg";
using (Stream stm = File.Create(file))
{
encoder.Save(stm);
}
counter++;
}
Here are some examples of the resulting images:
alt text http://www.randomnoise.org/temp/testpic_v1.jpg
alt text http://www.randomnoise.org/temp/testpic_v2.jpg
Thanks in advance.
Try this:
int height = (int)border.ActualHeight;
int width = (int)border.ActualWidth;
RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(border);
border being what you're trying to save as bitmap.
Ok, I finally found a solution. Gustavo you were on the right track. The problem was that the main container that I was trying to render as bitmap was being distorted by its parent container. The solution was to add the main container to a canvas, that doesn't have a layout engine that distorts its children. I still need to do some more experimenting but it looks very promising. Apparently RenderTargetBitmap doesn't like distorted fonts at all.

c# winforms: Getting the screenshot image that has to be behind a control

I have c# windows form which have several controls on it, part of the controls are located one on another. I want a function that will take for input a control from the form and will return the image that has to be behind the control. for ex: if the form has backgroundimage and contains a button on it - if I'll run this function I'll got the part of backgroundimage that located behind the button. any Idea - and code?
H-E-L-P!!!
That's my initial guess, but have to test it.
Put button invisible
capture current screen
Crop screen captured to the clientRectangle of the button
Restablish button.
public static Image GetBackImage(Control c) {
c.Visible = false;
var bmp = GetScreen();
var img = CropImage(bmp, c.ClientRectangle);
c.Visible = true;
}
public static Bitmap GetScreen() {
int width = SystemInformation.PrimaryMonitorSize.Width;
int height = SystemInformation.PrimaryMonitorSize.Height;
Rectangle screenRegion = Screen.AllScreens[0].Bounds;
var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(bitmap);
graphics.CopyFromScreen(screenRegion.Left, screenRegion.Top, 0, 0, screenRegion.Size);
return bitmap;
}
public static void CropImage(Image imagenOriginal, Rectangle areaCortar) {
Graphics g = null;
try {
//create the destination (cropped) bitmap
var bmpCropped = new Bitmap(areaCortar.Width, areaCortar.Height);
//create the graphics object to draw with
g = Graphics.FromImage(bmpCropped);
var rectDestination = new Rectangle(0, 0, bmpCropped.Width, bmpCropped.Height);
//draw the areaCortar of the original image to the rectDestination of bmpCropped
g.DrawImage(imagenOriginal, rectDestination, areaCortar, GraphicsUnit.Pixel);
//release system resources
} finally {
if (g != null) {
g.Dispose();
}
}
}
This is pretty easy to do. Each control on the form has a Size and a Location property, which you can use to instantiate a new Rectangle, like so:
Rectangle rect = new Rectangle(button1.Location, button1.Size);
To get a Bitmap that contains the portion of the background image located behind the control, you first create a Bitmap of the proper dimensions:
Bitmap bmp = new Bitmap(rect.Width, rect.Height);
You then create a Graphics object for the new Bitmap, and use that object's DrawImage method to copy a portion of the background image:
using (Graphics g = Graphics.FromImage(bmp))
{
g.DrawImage(...); // sorry, I don't recall which of the 30 overloads
// you need here, but it will be one that uses form1.Image as
// the source, and rect for the coordinates of the source
}
This will leave you with the new Bitmap (bmp) containing the portion of the background image underneath that control.
Sorry I can't be more specific in the code - I'm at a public terminal. But the intellisense info will tell you what you need to pass in for the DrawImage method.

Resources