Unable to create a scalable png image using C# in .NET - c#

I am trying to create an image based on a string. The image needs to be raster (otherwise it will lose resolution if I need to zoom out). I am using the following code:
Bitmap bitmapimage = new Bitmap(200, 100);
Graphics bitmapGraphics = Graphics.FromImage(bitmapimage );
bitmapGraphics .DrawString("test", new Font("Arial",50), Brushes.Black, new Point(0, 0));
bitmapimage .Save("Image.png", System.Drawing.Imaging.ImageFormat.png);
textPictureBox.Image = bitmapimage ;
What I get is an image like this (after zooming):
Why is this?

I think you misunderstand what "raster" means. Raster images are grids, with one pixel at each grid location. When you zoom on a raster image far enough the grid becomes clearly visible, even with techniques like anti-aliasing.
Vector graphics, on the other hand, are algorithm based. They store instructions on how to reproduce an image on a given canvas. When you zoom a vector image, the image will stay sharp because it's still following the instruction, rather than simply scaling the previous rendering.
All of the major image types (bmp, gif, png, jpeg) are raster types, and do not support vector graphics. The png image is your example is rastered... in fact, it's impossible to create a png image this is not rastered. An example of a vector image is certain font types or Photoshop (psd) files (sort of... in practice, Photoshop files tend to be more raster than vector in the end).
In this case, probably the easiest solution is to draw the image very large in the first place... large enough that you won't need to zoom in, and use a large enough font to fill the space. You also need to make sure that you are using a font that is fully vector-drawn.

Try using Vector Basic graphics, if you using it you will not have problems if you zoom in or out
o Vector Graphics in C# (MSDN)
o Example Project (MSDN)

Vector graphics are scalable, raster are not.
Text fonts are scalable (unless you use bitmap fonts), but once you draw a string on a bitmap this text becomes a raster image, so it can't be scaled anymore. Thus, if you need to draw text on a bitmap try to use a large image and use anti-aliased text (again, this image won't be scalable but if it's large enough there will be no need to zoom it in).
Here is a modified version of your code (the text will be as large as the PNG image):
add the following using:
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
Add this line into your form (as a private class field):
Random rnd = new Random();
the rest of the code:
int scl = rnd.Next(100, 451);
Bitmap bitmapimage = new Bitmap(2 * scl, scl);
Graphics bitmapGraphics = Graphics.FromImage(bitmapimage);
bitmapGraphics.CompositingMode = CompositingMode.SourceOver;
bitmapGraphics.TextRenderingHint = TextRenderingHint.AntiAlias; // text is now anti-aliased
bitmapGraphics.SmoothingMode = SmoothingMode.HighQuality;
bitmapGraphics.DrawString("test", new Font("Arial", scl * 9 / 10, GraphicsUnit.Pixel), Brushes.Black, new Point(0, 0));
bitmapimage.Save("Image.png", ImageFormat.Png);
bitmapGraphics.Dispose();
By the way, if you draw text on a printer's Graphics object, this text is still scalable since printers don't use pixels (but if you draw a raster image on a printer's Graphics object, this image will get blurry if it's zoomed in).

Related

Resize PNG image without losing color data from fully transparent pixels

