How to draw characters to a Texture2D? - c#

In C# XNA how is a single character drawn onto a Texture2D instead of the sprite batch ? I wish to do this in order to fill a bool[,] with the characters char\background data to analyze its shape.

You could use a render target. The basic idea is instead of rendering your text to the back buffer, you render to a separate buffer, which can then give you a Texture2D.
See here: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.rendertarget(v=xnagamestudio.31).aspx
question asker edit:
With permission I've added to this answer. At time of writing the information on the MSDN is very out of date and makes it look more complicated than it need so I wrote my own example of how to do this.
The class this is done in may have to inherit from IDisposable and implement void Dispose() which does nothing.
PresentationParameters pp = graphicsDevice.PresentationParameters;
byte width = 20, height = 20; // for example
// pp.BackBufferWidth, pp.BackBufferHeight // for auto x and y sizes
RenderTarget2D render_target = new RenderTarget2D(graphicsDevice,
width, height, false, pp.BackBufferFormat, pp.DepthStencilFormat,
pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
graphicsDevice.SetRenderTarget(render_target);
graphicsDevice.Clear(...); // possibly optional
spriteBatch.Begin();
// draw to the spriteBatch
spriteBatch.End();
graphicsDevice.SetRenderTarget(null); // Otherwise the SpriteBatch can't
// be used as a texture, this may also need to be done before using the
// SpriteBatch normally again to render to the screen.
// render_target can now be used as a Texture2D
At which point this might be useful. http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series2D/Texture_to_Colors.php

Related

Best way to draw text in MonoGame without using a SpriteFont?

Alright, so I'm working on a game in MonoGame which is set in a computer operating system. As expected, it does a lot of text rendering. The in-game OS allows users to customize almost every aspect of the operating system - people have made skins for the OS that make it look like Mac OS Sierra, almost every najor Windows release since 95, Xubuntu, Ubuntu, and way more.
This game used to be written in Windows Forms however there are features I want to implement that simply are not possible in WinForms. So, we decided to move from WinForms to MonoGame, and we are faced with one huge problem.
The skin format we've made allows the user to select any font installed on their computer to use for various elements like titlebar text, main UI text, terminal text etc. This was fine in WinForms because we could use System.Drawing to render text and that allows the use of any TrueType font on the system. If it can be loaded into a System.Drawing.Font, it can be rendered.
But, MonoGame uses a different technology for rendering text on-screen. SpriteFont objects. The problem is, there seems to be no way at all to dynamically generate a SpriteFont from the same data used to generate System.Drawing.Fonts (family, size, style, etc) in code.
So, since I seemingly can't create SpriteFonts dynamically, in my graphics helper class (which deals with drawing textures etc onto the current graphics device without needing copy-pasted code everywhere), I have my own DrawString and MeasureString methods which use System.Drawing.Graphics to composite text onto a bitmap and use that bitmap as a texture to draw onto the screen.
And, here's my code for doing exactly that.
public Vector2 MeasureString(string text, System.Drawing.Font font, int wrapWidth = int.MaxValue)
{
using(var gfx = System.Drawing.Graphics.FromImage(new System.Drawing.Bitmap(1, 1)))
{
var s = gfx.SmartMeasureString(text, font, wrapWidth); //SmartMeasureString is an extension method I made for System.Drawing.Graphics which applies text rendering hints and formatting rules that I need to make text rendering and measurement accurate and usable without copy-pasting the same code.
return new Vector2((float)Math.Ceiling(s.Width), (float)Math.Ceiling(s.Height)); //Better to round up the values returned by SmartMeasureString - it's just easier math-wise to deal with whole numbers
}
}
public void DrawString(string text, int x, int y, Color color, System.Drawing.Font font, int wrapWidth = 0)
{
x += _startx;
y += _starty;
//_startx and _starty are used for making sure coordinates are relative to the clip bounds of the current context
Vector2 measure;
if (wrapWidth == 0)
measure = MeasureString(text, font);
else
measure = MeasureString(text, font, wrapWidth);
using (var bmp = new System.Drawing.Bitmap((int)measure.X, (int)measure.Y))
{
using (var gfx = System.Drawing.Graphics.FromImage(bmp))
{
var textformat = new System.Drawing.StringFormat(System.Drawing.StringFormat.GenericTypographic);
textformat.FormatFlags = System.Drawing.StringFormatFlags.MeasureTrailingSpaces;
textformat.Trimming = System.Drawing.StringTrimming.None;
textformat.FormatFlags |= System.Drawing.StringFormatFlags.NoClip; //without this, text gets cut off near the right edge of the string bounds
gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.SingleBitPerPixel; //Anything but this and performance takes a dive.
gfx.DrawString(text, font, new System.Drawing.SolidBrush(System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B)), 0, 0, textformat);
}
var lck = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); //Lock the bitmap in memory and give us the ability to extract data from it so we can load it into a Texture2D
var data = new byte[Math.Abs(lck.Stride) * lck.Height]; //destination array for bitmap data, source for texture data
System.Runtime.InteropServices.Marshal.Copy(lck.Scan0, data, 0, data.Length); //cool, data's in the destination array
bmp.UnlockBits(lck); //Unlock the bits. We don't need 'em.
using (var tex2 = new Texture2D(_graphicsDevice, bmp.Width, bmp.Height))
{
for (int i = 0; i < data.Length; i += 4)
{
byte r = data[i];
byte b = data[i + 2];
data[i] = b;
data[i + 2] = r;
} //This code swaps the red and blue values of each pixel in the bitmap so that they are arranged as BGRA. If we don't do this, we get weird rendering glitches where red text is blue etc.
tex2.SetData<byte>(data); //Load the data into the texture
_spritebatch.Draw(tex2, new Rectangle(x, y, bmp.Width, bmp.Height), Color.White); //...and draw it!
}
}
}
I'm already caching heaps of textures created dynamically - window buffers for in-game programs, skin textures, etc, so those don't hit the performance hard if at all, but this text rendering code hits it hard. I have trouble even getting the game above 29 FPS!
So, is there a better way of doing text rendering without SpriteFonts, and if not, is there any way at all to create a spritefont dynamically in code simply by specifying a font family, font size and style (bold, italic, strikeout etc)?
I'd say I'm intermediate with MonoGame now but I have a hard enough time getting RenderTargets to work - so if you want to answer this question please answer it as if you were talking to a kindergarten student.
Any help would be greatly appreciated, and as this is a major hot-buttin' issue in my game's development team you may see yourself mentioned in the game's credits as a major help :P
You could create a custom spritefont using System.Drawing and use this one. It is basically every character that can be used, stored in a Dictionary with the corresponding Texture2D.
When you want to draw a text, you just draw every char next to eachother.
This is still slow (because drawing text without vector graphics is always slow) but at least you do not have to parse everything every frame.
Just specify somewhere what characters can be used and import them. Dictionaries are very fast in C# when it comes to indexing, so this shouldn't be a problem at all.
Hope this helps. Good luck.

