Silverlight Embedded ImageBrush not rendering when saved as WriteableBitmap - silverlight

I have an Silverlight application that would create a canvas, save the canvas as a WriteableBitmap and send the WriteableBitmap as a MemoryStream to the server side in order to embed the image inside of an email.
Everything works fine until I get a new requirement that I have to show certain images (Image brush with URL pointing to some image files, to avoid confusion, let's call it GreenIconBrush) inside of the canvas. Now, the canvas was rendering fine on the Silverlight side, however, in the email, everything showed up except the GreenIconBrush.
The canvas consists of an ItemsControl which uses DataTemplate. Depending on the data, I will load different ImageBrush which are defined as follows:
Resource:
<ImageBursh x:key="GreenIconBrush" ImageSource="Images/TrafficLightGreen.png"/>
Xaml:
<Grid>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Border Background="Binding ScoreByIconColor, Converter={StaticResource ScoreColorToBrushColorConverter}">
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Converter:
<Converts:ValueMapperConverter x:Key="ScoreColorToBrushConverter">
<Converts:ValueMapperConverter.Map>
<Converts:MappedItem key="green" Value="{StaticResource GreenIconBrush}" />
</Converts:ValueMapperConverter.Map>
</Converts:ValueMapperConverter>
Here is the code that renders the image on the canvas and create WriteableBitmap and call the web service to send email
ScoreCard target = new ScoreCard();
setupScoreCard(target, data);
target.UpdateLayout();
canvas.Children.Add(target);
canvas.UpdateLayout();
WriteableBitmap bitmap = new WriteableBitmap(canvas, null);
// bitmap.Invalidate(); <- tried this, does not help
MemoryStream stream = (MemoryStream)SaveToStream(bitmap);
stream.Seek(0, SeekOrigin.Begin);
data.ScoreCard = ReadToEnd(stream);
wcfService.SendEmailAsync(data);
On the web service side:
MailMessage msg = new MailMessage();
// setup to, from, SmtpClient, etc.
msg.IsBodyHtml = true;
AlternateView view = AlternateView.CreateAlternateViewFromString(emailTemplate.ToString(), null, MediaTypeNames.Text.Html);
MemoryStream fs = new MemoryStream();
fs.Write(data.ScoreCard, 0, data.ScoreCard.Length);
fs.Seek(0, SeekOrigin.Begin);
LinkedResource pic = new LinkedResource(fs, new System.Net.Mine.ContentType("image/png");
view.LinkedResources.Add(pic);
msg.AlternateViews.Add(view);
smtp.Send(msg);
I suspect the issue is related to the ImageBrush was not Rendered when I save the canvas to WriteableBitmap but I tried InvalidateMeasure(), InvalidateArrange() on both target and canvas and I even tried to apply transformation and Invalidate the WriteableBitmap but nothing worked.
Please help...
Sincerely,
Charlie Chang

Well, the problem is that the embedded PNG file does not get rendered on the canvas and become part of the writeablebitmap when I send it to the server side to be send in the email.
After wasting many hours trying to make this work, I decided just to recreate the images in Xaml (convert image to Xaml - vector was not a good idea either for you lose too much quality) and everything works fine now.
Charlie

Related

Reduce Image resolution in WPF image control

I have a image control in WPF. I need to reduce the image size control width and height. But when i Do that , the image is not looking good. The data loss is more.
So i thought to reduce the image resolution instead of just changing the image control width and height.
can any one help me how to change the image resolution of the binded image in WPF image control
[I mean image is already binded to image control now I have to the change the resolution only]
In .NET 4 they changed the default image scaling to a low quality one...so you can use BitmapScalingMode to switch back to the higher quality one:
<Image RenderOptions.BitmapScalingMode="HighQuality"
Source="myimage.png"
Width="100"
Height="100" />
http://10rem.net/blog/2010/05/01/crappy-image-resizing-in-wpf-try-renderoptionsbitmapscalingmode
You can also combine the above with other options like the Decode options if your source image is a huge image (this just reduces memory usage in your application).
http://www.shujaat.net/2010/08/wpf-saving-application-memory-by.html
other options to prevent "blurryness" is to put UseLayoutRounding="True" on your root element (i.e. Window)....it's recommended to use this in .NET 4 rather than SnapToDevicePixels:
When should I use SnapsToDevicePixels in WPF 4.0?
http://blogs.msdn.com/b/text/archive/2009/08/27/layout-rounding.aspx
You can use the DecodePixelWidth property like this:
<Image Stretch="Fill">
<Image.Source>
<BitmapImage CacheOption="OnLoad" DecodePixelWidth="2500" UriSource="Images/image.jpg"/>
</Image.Source>
</Image>
1) Have a try with a ViewBox : put your image within a ViewBox.
2) Sometimes the rendering engine looses quality because of pixel alignement issues, especially on low-resolution device. Have a look to SnapsToDevicePixels property, and try to set it to true in the containing control AND / OR in the ViewBox.
3) Obviously you could write a Control that perform a resolution change but it is quite some work.
Try this.
Image img = new Image();
var buffer = System.IO.File.ReadAllBytes(_filePath);
MemoryStream ms = new MemoryStream(buffer);
BitmapImage src = new BitmapImage();
src.BeginInit();
src.StreamSource = ms;
src.DecodePixelHeight = 200;//Your wanted image height
src.DecodePixelWidth = 300; //Your wanted image width
src.EndInit();
img.Source = src;

