Lockscreen background change first time and never again - c#

The app generate an image from a UserControl into IsoStorage:
var bitmap6 = new WriteableBitmap(336, 336);
bitmap6.Render(LockscreenBG, new TranslateTransform());
var stream6 = store.CreateFile("/shared/shellcontent/LockscreenBG.jpg");
bitmap6.Invalidate();
bitmap6.SaveJpeg(stream6, 336, 336, 0, 100);
stream6.Close();
within a common Class Library - which is used by the app and the background agent - is this:
if (LockScreenManager.IsProvidedByCurrentApplication)
{
LockScreen.SetImageUri(new Uri("ms-appdata:///local/shared/shellcontent/LockscreenBG.jpg", UriKind.RelativeOrAbsolute));
}
Everything works very well but only the first time. The image still is the same without any errors. Seems like the image is not overwritten or it is used the old one from the cache???

Related

Xamarin GoogleMaps - Acceptable pin/marker colors

I'm using Xamarin.Forms.GoogleMaps in my portable project.
it's working pretty well for iOS and Android.
right now I'm working on the pin/markers of the googleMap.
I'm trying to add pins with different colors, but not all colors are working.
How I'm doing it right now:
var pin = new Xamarin.Forms.GoogleMaps.Pin();
pin.Label = ... etc etc etc
pin.Icon = BitmapDescriptorFactory.DefaultMarker(Color.Azure);
The example above works well. But if I change it to Color.Black, for example, it doesn't work and show the marker with the default red color. And it also have problem with different platforms, in iOS, Black works, in Android don't. (no errors appear, just show the default red instead of Black)
So my question is:
Is there a list of acceptable colors to use as marker/pin color? Does every color should work or just some predefined colors?
Also, how do i change the default icon to another icon image? I tried with "FromBundle", but it throws an error, at least for me.(maybe I did it wrong. It told about image need to be a bitmap)
If possible, I want to avoid custom renderers, because right now it is working very well without any custom render (except some colors as I said).
If you prefer not to use Custom Renderers, Xamarin Forms Map markers/pins only have 300+ colors available to use.
But if you want to represent marker/pins colors as you would like them to be, you would need to implement the Xamarin Forms Custom Renderers to achieve/capture the exact marker/pin color that you want.
After you follow the steps in the Xamarin Forms custom renderer documentation, override the following method:
protected override MarkerOptions CreateMarker(Pin pin)
{
CustomPin customPin = (CustomPin)pin;
var marker = new MarkerOptions();
marker.SetPosition(new LatLng(pin.Position.Latitude, pin.Position.Longitude));
marker.SetTitle(pin.Label);
marker.SetSnippet(pin.Address);
marker.SetIcon(GetCustomBitmapDescriptor(customPin.Color));
return marker;
}
Then I created the following method that will do the actual changes to marker/pin colors that will be the exact color from your RGB/Hex color code:
private BitmapDescriptor GetCustomBitmapDescriptor(string text)
{
using (Paint paint = new Paint(PaintFlags.AntiAlias))
{
using (Rect bounds = new Rect())
{
using (Bitmap baseBitmap = BitmapFactory.DecodeResource(Resources, Resource.Drawable.marker))
{
Bitmap resultBitmap = Bitmap.CreateBitmap(baseBitmap, 0, 0, baseBitmap.Width - 1, baseBitmap.Height - 1);
Paint p = new Paint();
ColorFilter filter = new PorterDuffColorFilter(Android.Graphics.Color.ParseColor(text), PorterDuff.Mode.SrcAtop);
p.SetColorFilter(filter);
Canvas canvas = new Canvas(resultBitmap);
canvas.DrawBitmap(resultBitmap, 0, 0, p);
Bitmap scaledImage = Bitmap.CreateScaledBitmap(resultBitmap, 94, 150, false);
BitmapDescriptor icon = BitmapDescriptorFactory.FromBitmap(scaledImage);
resultBitmap.Recycle();
return (icon);
}
}
}
}
NOTES:
This sample code is just for Android. Not sure for iOS.
The Resource.Drawable.marker can be any marker that you can use. I just downloaded a generic red map marker online. It will be covered anyway by the GetCustomBitmapDescriptor method anyway.
The resultBitmap.Recycle(); is VERY important. Because bitmaps takes a lot of memory and the device application might stall so you need to reuse bitmap memory.
CustomPin is a class extending the Xamarin.Forms.Map.Pin class. I added a string attribute for the string Hex value of the color I want for my marker pin.
See sample image of a map with colors that are customized.

