Как я могу отобразить текст на 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 в потоке, но вы должны
- создать WriteableBitmap в основной поток пользовательского интерфейса
- сделать работу в фоновом потоке
- назначить 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;
}