I've been trying to figure out a way to resize a PNG image without losing color data from pixels that are completely transparent. Here is the code that I'm using to achieve this:
//sourceImage is also a bitmap read in earlier
using (var scaledBitmap = new Bitmap(desiredWidth, desiredHeight, PixelFormat.Format32bppArgb)){
using (var g = Graphics.FromImage(scaledBitmap)){
var attr = new ImageAttributes();
attr.SetWrapMode(WrapMode.TileFlipXY);
g.CompositingQuality = CompositingQuality.HighQuality;
g.CompositingMode = CompositingMode.SourceCopy;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(sourceImage, new Rectangle(0, 0, desiredWidth, desiredHeight), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attr);
}
scaledBitmap.Save(memoryStream, ImageFormat.Png);
//use new bitmap here
}
But the problem with this example is that anywhere that a pixel's alpha value is zero, the pixels become black.
My reputation is too low to post these images inline sadly.
Images
First Image is the image I've been using to test with. For quick reference, I've saved a copy without the alpha channel (Second Image). The entire image is a solid color and the alpha masks part of the image to create a circle.
Third Image is the resulting image from the code above. Again I've saved two copies. In Fourth Image, it is very clear that data is being lost during the conversion. What I'd expect to see is the same single color filling the whole image.
Doing some reading I discovered this in the PNG file specification:
The alpha channel can be regarded either as a mask that temporarily hides transparent parts of the image, or as a means for constructing a non-rectangular image. In the first case, the colour values of fully transparent pixels should be preserved for future use. In the second case, the transparent pixels carry no useful data and are simply there to fill out the rectangular image area required by PNG. In this case, fully transparent pixels should all be assigned the same colour value for best compression.
So it looks like it's up to the PNG encoder to decide how to handle color values where the alpha is zero. Is there any way to tell GDI+'s encoder to not zero out the color data?
The reason I need to keep the color data is for further, dynamic resizing of the image. Without a homogeneous color at the edges of the visible portion of the image, they are terribly prone to ringing artifacts. There's also a post on the Unity forums about this issue.
Please let my know if anyone else has encountered this and how you dealt with it. Also if anyone knows of any libraries that implement a C# PNG encoder that is less draconian it would be greatly appreciated.
Thanks!
While I realize this may not be sufficient for some, in my case the answer was to switch to using ImageMagick's .NET wrapper. I was also able to find a thread on their forums with the exact problem I was experiencing.
It appears that this is a common bug when resizing images and ImageMagick has fixed this issue in their library.
using ImageMagick;
using (var sourceImage = new MagickImage(texture)){ //texture is of type Stream
sourceImage.Resize(desiredWidth, desiredHeight);
sourceImage.Write(memoryStream, MagickFormat.Png);
}
// use memoryStream here
As you can see, this also greatly simplified my code due to ImageMagick's built in support for resize operations.
Without ImageMagick, you can simply use the following code and it works, don't change CompositingQuality or any other Graphics properties :
Bitmap NewImage = new Bitmap(OriginalImage.Width, OriginalImage.Height, OriginalImage.PixelFormat);
using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(NewImage)) {
g.DrawImage(OriginalImage, new Rectangle(0, 0, NewWidth, NewHeight), 0, 0, OriginalImage.Width, OriginalImage.Height, GraphicsUnit.Pixel);
}
NewImage.Save("myTransparent.png");

C# drawing in layers

I have an idea and maybe you guys can give me a good start or an idea in which path might be correct.
I have a picturebox right now loading a specific bmp file. What I want to do is load this bmp file into the picturebox and then load another picture on top of it. The kicker to this all is the 2nd picture must be drawn. The 2nd picture is just a fill in black box. This black box must also overlay on the first image exactly right, the black box has cordinates from paint on it (yes we have the # of the cordaints).
Still think the picturebox is the way to go, or is there a way to load paint into this, and then paint on top of the paint image?
1) Need to load an image
2) Need to read a specific file that has cords
3) Need to draw a black rectangle that matches those coords (Those cords were created in paint).
What do you think the best way to approach this is? A Picture box with code to draw in the cords of the redacted image
Here's a code sample that should do what you're after:
//Load in an image
pbTest.Image = Image.FromFile("c:\\Chrysanthemum.jpg");
//Create the graphics surface to draw on
using (Graphics g = Graphics.FromImage(pbTest.Image))
{
using (SolidBrush brush = new SolidBrush(Color.Black))
{
//Draw a black rectangle at some coordinates
g.FillRectangle(brush, new Rectangle(0, 0, 20, 10));
//Or alternatively, given some points
//I'm manually creating the array here to prove the point, you'll want to create your array from your datasource.
Point[] somePoints = new Point[] { new Point(1,1), new Point(20,25), new Point(35, 50), new Point(90, 100) };
g.FillPolygon(brush, somePoints);
}
}
The finished article:
This answer is written to apply to both web and non-web uses of C# (why I did not give specific examples.)
GDI and other graphics libs all have functions that will paint a filled rectangle on top of an image. This is the way to go. If you use two images there is a good chance for a standard user and a great chance for a hacker they will be able to view just the original image, exposing the information you are trying to hide.
If you only send an image with the areas redacted, you will never have to worry about them being seen.