Custom tile generates a System.OutOfMemoryException exception in wp8

Has anyone got any idea why I'm getting an OutOfMemoryException when I'm creating my custom tile?
I'm trying to create custom images for my primary tile on a windows phone 8 app from a ScheduledAgent. The error doesn't occur until my very last line of code is executed which is the NotifyComplete().
Here is the code (Not the cleanest but ok for prototyping I guess). This code only handles the wide tile and it tries to load an image an image downloaded from a website and then it tries to render a logo and a description over this image.
Here is the code:
private void CreateTiles()
{
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
for (int i = 0; i < 2; i++)
{
var bmp = new WriteableBitmap(691, 336);
var articleImg = new BitmapImage(new Uri(articles[i].ImageFilename, UriKind.Relative));
var articleImage = new Image { Source = articleImg };
articleImage.Stretch = Stretch.UniformToFill;
articleImg.CreateOptions = BitmapCreateOptions.None; // Force the bitmapimage to load it's properties so the transform will work
var bmpLogo = new WriteableBitmap(100, 100);
var logoImg = new BitmapImage(new Uri("/Assets/Tiles/FlipCycleTileSmall.png", UriKind.Relative));
var logoImage = new Image { Source = logoImg };
logoImage.Opacity = 1.0;
logoImg.CreateOptions = BitmapCreateOptions.None; // Force the bitmapimage to load it's properties so the transform will work
var articleBannerGrid = new Grid();
articleBannerGrid.Background = ColorExtensions.ToSolidColorBrush("#000F558E");
articleBannerGrid.Opacity = .5;
articleBannerGrid.Height = 100;
articleBannerGrid.VerticalAlignment = VerticalAlignment.Bottom;
articleBannerGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100) });
articleBannerGrid.ColumnDefinitions.Add(new ColumnDefinition());
articleBannerGrid.Children.Add(logoImage);
Grid.SetColumn(logoImage, 0);
var textBlock = new TextBlock();
textBlock.Text = articles[i].Description;
textBlock.FontWeight = FontWeights.Bold;
textBlock.Margin = new Thickness(10, 5, 30, 5);
textBlock.TextWrapping = TextWrapping.Wrap;
textBlock.Foreground = new SolidColorBrush(Colors.White); //color of the text on the Tile
textBlock.FontSize = 30;
textBlock.Opacity = 1.0;
var articleTextGrid = new Grid();
articleTextGrid.Height = 100;
articleTextGrid.VerticalAlignment = VerticalAlignment.Bottom;
articleTextGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(100) });
articleTextGrid.ColumnDefinitions.Add(new ColumnDefinition());
articleTextGrid.Children.Add(textBlock);
Grid.SetColumn(textBlock, 1);
var canvas = new Grid();
canvas.Width = articleImg.PixelWidth;
canvas.Height = articleImg.PixelHeight;
canvas.Children.Add(articleImage);
canvas.Children.Add(articleBannerGrid);
canvas.Children.Add(articleTextGrid);
bmp.Render(canvas, null);
bmp.Invalidate(); //Draw bitmap
FileStream fs = new FileStream(articles[i].ImageFilename, FileMode.Create);
bmp.SaveJpeg(fs, bmp.PixelWidth, bmp.PixelHeight, 0, 100);
fs.Close();
fs.Dispose();
articleImage = null;
articleImg = null;
}
//GC.Collect();
//GC.WaitForPendingFinalizers();
ShellTile TileToFind = ShellTile.ActiveTiles.FirstOrDefault();
if (TileToFind != null)
{
string title = articles[0].Tag;
string backTitle = articles[1].Tag;
string content = articles[0].Description;
string backContent = articles[1].Description;
FlipTileData tileData = new FlipTileData()
{
Title = title,
BackTitle = backTitle,
BackContent = backContent,
WideBackContent = backContent,
BackgroundImage = new Uri(articles[0].ImageFilename, UriKind.Relative),
BackBackgroundImage = new Uri(articles[1].ImageFilename, UriKind.Relative),
WideBackgroundImage = new Uri(articles[0].ImageFilename, UriKind.Relative),
WideBackBackgroundImage = new Uri(articles[1].ImageFilename, UriKind.Relative),
};
TileToFind.Update(tileData);
}
NotifyComplete();
});
}
I assume that it is the correct place to generate the custom tile i.e. ScheduledAgent.
I found an article Dynamic Live Tile issue WP8 [WriteableBitmap] on the Nokia website with the same problem but no solution there either.
I'll continue debugging this tomorrow by removing everything and adding each bit individually step by step to see if I can spot anything but if anyone has got any suggestion or solution, I'd appreciate if you could help.
If I'm going about it the wrong way, please let me know what's the best method to update tiles in the scheduled agent.
Thanks.
WP BackgroundAgents have a small memory cap. 20MB for WP8 without update 3 and 25 for WP8 with Update 3.
I would suggest that you create a another project and add Coding4Fun toolkit and MemoryCounter in to your page and then add the code in your BackgroudAgent to the sample page and try to create the tiles. Then look at how much memory it uses. I think amount of memory usage is higher than the 20/25MB cap. if so you have to find a way to reduce it.
As usual, Microsoft is omitting critical information from its documentation.
After reducing the size of my tile from 691x336 to 336x165, it worked, so it got me thinking and I thought that the size recommended by Microsoft in this article Flip Tile template for Windows Phone 8 for wp8 and wp8.1 seemed excessive, so I did a bit more research and this is when I found this excellent explanation in an article in stackoverflow i.e.
Windows Phone 8 Startscreen Tile sizes and margins
This clearly states the size of the tiles but not based on OS versions but based on screen resolutions.
In my test environment, I was using the default 'Emulator WVGA 512MB' and again by default the screen size is 480x800, so taking into account the information provided in the answer of this article, my tile size should have been 430x210.
I resized my (already reduced) tile back up to 430x210, and it still worked.
While there is a memory cap of 20/25Mb depending on OS and patch being used, which probably causes many problems from the various articles I've read on the web, in my instance I believe it was more likely to have been affected by tile size being incorrect, and therefore the additional memory, or lack thereof.
I will update this article once I get to using 8.1 in my IDE with a larger resolution but for now, I have to assume that it was definitely related to the size of my tile.
I'll definitely make sure to add code to create tile based on os, patch and resolution being used. Bit of a pain to best honest!
Hope this helps.

