Text on a bitmap
-
Wednesday, April 11, 2012 12:53 AM
I am trying to put text on an existing bitmap. I'm using the article from MSDN but it's not working. My WriteableBitmap already eixsts. I don't want to create a new bitmap. I want to write directly onto the bitmap that already exists.
Can you please let me know what I'm doing wrong?
FormattedText fText = new FormattedText(tile.ToString(), new CultureInfo(CULTURE), FlowDirection.LeftToRight, new Typeface(TYPE_FACE), FONT_SIZE, FONT_COLOR); DrawingVisual dv = new DrawingVisual(); DrawingContext dc = dv.RenderOpen(); dc.DrawText(fText, new Point(destinationX, destinationY)); dc.Close(); RenderTargetBitmap rtb = drawArea; // <-- AN EXISTING WRITEABLE BITMAP rtb.Render(dv);
All Replies
-
Wednesday, April 11, 2012 1:20 AM
I've just tried the other article as well. It states to do the following:
WriteableBitmap drawArea = new WriteableBitmap(); TextBlock tb = new TextBlock(); tb.Text = tile.ToString(); tb.Foreground = Brushes.White; tb.FontSize = 8; drawArea.Render(tb, null); drawArea.Invalidate();However, when I do that it states:
"WriteableBitmap does not contain a definition for Render()".
"WriteableBitmap does not contain a definition for Invalidate()".
-
Wednesday, April 11, 2012 2:21 AM
Hi,
use (for instance) a DrawingVisual, a Formatted Text and a RenderTargetBitmap:
(assumed you have two image-controls on your window)
if (image1.Source != null) { Brush textBrush = new SolidColorBrush(Colors.White); BitmapSource bmp = (BitmapSource)image1.Source; FormattedText ftX = new FormattedText("Width:" + bmp.PixelWidth.ToString() + "\r\nHeight:" + bmp.PixelHeight.ToString(), System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Comic Sans Ms"), 48, textBrush); DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawImage(bmp, new Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight)); dc.DrawText(ftX, new Point((bmp.PixelWidth - ftX.Width) / 2.0, (bmp.PixelHeight - ftX.Height) / 2.0)); } RenderTargetBitmap r = new RenderTargetBitmap(bmp.PixelWidth, bmp.PixelHeight, 96, 96, PixelFormats.Pbgra32); r.Render(dv); this.image2.Source = r; }Regards,
Thorsten
- Marked As Answer by Kee PoppyModerator Monday, April 30, 2012 10:10 AM
-
Thursday, April 12, 2012 9:24 PM
Thanks,
This seems to work for me, but I have a couple questions.
- r.Render(dv) is VERY slow, taking about 2-3 seconds to execute even when only drawing a single character. Is there any way to speed this up. In my painting/gaming programing this is going to be way too slow for me to use. One of the primary functions of this program is to draw text on the bitmap. The text its going to draw is very small, usually only a number or small piece of data. But it's still way too slow. I included a copy of my modified code from your example.
- Lastly, how can I convert the RenderTargetBitmap back to a WriteableBitmap so that I can save the changes in memory as well. You showed how to save the changes to an Image control on the interface, but those changes are never written back to the WriteableBitmap object in memory, so the next time this function is run, all previous data is lost. I have been searching for a way to do this, but I can't find any good examples. The only example I found was 75 lines of code.
Brush TextBrush = new SolidColorBrush(Colors.White); BitmapSource bmp = (BitmapSource)drawArea; // This is a WriteableBitmap FormattedText ftX = new FormattedText(tile.ToString(), System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Arrial Narrow"), 12, TextBrush); DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawImage(bmp, new Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight)); dc.DrawText(ftX, new Point(destinationX, destinationY)); } RenderTargetBitmap r = new RenderTargetBitmap(bmp.PixelWidth, bmp.PixelHeight, 96, 96, PixelFormats.Pbgra32); r.Render(dv); imgLevel.Source = r; // Saves changes to interface.. // Now how to I save the changes made to 'r' back into drawArea (a WriteableArea)?
Thanks! -
Thursday, April 12, 2012 11:20 PM
> I am trying to put text on an existing bitmap.
in the WPF you can just overlay text over an image. try using the following code:
<Window x:Class="WpfApplication2.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300"> <Grid> <Image Source="http://i1.social.s-msft.com/profile/u/avatar.jpg?displayname=malobukv" Stretch="Fill" /> <TextBlock Text="Hello" FontSize="60" /> </Grid> </Window>
-
Thursday, April 12, 2012 11:51 PM
I'm not sure how to use that XMAL code, I am doing everything in the code behind because I am making a DirectX video game. I need to be able to draw text onto the bitmap at runtime while the game is being played. I'm not sure how to do that with XAML.
The code example Thorsten Gudera provided does excatly what I need it to do, but it's a little slow, and I can't figure out how to convert back to a WriteableBitmap.
Can you give an example of your code using the code behind? Something that will allow me to draw the text at a X/Y location on the original bitmap? If its faster I would use that instead, but I don't think the XAML is going to work for a video game.
-
Friday, April 13, 2012 12:34 AM
> Can you give an example of your code using the code behind?
void Test() { var bmp = new BitmapImage(new Uri("http://i1.social.s-msft.com/profile/u/avatar.jpg?displayname=malobukv")); var grd = new Grid(); grd.Children.Add(new Image { Source = bmp, Stretch = Stretch.Fill }); grd.Children.Add(new TextBlock { Text = "Hello", FontSize = 60 }); var wnd = new Window(); wnd.Content = grd; wnd.Show(); }
-
Friday, April 13, 2012 1:02 AM
- Lastly, how can I convert the RenderTargetBitmap back to a WriteableBitmap so that I can save the changes in memory as well. You showed how to save the changes to an Image control on the interface, but those changes are never written back to the WriteableBitmap object in memory, so the next time this function is run, all previous data is lost. I have been searching for a way to do this, but I can't find any good examples. The only example I found was 75 lines of code.
You can create a new WriteableBitmap from it:
drawArea = new WriteableBitmap(r);
complete code (with an additional Stopwatch to measure the time - my image is 1737*1303px):
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); WriteableBitmap drawArea = null; if (image1.Source != null) { BitmapSource bmp = (BitmapSource)image1.Source; //I just create a new one here for testing, use your already present one here drawArea = new WriteableBitmap(bmp); Brush textBrush = new SolidColorBrush(Colors.White); FormattedText ftX = new FormattedText("Width:" + drawArea.PixelWidth.ToString() + "\r\nHeight:" + drawArea.PixelHeight.ToString(), System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Comic Sans Ms"), 48, textBrush); DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawImage(drawArea, new Rect(0, 0, drawArea.PixelWidth, drawArea.PixelHeight)); dc.DrawText(ftX, new Point((drawArea.PixelWidth - ftX.Width) / 2.0, (drawArea.PixelHeight - ftX.Height) / 2.0)); } RenderTargetBitmap r = new RenderTargetBitmap(drawArea.PixelWidth, drawArea.PixelHeight, 96, 96, PixelFormats.Pbgra32); r.Render(dv); //comment this out if not needed this.image2.Source = r; drawArea = new WriteableBitmap(r); } sw.Stop(); MessageBox.Show(sw.ElapsedMilliseconds.ToString()); //just a test MessageBox.Show(drawArea.PixelWidth.ToString());Here on my machine this gets excuted in about 45 millisecs.
Regards,
Thorsten
-
Friday, April 13, 2012 1:30 AM
Ok,
I was trying this at work earlier, so let me test it on my developer machine and see how fast it works there.
I will let you know it a little while what happen.
Edit: I will try the new constructor as well. I'm just learning WPF, and I'm trying to move the game over from GDI+. I still haven't learned all of the WPF classes, and how they work. It's a lot different than GDI+. But so far I like it a lot better too.- Edited by Kensino Friday, April 13, 2012 1:31 AM
-
Friday, April 13, 2012 2:18 AM
On both my machines at home the process takes 451ms-600ms to execute the code. My image is larger, 4096x4096. But that is the largest image I will need to work with.
-
Friday, April 13, 2012 3:25 AM
Do you need to write the text to such a large image? Maybe you could determine the affected size, copy just the cropped part of the large image, write the text to it and copy/write the pixels back to the WriteableBitmap.
Regards,
Thorsten
-
Friday, April 13, 2012 3:34 AM
I can try that and see what happens. I don't think I need to copy the entire image, however, that's the way the GDI+ version worked, so I was trying to keep it the same way. I'll see if I can change it.
Also, I keep getting OutOfMemory errors on this line:
RenderTargetBitmap r = new RenderTargetBitmap(bmp.PixelWidth, bmp.PixelHeight, 96, 96, PixelFormats.Pbgra32);
If I call the function several times in a row. I'm not sure if it's because the image is too big, or because I'm not cleaning something up. -
Friday, April 13, 2012 3:44 AM
Also, I keep getting OutOfMemory errors on this line:
RenderTargetBitmap r = new RenderTargetBitmap(bmp.PixelWidth, bmp.PixelHeight, 96, 96, PixelFormats.Pbgra32);
If I call the function several times in a row. I'm not sure if it's because the image is too big, or because I'm not cleaning something up.... what you have to properly close and dispose are the DrawingContext-objects (and everything else that implements IDisposable).
Do you work on a 32bit OS maybe and have more copies of the large bitmap around?
Regards,
Thorsten
-
Friday, April 13, 2012 12:59 PM> I'm just learning WPF, and I'm trying to move the game over from GDI+. I still haven't learned all of the WPF classes, and how they work. It's a lot different than GDI+.
take a look at the "An Introduction to Windows Presentation Foundation", especially at "2.3 Retained-mode Graphics"
-
Friday, April 13, 2012 3:06 PM
Yes, I'm working on a 32 bit system. I don't really have any copies of the bitmap... The bitmap is entirely created at runtime. It's initially created in the Window constructor and I just draw on it during the program.
public MainWindow() { // Create a Bitmap that fits the entire Image control. drawArea = new WriteableBitmap(4096, 4096, 96, 96, PixelFormats.Bgra32, null); imgLevel.Source = drawArea; }Brush TextBrush = new SolidColorBrush(Colors.White); BitmapSource bmp = (BitmapSource)drawArea; // This is a WriteableBitmap FormattedText ftX = new FormattedText(tile.ToString(), System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Arrial Narrow"), 12, TextBrush); DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawImage(bmp, new Rect(0, 0, bmp.PixelWidth, bmp.PixelHeight)); dc.DrawText(ftX, new Point(destinationX, destinationY)); } RenderTargetBitmap r = new RenderTargetBitmap(bmp.PixelWidth, bmp.PixelHeight, 96, 96, PixelFormats.Pbgra32); r.Render(dv); imgLevel.Source = r; drawArea = new WriteableBitmap(r);
That's the entire thing.Can you see if it runs slow for you as well?
- Edited by Kensino Friday, April 13, 2012 3:07 PM
-
Friday, April 13, 2012 4:42 PM
Ok,
here's test-code for the cropped bitmap scenario. This should run somewhat faster, here, with a 3000 * 5000 px bitmap it takes about 50-60 millisecs (when the drawArea-bitmap is created before the stopwatch starts, like it is in your scenario)
private void button1_Click(object sender, RoutedEventArgs e) { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); WriteableBitmap drawArea = null; if (image1.Source != null) { BitmapSource bmp = (BitmapSource)image1.Source; //I just create a new one here for testing, use your already present one here drawArea = new WriteableBitmap(bmp); Brush textBrush = new SolidColorBrush(Colors.White); FormattedText ftX = new FormattedText("Width:" + drawArea.PixelWidth.ToString() + "\r\nHeight:" + drawArea.PixelHeight.ToString(), System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Comic Sans Ms"), 48, textBrush); //get rectangleData for cropping and copying //here centered with width and height of the textsize int x = (int)Math.Ceiling((drawArea.PixelWidth - ftX.Width) / 2.0); int y = (int)Math.Ceiling((drawArea.PixelHeight - ftX.Height) / 2.0); int w = (int)Math.Ceiling(ftX.Width); int h = (int)Math.Ceiling(ftX.Height); //get the cropped part CroppedBitmap cb = new CroppedBitmap(); cb.BeginInit(); cb.Source = drawArea; cb.SourceRect = new Int32Rect(x, y, w, h); cb.EndInit(); //draw text to cropped image and render to a bitmap DrawingVisual dv = new DrawingVisual(); using (DrawingContext dc = dv.RenderOpen()) { dc.DrawImage(cb, new Rect(0, 0, cb.PixelWidth, cb.PixelHeight)); dc.DrawText(ftX, new Point((cb.PixelWidth - ftX.Width) / 2.0, (cb.PixelHeight - ftX.Height) / 2.0)); } RenderTargetBitmap r = new RenderTargetBitmap(drawArea.PixelWidth, drawArea.PixelHeight, 96, 96, PixelFormats.Pbgra32); r.Render(dv); //copy pixels back - *assuming a 32bpp image* change if needed r.CopyPixels(new Int32Rect(0, 0, w, h), //sourceRect drawArea.BackBuffer + (y * drawArea.BackBufferStride + x * 4), //StartAddress + Offset, *assuming a 32bpp image* drawArea.PixelWidth * drawArea.PixelHeight * 4, //SourceArraySize drawArea.BackBufferStride); //stride //comment this out if not needed this.image2.Source = drawArea; drawArea = new WriteableBitmap(drawArea); } sw.Stop(); MessageBox.Show(sw.ElapsedMilliseconds.ToString()); //just a test MessageBox.Show(drawArea.PixelWidth.ToString()); }Regards,
Thorsten
- Edited by Thorsten GuderaMicrosoft Community Contributor Friday, April 13, 2012 7:02 PM
-
Saturday, April 14, 2012 9:32 AM
... but I wonder why you want to call that method multiple times. (Other than preparing a bitmap for saving, or initializing or so). In WPF you just put your elements at the right location and (usually) have not to care much about rendering, so there's no need to draw everything to a bitmap or draw everything to a Control like in WindowsForms. You just define (declare) the elements and put them into the correct location, maybe dynamically change the content (for instance for textblocks). So if you want to put some text onto a Background-image, just put the Image to the Control and create a TexBlock (give it a Name), put it where you like to display it and in code only reference it to change the Text. See what Malobukv has said concerning the Retained Mode Graphics.
Regards,
Thorsten
-
Saturday, April 14, 2012 7:20 PMBecause I am creating a 2D video game. When you play a video game the graphics are changing constantly. If I just loaded the graphics when the program starts, and never redrew anything how would anyone play the game? How would they see the character moving, or walk through the dungen or kill stuff?
-
Sunday, April 15, 2012 10:05 AM
... If I just loaded the graphics when the program starts, and never redrew anything how would anyone play the game? How would they see the character moving, or walk through the dungen or kill stuff?
Hi,
by separating Background and Foreground-objects. One Bitmap as Background, maybe animated, different visual-entities as Players, Friends, Enimies, Annotations and other items...
Otherwise you would always draw the complete scene to one flat Bitmap and there would IMHO be no reason to change to WPF from a running WindowsForms version...
Of course you could self-control rendering with a custon drawingsurface, having several (drawing) visuals as children.
Regards,
Thorsten
- Edited by Thorsten GuderaMicrosoft Community Contributor Sunday, April 15, 2012 12:23 PM