Saving UserControl as a JPG - result is "squished"

I'm having a strange issue with saving a UserControl as a JPG. Basically what I want to do is create a Live Tile that I can update using a Background Agent (as well as from within the app itself.)
I've followed the steps in this blog post which work great for when I create the tile; I have a custom UserControl with some TextBlocks on it which gets saved as a JPG into IsolatedStorage, exactly as the post says.
Creating the tile is no problem - the UserControl is rendered nicely as it should be. However, when I try to update the tile (using the exact same method - saving a new control as a JPG, then using that JPG as the BackgroundImage), things break down.
The resulting image that gets placed on the tile (and saved in IsolatedStorage) looks like this: squished tile (pulled from IsolatedStorage using the Isolated Storage Explorer Tool)
The background is black, and all the text runs down the side of the image (and overlaps each other) - the expected result is the background being the phone's accent colour, and the text appearing horizontal near the top.
The code used to generate and save the image is exactly the same in both instances - I've abstracted it out into a static method that returns StandardTileData. The only difference is where it is called from: in the working case where the tile is created, it's called from a page within the main app; in the non-working case (where the tile is updated), it's called from a page that can only be accessed by deep-linking from the tile itself.
Any thoughts? I'm guessing something's going wrong with the rendering of the Control to a JPG, since the actual image comes out this way.
The snippet of code that generates the image is here:
StandardTileData tileData = new StandardTileData();
// Create the Control that we'll render into an image.
TileImage image = new TileImage(textA, textB);
image.Measure(new Size(173, 173));
image.Arrange(new Rect(0, 0, 173, 173));
// Render and save it as a JPG.
WriteableBitmap bitmap = new WriteableBitmap(173, 173);
bitmap.Render(image, null);
bitmap.Invalidate();
IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication();
String imageFileName = "/Shared/ShellContent/tile" + locName + ".jpg";
using (IsolatedStorageFileStream stream = storage.CreateFile(imageFileName))
{
bitmap.SaveJpeg(stream, 173, 173, 0, 100);
}
tileData.BackgroundImage = new Uri("isostore:" + imageFileName, UriKind.Absolute);
return tileData;
The XAML for the control I'm trying to convert is here:
<UserControl x:Class="Fourcast.TileImage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
d:DesignHeight="173" d:DesignWidth="173" FontStretch="Normal" Height="173" Width="173">
<Border Background="{StaticResource PhoneAccentBrush}">
<StackPanel>
<TextBlock HorizontalAlignment="Stretch"
TextWrapping="Wrap" VerticalAlignment="Stretch" Style="{StaticResource PhoneTextLargeStyle}" x:Name="Temperature"></TextBlock>
<TextBlock x:Name="Condition" HorizontalAlignment="Stretch"
TextWrapping="Wrap" VerticalAlignment="Stretch" Style="{StaticResource PhoneTextNormalStyle}">
</TextBlock>
</StackPanel>
</Border>
</UserControl>
Update: after some investigation with the debugger, it appears the calls to Measure and Arrange don't seem to do anything when the method is called from the class that's updating the tile. When the tile is being created, however, these calls function as expected (the ActualWidth and ActualHeight of the control get changed to 173).
Not sure what exactly is going on here, but here's what I ended up doing to workaround the issue.
I switched from trying to render a Control to defining a StackPanel in code, adding elements to it, then rendering that to an image. Oddly enough, though, the Arrange and Measure methods don't do anything unless another element has had them applied to it, and UpdateLayout called.
The full resulting code (minus the contents of the TextBlocks and writing to an image) is below.
StandardTileData tileData = new
// None of this actually does anything, but for some reason the StackPanel
// won't render properly without it.
Border image = new Border();
image.Measure(new Size(173, 173));
image.Arrange(new Rect(0, 0, 173, 173));
image.UpdateLayout();
// End of function-less code.
StackPanel stpContent = new StackPanel();
TextBlock txbTemperature = new TextBlock();
txbTemperature.Text = temperature;
txbTemperature.Style = (Style)Application.Current.Resources["PhoneTextLargeStyle"];
stpContent.Children.Add(txbTemperature);
TextBlock txbCondition = new TextBlock();
txbCondition.Text = condition;
txbCondition.Style = (Style)Application.Current.Resources["PhoneTextNormalStyle"];
stpContent.Children.Add(txbCondition);
stpContent.Measure(new Size(173, 173));
stpContent.Arrange(new Rect(0, 0, 173, 173));
The way to get the background of the image to use the phone's accent color is to make it transparent. This will require that you save a PNG rather than a JPG.
The ouput image looks like everything is wrapping more thna it needs to. You probably need to adjust the widths of the elements you're including in your control. Without the XAML it's hard to be more specific.
I also get this strange behaviour - squished tile (using Ree7 Tile toolkit),
Try call update tile into Dispatcher:
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
CustomTile tile = GetTile(card);
StandardTileData tileData = tile.GetShellTileData();
shellTile.Update(tileData);
});
Working for me