WP8 Background Agent - Code executes fine outside of agent, fails inside?

I have a background agent I created to update my live tile. The agent schedules and executes fine, but the code that the agent executes has became the problem - it simply fails to execute fully and provides no error. From what I can tell, there are no restricted APIs that I am using, unless the Cimbalino toolkit is somehow providing a problem, even though I am using the background-agent-specific version from NuGet.
Code appears to stop executing when RenderText() and RenderTextWide() fail to run. No error is provided. The same code works perfectly fine when run in the foreground app.
using System;
using System.Windows;
using Microsoft.Phone.Scheduler;
using Microsoft.Phone.Shell;
using Microsoft.Phone.Info;
using Cimbalino.Phone.Toolkit;
using System.Linq;
using System.Windows.Media.Imaging;
using System.Windows.Media;
using System.Windows.Controls;
using System.IO.IsolatedStorage;
using Cimbalino.Phone.Toolkit.Extensions;
namespace ScheduledTaskAgent1
{
public class ScheduledAgent : ScheduledTaskAgent
{
private static volatile bool _classInitialized;
public ScheduledAgent()
{
if (!_classInitialized)
{
_classInitialized = true;
// Subscribe to the managed exception handler
Deployment.Current.Dispatcher.BeginInvoke(delegate
{
Application.Current.UnhandledException += ScheduledAgent_UnhandledException;
});
}
}
/// Code to execute on Unhandled Exceptions
private void ScheduledAgent_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
if (System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Break();
}
}
//Method to create normal tile image
private static void RenderText(string currproperty)
{
WriteableBitmap b = new WriteableBitmap(336, 336);
var canvas = new Grid();
canvas.Width = b.PixelWidth;
canvas.Height = b.PixelHeight;
var background = new Canvas();
background.Height = b.PixelHeight;
background.Width = b.PixelWidth;
SolidColorBrush backColor = new SolidColorBrush(Colors.Transparent);
background.Background = backColor;
TextBlock currtemp = new TextBlock();
currtemp.FontSize = 100;
currtemp.FontFamily = new FontFamily("Segoe UI Light");
currtemp.FontWeight = FontWeights.Bold;
currtemp.Foreground = new SolidColorBrush(Colors.White);
currtemp.Text = currproperty;
currtemp.Margin = new Thickness(20, 10, 0, 0);
currtemp.Width = b.PixelWidth - currtemp.Margin.Left * 2;
canvas.Children.Add(currtemp);
b.Render(background, null);
b.Render(canvas, null);
b.Invalidate();
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream imageStream = new IsolatedStorageFileStream("/Shared/ShellContent/BackBackgroundImage2.png", System.IO.FileMode.Create, isf))
{
b.SavePng(imageStream);
}
}
}
//Method to create wide tile image
private static void RenderTextWide(string currproperty)
{
WriteableBitmap b = new WriteableBitmap(691, 336);
var canvas = new Grid();
canvas.Width = b.PixelWidth;
canvas.Height = b.PixelHeight;
var background = new Canvas();
background.Height = b.PixelHeight;
background.Width = b.PixelWidth;
//Created background color as Accent color
SolidColorBrush backColor = new SolidColorBrush(Colors.Transparent);
background.Background = backColor;
TextBlock currtemp = new TextBlock();
currtemp.FontSize = 100;
currtemp.FontFamily = new FontFamily("Segoe UI Light");
currtemp.FontWeight = FontWeights.Bold;
currtemp.Foreground = new SolidColorBrush(Colors.White);
currtemp.Text = currproperty;
currtemp.Margin = new Thickness(20, 10, 0, 0);
currtemp.Width = b.PixelWidth - currtemp.Margin.Left * 2;
canvas.Children.Add(currtemp);
using (IsolatedStorageFile isf = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream imageStream = new IsolatedStorageFileStream("/Shared/ShellContent/WideBackBackgroundImage2.png", System.IO.FileMode.Create, isf))
{
b.SavePng(imageStream);
}
}
}
//When task invokes, run the tile update code.
protected override void OnInvoke(ScheduledTask task)
{
ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault();
if (tile != null)
{
FlipTileData flipTile = new FlipTileData();
flipTile.Title = "";
flipTile.BackTitle = "Atmosphere";
RenderText("Test");
RenderTextWide("Test");
flipTile.BackBackgroundImage = new Uri("/Assets/NewUI/1.PNG", UriKind.Relative); //Default image for Background Image Medium Tile 336x336 px
flipTile.BackgroundImage = new Uri(#"isostore:/Shared/ShellContent/BackBackgroundImage2.png", UriKind.Absolute); //Generated image for Back Background 336x336
flipTile.WideBackBackgroundImage = new Uri("/Assets/NewUI/2.PNG", UriKind.Relative); ////Default image for Background Image Wide Tile 691x336 px
flipTile.WideBackgroundImage = new Uri(#"isostore:/Shared/ShellContent/WideBackBackgroundImage2.png", UriKind.Absolute);
tile.Update(flipTile);
//Tile updated, tell agent operation complete
NotifyComplete();
}
}
}
}
I just ran the code that you shared in a background agent with debugger attached, and the problem was quite obvious - it throws an exception! An invalid cross-thread access exception to be precise. All UI-related work must happen int the UI thread and instantiating controls and WriteableBitmaps is such work.
Solution
You need to use Deployment.Current.Dispatcher.BeginInvoke to invoke RenderText and RenderTextWide in the appropriate thread. You can also just invoke everything in OnInvoke in the UI thread.
Note: Deployment.Current.Dispatcher.BeginInvoke invokes the given action asynchronously, so if you don't run everything on the UI thread, you may need to do something to sync the different threads.
NotifyComplete
You must ALWAYS call NotifyComplete AFTER you've done all the work. Basically what you need to do is try-catch(all) and then call NotifyComplete. Also, you may want to put it in ScheduledAgent_UnhandledException just in case.
NotifyComplete shouldn't be in an if as in your code, although that condition is always true in Windows Phone. ShellTile.ActiveTiles always has at least one tile - the primary tile - even if it's not pinned.
Debugging background agents
Yes, you can do that. If the agent is not working, the best way to see what's the problem is to launch the app with the debugger attached, force the background agent's invocation and put a breakpoint in the code that you want to check. Use ScheduledActionService.LaunchForTest to start the background agent sooner than normal.
Offtopic/Questions
May I ask what are you trying to achieve with that private static volatile bool _classInitialized field? And why is it volatile?
I also have seen this problem with my application after upgrading from WP8.0 to WP8.1 Silverlight. My scheduled agent "crashes" absolutely silently at arbitrary points in my code (generating live tiles). The crashes or hangs occur frequently and no exceptions are thrown. I've been working on this on and off for over a month now with no success. I've been very side-tracked thinking I was causing the problem myself somehow. I did find a reference last night that may be the solution. I haven't fully tested it yet in my application.
In the WP8.1 Silverlight API change notes there is a very cryptic paragraph that says:
For a managed Windows Phone 8 ScheduledTaskAgent to be able to access
Silverlight 8.1 features, it runs on the modern execution stack, which
is converged with Windows. This uses a different CPU quota mechanism
than Windows Phone 8. In some cases, a Silverlight 8.1 background
agent might find that it gets less CPU time than it did previously. If
that happens, the app may choose to call RequestAccessAsync(). This
will increase the CPU quota given to the agent.
I've found some posts elsewhere indicating that the following code fixes the problem for some when placed immediately before adding a background agent task:
await Windows.ApplicationModel.Background.BackgroundExecutionManager.RequestAccessAsync();
I would love to hear if others are seeing these types of problems and if this helps them.
SOLVED!.
I have the same problem and I solved with the following code:
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderText("Test"); });
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderTextWide("Test"); });
The file ScheduledAgent.cs:
protected override void OnInvoke(ScheduledTask task)
{
ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault();
if (tile != null)
{
FlipTileData flipTile = new FlipTileData();
flipTile.Title = "";
flipTile.BackTitle = "Atmosphere";
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderText("Test"); });
Deployment.Current.Dispatcher.BeginInvoke(delegate() { RenderTextWide("Test"); });
flipTile.BackBackgroundImage = new Uri("/Assets/NewUI/1.PNG", UriKind.Relative); //Default image for Background Image Medium Tile 336x336 px
flipTile.BackgroundImage = new Uri(#"isostore:/Shared/ShellContent/BackBackgroundImage2.png", UriKind.Absolute); //Generated image for Back Background 336x336
flipTile.WideBackBackgroundImage = new Uri("/Assets/NewUI/2.PNG", UriKind.Relative); ////Default image for Background Image Wide Tile 691x336 px
flipTile.WideBackgroundImage = new Uri(#"isostore:/Shared/ShellContent/WideBackBackgroundImage2.png", UriKind.Absolute);
tile.Update(flipTile);
//Tile updated, tell agent operation complete
NotifyComplete();
}
}