Is there a benefit to using Graphics.DrawImage(Image, RectangleF destRect, RectangleF srcRect, GraphicsUnit srcUnit) when the image is a Bitmap?

Unless I miss something bitmaps are quantized (pixel-oriented). So what happens when someone tries the following:
public void Foo(Bitmap image)
{
var destinationRect = new Rectangle(0, 0, 50, 50);
var resultingSubimage = new Bitmap(destinationRect.Width, destinationRect.Height, PixelFormat.Format24bppRgb);
using (var graphics = Graphics.FromImage(resultingSubimage))
{
graphics.DrawImage(image, destinationRect, new RectangleF(30.3245F /*x*/, 23.234234F /*y*/, 50F, 50F), GraphicsUnit.Pixel);
// vs graphics.DrawImage(image, destinationRect, new Rectangle(30 /*x*/, 23 /*y*/, 50, 50), GraphicsUnit.Pixel);
}
}
notice that the x and y fields are decimals which point to a sub-pixel point. But is there even such a thing as a sub-pixel point for bitmaps? What's going on under the hood? Sorry if this has been answered elsewhere but the online documentation for both Graphics.DrawImage() and for the underlying p/invoke function 'GdipDrawImageRectRect' do not shed any light in this.
It is not well known, that if you draw an image, e.g.:
graphics.DrawImage(image, top, left);
the image will be scaled. This is because DrawImage looks at the dpi setting of the image (e.g. 72dpi from photoshop), and scales the image to match the destination display (typically 96dpi).
If you want to draw an image without any scaling, you must explicitly give DrawImage the size of the image:
graphics.DrawImage(img, top, left, img.Width, img.Height);
By calling, DrawImage with the the destination rectangle that matches the original image's pixel size, you are avoiding a resampling/rescaling.
Bonus Reading
.NET: What does Graphics.DrawImageUnscaled do?
Define "benefit". What overload would you use instead? I.e. "benefit" as compared to what?
It is most assuredly not the case that the overload is completely useless when dealing with Bitmap objects. Firstly, the GraphicsUnit value determines how the coordinates passed to the method are interpreted and of course one might pass something other than GraphicsUnit.Pixel. For example, suppose you are using GraphicsUnit.Inch and the resolution of the image is 120 dpi. Then each pixel is only 1/120th of an inch, and for per-pixel precision, your floating point values would be multiples of that (i.e. multiples of 0.0083333333333333), and not integer values.
Secondly, the Graphics object can be configured to do sub-pixel sampling in a variety of ways, and in such cases, a fractional pixel value could have meaning, even if the units being described were pixels.
You ask "what's going on under the hood", but I'm afraid that part is too broad a question for Stack Overflow. The Graphics object uses GDI+ as the underlying mechanism when using it on a Windows platform; the answer to what specifically happens with different configurations of the Graphics object would require a lengthy treatise.
If you want that level of detail, the right place to start would be the MSDN documentation for the GDI+ in the native Windows API. For most parts of Graphics, there's a one-for-one correspondence between the .NET API and the native one.
By the way, to be clear: your coordinates in this scenario are float values. I would be cautious about using the word "decimal" here, because .NET/C# has an actual decimal type, and you're definitely not using that. :)

