Saving UserControl as a JPG - result is "squished" - silverlight

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

Related

WPF set new control position relative within a grid

This is somewhat a duplicate of other questions so apologies in advance but I haven't been able to make sense of the existing answers (probably because I'm a WPF newb).
I have a grid within a canvas. The grid is added programmatically, not in xaml and is much smaller than the canvas. I want to programmatically add a control (text box) at the position the user clicks on the grid. The application may or may not be full screen and users screen resolutions may differ.
Currently I'm using a mouse down event and getting a point:
Dim p As Point = Mouse.GetPosition(myGrid)
And then using the point.x and point.y with Canvas.SetLeft and Canvas.SetTop but this only works when the app is full screen and the screen res is consistent.
I know it's bad form to ask for code but please include a snippet in your answer as I've been wrestling with this for some time & going round in circles. I'm using VB but answers in any language will be welcome. Thanks very much.
Grid arranges its children basically mostly on the child's Margin property.
So do this OnClick of your grid:
// dont forget to add an event handler on creating the grid
Grid myGrid = new Grid();
myGrid.MouseDown += myGrid_MouseDown;
private void myGrid_MouseDown(object sender, MouserEventArgs e)
{
Point p = e.GetPosition(myGrid);
TextBox tb = new TextBox();
tb.Margin = new Thickness(p.X, p.Y, 0, 0);
tb.HorizontalAlignment = HorizontalAlignment.Left;
tb.VerticalAlignment = VerticalAlignment.Top; // cuz we set margin on Top and Left sides..
myGrid.Children.Add(tb);
}
Hope it helps :)
I think that the problem is not on your code but in your xaml.
Mouse.GetPosition(myGrid)
should works well.
I think that your Grid is not the same size as your Canvas.
Try something like this:
<DockPanel>
<Canvas x:Name="can">
<Grid Height="{Binding ElementName=can, Path=ActualHeight}" Width="{Binding ElementName=can, Path=ActualWidth}" Background="Red" PreviewMouseDown="Grid_PreviewMouseDown" />
</Canvas>
</DockPanel>

Silverlight Embedded ImageBrush not rendering when saved as WriteableBitmap

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

Capture screen from a Silverlight Application to be included in a printed report

I want to include in a report (e.g., written in a word file) a screen shot from a Silverlight application. The problem is that the image resolution is very bad and the printed result is unsatisfactory when using the print-screen button.
Is there a way to create better screen captures?
Note: I have access to the source code and i can modify the application if needed. The app is written in Silverlight version 4.
Here's a sample code I use to capture a UIElement and save it as a .png image. The resolution is the size of the displayed element at the time you capture it.
I use PngEncoder from the ImageTools library. You need to add references to it in your project.
Here's a simple XAML page displaying a blue square with green borders, and a button to capture it:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid x:Name="LayoutRoot">
<StackPanel>
<Border x:Name="myElement"
Width="200"
Height="200"
BorderBrush="Green"
Background="AliceBlue"
BorderThickness="2" />
<Button Click="SaveScreenShot"
Content="Capture"/>
</StackPanel>
</Grid>
</UserControl>
And here's the SaveScreenShot method:
private void SaveScreenShot(object sender, RoutedEventArgs e)
{
//Capture the element
var screenShot = new WriteableBitmap((UIElement)myElement, null);
var encoder = new PngEncoder();
SaveFileDialog saveDialog = new SaveFileDialog();
saveDialog.Filter = "Picture Files (*.png)|*.png";
bool? result = saveDialog.ShowDialog();
if (result.Value)
{
using (Stream saveStream = saveDialog.OpenFile())
{
encoder.Encode(screenShot.ToImage(), saveStream);
}
}
}
When clicking the button, it will open a dialog for you to save the captured image as a file. You cannot use the clipboard as Silverlight doesn't allow to put images in it (See the "remarks" section of the MSDN page)
So if you add the button with the associated method in your page, and change the "myElement" in the callback to the name of the element you want to capture, you should be good.

Windows Phone Binding Image Location

I am trying to place a collection of images at certain places on a canvas through Binding.
For some reason, the images are displaying but are NOT at the locations which I have specified.
C#
_roomView.Room = new Room
{
Items = new List<Item> {
new Item {ImageUri = "/Escape;component/Images/Items/a.jpg", XPosition = 190, YPosition = 50},
new Item {ImageUri = "/Escape;component/Images/Items/b.png", XPosition = 390, YPosition = 100},
new Item {ImageUri = "/Escape;component/Images/Items/b.png", XPosition = 490, YPosition = 600}}
};
listBoxItems.ItemsSource = _roomView.Room.Items;
XML
<Canvas>
<Image Source="{Binding ImageUri}" Stretch="None" Canvas.Left="{Binding Room.Items.XPosition}" Canvas.Top="{Binding Room.Items.YPosition}"/>
</Canvas>
XPosition and YPosition are of type int. I have tried changing them to double but the images still aren't being displayed where I want them to be. They only get displayed at the top left of the screen - on top of each other.
Can anyone see the problem?
You're using the pack URI format which is only valid if your images have a Build Action of "Embedded Resource". Even then, I'm not sure this URI format is actually supported for Image.Source and if the URI has to be Relative or Absolute. Check the images Build Action (Right click --> Properties on those files) and try changing the UriKind.
Either way, unless you have a very good reason it's best to keep media assets as:
Build Action = Content and use the normal path URIs. Adding images to DLLs as Embedded Resources is bad for startup performance since it takes longer to load your bigger DLLs into the AppDomain.
It's also bad from a memory usage perspective since the DLLs loaded into memory are now bigger.
Bottom line:
<Image Source="myPicture.png" /> format is much better for performance than
<Image Source="myAssemblyName;component/myPicture.png" /> syntax.
Your C# code has collection of images.
Your XAML only shows a single image. If the XAML is in the listbox data template, you'll get several canvases, with 1 image each.
To display a collection of images on the single Canvas, you could use e.g. <ItemsControl>, set ItemsPanel to a Canvas, and in the data template you specify a single image with source, canvas.left and canvas.top.
If you're trying to do what I've described with a ListBox instead of ItemsControl, it wont work because it has one more layer of UI elements between ItemsPanel and item, namely the item container.
P.S. Fastest way to troubleshoot such GUI problems - XAML Spy, I've bought myself a personal license. Evaluation is 21 days long, fully functional.

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;

Resources