Im working on app for my educational purposes.
I want to make any image (jpg, png) from 24/32bpp to 8bpp, because i only want values from 0 to 255 in one scale (in this situation, gryascale) and i dont need to have RGB or RGBA color space.
I found a solution for it, can i use Graphics.DrawImage for making a copy of my input image, but with cutting not needed channels? Of course, after binarize the image.
//binarization first
static public Bitmap CreateNonIndexedImage(Bitmap bitmap)
{
Bitmap newBmp = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format32bppArgb); // i want to change pixel format here
using (Graphics graphics = Graphics.FromImage(newBmp))
{
graphics.DrawImage(bitmap, 0, 0); //if it make an exact copy of input bitmap?
}
return newBmp;
}
Thanks for any advices
Related
I'm implementing image scaling function. When I use HighQualityBicubic interpolation mode (which is preferred from quality perspective) I'm getting black background on resized image(only in rectangle of source image. In padding area of destination rectangle it is still transparent).
Input image is bmp with transparent background.
Output is also bmp 32 bpp.
Interesting thing when I change InterpolationMode to NearestNeighbor with some input images background is preserved as transparent after resizing and with another inputs it doesn't help.
What I'm doing wrong?
public Bitmap DrawScaledImage(Image img, int width, int height, int scaledWidth, int scaledHeight)
{
var resultImg = new Bitmap(width, height, PixelFormat.Format32bppArgb);
resultImg.SetResolution(img.HorizontalResolution, img.VerticalResolution);
using (Graphics grPhoto = Graphics.FromImage(resultImg))
{
grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
grPhoto.DrawImage(img,
new Rectangle(0, 0, scaledWidth, scaledHeight), //0;0 - image is drawn in left top corner
new Rectangle(0, 0, img.Width, img.Height),
GraphicsUnit.Pixel);
}
return resultImg;
}
Update
Found funny thing:
I've picked random transparent pixel in source image and took its ARGB in debug: it is ARGB=(255, 0, 0, 0) - pretty clear
Then I've picked same pixel which was intended to be transparent in destination image after scaling and it is still ARGB=(255, 0, 0, 0) but in Paint.Net it is displayed as black.
Then I've picked another random pixel from destination image that is displayed as transparent in Paint.Net and its ARGB schema is ARGB=(0, 0, 0, 0) - what?
So maybe stupid question - why for 1st pic transparent pixel is [255;0;0;0] but for 2nd [255;0;0;0] means black and transparent is [0;0;0;0].
it looks like something went down-under. and Alfa-channel reversed its meaning from 255- transparent to 255-opaque after scaling. Any ideas?
So I found that reason is some buggy behavior saving file in BMP format after resizing with InterpolationMode = InterpolationMode.HighQualityBicubic. Here are cases for reproduction:
1. If I create brand new image with pixel format PixelFormat.Format32bppArgb make some program manipulations with pixels (e.g. paint some line) and save as bmp - no problem at all, transparency is saved and visible in Paint.Net.
2. If I open existing bmp image already with transparency scale it up/down with Graphics.DrawImage method all seems good in debug. Transparent pixels have Alpha = 0. Then I save that image as bmp. Then Open it again in code like Image.FromFile("resized.bmp") and its pixel format is not same as it was before saving: it is now Format32bppRgb however saved image had Format32bppArgb. On opened image transparent pixels now have Alpha = 255. This is the cause why opening in Paint.Net I see black instead of transparent after resizing.
I found 2 workarounds to preserve transparency after resizing:
1. Change InterpolationMode from InterpolationMode.HighQualityBicubic to InterpolationMode.NearestNeighbor. This solves transparency issue but not acceptable due to low quality method.
2. Save resized image as .png. This saves correct Alpha values and reopening image preserves it still. So it is my solution to refuse using BMP file format at all.
using (var bmp = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb))
using (var g = Graphics.FromImage(bmp))
{
g.Clear(Color.Transparent);
g.DrawImage(image, 0, 0);
bmp.Save("image.bmp", ImageFormat.Bmp);
}
The question should be clear: why saving to BMP trashes transparency to black, while saving to PNG keeps it ?
Just to clarify: image is in Format8bppIndexed format and its palette does contain transparent colors (e.g. it draws correctly onto form/picturebox)
Edit: My bad, Bitmap.Save() actually saves BMP in Format32bppRgb format, even though the bitmap format is Format32bppArgb.
It's because by default the implementation of the bmp file format doesn't support transparency while the png file format does.
If you want transparency that will be read by other applications you are going to have to use png. The compression algorithms are lossless so you're not going to get artefacts in your image. The file will take up less space on disk too.
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");
I'm working on a Machine Learning problem at college and my first job is to convert images into black and white.
The problem is that my image has transparency, and I don't know how to remove it.
What I am trying:
public static Bitmap RemoveTransparency (Bitmap src)
{
Bitmap target = new Bitmap (src.Size.Width, src.Size.Height);
Graphics g = Graphics.FromImage (target);
g.Clear (Color.White);
g.DrawImage (src, 0, 0);
return target;
}
An example of an input image:
An example of output image after "RemoveTransparency" call:
Does anyone know what's going on? Seems like background an the letters have the same color... and my my background is black if I'm coloring to white?
Thank you!
You need to set the CompositingMode of your Graphics object to SourceOver before drawing the other image on top of it.
g.Clear(Color.White);
g.CompositingMode = CompositingMode.SourceOver;
g.DrawImage(src, 0, 0);
The default CompositingMode is SourceCopy, which is taking the transparent black (R=G=B=A=0) pixels in your src image and rendering them as black pixels. SourceOver will do alpha blending, which is what you're looking for.
See here for details: CompositingMode Enumeration
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).