Creating UI Image from Texture2D

I working on a live stream App that receives JPEG image as arrays of bytes and displays it on the screen with UI.Image. It works fine but I am making optimization and have few questions. Currently, the code I have below converts arrays of bytes to Texture2D then creates a Sprite from the Texture2D then assign that Sprite to UI.Iamge to display on the screen.
Texture2D camTexture;
Image screenDisplay;
public byte[] JPEG_VIDEO_STREAM;
bool updateScreen = false;
//Initializing
JPEG_VIDEO_STREAM = new byte[20000];
camTexture = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
//Main Code that runs in the Update function
if(updateScreen){
camTexture.LoadImage(JPEG_VIDEO_STREAM);
Sprite tempSprite = Sprite.Create(camTexture, new Rect(0, 0, camTexture.width, camTexture.height), Vector2.zero, 0);
screenDisplay.sprite = tempSprite;
updateScreen = false;
}
The code above currently perform 3 steps just to display image to screen.
byte array -> Texture2D -> Sprite -> UI.Image.
but I want it to look like byte array -> Texture2D-> UI.Image.
I want to write Texture2D directly to UI.Image without creating new Sprite because I believe that Sprite.Create(camTexture, new Rect(0, 0, camTexture.width, camTexture.height), Vector2.zero, 0); allocates new memory each time Sprite.Create called. I looked at the Unity Documentation and couldn't find any other way to do this.
My questions are:
How can I assign camTexture(Texture2D) to the screen screenDisplay(UI.Image) without converting camTexture(Texture2D) to Sprite first?
Does Sprite.Create allocate new memory when called?
If there is a solution to this, is that solution better than what I currently have in terms of performance and memory management?
Note: I have no plans on using OnGUI to draw Texture2D. I want to do this with the new Unity UI. Thanks.
Edit:
With Joe's answer of RawImage, the final code looks like this:
RawImage screenDisplay;
if(updateScreen){
camTexture.LoadImage(JPEG_VIDEO_STREAM);
screenDisplay.texture = camTexture;
updateScreen = false;
}
No more Sprite needed.
I think that by specifically using a RawImage rather than Image, one can do this.
I use RawImage extensively, because, we have to "display PNGs" and it's easier.
Consider the very handy trick:
just start with a trivial gray PNG which you have imported .. and then modify that .. rather than try to build from scratch?
An interesting curiosity I found is: normally to mirror an image, you just simply scale of x or y to -1. Unless it's been fixed, Unity has a problem where this won't work for RawImage.
// currently in Unity, the ONLY way to mirror a RAW image is by fooling with
// the uvRect. changing the scale is completely broken.
if ( shouldWeMirror )
rawImage.uvRect = new Rect(1,0,-1,1); // means mirror
else
rawImage.uvRect = new Rect(0,0,1,1); // means no flip
Another interesting factor. For this reason, many Unity projects still use (even 2017) the superlative 2dToolkit. It instantly solves issues such as this.

a texture that repeats across the world, based on X, Y coordinates