Is it possible to load ListboxItems of type Image as user scrolls the content using a WrapPanel as the PanelTemplate?

I'm developing an app for Windows Phone 7 that populates a WrapPanel with a list of objects retrieved from an ObservableCollection<Photo>.
<ListBox ItemsSource="{Binding Photo}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding File}" />
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<toolkit:WrapPanel ItemHeight="150" ItemWidth="150" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
It's working how it's supposed to work (in the means that it's loading all the images), but I'm having some performance issues since WrapPanel : Panel. So it doesn't virtualize any data, loading all the Image objects of the ListBox, even the ones that the user can't see.
This approach is OK when ObservableCollection<Photo>.Count <= 30 but as the Collection gets bigger and bigger things start to get slow.
Since the user can have up to 1000 images, it's simply not going to work this way. Even though I'm binding Thumbnails to display the Image object.
I've tried to use David Anson's LowProfileImageLoader to create Images off the UI thread. And to bind the Images as the user scrolls the ListBox. But it doesn't work since it's expecting an UriSource and I'm actually binding a BitmapImage to the Image.Source, because the images are beeing loaded from IsolatedStorage.
public class Photo : INotifyPropertyChanged, INotifyPropertyChanging
{
...
...
public BitmapImage File
{
get
{
// Thumbnail
string filePath = Path.Combine("Images", FileName);
byte[] data;
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isfs = isf.OpenFile(filePath, FileMode.Open, FileAccess.Read))
{
data = new byte[isfs.Length];
isfs.Read(data, 0, data.Length);
isfs.Close();
}
}
MemoryStream ms = new MemoryStream(data);
BitmapImage bi = new BitmapImage();
bi.SetSource(ms);
return bi;
}
}
}
Can anyone help me in this task of making the content (photos) load as the user sees it? Is there anything such as a VirtualizingWrapPanel or WrapPanel : VirtualizingPanel?
Thanks. If any code snippet is needed feel free to ask.
Loading the images to memory from the storage when the user scrolls will make your UI jittery especially when the user tries to scroll or see more elements very quickly.
But, if you have all the images in memory and then would like to bind the images to the listbox on scrolling, you could use the VirtualizingStackPanel. A wrap panel would need to know the size of the images to display correctly. A workaround is possible if you know the size of the images and if they're all the same.
You could customize the VirtualizingStackPanel and position the images in rows like the way they would in a wrappanel, or any other way you like.
Hope that helps.
Although,
I am not sure if this will solve the problem when you have 1000 heavy images. But it is worth a try, if you haven't done so.
EDIT: This article might help.
Try creating a wrap panel from a virtualized listbox.
Here is a link how to do it. it works pretty good, but it has 1 problem... it will have a tilt effect on a whole line instead of an item... I'm currently investigating that problem.

