Как я могу визуализировать текст в 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)?

ИЗМЕНИТЬ 2

Чтобы рассмотреть альтернативы WriteableBitmap, мне нужны следующие функции:

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

person Ran    schedule 14.04.2011    source источник
comment
Какие текстовые функции вам нужны? Я могу опубликовать метод с использованием SpriteSheets, который подойдет, если вам не нужно много вариантов шрифта или размера. Однако, если вам действительно нужно расширенное форматирование или вы хотите отобразить абзацы текста, это не совсем подходит.   -  person Kris    schedule 15.04.2011
comment
@Kris: звучит многообещающе, я перечислил функции, которые мне нужны. Спасибо.   -  person Ran    schedule 15.04.2011
comment
Вам удалось найти все, что вы искали? Я сейчас портирую приложение на WP7 и ищу то же самое: нарисуйте фоновое изображение. Измерьте ширину и высоту однострочной строки с учетом семейства и размера шрифта (и, желательно, стиля). Нет необходимости в переносе слов. Нарисуйте однострочную строку с заданным семейством шрифтов, размером, стилем и заданными координатами. Не могли бы вы поделиться своими выводами? Спасибо   -  person jyavenard    schedule 20.10.2011
comment
По правде говоря, я не нашел достаточно хорошего решения. Библиотека WriteableBitmapEx может подойти, но у меня возникли проблемы с ее использованием. В частности, необходимость предварительно генерировать спрайты для всех шрифтов, стилей и размеров и иметь более низкое качество рендеринга из-за того, что рендеринг текста больше не был векторным.   -  person Ran    schedule 20.10.2011


Ответы (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 вместо их воссоздания. В противном случае код можно было бы изменить для работы с массивами.

person Kris    schedule 15.04.2011
comment
Спасибо, Крис! Кажется, это ближе всего к тому, что я искал. Я дам ему попробовать. - person Ran; 17.04.2011
comment
@chendang Spritefont имеет вкладку, которая позволяет вам выбирать, какие символы экспортировать, поэтому, если вы это сделаете и используете соответствующий шрифт, он должен работать. - person Kris; 02.03.2013

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

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

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

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

person DevTheo    schedule 14.04.2011
comment
Спасибо. Мне XAML особо не нужен, я уже делаю всевозможные вычисления, чтобы разместить тексты на изображении в коде. Я знал о WriteableBitmapExtensions, но, как мне кажется, он занимается только рисованием всевозможных геометрических фигур, и мне нужно рисовать строки с помощью шрифтов. - person Ran; 15.04.2011

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

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

person Derek Lakin    schedule 14.04.2011
comment
Вы знаете такую ​​универсальную библиотеку обработки изображений для Silverlight? Я прибегал к использованию WriteableBitmap и TextBlock только потому, что System.Drawing и Graphics недоступны в Silverlight. - person Ran; 14.04.2011

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

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

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

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

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

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

person Lukasz Madon    schedule 14.04.2011
comment
Производительность подходит для моих нужд. Обычно мне приходилось генерировать только 1-3 изображения за раз и только в ответ на действие пользователя, которое обычно происходит раз в минуту или около того. Дело в том, что рендеринг изображений приводит к зависанию пользовательского интерфейса на очень короткое время (может быть, 200 мс или около того), и я бы тоже хотел этого избежать. - person Ran; 14.04.2011
comment
Я бы не возражал против создания Canvas вместо изображения, а затем только рендеринга его в пользовательском интерфейсе или присоединения его к визуальному дереву, но я даже не могу сделать это в фоновом режиме. - person Ran; 14.04.2011
comment
Спасибо, но я думаю, что это не очень актуально для моего сценария. Я развиваю своего рода зрителя. Когда пользователь переходит в новое место, я хотел бы визуализировать окружающие области на заднем плане, чтобы при повторной прокрутке пользователя изображения были готовы. Если я правильно понимаю, вы предлагаете создать Canvas с дочерним TextBlock. На самом деле у меня может быть много-много TextBlock на каждом изображении в координатах, которые я вычисляю в коде. Я хотел бы выполнить эту работу в другом потоке. - person Ran; 14.04.2011

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

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

Я согласен с ответом Дерека: вы пытаетесь использовать элементы управления пользовательского интерфейса без пользовательского интерфейса.

Если вы хотите визуализировать растровое изображение, вам нужно придерживаться классов для рисования текста на растровых изображениях.

Я полагаю, что в 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;
}
person Ian Boyd    schedule 14.04.2011
comment
Нет, к сожалению, это не так. Я бы так хотел. Windows Phone 7 поддерживает Silverlight и XNA. System.Drawing недоступен. У вас нет Bitmap, Graphics или DrawString. Я действительно портирую приложение с Windows Mobile на Silverlight и с удивлением обнаружил, что Graphics больше нет. Единственной альтернативой, которую я нашел до сих пор, была эта неудобная техника. - person Ran; 14.04.2011
comment
Аааа. Я знал, что Silverlight изначально был разрабатываемым WPF / e (Windows Presentation Foundation Everywhere). Но в своей голове я понял, что это означает .NET Compact Framework). - person Ian Boyd; 14.04.2011