I've made a ListBox with items that have an image bound to them (Binding BitmapSource, UpdateSourceTrigger=PropertyChanged). They updated at runtime and all was fine but their background was black and not Transparent as i wished for.
Now i want to have the same functionality with PNG.
So now I did bind to the URI of the PNG and tried to change the image and notify afterwards, but i get an error (probably because i want to save an image while its already open?)
I'll try my best to show all the relevant code:
XAML:
<Image Source="{Binding Path=OutfitImageString, UpdateSourceTrigger=PropertyChanged}"/>
C# URI string, I wanted to use this to tell when the PNG was changed:
private string _OutfitImageString;
public string OutfitImageString
{
get { return _OutfitImageString; }
set
{
_OutfitImageString = value;
NotifyPropertyChanged("OutfitImageString");
}
}
And every time I change the Bitmap picture (its bound to an instance of a class) I run this method:
public void UpdateImage()
{
// new bitmap (transparent background by default)
Bitmap nb = new Bitmap(100, 110, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// [ ... ] Create the Bitmap
// save to PNG to get a transparent background, every Person has a unique name
string saveAt = Directory.GetCurrentDirectory() + Name + "_outfit.png";
nb.Save(saveAt, System.Drawing.Imaging.ImageFormat.Png);
// notify that we changed the image (even tho the URI string is the same)
OutfitImageString = saveAt;
}
This line creates an Error, as soon as it is run for a 2. time:
nb.Save(saveAt, System.Drawing.Imaging.ImageFormat.Png);
Exception type -2147467259 Allgemeiner Fehler in GDI+. overflow excpetion: System.Runtime.InteropServices.ExternalException (0x80004005): Allgemeiner Fehler in GDI+. bei System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams) bei System.Drawing.Image.Save(String filename, ImageFormat format)
I stored the BitmapSource of the Bitmap before and Bound to that aswell, this used to work perfectly (just the background was not transparent).
Also since these images are kind of temporary, I don't like saving them all the time :/
Thanks for the help, I am sorry the description is kinda messy... Just write if you need further details!
It is not at all necessary to save a bitmap file.
Change the type of your property from string to ImageSource
private ImageSource _OutfitImage;
public ImageSource OutfitImage
{
get { return _OutfitImage; }
set
{
_OutfitImage = value;
NotifyPropertyChanged(nameof(OutfitImage));
}
}
and bind to it like shown below (where setting UpdateSourceTrigger=PropertyChange is pointless).
<Image Source="{Binding Path=OutfitImage}"/>
Then assign a value like this:
OutfitImage = BitmapToBitmapSource(nb);
...
public static BitmapSource BitmapToBitmapSource(System.Drawing.Bitmap bitmap)
{
var bitmapImage = new BitmapImage();
using (var stream = new MemoryStream())
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
stream.Position = 0;
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
}
return bitmapImage;
}
Related
I am trying to Crop a BitmapImage in my WPF application StageWindow.Stage using the CroppedBitmap class.
When saving the new cropped image to file JpegBitmapEncoder I can see the correctly cropped image. CroppedBitmap Crop
However I actually want to save the cropped image into a List\<BitmapImage\> for later use in another WPF image control. That list contains BitmapImages already so changing it to CroppedBitmaps would not suffice.
to be able to store the new croppedBitmap I use something like this:
BitmapImage xx = CroppedBitmap1 as BitmapImage;
when setting an WPF image control to the new BitmapImage (ExpectedCroppedImage) it still displays the CroppedBitmaps original source image without the crop.
I suspect the code above to delete the crop attribute from the new BitmapImage because the BitmapImage itself has no attributes for the cropped Area. But how can I save only the cropped part into a new BitmapImage?
I have been searching around but it seems CroppedBitmap should do the trick I just dont know how to convert it back in the correct manner.
Here is the Code for clarification:
//Stage is the WPF image element that im reading the source image from.
ImageSource StageWindowImageSource = StageWindow.Stage.Source;
CroppedBitmap Crop = new CroppedBitmap((BitmapSource)StageWindowImageSource, new Int32Rect(0,0,50,50));
ExpectedCroppedImage = Crop.Source as BitmapImage;
JpegBitmapEncoder jpg = new JpegBitmapEncoder();
jpg.Frames.Add(BitmapFrame.Create(Crop));
FileStream fp = new FileStream("F:/Crop.jpg", FileMode.Create, FileAccess.Write);
jpg.Save(fp);
fp.Close();
The line
ExpectedCroppedImage = Crop.Source as BitmapImage;
does not return the cropped bitmap, but the original one from which the crop was taken, i.e. the Source bitmap.
Besides that, you don't need any conversion from CroppedBitmap to BitmapImage.
Instead, use a base class of BitmapImage and CroppedBitmap as element type of the List, i.e. use a List<BitmapSource> or List<ImageSource> for your collection.
Now you can assign instances of any class derived from the element type.
List<BitmapSource> images = ...
CroppedBitmap croppedImage = new CroppedBitmap(...);
images.Add(croppedImage);
BitmapImage someOtherImage = new BitmapImage(...);
images.Add(someOtherImage);
private void MyMethod()
{
ImageSource StageWindowImageSource = img.Source;
CroppedBitmap Crop = new CroppedBitmap((BitmapSource) StageWindowImageSource, new Int32Rect(20, 20, 150, 110));
img2.Source = GetImage(Crop, new JpegBitmapEncoder());
}
private BitmapImage GetImage(BitmapSource source, BitmapEncoder encoder)
{
var image = new BitmapImage();
using (var sourceMs = new MemoryStream())
{
encoder.Frames.Add(BitmapFrame.Create(source));
encoder.Save(sourceMs);
sourceMs.Position = 0;
using (var destMs = new MemoryStream(sourceMs.ToArray()))
{
image.BeginInit();
image.StreamSource = destMs;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
}
}
return image;
}
I have an image that can and must only exist in RAM and not be directly derived off of ANYTHING that came from my hard disk or from the internet.
This is because I am testing my own (rather awful) compression functions and must be able to read my own image format. This means that image data must be stored outside persistent memory.
Most tutorials for setting background images for canvas objects require me to create an Image object (Image is abstract) and the only subclasses that I found so far have URI objects, which to me imply that they reference objects that exist in persistent space, which is far from what I want to do.
Ideally, I would like to be able to store, in a non-persistent manner, images that are represented by arrays of pixels, with a width and a length.
public partial class MyClass : Window {
System.Drawing.Bitmap[] frames;
int curFrame;
private void Refresh()
{
//FrameCanvas is a simple Canvas object.
//I wanted to set its background to reflect the data stored
//in the
FrameCanvas.Background = new ImageBrush(frames[curFrame]);
//this causes an error saying that it can't turn a bitmap
//to windows.media.imagesource
//this code won't compile because of that
}
}
There are two ways to create a BitmapSource from data in memory.
Decode a bitmap frame, e.g. a PNG or JPEG:
byte[] buffer = ...
BitmapSource bitmap;
using (var memoryStream = new MemoryStream(buffer))
{
bitmap = BitmapFrame.Create(
memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
Or create a bitmap from raw pixel data:
PixelFormat format = ...
var stride = (width * format.BitsPerPixel + 7) / 8;
bitmap = BitmapSource.Create(
width, height,
dpi, dpi,
format, null,
buffer, stride);
See BitmapSource.Create for details.
Then assign the bitmap to the ImageBrush like this:
FrameCanvas.Background = new ImageBrush(bitmap);
i am using the below code to load a .png file to bitmap
Bitmap original;
if (tbp.LanguageTypingdir =="LTR")
{
original = new Bitmap(#"Images\CC_Logo.png");
}
else
{
original = new Bitmap(#"Images\CC_Logo_ar.png");
}
i get error Additional information: Parameter is not valid.
I don't know about yours, but I know the following syntax works for loading a bitmap image:
Image img = Bitmap.FromFile("C:/Users/xxx/Pictures/1.jpg");
If you need to work on the Bitmap class, just cast the Image object.
Bitmap myBitmap = img as Bitmap;
My program has a lot of small images (Image controls are small, not the images themselves) and by saying a lot I mean more than 500. These images are generated asynchronously and then assigned to the Image controls, which were initialized before.
Basically my code does the following:
filename = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format("{0}.JPG", Guid.NewGuid().GetHashCode().ToString("x2")));
converter.ConvertPdfPageToImage(filename, i);
//Fire the ThumbnailCreated event
onThumbnailCreated(filename, (i - 1));
There is no memory leak in code that creates the images, I have the following code:
string[] files = Directory.GetFiles("C:\\Users\\Daniel\\Pictures", "*.jpg");
for(int i=0; i<files.Length; i++){
onThumbnailCreated(files[i], i);
}
Still the problem persists.
This happens in the event handler method:
void Thumbnails_ThumbnailCreated(ThumbnailCreatedEventArgs e, object sender)
{
//Since we generate the images async, we need to use Invoke
this.parent.Dispatcher.Invoke(new SetImageDelegate(SetImage), e.Filename, e.PageNumber);
}
private void SetImage(string filename, int pageNumber)
{
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
//I am trying to make the Image control use as less memory as possible
//so I prevent caching
bitmap.CacheOption = BitmapCacheOption.None;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
//We set the bitmap as the source for the Image control
//and show it to the user
this.images[pageNumber].Source = bitmap;
}
With 468 images the program uses about 1Gb of memory and then runs out of it at all. Is my task even possible to achieve using WPF or is the number of images too high? Maybe there is something wrong with my code?
Thanks in advance
You should freeze these images and set their width (or height) to that will be actually used in the application if possible:
// ...
bitmap.DecodePixelWidth = 64; // "displayed" width, this improves memory usage
bitmap.EndInit();
bitmap.Freeze();
this.images[pageNumber].Source = bitmap;
Try this:
private void SetImage(string filename, int pageNumber)
{
using (BitmapImage bitmap = new BitmapImage())
{
bitmap.BeginInit();
//I am trying to make the Image control use as less memory as possible
//so I prevent caching
bitmap.CacheOption = BitmapCacheOption.None;
bitmap.UriSource = new Uri(filename);
bitmap.EndInit();
this.images[pageNumber].Source = bitmap;
}
}
That will dispose of your bitmaps when you're done with them.
It may be the event handlers that are causing your memory leaks.
See this SO question:
Why and How to avoid Event Handler memory leaks?
I have a VideoRenderer class, which inherits an Image control, a MyVideo class, which has a VideoRenderer instance, and a RenderFrames method in MainWindow code-behind
I've attached the relevant sections of those classes, as well as the RenderFrames method and a bit of the MainWindow code-behind constructor below.
VideoRenderer receives external video and generates video frames as Bitmap objects, stored in the "bitmap" field. After each finished process, I store a clone of the bitmap in the field "bitmapCopy".
MyVideo controls when the VideoRenderer instance, "sharedVideoRenderer", starts and stops receiving and sending frames to MainWindow RenderFrames method, passing the VideoRenderer instance as an object parameter, using ThreadPool.QueueUserWorkItem.
RenderFrames loops until MyVideo says to stop by changing either of its bool "are we rendering" properties, and it does a conversion from Bitmap -> BitmapImage -> Image.Source, and then sets VideoContentControl.Content as the image, using the MainWindow Dispatcher.
Everything works, and video is renderered, but GUI control is essentially frozen, other buttons and things don't work, as dispatching the entire operations onto the MainWindow thread and looping constantly is tying up the thread.
My question is this: What other methods might I try to transport Bitmap frames from "sharedVideoRenderer" to VideoContentControl, and keep updating it with new images, without freezing the GUI?
Any help would be appreciated.
Relevant code:
VideoRenderer.cs:
internal void DrawBitmap()
{
lock (bitmapLock)
{
bitmapCopy = (Bitmap)bitmap.Clone();
}
}
MyVideo.cs:
public static void RenderVideoPreview()
{
sharedVideoRenderer.VideoObject = videoPreview;
sharedVideoRenderer.Start();
videoPreviewIsRendering = true;
ThreadPool.QueueUserWorkItem((Application.Current.MainWindow as MainWindow).RenderFrames, sharedVideoRenderer);
}
MainWindow.xaml.cs:
Dispatcher mainWindowDispatcher;
public MainWindow()
{
InitializeComponent();
mainWindowDispatcher = this.Dispatcher;
...
public void RenderFrames(object videoRenderer)
{
while (MyVideo.VideoPreviewIsRendering || MyVideo.LiveSessionParticipantVideoIsRendering)
{
mainWindowDispatcher.Invoke(new Action(() =>
{
try
{
System.Drawing.Bitmap bitmap;
bitmap = (videoRenderer as VideoRenderer).BitmapCopy;
using (MemoryStream memory = new MemoryStream())
{
BitmapImage bitmapImage = new BitmapImage();
ImageSource imageSource;
Image image;
image = new Image();
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
imageSource = bitmapImage;
image.Source = imageSource;
VideoContentControl.Content = image;
memory.Close();
}
}
catch (Exception)
{
}
}));
}
mainWindowDispatcher.Invoke(new Action(() =>
{
VideoContentControl.ClearValue(ContentProperty);
VideoContentControl.InvalidateVisual();
}));
}
ThreadPool.QueueUserWorkItem((Application.Current.MainWindow as MainWindow).RenderFrames, sharedVideoRenderer);
////
public void RenderFrames(object videoRenderer)
{
while (MyVideo.VideoPreviewIsRendering || MyVideo.LiveSessionParticipantVideoIsRendering)
{
mainWindowDispatcher.Invoke(new Action(() =>
These lines are causing the pain I guess.
Although the Renderframes method is called on a threadpool thread, the main thing it does is passing control back to the UI thread and thus claiming/blocking it.
You could try and find a smaller scope:
while (MyVideo.VideoPreviewIsRendering || MyVideo.LiveSessionParticipantVideoIsRendering)
{
try
{
System.Drawing.Bitmap bitmap;
bitmap = (videoRenderer as VideoRenderer).BitmapCopy;
using (MemoryStream memory = new MemoryStream())
{
BitmapImage bitmapImage = new BitmapImage();
ImageSource imageSource;
Image image;
image = new Image();
bitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
imageSource = bitmapImage;
mainWindowDispatcher.Invoke(new Action(() =>
{
image.Source = imageSource;
VideoContentControl.Content = image;
memory.Close();
}
}
}
catch (Exception)
{
}
}));
}
I am not quite sure I changed the scope too little or too much because I don't really know what belongs to the UI thread but this might give you a start and you might want to move things around.
Another trick you could/should use is to minimize the number of UI Elements you are making. In the current code you are creating an Image element every frame. You could, instead, make a single WriteableBitmap, point the an Image's ImageSource to it and simply blit the picture data to it. See: What is a fast way to generate and draw video in WPF?