Switch Foreground and Background for Nokia Imaging SDK: BlendFilter()

I'm trying to use the Nokia Imaging SDK to blend an image I used the ChromaKeyFilter on onto a background image. I'd also like to to this in the same rendering process.
So far I've got this:
IList<IFilter> finalFilters = new List<IFilter>();
finalFilters.Add(_chromaKeyFilter);
finalFilters.Add(blendFilter);
However, now the background image is on top, which of course should be the other way arround. Can I somehow switch this? Or is this even the right way to go? Thanks.
In the typical case, you have a background image (set up using one *ImageSource) and a foreground image (set up using another *ImageSource).
The ChromaKeyFilter (in a FilterEffect) can be seen as a modifier, one that creates transparency in the image. Here it is applied to the foreground, this basically cuts out transparent holes (zero alpha) wherever the settings match the pixels.
The BlendFilter (in another FilterEffect) is then applied on the background, taking the FilterEffect from the foreground as its ForegroundSource to put the foreground image on top of it.
Attempt at example using pseudo code:
// Foreground chain:
var fg = new BufferImageSource(...);
var fgWithTransparency = new FilterEffect(fg)
{
Filters = new [] { new ChromaKeyFilter(...) }
};
// Background/compositing chain:
var bg = new BufferImageSource(...);
var bgAndFgComposited = new FilterEffect(bg)
{
Filters = new [] { new BlendFilter(fgWithTransparency) }
};
If you now render bgFilterEffect you get the composited result showing bg with the fg blended on top.

