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

时间:2011-04-14 16:56:31

标签: c# multithreading silverlight windows-phone-7 writeablebitmap

我正在尝试在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;
}

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

当我使用BackgroundWorker这样做时,TextBlock的构造函数会抛出UnauthorizedAccessException声称这是一个无效的跨线程访问。

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

  • 请不要建议使用网络服务进行渲染。我需要渲染大量图像,带宽成本不能满足我的需求,离线工作是一项主要要求。
  • 如果有其他方式呈现文字,则解决方案不一定必须使用WriteableBitmapUIElements

修改

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

编辑2

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

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

6 个答案:

答案 0 :(得分:15)

此方法复制预制图像中的字母而不是使用TextBlock,它基于我对此question的回答。主要限制是需要为每种字体和大小提供不同的图像。大小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;
        }
    }
}

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

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

答案 1 :(得分:2)

我不确定这是否能完全解决您的问题,但我在漫画书阅读器中使用了2种工具(我不会无耻地将其插入此处,但我很有诱惑力......如果你有一个提示正在寻找它..它是“惊人的”)。有时我需要将一堆图像拼接在一起。我使用Rene Schulte(以及其他一些贡献者)WriteableBitmapExtensions(http://writeablebitmapex.codeplex.com/)。我已经能够将图像的渲染/拼接卸载到后台线程,然后将生成的WriteableBitmap设置为UI线程上某些图像的源。

这个领域的另一个新成员是.NET Image Tools(http://imagetools.codeplex.com/)。它们有许多用于保存/读取各种图像格式的实用程序。他们也有一些低级别,我希望有一个简单的方法来使用它们(但没有)。

WP7中的所有上述工作。

我猜主要区别在于这些工具你将不会使用XAML,你将直接写入你的图像(因此你可能需要对你的文本和类似的东西进行大小检测)。

答案 2 :(得分:0)

首先,您确定将其渲染为位图吗?如何使用图片生成CanvasTextBlock

  

我需要渲染大量图像

我觉得这种产生会扼杀手机性能。通常,对于位图主要配置,最好的方法是使用XNA。 XNA框架的某些部分可以很好地完成Silverlight项目。 (BTW the refreshed Windows Phone Developer Tools 将允许Silverlight和XNA在同一个项目中共存)

我会退后一步思考这个功能。像这样开发一个星期然后以不可接受的性能结束将使我成为一个悲伤的熊猫。

修改

据我所知,你需要某种弹出图像作为背景和信息。

使用TextBlock制作画布但隐藏它。

<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>

当您收到新消息时,在后台线程上完成渲染时,向用户显示Canvas(不透明度动画会很好)。

messageTextBlock.Text = message;
backgroundImage.Source = new BitmapImage(renderedImage);

显然,这是更新的问题。 UIelements只能从UI线程更新,因此更新必须与Dispatcher一起排队

Dispatcher.BeginInvoke(DispatcherPriority.Background, messageUpdate);  //messageUpdate is an Action or anthing that can be infered to Delegate

PS。没编译,这是更多的伪代码。

答案 3 :(得分:0)

UI元素的本质需要在UI线程上与它们进行交互。即使您可以在后台线程上创建它们,当您尝试将它们渲染到WriteableBitmap中时,您会得到类似的异常,即使这样,如果它允许您这样做,元素实际上也不会具有可视化表示直到将它们添加到可视树中。您可能需要使用通用图像处理库,而不是使用UI元素。

也许您可以在更广泛的基础上描述您的场景,我们可能会为您提供更好的解决方案:)

答案 4 :(得分:0)

你可以在线程中绘制WriteableBitmap,但你必须

  1. 在主UI线程中创建WriteableBitmap
  2. 在后台线程中绘制工作
  3. 在主UI线程中分配BitmapSource

答案 5 :(得分:-3)

我同意Derek的回答:你试图在没有用户界面的情况下使用UI控件。

如果要渲染位图,则需要坚持使用类在位图上绘制文本。

我认为Windows Phone 7具有.NET Compact Framework。

<强> psudeo代码:

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;
}