Working in WPF and C#, I have a TransformedBitmap object that I either:
Need to save to disk as a bitmap type of file (ideally, I'll allow users to choose whether it's saved as a BMP, JPG, TIF, etc, though, I'm not to that stage yet...)
Need to convert to a BitmapImage object as I know how to get a byte[] from a BitmapImage object.
Unfortunately, at this point I'm really struggling to get either one of those two things done.
Can anyone offer any help or point out any methods I might be missing?
All of your encoders are using BitmapFrame class for creating frames that will be added to Frames collection property of encoder. BitmapFrame.Create method has variety of overloads and one of them accepts parameter of BitmapSource type. So as we know that TransformedBitmap inherits from BitmapSource we can pass it as a parameter to BitmapFrame.Create method. Here are the methods which works as you have described:
public bool WriteTransformedBitmapToFile<T>(BitmapSource bitmapSource, string fileName) where T : BitmapEncoder, new()
{
if (string.IsNullOrEmpty(fileName) || bitmapSource == null)
return false;
//creating frame and putting it to Frames collection of selected encoder
var frame = BitmapFrame.Create(bitmapSource);
var encoder = new T();
encoder.Frames.Add(frame);
try
{
using (var fs = new FileStream(fileName, FileMode.Create))
{
encoder.Save(fs);
}
}
catch (Exception e)
{
return false;
}
return true;
}
private BitmapImage GetBitmapImage<T>(BitmapSource bitmapSource) where T : BitmapEncoder, new()
{
var frame = BitmapFrame.Create(bitmapSource);
var encoder = new T();
encoder.Frames.Add(frame);
var bitmapImage = new BitmapImage();
bool isCreated;
try
{
using (var ms = new MemoryStream())
{
encoder.Save(ms);
ms.Position = 0;
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
isCreated = true;
}
}
catch
{
isCreated = false;
}
return isCreated ? bitmapImage : null;
}
They accept any BitmapSource as the first parameter and any BitmapEncoder as a generic type parameter.
Hope this helps.
Related
I'm developing a small application which will get RAW image files, convert them to low quality JPEG's then load those JPEG's as thumbnails into a Grid.
My problem: I am having issues with the UI getting blocked while the images are being converted. I am dynamically adding controls to host those images in the grid just after the conversion is taking place for each image. Also I am binding those images to my Image control's Source with my ControlProperties ViewModel in code-behind.
My coding:
Here I am creating a new instance of my ControlProperties view model and inside I am doing the image conversion at the ImageSource.
cp = new ControlProperties()
{
ImageId = controlCount += 1, ImageSource = ThumbnailCreator.CreateThumbnail(imagePath)
};
My question:
Seeing as the images take a while to load, I am in need to get full control of my UI while they are being converted and added into my grid, but I am not getting it right at all. Can someone please help me with some advice or coding snippets to get me going please?
My ThumbnailCreator class
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
namespace SomeProjName
{
public class ThumbnailCreator
{
private static string imageLocation;
private static int currentImage;
public static BitmapImage CreateThumbnail(string oldImagePath)
{
ConvertHighQualityRAWImage(oldImagePath);
if (imageLocation != string.Empty && imageLocation != null)
return OpenImage(imageLocation);
else return null;
}
//Creates low quality JPG image from RAW image
private static void ConvertHighQualityRAWImage(string oldImagePath)
{
BitmapImage image = new BitmapImage(new Uri(oldImagePath));
var encoder = new JpegBitmapEncoder() { QualityLevel = 17 };
encoder.Frames.Add(BitmapFrame.Create(image));
using (var filestream = new FileStream(GetImageLocation(), FileMode.Create))
encoder.Save(filestream);
image.UriSource = null;
image.StreamSource = null;
image = null;
GC.WaitForPendingFinalizers();
GC.Collect();
}
//Returns low quality JPG thumbnail to calling method
private static BitmapImage OpenImage(string imagePath)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.DecodePixelWidth = 283;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(imagePath, UriKind.Relative);
image.EndInit();
DeleteImage();
return image;
}
private static string GetImageLocation()
{
imageLocation = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "thumbnails")).FullName + GetCurrentImage();
return imageLocation;
}
private static string GetCurrentImage()
{
return "\\" + (currentImage += 1).ToString() + ".jpg";
}
private static void DeleteImage()
{
if (File.Exists(imageLocation))
File.Delete(imageLocation);
}
}
}
You don't need to save your thumbnails to file. Use a MemoryStream instead:
public class ThumbnailCreator
{
public static BitmapImage CreateThumbnail(string imagePath)
{
BitmapFrame source;
using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
source = BitmapFrame.Create(
stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
var encoder = new JpegBitmapEncoder() { QualityLevel = 17 };
encoder.Frames.Add(BitmapFrame.Create(source));
var bitmap = new BitmapImage();
using (var stream = new MemoryStream())
{
encoder.Save(stream);
stream.Position = 0;
bitmap.BeginInit();
bitmap.DecodePixelWidth = 283;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
bitmap.Freeze();
return bitmap;
}
The intermediate encoding and decoding pass doesn't even seem to be necessary, so you could simply write this:
public class ThumbnailCreator
{
public static BitmapImage CreateThumbnail(string imagePath)
{
var bitmap = new BitmapImage();
using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
bitmap.BeginInit();
bitmap.DecodePixelWidth = 283;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
bitmap.Freeze();
return bitmap;
}
}
If you want to call the CreateThumbnail method asynchronously, use Task.Run():
cp.ImageSource = await Task.Run(() => ThumbnailCreator.CreateThumbnail(fileName));
Final Solution:
I just want to add this comment to Clemens solution. I also use the Garbage Collector to stop massive memory usage build-up when loading a lot of images. Here is the final method that I use to create the thumbnails.
public static BitmapImage CreateThumbnail(string imagePath)
{
var bitmap = new BitmapImage();
using (var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
bitmap.BeginInit();
bitmap.DecodePixelWidth = 283;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
bitmap.Freeze();
GC.WaitForPendingFinalizers();
GC.Collect();
return bitmap;
}
I have saved images in our database by using the following method to convert them to byte arrays in different ImageFormats:
public byte[] foo()
{
Image img = Image.FromFile(path);
var tmpStream = new MemoryStream();
ImageFormat format = img.RawFormat;
img.Save(tmpStream, format);
tmpStream.Seek(0, SeekOrigin.Begin);
var imgBytes = new byte[MAX_IMG_SIZE];
tmpStream.Read(imgBytes, 0, MAX_IMG_SIZE);
return imgBytes;
}
Now I need to read them out and convert them back into the BitmapImage type so I can display them to the user. I was thinking about using the Image.FromStream(Stream) method but that doesn't seem to take into account the different ImageFormats... Anyone know what to do? Thanks in advance.
You shouldn't use classes from the WinForms System.Drawing namespace in a WPF application (like you do with Image.FromFile).
WPF provides its own set of classes to load and save bitmaps from Streams and URIs, and has built-in support for automatically detecting the format of a bitmap frame buffer.
Just create a BitmapImage or a BitmapFrame directly from a Stream:
public static BitmapSource BitmaSourceFromByteArray(byte[] buffer)
{
var bitmap = new BitmapImage();
using (var stream = new MemoryStream(buffer))
{
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.StreamSource = stream;
bitmap.EndInit();
}
bitmap.Freeze(); // optionally make it cross-thread accessible
return bitmap;
}
or
public static BitmapSource BitmaSourceFromByteArray(byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
{
return BitmapFrame.Create(
stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
Either method returns a BitmapSource, which is the base class of BitmapImage and BitmapFrame, and should be sufficient to deal with bitmaps in the rest of your application. E.g. the Source property of an Image control uses another base class, ImageSource, as property type.
Note also that when you load a BitmapSource from a Stream that is to be closed after loading, you have to set BitmapCacheOption.OnLoad. Otherwise the Stream must be kept open until the bitmap is eventually shown.
For encoding a BitmapSource you should be using a method like this:
public static byte[] BitmapSourceToByteArray(BitmapSource bitmap)
{
var encoder = new PngBitmapEncoder(); // or any other BitmapEncoder
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (var stream = new MemoryStream())
{
encoder.Save(stream);
return stream.ToArray();
}
}
Since two days I try to manage my images in a WPF application but I have errors and errors errors, ...
The image is show in a System.Windows.Control.Image.
For this reason I try to work with variable BitMapImage.
Now I have the error : "Impossible to access close stream" and I can not find a solution.
I have created two function for convert :
public static BitmapImage ConvertToBitMapImage(byte[] bytes)
{
if (bytes == null || bytes.Length == 0) return null;
var image = new BitmapImage();
using (var mem = new MemoryStream(bytes))
{
mem.Position = 0;
image.BeginInit();
image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = null;
image.StreamSource = mem;
image.EndInit();
}
//image.Freeze();
return image;
}
public static byte[] ImageToByte(BitmapImage imageSource)
{
Stream stream = imageSource.StreamSource;
Byte[] buffer = null;
if (stream != null && stream.Length > 0)
{
using (BinaryReader br = new BinaryReader(stream))
{
buffer = br.ReadBytes((Int32)stream.Length);
}
}
return buffer;
}
In my object I have a property :
public BitmapImage MiniatureApp
{
get
{
if (IMAGES != null)
_MiniatureApp = Tools.BinaryImageConverter.ConvertToBitMapImage(IMAGES.IMAGE);
return _MiniatureApp;
}
set
{
if (this.IMAGES != null)
this.IMAGES.IMAGE = Tools.BinaryImageConverter.ImageToByte((BitmapImage)value);
else
{
IMAGES img = new IMAGES();
img.NOM = "";
img.IMAGE = Tools.BinaryImageConverter.ImageToByte((BitmapImage)value);
this.IMAGES = img;
}
NotifyPropertyChanged();
}
}
And in my main I do this :
FileStream fs = new FileStream(pathImage, FileMode.Open, FileAccess.Read);
byte[] data = new byte[fs.Length];
fs.Read(data, 0, System.Convert.ToInt32(fs.Length));
fs.Close();
VMAppareil.VMApp.CurrentAppareil.MiniatureApp = Tools.BinaryImageConverter.ConvertToBitMapImage(data);
Exist a solution for my problem or exist a best way to do this ?
Gobelet.
You can fix your solution replacing the ImageToByte method with the following (borrowed from https://stackoverflow.com/a/6597746/5574010)
public static byte[] ImageToByte(BitmapImage imageSource)
{
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(imageSource));
using (var ms = new MemoryStream())
{
encoder.Save(ms);
return ms.ToArray();
}
}
Your previous solution did not work because the ConvertToBitMapImage method close the stream that you assign to image.StreamSource as soon as your code exit the unit statement. When you call the ImageToByte method as part of the MiniatureApp setter the StreamSource will be close and you get an error.
I'm not really sure what you're doing with the getter-setter.
For getting a byte array from a file, I would have this:
public static byte[] FileToByteArray(string fileName)
{
byte[] fileData = null;
using (FileStream fs = new File.OpenRead(fileName))
{
var binaryReader = new BinaryReader(fs);
fileData = binaryReader.ReadBytes((int)fs.Length);
}
return fileData;
}
And once you have the bytes from that function, you can use ConvertToBitMapImage() and assign that to the Image.Source, like this:
byte[] bytes = FileToByteArray(fileName);
YourImage.Source = ConvertToBitMapImage(bytes);
I'm not sure why you would need to convert a BitmapImage into bytes, but you should be able to call your function directly for that:
BitmapImage bImg = new BitmapImage();
bImg.Source = ConvertToBitMapImage(bytes);
byte[] bytes = ImageToByte(bImg); // these should be the same bytes as went in
Set it up like this and step through the code to see where it snags. It's possible the bytes might need encoding when you grab it in ImageToByte().
But also, you don't just then set the byte[] directly to the IMAGES or this.IMAGES.IMAGE, because that isn't an Image object, it's a byte[]. Without knowing what your IMAGES model construct looks like with its property types, it's a little vague to know what is being set to what and why, but this seems like a bad idea to over-complicate what you should just do with function calls as it is needed, without a getter-setter and an IMAGES model in the way.
How can I get ImageSource from MemoryStream in WPF using c# ? or convert MemoryStream to ImageSource to display it as image in wpf ?
using (MemoryStream memoryStream = ...)
{
var imageSource = new BitmapImage();
imageSource.BeginInit();
imageSource.StreamSource = memoryStream;
imageSource.EndInit();
// Assign the Source property of your image
image.Source = imageSource;
}
Additional to #Darin Dimitrov answer if you disposed the stream before assigning to Image.Source nothing will show, so be careful
For example, the Next method will not work, From one of my projects using LiteDB
public async Task<BitmapImage> DownloadImage(string id)
{
using (var stream = new MemoryStream())
{
var f = _appDbManager.DataStorage.FindById(id);
f.CopyTo(stream);
var imageSource = new BitmapImage {CacheOption = BitmapCacheOption.OnLoad};
imageSource.BeginInit();
imageSource.StreamSource = stream;
imageSource.EndInit();
return imageSource;
}
}
you can not use returned imageSource from the last function
But this implementation will work
public async Task<BitmapImage> DownloadImage(string id)
{
// TODO: [Note] Bug due to memory leaks, if we used Using( var stream = new MemoryStream()), we will lost the stream, and nothing will shown
var stream = new MemoryStream();
var f = _appDbManager.DataStorage.FindById(id);
f.CopyTo(stream);
var imageSource = new BitmapImage {CacheOption = BitmapCacheOption.OnLoad};
imageSource.BeginInit();
imageSource.StreamSource = stream;
imageSource.EndInit();
return imageSource;
}
I am using visual studio 2010, (desktop application) and using LINQ to SQL to save image/video or audio files to database in dataType VarBinary (MAX). This I can do... Problem is, I can't get them and display them in xaml because I can't get the converting part correct. Here is what I have so far (though its not working);
private void bt_Click (object sender, RoutedEventArgs e)
{
databaseDataContext context = new databaseDataContext();
var imageValue = from s in context.Images
where s.imageID == 2
select s.imageFile;
value = imageValue.Single().ToString();
//convert to string and taking down to next method to get converted in image
}
public string value { get; set; }
public object ImageSource //taking from http://stackoverflow.com/
{
get
{
BitmapImage image = new BitmapImage();
try
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.UriSource = new Uri(value, UriKind.Absolute);
image.EndInit();
Grid.Children.Add(image);
}
catch { return DependencyProperty.UnsetValue; } return image;
}
}
I not even sure if I am on the correct track? And I am assuming that video or audio is quite similar methods?
Since your image is stored in binary format in the database, you want to "stream" this into an image object by leveraging the MemoryStream object.
Looking at your code, your solution will look something like this:
BitmapImage bmpImage = new BitmapImage();
MemoryStream msImageStream = new MemoryStream();
msImageStream.Write(value, 0, value.Length);
bmpCardImage.BeginInit();
bmpCardImage.StreamSource = new MemoryStream(msImageStream.ToArray());
bmpCardImage.EndInit();
image.Source = bmpCardImage;
It's very easy, if you have a binary data and want to create an Image object, use this code:
public Image BinaryToImage(byte[] binaryData)
{
MemoryStream ms = new MemoryStream(binaryData);
Image img = Image.FromStream(ms);
return img;
}
If you already have the bytes, to verify that what you saved is correct you can save the bytes to a file and open it....
string tempFile = Path.GetTempFileName();
MemoryStream ms = new MemoryStream(bytes); //bytes that was read from the db
//Here I assume that you're reading a png image, you can put any extension you like is a file name
FileStream stream = new FileStream(tempFile + ".png", FileMode.Create);
ms.WriteTo(stream);
ms.Close();
stream.Close();
//And here we open the file with the default program
Process.Start(tempFile + ".png");
And later you can use the answer of Dillie-O and stream....
Dillie-O's code makes for a very nice extension method:
// from http://stackoverflow.com/questions/5623264/how-to-convert-varbinary-into-image-or-video-when-retrieved-from-database-in-c:
public static BitmapImage ToImage(this Binary b)
{
if (b == null)
return null;
var binary = b.ToArray();
var image = new BitmapImage();
var ms = new MemoryStream();
ms.Write(binary, 0, binary.Length);
image.BeginInit();
image.StreamSource = new MemoryStream(ms.ToArray());
image.EndInit();
return image;
}