How can I render text on a WriteableBitmap on a background thread, in Windows Phone 7?

I am trying to render text on a bitmap in a Windows Phone 7 application.
Code that looks more or less like the following would work fine when it's running on the main thread:
public ImageSource RenderText(string text, double x, double y)
{
var canvas = new Canvas();
var textBlock = new TextBlock { Text = text };
canvas.Children.Add(textBloxk);
Canvas.SetLeft(textBlock, x);
Canvas.SetTop(textBlock, y);
var bitmap = new WriteableBitmap(400, 400);
bitmap.Render(canvas, null);
bitmap.Invalidate();
return bitmap;
}
Now, since I have to render several images with more complex stuff, I would like to render the bitmap on a background thread to avoid an unresponsive UI.
When I use a BackgroundWorker to do so, the constructor for TextBlock throws an UnauthorizedAccessException claiming that this is an invalid cross-thread access.
My question is: how can I render text on a bitmap without blocking the UI?
Please don't suggest using a web service to do the rendering. I need to render a large number of images and the bandwidth cost is not acceptable for my needs, and the ability to work offline is a major requirement.
The solution doesn't necessarily has to use WriteableBitmap or UIElements, if there is another way to render text.
EDIT
Another thought: does anyone know if it should be possible to run a UI message loop in another thread, and then have that thread do the work? (instead of using a BackgroundWorker)?
EDIT 2
To consider alternatives to WriteableBitmap, the features I need are:
Draw a background image.
Measure the width and height of a 1-line string, given a font familiy and size (and preferably style). No need for word wrapping.
Draw a 1-line string, with given font family, size, style, at a given coordinate.
Text rendering should support a transparent background. I.e. you should see the background image between the characters.
This method copies the letters from an pre-made image instead of using TextBlock, it's based on my answer to this question. The main limitation is requiring a different image for each font and size needed. A size 20 Font needed about 150kb.
Using SpriteFont2 export the font and the xml metrics file in the sizes you require. The code assumes they're named "FontName FontSize".png and "FontName FontSize".xml add them to your project and set the build action to content. The code also requires WriteableBitmapEx.
public static class BitmapFont
{
private class FontInfo
{
public FontInfo(WriteableBitmap image, Dictionary<char, Rect> metrics, int size)
{
this.Image = image;
this.Metrics = metrics;
this.Size = size;
}
public WriteableBitmap Image { get; private set; }
public Dictionary<char, Rect> Metrics { get; private set; }
public int Size { get; private set; }
}
private static Dictionary<string, List<FontInfo>> fonts = new Dictionary<string, List<FontInfo>>();
public static void RegisterFont(string name,params int[] sizes)
{
foreach (var size in sizes)
{
string fontFile = name + " " + size + ".png";
string fontMetricsFile = name + " " + size + ".xml";
BitmapImage image = new BitmapImage();
image.SetSource(App.GetResourceStream(new Uri(fontFile, UriKind.Relative)).Stream);
var metrics = XDocument.Load(fontMetricsFile);
var dict = (from c in metrics.Root.Elements()
let key = (char) ((int) c.Attribute("key"))
let rect = new Rect((int) c.Element("x"), (int) c.Element("y"), (int) c.Element("width"), (int) c.Element("height"))
select new {Char = key, Metrics = rect}).ToDictionary(x => x.Char, x => x.Metrics);
var fontInfo = new FontInfo(new WriteableBitmap(image), dict, size);
if(fonts.ContainsKey(name))
fonts[name].Add(fontInfo);
else
fonts.Add(name, new List<FontInfo> {fontInfo});
}
}
private static FontInfo GetNearestFont(string fontName,int size)
{
return fonts[fontName].OrderBy(x => Math.Abs(x.Size - size)).First();
}
public static Size MeasureString(string text,string fontName,int size)
{
var font = GetNearestFont(fontName, size);
double scale = (double) size / font.Size;
var letters = text.Select(x => font.Metrics[x]).ToArray();
return new Size(letters.Sum(x => x.Width * scale),letters.Max(x => x.Height * scale));
}
public static void DrawString(this WriteableBitmap bmp,string text,int x,int y, string fontName,int size,Color color)
{
var font = GetNearestFont(fontName, size);
var letters = text.Select(f => font.Metrics[f]).ToArray();
double scale = (double)size / font.Size;
double destX = x;
foreach (var letter in letters)
{
var destRect = new Rect(destX,y,letter.Width * scale,letter.Height * scale);
bmp.Blit(destRect, font.Image, letter, color, WriteableBitmapExtensions.BlendMode.Alpha);
destX += destRect.Width;
}
}
}
You need to call RegisterFont once to load the files then you call DrawString. It uses WriteableBitmapEx.Blit so if your font file has white text and a transparent background alpha is handled correctly and you can recolour it. The code does scale the text if you draw at a size you didn't load but the results aren't good, a better interpolation method could be used.
I tried drawing from a different thread and this worked in the emulator, you still need to create the WriteableBitmap on the main thread. My understanding of your scenario is that you want to scroll through tiles similar to how mapping apps work, if this is the case reuse the old WriteableBitmaps instead of recreating them. If not the code could be changed to work with arrays instead.
I'm not sure if this will fully resolve your issues, but there are 2 tools that I use in my comic book reader (I won't shamelessly plug it here, but I'm tempted.. a hint if you are searching for it.. it is "Amazing"). There are times where I need to stitch together a bunch of images. I use Rene Schulte's (and a bunch of other contributors) WriteableBitmapExtensions (http://writeablebitmapex.codeplex.com/). I have been able to offload rendering/stitching of an image to a background thread and then set the resulting WriteableBitmap as the source of some image on the UI thread.
Another up and comer in this space is the .NET Image Tools (http://imagetools.codeplex.com/). They have a bunch of utilities for saving/reading various image formats. They also have a few of the low levels, and I wish there were an easy way to use both (but there isn't).
All of the above work in WP7.
I guess the major difference is with these tools you won't be using XAML you will be writing directly to your image (so you may need to do size detection of your text and stuff like that).
The very nature of UI elements requires interaction with them on the UI thread. Even if you could create them on a background thread, when you came to try to render them into the WriteableBitmap you'd get a similar exception, and even then if it allowed you to do that, the elements wouldn't actually have a visual representation until they were added into the visual tree. You might need to use a generic image manipulation library instead of using UI elements.
Perhaps you could describe your scenario on a wider basis, we might have a better solution for you :)
First off, are you sure about rendering this as a bitmap? How about generating a Canvas with an image and TextBlock?
I need to render a large number of images
I have a feeling that this generating will kill phone performance. Generally, for bitmap mainupulation, the best way is to use XNA. Some parts of the XNA framework do a great job Silverlight projects. (BTW the refreshed Windows Phone Developer Tools will allow Silverlight and XNA coexist in the same project)
I would step back and think about this feature. Developing something like this for a week and then end up with unacceptable performance would make me a sad panda.
EDIT
As far I understand you need some kind of popup with image as a background and the message.
Make a Canvas with TextBlock but hide it.
<Canvas x:Name="userInfoCanvas" Height="200" Width="200" Visibility="Collapsed">
<Image x:Name="backgroundImage"> </Image>
<TextBlock x:Name="messageTextBlock" Canvas.ZIndex="3> </TextBlock> <!--ZIndex set the order of elements -->
</Canvas>
When you got the new message, show the Canvas to the user (a opacity animation would be nice), when you finish rendering on the background thread.
messageTextBlock.Text = message;
backgroundImage.Source = new BitmapImage(renderedImage);
Obviouslly, here is a problem with update. UIelements can be updated only form the UI Thread, hence update must be queue with Dispatcher
Dispatcher.BeginInvoke(DispatcherPriority.Background, messageUpdate); //messageUpdate is an Action or anthing that can be infered to Delegate
PS. didn't compile, this is more pseudocode.
You can draw on WriteableBitmap in thread, but You have to
create WriteableBitmap in main UI thread
do draw work in background thread
assign BitmapSource in main UI thread
i'll agree with Derek's answer: you're trying to use UI controls without a UI.
If you want to render a bitmap you need to stick to classes for drawing text on bitmaps.
i presume Windows Phone 7 has the .NET Compact Framework.
psudeo-code:
public Bitmap RenderText(string text, double x, double y)
{
Bitmap bitmap = new Bitmap(400, 400);
using (Graphics g = new Graphics(bitmap))
{
using (Font font = SystemFonts....)
{
using (Brush brush = new SolidColorBrush(...))
{
g.DrawString(text, font, brush, new Point(x, y));
}
}
}
return bitmap;
}

Categories