Create thumbnail image directly from header-less image byte array - wpf

My application shows a large number of image thumbnails at once. Currently, I am keeping all full size images in memory, and simply scaling the image in the UI to create the thumbnail. However, I'd rather just keep small thumbnails in memory, and only load the fullsize images when necessary.
I thought this would be easy enough, but the thumbnails I'm generating are terribly blurry compared to just scaling the fullsize image in the UI.
The images are byte arrays with no header information. I know the size and format ahead of time, so I can use BitmapSource.Create to create an ImageSource.
//This image source, when bound to the UI and scaled down creates a nice looking thumbnail
var imageSource = BitmapSource.Create(
imageWidth,
imageHeight,
dpiXDirection,
dpiYDirection,
format,
palette,
byteArray,
stride);
using (var ms = new MemoryStream())
{
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(imageSource);
encoder.Save(ms);
var bi = new BitmapImage();
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
//I can't just create a MemoryStream from the original byte array because there is no header info and it doesn't know how to decode the image!
bi.StreamSource = ms;
bi.DecodePixelWidth = 60;
bi.EndInit();
//This thumbnail is blurry!!!
Thumbnail = bi;
}
I'm guessing its blurry since I'm converting it to png first, but when I use the BmpBitmapEncoder I get that "No imaging component available" error. In this case my image is Gray8, but I'm not sure why the PngEncoder can figure it out but the BmpEncoder can't.
Surely there must be someway to create a thumbnail from the original ImageSource without having to encode it to a bitmap format first? I wish BitmapSource.Create just let you specify a decode width/height like the BitmapImage class does.
EDIT
The final answer is to use a TransformBitmap with a WriteableBitmap to create the thumbnail and eliminate the original, full-size image.
var imageSource = BitmapSource.Create(...raw bytes and stuff...);
var width = 100d;
var scale = width / imageSource.PixelWidth;
WriteableBitmap writable = new WriteableBitmap(new TransformedBitmap(imageSource, new ScaleTransform(scale, scale)));
writable.Freeze();
Thumbnail = writable;

You should be able to create a TransformedBitmap from the original one:
var bitmap = BitmapSource.Create(...);
var width = 60d;
var scale = width / bitmap.PixelWidth;
var transform = new ScaleTransform(scale, scale);
var thumbnail = new TransformedBitmap(bitmap, transform);
In order to get ultimately rid of the original bitmap, you could create a WriteableBitmap from the TransformedBitmap:
var thumbnail = new WriteableBitmap(new TransformedBitmap(bitmap, transform));

Related

WPF what is the best way to cache an image data

I have an application where I get lot of images in the form of byte[],
I store them in the memory for later use by the user demand
Should I store them in byte[]? or there is another way to store them for quicker loading on user demand?
My code that loads the image is like this
private static BitmapImage LoadImage(byte[] imageData)
{
if (imageData == null || imageData.Length == 0) return null;
var image = new BitmapImage();
using (var mem = new MemoryStream(imageData))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = mem;
image.EndInit();
}
image.Freeze();
return image;
}
Thank you!
Ron
Could store the images in a Dictionary.
The key is the unique identifier (E.G. Int32).
The image could be stored as byte[] or BitmapImage
If you store it as BitmapImage you have to convert the byte[] up front
But then you don't need to convert on demand
Dictionary<Int32, byte[]>
or
Dictionary<Int32, BitmapImage>
Pretty sure BitmapImage is going to be bigger so converting on demand would use less memory.
Your question said a lot of images but you also asked for quicker user loading.
Test both ways.

BitmapImage from file PixelFormat is always bgr32

