Как я могу отобразить текст на 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;
}

теперь, поскольку мне нужно отобразить несколько изображений с более сложным материалом, я хотел бы отобразить растровое изображение в фоновом потоке, чтобы избежать невосприимчивого пользовательского интерфейса.

когда я использую BackgroundWorker для этого конструктор для TextBlock выдает UnauthorizedAccessException утверждая, что это недопустимый доступ через поток.

мой вопрос: как я могу отображать текст на растровом изображении без блокировки пользовательского интерфейса?

  • пожалуйста, не предлагайте использовать веб-службу для рендеринга. Мне нужно сделать большое количество изображений, а стоимость полосы пропускания неприемлема для моих потребностей, и возможность работать в автономном режиме является основным требованием.
  • решение не обязательно использовать WriteableBitmap или UIElements, если есть другой способ визуализации текст.

редактировать

другая мысль: кто-нибудь знает, можно ли запустить цикл сообщений пользовательского интерфейса в другом потоке, а затем выполнить эту работу? (вместо BackgroundWorker)?

EDIT 2

рассмотреть альтернативы WriteableBitmap функции мне нужны:

  • нарисовать фоновое изображение.
  • Измерьте ширину и высоту строки 1-line, учитывая шрифт familiy и размер (и предпочтительно стиль). Нет необходимости в обертывании слов.
  • нарисуйте 1-строчную строку с заданным семейством шрифтов, размером, стилем в заданной координате.
  • рендеринг текста должен поддерживать прозрачный фон. Т. е. вы должны увидеть фоновое изображение между персонажами.

6 ответов


этот метод копирует буквы из предварительно сделанного изображения вместо использования TextBlock, он основан на моем ответе на это вопрос. Основное ограничение требует другого изображения для каждого шрифта и необходимого размера. Размер шрифта 20 требуется около 150 КБ.

С помощью 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 поэтому, если ваш файл шрифта имеет белый текст и прозрачный фон Альфа обрабатывается правильно, и вы можете перекрасить его. Код масштабирует текст, если вы рисуете размер, который вы не загружали, но результаты не очень хорошие, можно использовать лучший метод интерполяции.

я попробовал рисовать из другого потока, и это сработало в эмуляторе, вам все равно нужно создать WriteableBitmap в основном потоке. Мое понимание вашего сценария заключается в том, что вы хотите прокручивать плитки, подобные тому, как работают картографические приложения, если это так, используйте старые WriteableBitmaps вместо их воссоздания. Если нет, код может быть изменен для работы с массивами.


Я не уверен, что это полностью решит ваши проблемы, но есть 2 инструмента, которые я использую в моем читателе комиксов (я не буду бесстыдно подключать его здесь, но я соблазн.. подсказка если вы ищете его.. это "удивительно"). Есть моменты, когда мне нужно сшить кучу изображений. Я использую Rene Schulte (и кучу других участников) WriteableBitmapExtensions (http://writeablebitmapex.codeplex.com/). Я смог разгрузить рендеринг / сшивание изображения для фоновый поток, а затем установите результирующий WriteableBitmap в качестве источника некоторого изображения в потоке пользовательского интерфейса.

еще один вверх и угол в этом пространстве-это .NET Image Tools (http://imagetools.codeplex.com/). У них есть куча утилит для сохранения / чтения различных форматов изображений. У них также есть несколько низких уровней, и я хотел бы, чтобы был простой способ использовать оба (но его нет).

все вышеперечисленные работы в WP7.

Я думаю, майор разница в том, что с этими инструментами вы не будете использовать XAML, вы будете писать непосредственно на свое изображение (поэтому вам может потребоваться определить размер вашего текста и тому подобное).


сама природа элементов пользовательского интерфейса требует взаимодействия с ними в потоке пользовательского интерфейса. Даже если вы можете создать их в фоновом потоке, когда вы попытаетесь отобразить их в WriteableBitmap, вы получите аналогичное исключение, и даже тогда, если это позволит вам сделать это, элементы фактически не будут иметь визуального представления, пока они не будут добавлены в визуальное дерево. Вместо элементов пользовательского интерфейса может потребоваться использовать общую библиотеку обработки изображений.

возможно вы можете описать свой сценарий на более широкой основе, у нас может быть лучшее решение для вас:)


во-первых, вы уверены в рендеринге этого как растрового изображения? Как насчет генерации Canvas С изображением и TextBlock?

мне нужно сделать большое количество изображений

у меня такое чувство, что это генерация убьет производительность телефона. Как правило, для растровых mainupulation, лучший способ заключается в использовании КСНК. Некоторые части XNA framework отлично справляются с проектами Silverlight. (Кстати обновленные средства разработчика Windows Phone будет разрешить 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>

когда вы получили новый сообщение, покажите холст пользователю (анимация непрозрачности была бы хорошей), когда вы закончите рендеринг в фоновом потоке.

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

Obviouslly, здесь проблема с обновлением. UIelements можно обновить только из потока пользовательского интерфейса, поэтому обновление должно быть очередью с Dispatcher

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

PS. не компилировался, это больше псевдокод.


вы можете рисовать на WriteableBitmap в потоке, но вы должны

  1. создать WriteableBitmap в основной поток пользовательского интерфейса
  2. сделать работу в фоновом потоке
  3. назначить BitmapSource в основной поток пользовательского интерфейса

Я согласен с ответом Дерека: вы пытаетесь использовать элементы управления 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;
}