I'm trying to resize an image in Silverlight 3 that has been submitted by a user via the OpenFileDialog control. I can grab the contents of the file and put it into a WriteableBitmap object and then display it on the screen just fine into an Image control. The Image control will even resize it to fit the size of the image control for me which is great.
The problem is the in memory image is still the original full resolution image, I kinda need to resize it in memory because I have a bunch of expensive operations I need to perform on it on a per pixel basis. So far I have the following code...
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
btnUploadPhoto.Click += new RoutedEventHandler(UploadPhoto_Click);
}
private void UploadPhoto_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.Filter = "Image files (*.png;*.jpg;*.gif;*.bmp)|*.png;*.jpg;*.gif;*.bmp";
if (dialog.ShowDialog() == true)
{
WriteableBitmap bitmap = new WriteableBitmap(500, 500);
bitmap.SetSource(dialog.File.OpenRead());
imgMainImage.Source = bitmap;
txtMessage.Text = "Image size: " + bitmap.PixelWidth + " x " + bitmap.PixelHeight;
}
}
}
Problem is the WriteableBitmap class doesn't have a Resize method on it, and setting the height and width in the constructor doesn't seem to have any effect.
What you can do is create a new Image element and set its source to a Writeable bitmap created from the stream. Don't add this Image element to the visual tree. Create another WriteableBitmap of the final size you want. Then call Render on this WriteableBitmap passing the Image element and a ScaleTransform to resize the image to the appropriate size. You can then use the second WriteableBitmap as the source for a second Image element and add that to the visual tree. You can then allow the first Image and WriteableBitmap objects to get GCed so you get the memory back.
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 have used FJCore with some success, it's an open source C# imaging toolkit from Occipital. Includes in-memory resizing capability.
Also check out ImageMagick.
Related
I want the image which is added to FlowDocument is loaded as a bitmap data when this image is visible by page change or scrolling.(not IsVisible property)
because My senario is that images(png, jpg...) are in a zip file.
and I will load zip file to memory and decompress it to memory stream.
So, this memory stream has png, jpg binary(low size).
and I change this png binary data to BitmapImage class.
This bitmapImage class is added to flowDocument.
The problem is zip file has many image files and after i changed the images to BitmapImage classes, it takes so many Memory Size.
So, I want to change it to below.
1. Save decompressed image(png, jpg...) data to MemoryStream.
2. Add this images without changing it to BitmapClasses.
3. Dynamically change images data to BitmapClasses when the image should be displayed by chainging page or scrolling.
However, I cannot find the way of number 2 above.
I tried Display Image from Byte Array in WPF - Memory Issues but it is different situation.
How Can I load image dynamically when the image is visible at FlowDocument?
You can try this way.
1. extend Image Class and Add IsVisibleChanged event handler.
2. The class has memory stream variable.
3. when you add images, just add memory stream except Source.
4. when IsVisibleChanged handler is called, add memory stream to source.
This is my sample code.
public class sampleImage : Image
{
public MemoryStream memoryStream = null;
public sampleImage () : base()
{
IsVisibleChanged += new DependencyPropertyChangedEventHandler(shandler);
}
void shandler(object sender, DependencyPropertyChangedEventArgs e)
{
if (IsVisible)
{
memoryStream.Position = 0;
var bitmap = BitmapFrame.Create(memoryStream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
Source = bitmap;
} else {
Source = null;
GC.Collect(); // it depends on you.
}
}
}
I use on listbox control own datatemplate. Listbox item consist one image control and some textblock.
On image source I bind property type of Uri (absolute url - for example: http://u.aimg.sk/fotky/1730/71/17307141.jpg?v=2)
Listbox have about 50 - 300 items.
If I test app, I sometimes see blank - white or black image instead user images.
The problem you can see on this images:
I would like to know what cause this problem and how can I solve this problem.
Image sources are good, I check it in browser.
Thank for advice.
I think what's happening is a race condition. Some of your images haven't completed downloading by the time you are asking them to display. There is a pretty good example given here http://social.msdn.microsoft.com/Forums/en/wpf/thread/dc4d6aa9-299f-4ee8-8cd4-27a21ccfc4d0 which I'll sum up:
private ImageSource _Src;
public ImageSource Src
{
get { return _Src; }
set
{
_Src = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Src"));
((BitmapImage)_Src).DownloadCompleted += new EventHandler(MainWindow_DownloadCompleted);
}
}
void MainWindow_DownloadCompleted(object sender, EventArgs e)
{
PropertyChanged(this, new PropertyChangedEventArgs("Src"));
((BitmapImage)_Src).DownloadCompleted -= MainWindow_DownloadCompleted;
}
With the above code, your images that are binding to your property will be told to update with the PropertyChanged call when the value is first assigned as well as AFTER the images have downloaded 100%. This is taken care of in the DownloadCompleted event handler that is utilized in the above example. This should make them not appear as a black image anymore, but as their fully-ready selves.
Also, if you are using a stream to as the source for your images, you need to make sure you use BitmapCacheOption.OnLoad. Such as:
BitmapImage source = new BitmapImage();
source.BeginInit();
source.CacheOption = BitmapCacheOption.OnLoad;
source.StreamSource = yourStream;
source.EndInit();
This is because by default the image using the source will lazy load it and by then your stream is probably closed, which could also be why you get blank/black images.
Good luck.
I am having some difficulties getting images contained in a FlowDocument to show when the FlowDocument is saved as an XPS document.
Here is what I do:
Create an image using the Image control of WPF. I set the image source bracketed by calls to BeginInit/EndInit.
Add the image to the FlowDocument wrapping it in a BlockUIContainer.
Save the FlowDocument object to an XPS file using a modified version of this code.
If I then view the saved file in the XPS viewer, the image is not shown. The problem is that the images are not loaded until actually shown on the screen by WPF so they are not saved to the XPS file. Hence, there is a workaround: If I first show the document on screen using the FlowDocumentPageViewer and then save the XPS file afterwards, the image is loaded and shows up in the XPS file. This works even if the FlowDocumentPageViewer is hidden. But that gives me another challenge. Here is what I wish to do (in pseudocode):
void SaveDocument()
{
AddFlowDocumentToFlowDocumentPageViewer();
SaveFlowDocumentToXpsFile();
}
This of course does not work since the FlowDocumentPageViewer never gets a chance to show its contents before the document is saved to the XPS file. I tried wrapping SaveFlowDocumentToXpsFile in a call to Dispatcher.BeginInvoke but it did not help.
My questions are:
Can I somehow force the images to load before saving the XPS file without actually showing the document on screen? (I tried fiddling with BitmapImage.CreateOptions with no luck).
If there is no solution to question #1, is there a way to tell when FlowDocumentPageViewer has finished loading its contents so that I know when it is save to create the XPS file?
The eventual solution was the same as you came to, which is to put the document in a viewer and briefly show it on screen. Below is the helper method that I wrote to do this for me.
private static string ForceRenderFlowDocumentXaml =
#"<Window xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
<FlowDocumentScrollViewer Name=""viewer""/>
</Window>";
public static void ForceRenderFlowDocument(FlowDocument document)
{
using (var reader = new XmlTextReader(new StringReader(ForceRenderFlowDocumentXaml)))
{
Window window = XamlReader.Load(reader) as Window;
FlowDocumentScrollViewer viewer = LogicalTreeHelper.FindLogicalNode(window, "viewer") as FlowDocumentScrollViewer;
viewer.Document = document;
// Show the window way off-screen
window.WindowStartupLocation = WindowStartupLocation.Manual;
window.Top = Int32.MaxValue;
window.Left = Int32.MaxValue;
window.ShowInTaskbar = false;
window.Show();
// Ensure that dispatcher has done the layout and render passes
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {}));
viewer.Document = null;
window.Close();
}
}
Edit: I just added window.ShowInTaskbar = false to the method as if you were quick you could see the window appear in the taskbar.
The user will never "see" the window as it is positioned way off-screen at Int32.MaxValue - a trick that was common back in the day with early multimedia authoring (e.g. Macromedia/Adobe Director).
For people searching and finding this question, I can tell you that there is no other way to force the document to render.
HTH,
Couple things...
You sure the image is sized before its written? Usually you have to call Measure on the control so that it may size itself accordingly (infinity lets the control expand to its Width and Height)
image.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Also, sometimes you have to bump the UI thread so that everything gets updated in the control
Dispatcher.Invoke(DispatcherPriority.Render, new Action(() =>{}));
You do not have to display the document in order to have images saved into the xps. Are you calling commit on the XpsSerializationManager?
FlowDocument fd = new FlowDocument();
fd.Blocks.Add(new Paragraph(new Run("This is a test")));
string image = #"STRING_PATH";
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(image, UriKind.RelativeOrAbsolute);
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.EndInit();
MemoryStream ms = new MemoryStream();
Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
Uri pkgUri = bi.UriSource;
PackageStore.AddPackage(pkgUri, pkg);
Image img = new Image();
img.Source = bi;
BlockUIContainer blkContainer = new BlockUIContainer(img);
fd.Blocks.Add(blkContainer);
DocumentPaginator paginator = ((IDocumentPaginatorSource)fd).DocumentPaginator;
using (XpsDocument xps = new XpsDocument(#"STRING PATH WHERE TO SAVE FILE", FileAccess.ReadWrite, CompressionOption.Maximum))
{
using (XpsSerializationManager serializer = new XpsSerializationManager(new XpsPackagingPolicy(xps), false))
{
serializer.SaveAsXaml(paginator);
serializer.Commit();
}
}
I was able to address this by throwing the flowdocument into a viewer, and then do a measure/arrange.
FlowDocumentScrollViewer flowDocumentScrollViewer = new FlowDocumentScrollViewer();
flowDocumentScrollViewer.Document = flowDocument;
flowDocumentScrollViewer.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
flowDocumentScrollViewer.Arrange(new Rect(new Point(0, 0), new Point(Double.MaxValue, Double.MaxValue)));
I am loading an image to my silverlight application. This image will fit in a 3d model as a texture map. I need to fetch image attributes. For that I am using ImageOpened event, like this:
public MainPage()
{
BitmapImage img = new BitmapImage(new Uri("imagens/textura.jpg", UriKind.Relative));
img.ImageOpened += new EventHandler<RoutedEventArgs>(img_ImageOpened);
img.ImageFailed += new EventHandler<ExceptionRoutedEventArgs>(img_ImageFailed);
imageBrush.ImageSource = img;
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
this.MouseLeftButtonUp += new MouseButtonEventHandler(MainPage_MouseLeftButtonUp);
(...)
and then:
private void img_ImageOpened(object sender, RoutedEventArgs e)
{
BitmapImage i = sender as BitmapImage;
ImgSize.Width = i.PixelWidth;
ImgSize.Height = i.PixelHeight;
MessageBox.Show("LOADED IMAGE SIZE\n W:" + ImgSize.Width.ToString() + " H:" + ImgSize.Height.ToString());
}
The messagebox is showing the correct values for the loaded picture. But this is running after the scene is loaded, so the size is always default (0,0) ... I don't know how to fix this. I've runned the debugger, i've noticed that the scene and the model is rendered and the picture width and height is zero. After this, the event is fired ... I can't figure it out.
Thanks in advance,
Jose'
First off this is a little confusing:-
imageBrush.ImageSource = img;
InitializeComponent();
Unless you have something quite unusual happening the imageBrush object will be null until after the InitializeComponent has run.
As to your question at a guess you load the 3D model in MainPage_Loaded. The problem then is that his happens asynchronously with the arrival of the bitmap. Hence you do not want to actually load the 3D Model until both Loaded and ImageOpened events have both occured. Note it would be dangerous to assume that ImageOpened would always happen last.
The simplest solution I can think of would be to move all your existing code in the MainPage_Loaded event to ImageOpened then move the code that fetches the image to the MainPage_Loaded. This serialises the sequence, when execution of ImageOpened you are guaranteed that the page has loaded.
Not the most sophisticated solution and doesn't make use of benefits of the asynchronous nature of SL. However it should get you going and you can assess whether there is any benefit in having Page loading and bitmap downloading operating in parallel.
I'm having a problem in WPF where a window doesn't release it's file-lock on the background image file after closing, before another part of the application tries to write to the image.
So as an example; say I have a WPF app consisting of 3 windows, 1 "menu" selection window and 2 others. Both of the windows create an ImageBrush using a BitmapImage as the ImageSource (the same image).
Window A has a button that when pressed, cycles through the available background images by copying them each over the file used as the original ImageSource and creating a new ImageBrush and setting the Window.Background to the new brush.
Window B simply uses the ImageBrush to draw the Window.Background.
If Window A is launched, backgrounds switched, closed and then Window B launched, everything is fine.
If Window B is launched, closed, then Window A is launched and backgrounds switched it crashes. Trying to switch the backgrounds throws an IOException because:
"The process cannot access the file 'C:\Backgrounds\Background.png' because it is being used by another process."
So Window B must still be holding onto it somehow!? I have tried doing a GC.Collect(); GC.WaitForPendingFinalizers(); to see if that cures the problem but it doesn't.
The answer Thomas gave is correct, and works well if you have a file path, don't want to cache the bitmap, and don't want to use XAML.
However it should also be mentioned that BitmapImage has a built-in way to load the bitmap immediately by setting BitmapCacheOption:
BitmapImage img = new BitmapImage { CacheOption = BitmapCacheOption.OnLoad };
img.BeginInit();
img.UriSource = imageUrl;
img.EndInit();
or
<BitmapImage CacheOption="OnLoad" UriSource="..." />
This will load the bitmap immediately and explicitly close the stream, just as using a FileStream would, but with several differences:
It will work with any Uri, such as a pack:// Uri.
It can be used directly from XAML
The bitmap is cached in the bitmap cache, so future use of the same Uri won't go to the disk. In your particular application this may be a bad thing, but for other uses it may be a desirable feature.
I assume you're loading the image directly from the file, like that ?
BitmapImage img = new BitmapImage();
img.BeginInit();
img.UriSource = imageUrl;
img.EndInit();
Try to load it from a stream instead ; that way you can close the stream yourself after the image is loaded, so that the file isn't locked :
BitmapImage img = new BitmapImage();
using (FileStream fs = File.OpenRead(imageFilePath)
{
img.BeginInit();
img.StreamSource = fs;
img.EndInit();
}