Why does WPF make working with images so much more difficult?

I used to be able to do so very much with just the Bitmap and Graphics objects. Now that I've been using WPF the only thing I seem to be able to do is load an image and show it and make it dance around the stupid screen. Why did they get rid of these very useful tools. Are they trying to stupefy .Net?
All I want to do is load an image from a file and cut it into two parts. This was easy with .Net 2.0 and System.Drawing. But with WPF, I'm hitting a brick wall without using some very low level code. I've tried working with WriteableBitmap. But it doesn't seem to really be what I'm wanting. Is there no way to wrap a DrawingContext around a BitmapImage or something?
Please tell me that WPF is more than HTML for applications. I am REALLY frustrated!!
Edits:
Also, how on earth does one save an image to a file?
If you want to cut the image in two parts, why not use the CroppedBitmap class?
Consider the following XAML. One source BitmapImage shared by two CroppedBitmaps, each showing different parts of the source.
<Window.Resources>
<BitmapImage x:Key="bmp" UriSource="SomeBitmap.jpg" />
</Window.Resources>
<StackPanel>
<Image>
<Image.Source>
<CroppedBitmap Source="{StaticResource ResourceKey=bmp}">
<CroppedBitmap.SourceRect>
<Int32Rect X="0" Y="0" Width="100" Height="100" />
</CroppedBitmap.SourceRect>
</CroppedBitmap>
</Image.Source>
</Image>
<Image>
<Image.Source>
<CroppedBitmap Source="{StaticResource ResourceKey=bmp}">
<CroppedBitmap.SourceRect>
<Int32Rect X="100" Y="150" Width="50" Height="50" />
</CroppedBitmap.SourceRect>
</CroppedBitmap>
</Image.Source>
</Image>
</StackPanel>
Update: to do something similar in code:
var bitmapImage = new BitmapImage(new Uri(...));
var sourceRect = new Int32Rect(10, 10, 50, 50);
var croppedBitmap = new CroppedBitmap(bitmapImage, sourceRect);
Well there is this http://www.nerdparadise.com/tech/csharp/wpfimageediting/
or perhaps you could add a reference to System.Drawing to your project and then do the editing the way you are comfortable with.
You are probably best off using TransformedBitmap. Load your Bitmap as a BitmapSource, then set the Transform property to however you want the image to transform. You have several different transformation options here. This allows you to rotate, screw, matrix, etc. transformations. If you want to apply more than one, use a TransformGroup and apply several transformations at once.
You can also use BitmapFrame.Create(...) to work with the transformed image more.
Some Pseudo code:
var image = new BitmapSource(...); //Your image
var transformBitmap = new TransformedBitmap(image);
var transformBitmap.Transform = ..//Set your transform;
//optionally:
var frame = BitmapFrame.Create(transformBitmap);

How to display an image in Silverlight?

This should be simple, but...
I created a folder in my solution called Images. I dragged an image into it. How do I now display this image on a Page or View?
Make sure the image is set as a Resource. It can be in any folder in any of your projects in your solution.
You can then reference this as [assembly];component/[path]/[imagename.extension]
For example:
<Image Source="/mynamespace.myassembly;component/ResourcesFolder/image.png" Width="16" Height="16" />
There are a couple of ways to get at it--here's the way that involves setting the image as a Resource in the Visual Studio file properties:
using (var stream = Application.GetResourceStream(
new Uri("SilverlightAssemblyName;component/Images/myImage.png",
UriKind.Relative)))
{
// read from stream
}
Where SilverlightAssemblyName is replaced by the Assembly Name you specified in the Silverlight tab of your Silverlight project.
If you want to use the image in code:
var bitmap = new BitmapImage();
bitmap.SetSource(stream);
myImageControl.ImageSource = bitmap;
Or, if you want to use the resource in XAML, you don't need any of the code:
<Image Source="/Images/myImage.png" Width="16" Height="16" />

Resources