如何在Windows Phone 7中的后台线程上的WriteableBitmap上呈现文本?

Ran*_*Ran 14 c# silverlight multithreading writeablebitmap windows-phone-7

我试图在Windows Phone 7应用程序中的位图上呈现文本.

看起来或多或少类似于以下的代码在主线程上运行时可以正常工作:

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;
}
Run Code Online (Sandbox Code Playgroud)

现在,由于我必须使用更复杂的东西渲染多个图像,我想在后台线程上渲染位图以避免无响应的UI.

当我使用a BackgroundWorker来执行此操作时,构造函数TextBlock会抛出UnauthorizedAccessException声称这是无效的跨线程访问.

我的问题是:如何在不阻塞UI的情况下在位图上呈现文本?

  • 请不要建议使用Web服务进行渲染.我需要渲染大量图像,带宽成本不能满足我的需求,离线工作的能力是一个主要要求.
  • 解决方案不一定必须使用WriteableBitmapUIElements,如果有另一种方式来呈现文本.

编辑

另一个想法:有没有人知道是否应该可以在另一个线程中运行UI消息循环,然后让该线程完成工作?(而不是使用BackgroundWorker)?

编辑2

要考虑替代方案WriteableBitmap,我需要的功能是:

  • 绘制背景图像.
  • 给定字体族和大小(最好是样式),测量1行字符串的宽度和高度.不需要自动换行.
  • 在给定坐标处绘制具有给定字体系列,大小,样式的1行字符串.
  • 文本渲染应支持透明背景.即你应该看到角色之间的背景图像.

Kri*_*is 15

此方法复制预制图像中的字母而不是使用TextBlock,这是基于我对此问题的回答.主要限制是需要为每种字体和大小提供不同的图像.大小20字体需要大约150kb.

使用SpriteFont2以您需要的大小导出字体和xml指标文件.代码假定它们被命名为"FontName FontSize".png和"FontName FontSize".xml将它们添加到项目中并将构建操作设置为内容.该代码还需要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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您需要调用RegisterFont一次来加载文件,然后调用DrawString.它使用WriteableBitmapEx.Blit,因此如果您的字体文件具有白色文本并且正确处理透明背景alpha并且您可以重新着色它.如果您以未加载的大小绘制但结果不好,代码会缩放文本,可以使用更好的插值方法.

我尝试从一个不同的线程绘图,这在模拟器中工作,你仍然需要在主线程上创建WriteableBitmap.我对您的场景的理解是,您希望滚动类似于映射应用程序工作方式的切片,如果是这种情况,请重用旧的WriteableBitmaps而不是重新创建它们.如果不是,则可以将代码更改为使用数组.