Image Resizing Performance: System.Drawing vs System.Windows.Media

I've got a situation where I need to resize a large number of images. These images are stored as .jpg files on the file system currently, but I expect to just have byte[] in memory later on in the project. The source image size is variable, but the output should be 3 different predetermined sizes. Aspect ratios should be preserved, padding the original image with white space (ie, a really tall image would be resized to fit within the square target image size, with large areas of white on the left and right).
I initially built the project targeting .NET 2.0, and using System.Drawing classes to perform the load/resize/save. Relevant code includes:
original = Image.FromFile(inputFile); //NOTE: Reused for each of the 3 target sizes
Bitmap resized = new Bitmap(size, size);
//Draw the image to a new image of the intended size
Graphics g = Graphics.FromImage(resized);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.Clear(Color.White);
g.DrawImage(original, center - width / 2f, center - height / 2f, width, height);
g.Dispose();
//Save the new image to the output path
resized.Save(outputFile, ImageFormat.Jpeg);
I wanted to port this project to .NET 3.5, so tried using the System.Windows.Media classes to perform the same function. I got it working, however performance is terrible; processing time per image is about 50x longer. The vast majority of the time is spent loading the image. Relevant code includes:
BitmapImage original = new BitmapImage(); //Again, reused for each of the 3 target sizes
original.BeginInit();
original.StreamSource = new MemoryStream(imageData); //imageData is a byte[] of the data loaded from a FileStream
original.CreateOptions = BitmapCreateOptions.None;
original.CacheOption = BitmapCacheOption.Default;
original.EndInit(); //Here's where the vast majority of the time is spent
original.Freeze();
// Target Rect for the resize operation
Rect rect = new Rect(center - width / 2d, center - height / 2d, width, height);
// Create a DrawingVisual/Context to render with
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(original, rect);
}
// Use RenderTargetBitmap to resize the original image
RenderTargetBitmap resizedImage = new RenderTargetBitmap(
size, size, // Resized dimensions
96, 96, // Default DPI values
PixelFormats.Default); // Default pixel format
resizedImage.Render(drawingVisual);
// Encode the image using the original format and save the modified image
SaveImageData(resizedImage, outputFile);
Am I doing something wrong here, to take so much time? I've tried just using the constructor on BitmapImage that takes a URI, same performance issue there. Anyone done anything like this before, know if there's a more performance-minded way to do this? Or am I just going to need to use System.Drawing still? Thanks!
And after typing all that up, it occurred to me that I could load the symbols from MS for the System.Windows.Media classes, and step through where it was slow. Immediately found the cause, and the solution. The input images were saved with a color profile, and it was attempting to load that color profile (from the file system) of each image. By switching from BitmapCreateOptions.None to BitmapCreateOptions.IgnoreColorProfile in the code above, it no longer does that, and performs just as fast as System.Drawing did.
Hope this helps anyone else that runs into this problem!
You appear to be doing this the hard way. You can let WPF do the work for you by just setting DecodePixelHeight and DecodePixelWidth. This will cause the resize to happen during the image load:
BitmapImage resizedImage = new BitmapImage
{
StreamSource = new MemoryStream(imageData),
CreateOptions = BitmapCreateOptions.IgnoreColorProfile,
DecodePixelHeight = height,
DecodePixelWidth = width,
}
resizedImage.BeginInit(); // Needed only so we can call EndInit()
resizedImage.EndInit(); // This does the actual loading and resizing
imageSaveImageData(resizedImage, outputFile);
I also included the IgnoreColorProfile solution you found in my code.
Update I reread your question and realized the reason you're using DrawingVisual is that you need whitespace around your image to make it square. DecodePixelHeight and DecodePixelWidth would not accomplish that goal, so my solution does not answer your question.
I will leave my answer here in case someone who just needs a resize without whitespace comes across this question.
I think this from the System.Drawing page on MSDN might be relevant:
The System.Drawing namespace provides access to GDI+ basic graphics functionality. More advanced functionality is provided in the System.Drawing.Drawing2D, System.Drawing.Imaging, and System.Drawing.Text namespaces.
The Graphics class provides methods for drawing to the display device. Classes such as Rectangle and Point encapsulate GDI+ primitives. The Pen class is used to draw lines and curves, while classes derived from the abstract class Brush are used to fill the interiors of shapes.
By using System.Drawing you are closer to the actual basic graphics functionality than if you go via System.Windows.Media which:
Defines objects that enable integration of rich media, including drawings, text, and audio/video content within Windows Presentation Foundation (WPF) applications.
System.Drawing is still supported, so I'd stick with that.
I found an interesting situation in your code. Remove using from following line:
using(DrawingContext drawingContext = drawingVisual.RenderOpen())
I'm not sure why this speed up code, but you can give it a try.