I'm using XNA/MonoGame to draw some 2D polygons for me. I'd like a Texture I have to repeat on multiple polygons, based on their X and Y coordinates.
here's an example of what I mean:
I had thought that doing something like this would work (assuming a 256x256 pixel texture)
verticies[0].TextureCoordinate = new Vector2(blockX / 256f, (blockY + blockHeight) / 256f);
verticies[1].TextureCoordinate = new Vector2(blockX / 256f, blockY / 256f);
verticies[2].TextureCoordinate = new Vector2((blockX + blockWidth) / 256f, (blockY + blockHeight) / 256f);
verticies[3].TextureCoordinate = new Vector2((blockX + blockWidth) / 256f, blockY / 256f);
// each block is draw with a TriangleStrip, hence the odd ordering of coordinates.
// the blocks I'm drawing are not on a fixed grid; their coordinates and dimensions are in pixels.
but the blocks end up "textured" with long-horizontal lines that look like the texture has been extremely stretched.
(to check if the problem had to do with TriangleStrips, I tried removing the last vertex and drawing a TriangleList of 1 - this had the same result on the texture, and the expected result of drawing only one half of my blocks.)
what's the correct way to achieve this effect?
my math was correct, but it seems that other code was wrong, and I was missing at least one important thing.
maybe-helpful hints for other people trying to achieve this effect and running into trouble:
GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
^ you need that code. but importantly, your SamplerState and other settings will get reset when you draw sprites (SpriteBatch's Begin()), so especially if you're abstracting your polygon-rendering code into little helper functions, be mindful of when and where you call them! ex:
spriteBatch.Begin();
// drawing sprites
MyFilledPolygonDrawer(args);
// drawing sprites
spriteBatch.End();
if you do this (assuming MyFilledPolygonDrawer uses 3D methods), you'll need to change all the settings (such as SamplerState) before you draw in 3D, and possibly after (depending on what settings you use for 2D rendering), all of which comes with a little overhead (and makes your code more fragile - you're more likely to screw up :P)
one way to avoid this is to draw all your 3D stuff and 2D stuff separately (all one, then all the other).
(in my case, I haven't got my code completely separated out in this way, but I was able to at least reduce some switching between 2D and 3D by using 2D methods to draw solid-color rectangles - Draw Rectangle in XNA using SpriteBatch - and 3D stuff only for less-regular and/or textured shapes.)

Is the behaviour of DrawLine predictable with out of range parameters?

Can DrawLine handle coordinates outside the defined area?
For example myGraphics.DrawLine(MyPen, -20, -80, 20, 90);
I would expect this to produce a line correctly as though it had used an infinite canvas but plotting only the section within my graphic.
My code is as follows. I am plotting movement from coordinates recorded in a database. Occasionally the subject moves further than expected, beyond the edges of my bitmap. I do not check for this occurrence as I was relying on DrawLine to handle it.
Bitmap Border = new Bitmap(5000, 5000);
Border.SetResolution(254, 254);
Graphics MyGraphics= Graphics.FromImage(Border);
Pen MyPen = new Pen(Color.Black, 1);
for (Int32 Point = 1; Point <= Points; Point++)
{
XCoord2 = XCoord1;
YCoord2 = YCoord1;
XCoord1 = *READ FROM DATABASE*
YCoord1 = *READ FROM DATABASE*
if (Point > 1)
{
MyGraphics.DrawLine(MyPen, XCoord1, YCoord1, XCoord2, YCoord2);
}
}
In reality, my plots work most of the time. However I do get an occasional graphic with missing lines or with an obscure line originating from a strange coordinate.
In summary, should the behaviour of DrawLine predictable with unusual parameters. Should I introduce some trigonometry to force the plots to always be within my grid?
The actual limits are a billion positive or negative see this past question (that used .net )
What are the hard bounds for drawing coordinates in GDI+?
My guess is that your database pulls are wrong, this can happen if you are using a string data storage and forcing that to be parsed.
Add a thread.sleep() and have it Debug.WriteLine the new pulls (or just breakpoint things), likely a value is getting in there that is either odd or getting parsed oddly
After more experimentation, I finally cured my problem with...
SolidBrush WhiteBrush = new SolidBrush(Color.White);
myGraphics.FillRectangle(WhiteBrush,0,0,5000,5000);
i.e. I gave my graphics a solid white background before I drew any lines. Before I was drawing a black line on a NULL background. I have no idea why this would affect anything, but it did!

Categories