I am loading an image from file with this code:
BitmapImage BitmapImg = null;
BitmapImg = new BitmapImage();
BitmapImg.BeginInit();
BitmapImg.UriSource = new Uri(imagePath);
BitmapImg.CacheOption = BitmapCacheOption.OnLoad;
BitmapImg.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
BitmapImg.EndInit();
It works as expected except for the fact that no matter what kind of image I'm loading (24bit RGB, 8bit grey, 12bit grey,...), after .EndInit() the BitmapImage always has as Format bgr32. I know there have been discussions on the net, but I have not found any solution for this problem.
Does anyone of you know if it has been solved yet?
Thanks,
tabina
From the Remarks section in BitmapCreateOptions:
If PreservePixelFormat is not selected, the PixelFormat of the image
is chosen by the system depending on what the system determines will
yield the best performance. Enabling this option preserves the file
format but may result in lesser performance.
Therefore you also need to set the PreservePixelFormat flag:
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(imagePath);
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.CreateOptions = BitmapCreateOptions.IgnoreImageCache
| BitmapCreateOptions.PreservePixelFormat;
bitmap.EndInit();
For some reason I don't understand, using BitmapCreateOptions.PreservePixelFormat with new BitmapImage() didn't work for me. But this did lead me onto the right track. If anyone else tries the other answer and it doesn't work, this alternate method is what worked for me:
var decoder = new PngBitmapDecoder(new Uri(imagePath), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapFrame frame = decoder.Frames[0];
For whatever reason PngBitmapDecoder worked where BitmapImage didn't.
Note: BitmapFrame inherits BitmapSource just like BitmapImage, for my purpose that was all I needed.

Converting a BitmapSource to Image keeping all frames

I have a method here that is supposed to produce a System.Drawing.Image instance. Consider the following prerequesites:
I get a BitmapSource as a method parameter
Below you find the code that does
the transformation from BitmapSource to Image.
Conversion:
public Image ConvertBitmapSourceToImage(BitmapSource input)
{
MemoryStream transportStream = new MemoryStream();
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(input));
enc.Save(transportStream);
transportStream.Seek(0, SeekOrigin.Begin);
return Image.FromStream(transportStream);
}
Now imagine the BitmapSource has been created from a Multipage Tif file. What I need to do is make any nth page available in code. The BitmapSource class offers no support for this, so do you know a way how the get any one but the first frame out of my input? Or does BitmapSource read in the whole Tif as one frame, losing the framing information?
If it were possible, I could add another parameter to my method signature, like so:
public Image ConvertBitmapSourceToImage(BitmapSource input, int frame)
{
///[..]
}
Any Ideas?
Thanks in advance!
As you've said already, a BitmapSource does not support multiple frames. Perhaps it would be an option to step in at the point where the TIFF is decoded and convert an Image from every frame:
TiffBitmapDecoder decoder = new TiffBitmapDecoder(...) // use stream or uri here
System.Drawing.Image[] images = new System.Drawing.Image[decoder.Frames.Count];
for (int i = 0; i < decoder.Frames.Count; i++)
{
// use your converter function here
images[i] = ConvertBitmapSourceToImage(decoder.Frames[i]));
}
I didn't test the above code, so sorry for any flaws.

Set System.Windows.Controls.Image from StreamReader?

I've got an image in PNG format in a StreamReader object. I want to display it on my WPF form. What's the easiest way to do that?
I've put an Image control on the form, but I don't know how to set it.
The Image.Source property requires that you supply a BitmapSource instance. To create this from a PNG you will need to decode it. See the related question here:
WPF BitmapSource ImageSource
BitmapSource source = null;
PngBitmapDecoder decoder;
using (var stream = new FileStream(#"C:\Temp\logo.png", FileMode.Open, FileAccess.Read, FileShare.Read))
{
decoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.None);
if (decoder.Frames != null && decoder.Frames.Count > 0)
source = decoder.Frames[0];
}
return source;
This seems to work:
image1.Source = BitmapFrame.Create(myStreamReader.BaseStream);
Instead of using a StreamReader, I would directly generate the Stream,
FileStream strm = new FileStream("myImage.png", FileMode.Open);
PngBitmapDecoder decoder = new PngBitmapDecoder(strm,
BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
myImage.Source = decoder.Frames[0];
where myImage is the name of your Image in XAML
<Image x:Name="myImage"/>
Update: If you have to use the StreamReader, you get the Stream by using .BaseStream.

Is there a way to display a image in WPF stored in memory?

What I got is something like a screenshot making application. (managed to serialize, thank god!!!) When a button is clicked a screenshot is taken by accessing a handling classe's method. now the tricky part is that the class has another method for operating with the above said result, in such a manner than when the respective handling method is called, a window is created(shown) and the bitmap image should go into a display container in that window. The problem is that so far, I've noticed that in WPF the image control's source cannot be attribuited to a variable which stores the image. How can i display the image stored in that variable without having to save it first,get uri,etc. ?
You need to create an image from a memory stream, this has been well documented by many people. Here are two links that may get you started:
http://forums.silverlight.net/forums/p/44637/166282.aspx
http://www.wpftutorial.net/Images.html
thanks for the links slugster. Here's how I did it:
MemoryStream ms = new MemoryStream();
sBmp = gBmp; //note: gBmp is a variable that stores the captured image and passes it on to the method that uses sBMP as a distribuitor of the variable holding the captured image data
//variable that holds image
sBmp.Save(ms,ImageFormat.Bmp);
//my buffer byte
byte[] buffer = ms.GetBuffer();
//Create new MemoryStream that has the contents of buffer
MemoryStream bufferPasser = new MemoryStream(buffer);
//Creates a window with parents classthatholdsthismethod and null
Edit childEdit = new Edit(this, null);
childEdit.Show();
//I create a new BitmapImage to work with
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.StreamSource = bufferPasser;
bitmap.EndInit();
//I set the source of the image control type as the new BitmapImage created earlier.
childEdit.imgImageCanvas.Source = bitmap;
childEdit.Activate();
I've basically combined what I had found on those pages with some info I found on how to pass on a bmp to a memstream. I got this to work 100% :)

Resources