I'm writing something to create small bitmap previews from a vector format file. From the file I have a (large) list of line coordinates. What I want to do is scale it to make it fit in a fixed-width image (96x96). I had previously been drawing the bitmap at full size and then just resizing it to 96x96 but since the thumbnails needed to be created on the fly that's turned out to not be fast enough (and it was a really dumb way to do it in the first place!). Now I just want to scale all of the coordinates as if the original size was 96x96, drop all of the points that draw on top of each other and that should greatly increase performance.
I'm an absolute newbie with any and all of the .NET Graphics/GDI stuff and the first version was pretty simple (code below). I'm wondering if there is something in the Graphics library (or elsewhere) that does that without me having to loop through all of the points and do the math on each one.
Can a Graphics/GDI guru point me in the right direction (or let me know there isn't a direction)? I'm using C#, and .NET framework target is OK.
So far it's pretty simple (tmpblocks is an array of points):
Bitmap DrawArea;
Graphics xGraph;
DrawArea = new Bitmap(64, 64);
// ^- this is GetWidth() and GetHeight() when drawing the full file at full size
xGraph = Graphics.FromImage(DrawArea);
for (int i = 0; i < tmpblocks.Count; i++)
{
if (tmpblocks[i].stitches.Length > 1)
{
Pen tempPen = new Pen(tmpblocks[i].color, 1.0f);
tempPen.StartCap = System.Drawing.Drawing2D.LineCap.Round;
tempPen.EndCap = System.Drawing.Drawing2D.LineCap.Round;
tempPen.LineJoin = System.Drawing.Drawing2D.LineJoin.Round;
xGraph.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
xGraph.DrawLines(tempPen, tmpblocks[i].stitches);
}
}
For the thumbnail I'd just scale the resulting bitmap using the .GetThumbnail method. Really slow, though (obviously)...
You can use the Graphics.ScaleTransform(float sx, float sy) to accomplish this.
You can get sx from: TargetWidth / SourceWidth
and sy from: TargetHeight / SourceHeight
where the target is defined by your target image size, and source is your source image size.
Related
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.
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
How to Resize Image and save in folder?
I would like to resize and save as images in a given folder. But i want to do it with maximum possible quality. I am willing to use third party software.
Like paint.net or photoshop.
So what is the best possible way of achieving this ?
Thank you.
Disclaimer: The following link/code is from my blog:
You should take a look at this post on my site http://samuelhaddad.com/2011/01/26/net-high-quality-thumbnail-generation/
The code from the post looks something like:
//Image Resize Helper Method
private static Bitmap ResizeImage(String filename, int maxWidth, int maxHeight)
{
using (Image originalImage = Image.FromFile(filename))
{
//Caluate new Size
int newWidth = originalImage.Width;
int newHeight = originalImage.Height;
double aspectRatio = (double)originalImage.Width / (double)originalImage.Height;
if (aspectRatio <= 1 && originalImage.Width > maxWidth)
{
newWidth = maxWidth;
newHeight = (int)Math.Round(newWidth / aspectRatio);
}
else if (aspectRatio > 1 && originalImage.Height > maxHeight)
{
newHeight = maxHeight;
newWidth = (int)Math.Round(newHeight * aspectRatio);
}
Bitmap newImage = new Bitmap(newWidth, newHeight);
using (Graphics g = Graphics.FromImage(newImage))
{
//--Quality Settings Adjust to fit your application
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);
return newImage;
}
}
}
You can wrap that in a for loop for batch processing.
Edit: Actually for much better presentation and description of the type of information I talk about below see this library and this article.
But I would also imagine most images libraries would have lanczos available. I have used FreeImage, which seems to have a mostly-decent C# wrapper.
Quality is largely about the interpolation algorithm used, and the ratio of the resizing. In Paint.net and Photoshop they have a dropdown to select them. Each algorithm will vary in what it does best (downsizing, upsizing, resizing by a integer ratio...) and each will have different speeds. Also Paint.net has the "Best Quality" option which will tell you what it decides to use (although I'm not sure what super sampling refers to...), doing manually do that with a few of your images and target size to see what it uses might help you choose a quicker algorithm.
#SamPlusPlus's answer uses HighQualityBilinear which IIRC is great for resizing and adding a small amount of blur (so sharp lines would be anti-aliased by the algorithm) Where Bicubic will retain more sharpness of lines. So out of these two, bilinear is generally better for upsizing, and bicubic is generally better for downsizing.
I believe the Lanczos algorithm is one of the best all-around algorithms (does very good at any amount of upsizing or downsizing) but is also one of the slower ones. I'd highly suggest it if you were reducing images by say 7% (the ratio 100:93 is difficult to reduce).
If you're doing 50%, 25%, 200%, etc all the algorithms will be very similar results, with speed being the only difference.
I would suggest looking for a library that has Lanczos resizing algorithm if the size of either the input or the output files is going to vary much, due to it being very good no matter the type of resizing, and because you did not mention speed being important, only quality. I have not used such a library in C#, so I do not have any direct recommendations.
You should look at color quantization. I've implemented a library for this and it works very well. Do a search and you should find quite a bit. I might be able to post some code later today.
Color Image Quantization in .NET
A Simple Palette Quantizer in C#
http://msdn.microsoft.com/en-us/library/aa479306.aspx
I have identified this call as a bottleneck in a high pressure function.
graphics.DrawImage(smallBitmap, x , y);
Is there a faster way to blend small semi transparent bitmaps into a larger semi transparent one?
Example Usage:
XY[] locations = GetLocs();
Bitmap[] bitmaps = GetBmps(); //small images sizes vary approx 30px x 30px
using (Bitmap large = new Bitmap(500, 500, PixelFormat.Format32bppPArgb))
using (Graphics largeGraphics = Graphics.FromImage(large))
{
for(var i=0; i < largeNumber; i++)
{
//this is the bottleneck
largeGraphics.DrawImage(bitmaps[i], locations[i].x , locations[i].y);
}
}
var done = new MemoryStream();
large.Save(done, ImageFormat.Png);
done.Position = 0;
return (done);
The DrawImage calls take a small 32bppPArgb bitmaps and copies them into a larger bitmap at locations that vary and the small bitmaps might only partially overlap the larger bitmaps visible area. Both images have semi transparent contents that get blended by DrawImage in a way that is important to the output. I've done some testing with BitBlt but not seen significant speed improvement and the alpha blending didn't come out the same in my tests. I'm open to just about any method including a better call to bitblt or unsafe c# code.
After some testing I see that Dan was right (see comments above). It is possible to beat GDI Draw Image performance if you don't need all the blending features it offers, but in the case of transparency blends I don't think there is room for material improvement.
I'm writing a tool to automate some of our asset making for a game. What I want to do is take a folder of PNG files, combine them into a texture atlas and then export the atlas as a TGA and the UV coords to XML.
I'm not sure which method I should use to load the PNG files in C# as there seem to be several. What is the recommended method to load images in C# that gives access to the colour/alpha data so I can extract it to the TGA?
I also already have TGA creation code in C++ which I plan to move to C# but I'm wondering if there is anything already available in .Net to create/save TGAs?
Thanks for reading.
Loading a PNG file into a .Net Bitmap is easy:
Bitmap bmp = (Bitmap)Bitmap.FromFile("c:\wherever\whatever.png");
// yes, the (Bitmap) cast is necessary. Don't ask me why.
Once you have the Bitmap loaded, you can access all of its info (including alpha channel info) most efficiently using the Bitmap's LockBits method (there are many LockBits code samples on StackOverflow).
Update: here's a code sample that shows how to use LockBits to access the Bitmap's data pixel-by-pixel:
System.Drawing.Imaging.BitmapData data =
bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
unsafe
{
// important to use the BitmapData object's Width and Height
// properties instead of the Bitmap's.
for (int x = 0; x < data.Width; x++)
{
int columnOffset = x * 4;
for (int y = 0; y < data.Height; y++)
{
byte* row = (byte*)data.Scan0 + (y * data.Stride);
byte B = row[columnOffset];
byte G = row[columnOffset + 1];
byte R = row[columnOffset + 2];
byte alpha = row[columnOffset + 3];
}
}
}
bmp.UnlockBits(data);
You need to set the "Allow unsafe code" compiler option for your project to use this code. You could also use the GetPixel(x, y) method of the Bitmap, but this is amazingly slow.
I dont have a code sample, but i can give you a guideline
Load PNG using Image.FromFile(). .NET supports loading PNG
Open a file handle to your targa. Create an XmlDocument. .NET doesnt support targa, so you have to manually write it.
Lock the bitmap to get at the pixels. GetPixel() is very slow. I think the method is named LockBits(). You get a pointer to the surface to read the pixels
Write to targa. Targa is a container format, so any bitmap should fit.
Save the UV as Xml.
Targa format
Do you want to use a palette ? Since your making a game, i would recommend you compute a palette for your bitmaps and put that into the targa to reduce the file size.
Oh , before i forget, .NET doesnt use RGBA, instead, the pixels are BGRA. Dont ask me why, but its like that.
The function below prints a color raster image to a PCL-5 printer. The function was adapted from a 2-color (1bpp) printing function we had that worked perfectly, except for the grainy 2-color printing. The problem is that the image comes out with a large black bar extending from the right of the image to the edge of the page like this:
IMAGE#########################################
IMAGE#########AREA COMPLETELY BLACK###########
IMAGE#########################################
The image itself looks perfect, otherwise.
Various PCL-to-PDF tools don't show the image at all, which leads me to believe I've forgotten do to something. Appropriate resets (\u001bE\u001b%-12345X) were sent before, and page-feeds after.
Any PCL experts out there? I've got the PCL 5 Color Technical Reference Manual, and it's gotten me this far. This last thing is driving me crazy though.
*Edit:
I now know what command is causing the problem, but I don't know why:
stream("\u001b*r0F");
This should keep the image rotated along with the page (portrait, landscape). If I remove this, the problem goes away. I can compensate by rotating the bitmap beforehand, but I really want to know what caused this!
static void PrintImage()
{
// Get an image into memory
Image original = Image.FromFile("c:\\temp\\test.jpg");
Bitmap newBitmap = new Bitmap(original, original.Width, original.Height);
stream(String.Format("\u001b*p{0:d}x*p{1:d}Y", 1000, 1000));// Set cursor.
stream("\u001b*t300R"); // 300 DPI
stream(String.Format("\u001b*r{0:d}T", original.Height)); // Height
stream(String.Format("\u001b*r{0:d}S", original.Width)); // Width
stream("\u001b*r3U"); // 8-bit color palette
stream("\u001b*r0F"); // Follow logical page layout (landscape, portrait, etc..)
// Set palette depth, 3 bytes per pixel RGB
stream("\u001b*v6W\u0000\u0003\u0000\u0008\u0008\u0008");
stream("\u001b*r1A"); // Start raster graphics
stream("\u001b*b0M"); // Compression 0 = None, 1 = Run Length Encoding
// Not fast, but fast enough.
List<byte> colors = new List<byte>();
for (int y2 = 0; y2 < original.Height; y2++)
{
colors.Clear();
for (int x2 = 0; x2 < original.Width; x2++)
{
Color c = newBitmap.GetPixel(x2, y2);
colors.Add(c.R);
colors.Add(c.G);
colors.Add(c.B);
}
stream(String.Format("\u001b*b{0}W", colors.Count)); // Length of data to send
streamBytes(colors.ToArray()); // Binary data
}
stream("\u001b*rB"); // End raster graphics (also tried *rC -- no effect)
}
There are a few problems with your code. First off your cursor position code is incorrect, it should read:
"\u001b*p{0:d}x1:d}Y", 1000, 1000
This equates to:
<esc>*p1000x1000Y
you had:
<esc>*p1000x*p1000Y
When joining PCL commands together you match up the same parameterized value and group and then simply add value + parametrized character + value + parametrized character etc. Ensure that the final parametrized character is a capital letter which signifies the end of the PCL command.
Also when defining an image I recommend you also specify the width & hight in decipoints, this should help with the scaling of the image (*r3A) on the page so add this (just after your resolution command should be an okay place for it):
Int32 deciHeight = original.Height / (int)original.HorizontalResolution * 720;
Int32 deciWidth = original.Width / (int)original.VerticalResolution * 720;
stream("\u001b*t{0:d}h{1:d}V", deciHeight, deciWidth));
The other recommendation is to write all of this to file (watch your encodings) and use one of the handful of PCL viewers to view your data vs. always printing it. Should save you some time and a forest or two! I've tried all of them and wold recommend spending the $89 and purchasing pclWorks. They also have a complete SDK if you are going to do a lot of PCL. We don't use that as we hardcode all PCL ourselves but it does look good.
As for rotation, we've had problems on some device, You could just rotate the the jpg first (original.RotateFlip) an then write it out.
I don't have much time today but hope that my comments assist. I can test your code on Monday or Tuesday and work with it and post any further comments.
Keep in mind that even though PCL is a standard its support from manufacturer to manufacturer and device to device can be a problem and vastly different. When doing basic things most devices seem okay; however, if you get into macros or complex graphics you will find difference.