14

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

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

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

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

编辑

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

编辑 2

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

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

6 回答 6

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

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

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

于 2011-04-15T14:57:06.820 回答
2

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

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

以上所有工作在 WP7 中。

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

于 2011-04-14T19:31:57.173 回答
0

首先,您确定要将其渲染为位图吗?Canvas用图像和生成一个怎么样TextBlock

我需要渲染大量图像

我有一种感觉,这种生成会破坏手机的性能。通常,对于位图主制作,最好的方法是使用 XNA。XNA 框架的某些部分在 Silverlight 项目中表现出色。(顺便说一句,更新后的 Windows Phone 开发者工具将允许 Silverlight 和 XNA 在同一个项目中共存)

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

编辑

据我了解,您需要某种以图像为背景和消息的弹出窗口。

使用 TextBlock 制作 Canvas 但将其隐藏。

<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 Thread 更新,因此更新必须与 Dispatcher 一起排队

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

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

于 2011-04-14T17:26:48.380 回答
0

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

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

于 2011-04-14T17:28:20.937 回答
0

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

  1. 在主 UI 线程中创建 WriteableBitmap
  2. 在后台线程中绘制工作
  3. 在主 UI 线程中分配 BitmapSource
于 2011-09-18T14:08:15.187 回答
-3

我同意 Derek 的回答:您正在尝试使用没有 UI 的 UI 控件。

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

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

伪代码:

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;
}
于 2011-04-14T17:46:36.363 回答