C# compact framework : load a png alpha blending file

I have a png image file with alpha blending of its own. Now I want to load it onto a form in a mobile. I tried so many ways but not work. Is there any solution? Thanks in advance.
This is what I use to load the image from resource:
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("drawObj.Graph.png");
Bitmap myPNGImg = new Bitmap(stream);
Then create new bitmap with same size of the images Graph.png:
Bitmap myBlankImg = new Bitmap(48,48);
Graphics mynewGraph = Graphics.FromImage(myNew);
mynewGraph.Clear(Color.Transparent);
Draw the PNG bitmap: mynewGraph.DrawImage(myPNGImg, 0, 0);
And then something I read from internet:(
Rectangle rectDest = new Rectangle(50,50, 100, 100);
ImageAttributes imgatt = new ImageAttributes();
imgatt.SetColorKey(Color.Transparent, Color.Transparent);
myGraph.DrawImage(myNew, rectDest, 0, 0, 99, 99,GraphicsUnit.Pixel, imgatt);
It works, but just clear the four corner of the images(somekind of rounded rectangle). There's still some white border around the images left.
Loading images using Bitmap on Compact Framework will lose alpha information. Setting the color key is an alternative way of doing transparency where you sacrifice an exact single color as the transparent color.
To use alpha blending on Compact Framework, you can use the helper classes from OpenNETCF to load the PNG file, keeping the alpha information (see Transparency and alpha blending), then P/Invoke AlphaBlend. It's not pretty, but it's what it takes. Also be warned that you will take a heavy performance hit for using alpha blending. To bake some images dynamically, it's fine, but for generic on screen drawing operations, you might want to use another approach.
The problem is that in the CF, filling with Color.Transparent actually fills with white (see these two blog entries). Project Resistance has a very good example of how to do this blending (actually several of them).

C#: Retain size despite different resolution using Graphics.DrawImage

I am trying to draw two images side-by-side using the C# Drawing namespace.
Here is a very simple example that assumes we have two images of the same height:
Image[] oldImages = GetOldImages();
var newImage = new Bitmap(oldImages[0].Width + oldImages[1].Width, 800);
using (var newImageGraphics = Graphics.FromImage(newImage))
{
newImageGraphics.DrawImage(oldImages[0], 0, 0);
newImageGraphics.DrawImage(oldImages[1], oldImage[0].Width, 0);
newImageGraphics.Save();
}
This works OK if the resolution of the two old images are the same.
However, if the resolutions are different then the image is resized, causing problems. For example, if the first image has a different resolution, then the second image will be positioned incorrectly.
Does anyone know how I can fix this problem easily? Ideally I want the original image's height and width to remain the same when they are drawn on to the new image.
Try this trick:
Bitmap picture_1 = new Bitmap(picture_1_path);
Graphics graphics = Graphics.FromImage(picture_1);
Bitmap picture_2 = new Bitmap(picture_2_path);
picture_2.SetResolution(graphics.DpiX, graphics.DpiY);
//Then do with pictures anything
Basically you'll need to resize the second image before adding to the new image.
Though as you say you want to retain the original height and width you'll need to change the canvas size of the second image. This increases the size of the image by adding empty space around the actual image. If the second image is larger than the first you'll need to do this to the first image instead.

Categories

Resources