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 を回避するためにバックグラウンド スレッドでビットマップをレンダリングしたいと考えています。

これを行うためにa を使用するBackgroundWorkerと、コンストラクターは、これが無効なクロススレッド アクセスであるという主張をTextBlockスローします。UnauthorizedAccessException

私の質問は: UI をブロックせずにビットマップにテキストをレンダリングするにはどうすればよいですか?

  • Web サービスを使用してレンダリングを行うことはお勧めしません。大量の画像をレンダリングする必要がありますが、帯域幅のコストが自分のニーズに対して受け入れられず、オフラインで作業できることが主要な要件です。
  • テキストをレンダリングする別の方法がある場合、ソリューションは必ずしもWriteableBitmaporを使用する必要はありません。UIElements

編集

もう 1 つの考え: 別のスレッドで UI メッセージ ループを実行し、そのスレッドに作業をさせることができるかどうかを知っている人はいますか? (を使用する代わりにBackgroundWorker)?

編集2

の代替を検討するためWriteableBitmapに必要な機能は次のとおりです。

  • 背景画像を描画します。
  • フォントファミリーとサイズ (そしてできればスタイル) が与えられた場合に、1 行の文字列の幅と高さを測定します。ワードラップは必要ありません。
  • 指定されたフォント ファミリ、サイズ、スタイルで、指定された座標に 1 行の文字列を描画します。
  • テキスト レンダリングは透明な背景をサポートする必要があります。つまり、キャラクター間の背景画像が表示されるはずです。
4

6 に答える 6

15

このメソッドは、TextBlock を使用する代わりに、事前に作成された画像から文字をコピーします。これは、この質問に対する私の回答に基づいています。主な制限は、必要なフォントとサイズごとに異なる画像が必要なことです。サイズ 20 のフォントには約 150kb が必要です。

SpriteFont2を使用して、フォントと xml メトリック ファイルを必要なサイズでエクスポートします。コードは、"FontName FontSize".png および "FontName FontSize".xml という名前が付けられていることを前提として、それらをプロジェクトに追加し、ビルド アクションを content に設定します。コードには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 を 1 回呼び出してから、DrawString を呼び出す必要があります。WriteableBitmapEx.Blit を使用するため、フォント ファイルに白いテキストがあり、透明な背景のアルファが正しく処理され、色を変更できる場合。ロードしていないサイズで描画した場合、コードはテキストをスケーリングしますが、結果は良くありません。より良い補間方法を使用できます。

別のスレッドから描画しようとしましたが、これはエミュレーターで機能しましたが、メインスレッドで WriteableBitmap を作成する必要があります。あなたのシナリオについての私の理解は、マッピングアプリがどのように機能するかに似たタイルをスクロールしたいということです。その場合、古い WriteableBitmaps を再作成するのではなく再利用します。そうでない場合は、代わりに配列を使用するようにコードを変更できます。

于 2011-04-15T14:57:06.820 に答える
2

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

于 2011-04-14T19:31:57.173 に答える
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 に答える
0

まず、これをビットマップとしてレンダリングしてもよろしいですか? Canvas画像と で を生成するのはどうTextBlockですか?

大量の画像をレンダリングする必要がある

この生成により、電話のパフォーマンスが低下する気がします。一般に、ビットマップのメインアップには、XNA を使用するのが最善の方法です。XNA フレームワークの一部は、Silverlight プロジェクトで優れた機能を果たします。(ところで、更新された Windows Phone 開発者ツールでは、Silverlight と XNA を同じプロジェクトに共存させることができます)

一歩下がって、この機能について考えてみます。このようなものを 1 週間開発した後、容認できないパフォーマンスに終わると、私は悲しいパンダになります。

編集

私が理解している限り、背景とメッセージとして画像を含むある種のポップアップが必要です。

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

明らかに、ここに更新の問題があります。UI要素はUIスレッドからのみ更新できるため、更新は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 に答える
-3

私はデレクの答えに同意します.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 に答える