Подтвердить что ты не робот

Как я могу визуализировать текст в 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, мне нужны следующие функции:

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

Ответ 1

Этот метод копирует буквы из предварительно сделанного изображения вместо использования TextBlock, основываясь на моем ответе на этот question. Основное ограничение требует наличия другого изображения для каждого шрифта и необходимого размера. Размер шрифта 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;
        }
    }
}

Чтобы загрузить файлы, вы вызываете DrawString. Он использует WriteableBitmapEx.Blit, поэтому, если ваш файл шрифта имеет белый текст, а прозрачный фоновый альфа обрабатывается правильно, и вы можете перекрасить его. Код действительно масштабирует текст, если вы рисуете с размером, который не загружался, но результаты не очень хорошие, может быть использован лучший метод интерполяции.

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

ИЗМЕНИТЬ

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

Создайте холст с помощью 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);

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

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

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

Ответ 4

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

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

Ответ 5

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

  • create WriteableBitmap в главном потоке пользовательского интерфейса.
  • сделать рисование работы в фоновом потоке
  • назначить BitmapSource в основном потоке пользовательского интерфейса.

Ответ 6

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

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

Я предполагаю, что 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;
}