Text on a bitmap
-
2012年4月11日 上午 12:53
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);
所有回覆
-
2012年4月11日 上午 01:20
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()".
-
2012年4月11日 上午 02:21
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
- 已標示為解答 Kee PoppyModerator 2012年4月30日 上午 10:10
-
2012年4月12日 下午 09:24
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! -
2012年4月12日 下午 11:20
> 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>
-
2012年4月12日 下午 11:51
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.
-
2012年4月13日 上午 12:34
> 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(); }
-
2012年4月13日 上午 01:02
- 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
-
2012年4月13日 上午 01:30
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.- 已編輯 Kensino 2012年4月13日 上午 01:31
-
2012年4月13日 上午 02:18
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.
-
2012年4月13日 上午 03:25
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
-
2012年4月13日 上午 03:34
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. -
2012年4月13日 上午 03:44
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
-
2012年4月13日 下午 12:59> 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"
-
2012年4月13日 下午 03:06
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?
-
2012年4月13日 下午 04:42
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
- 已編輯 Thorsten GuderaMicrosoft Community Contributor 2012年4月13日 下午 07:02
-
2012年4月14日 上午 09:32
... 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
-
2012年4月14日 下午 07:20Because 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?
-
2012年4月15日 上午 10:05
... 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
- 已編輯 Thorsten GuderaMicrosoft Community Contributor 2012年4月15日 下午 12:23

