Как я могу отобразить текст на WriteableBitmap в фоновом потоке, в Windows Phone 7? - PullRequest
14 голосов
/ 14 апреля 2011

Я пытаюсь отобразить текст на растровом изображении в приложении 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, если есть другой способ визуализации текста.

EDIT

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

РЕДАКТИРОВАТЬ 2

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

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

Ответы [ 6 ]

15 голосов
/ 15 апреля 2011

Этот метод копирует буквы из готового изображения вместо использования 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 голосов
/ 14 апреля 2011

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

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

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

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

0 голосов
/ 18 сентября 2011

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

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

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

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

Я предполагаю, что 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;
}
0 голосов
/ 14 апреля 2011

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

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

0 голосов
/ 14 апреля 2011

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

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

У меня такое ощущение, что это генерирование снизит производительность телефона. Как правило, для поддержки растрового изображения лучшим способом является использование XNA. Некоторые части платформы XNA отлично справляются с проектами Silverlight. (КСТАТИ обновленные инструменты разработчика Windows Phone позволят Silverlight и XNA сосуществовать в одном проекте)

Я бы отступил и подумал об этой функции. Разработка чего-то подобного в течение недели и затем недопустимая производительность сделают меня грустной пандой.

EDIT

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

Создайте Canvas с 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);

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

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

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

...