Create real grayscale images using C# - c#

I'm trying to create a real grayscale image, but when my designer checks the file he is saying that the image remains RGB.
You can see the photoshop print here:
I've tried 2 methods so far:
1) Using the MakeGrayScale3 method:
public static Image MakeGrayscale3(Image original)
{
//create a blank bitmap the same size as original
Bitmap newBitmap = new Bitmap(original.Width, original.Height);
newBitmap.SetResolution(original.HorizontalResolution, original.VerticalResolution);
//get a graphics object from the new image
Graphics g = Graphics.FromImage(newBitmap);
//create the grayscale ColorMatrix
ColorMatrix colorMatrix = new ColorMatrix(
new float[][]
{
new float[] {.3f, .3f, .3f, 0, 0},
new float[] {.59f, .59f, .59f, 0, 0},
new float[] {.11f, .11f, .11f, 0, 0},
new float[] {0, 0, 0, 1, 0},
new float[] {0, 0, 0, 0, 1}
});
//create some image attributes
ImageAttributes attributes = new ImageAttributes();
//set the color matrix attribute
attributes.SetColorMatrix(colorMatrix);
//draw the original image on the new image
//using the grayscale color matrix
g.DrawImage(original, new Rectangle(0, 0, original.Width, original.Height),
0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes);
//dispose the Graphics object
g.Dispose();
return newBitmap;
}
2) Using the ImageMagick.Net library with this code:
using (MagickImage image = new MagickImage(file))
{
image.ColorSpace = ColorSpace.GRAY;
image.Write(originalFile);
}
If anyone had this problem before or have a clue of what to do to change that..
Thanks!
Original Image:
Result Image (ImageMagick):
Result Image (MakeGrayscale3):

The grayscale format used by jpeg isn't available in the classic System.Drawing classes. It is, however, available in System.Windows.Media. You need to add PresentationCore and WindowsBase as references for using them (this will not be portable on Linux).
public static void SaveAsGrayscaleJpeg(String loadPath, String savePath)
{
using (FileStream fs = new FileStream(savePath, FileMode.Create))
{
BitmapSource img = new BitmapImage(new Uri(new FileInfo(loadPath).FullName));
FormatConvertedBitmap convertImg = new FormatConvertedBitmap(img, PixelFormats.Gray8, null, 0);
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(convertImg));
encoder.Save(fs);
}
}
As a bonus, this does the grayscale conversion automatically.
If you're actually starting from Image/Bitmap class, there are some solutions around for converting between them.
Also you should pay attention to:
https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only

Thanks for all answers but after read the topic that Hans mentioned there they talk about the FreeImageNET library that I'm using to solve my problem.
Using this code I've managed to save the image as a grayscale image:
FreeImageAPI.FreeImage.ConvertTo8Bits(newimg)

The issue is this line:
Bitmap newBitmap = new Bitmap(original.Width, original.Height);
If you look at the MSDN page it says:
This constructor creates a Bitmap with a PixelFormat enumeration value of Format32bppArgb.
So, you're just creating another color image. What you need to do is to use a different constructor to force a different format:
Bitmap newBitmap = new Bitmap( original.Width, original.Height,
PixelFormat.Format8bppIndexed );
But then that gives you another issue - you can't paint to bitmaps with that pixel format with a Graphics object. There is a PixelFormat.Format16bppGrayScale which appears to work even less.
So, my suggestion would be to to this manually. Use LockBits to get access to the raw bitmap and do the conversion yourself. It is a little bit more work (i.e. you can't use the ColorMatrix stuff, and you have to take care to check if the source bitmap is 24 bit or 32 bit), but it will work just fine.
One issue to be aware of is that it's not a "real" grayscale format, it's 8 bit indexed. So you will also have to create a palette. Simply map each of the 256 values to the same, i.e. 0 -> 0, 100 -> 100, 255 -> 255.

Related

Change Bitmap PixelFormat from Format16bppGrayScale to Format32bppArgb - Out of Memory Exception

I am trying to convert a grayscale Bitmap (Format16bppGrayScale) to a color Bitmap (Format32bppArgb) like so:
Bitmap color = gray.Clone(new Rectangle(0, 0, gray.Width, gray.Height), PixelFormat.Format32bppArgb);
I keep getting a System.OutOfMemoryException. I have been researching and this error usually occurs when the rectangle provided to Clone is bigger than the actual image that you are trying to clone. This is not the case here since I am using the image dimensions to create the rectangle. Are there known issues with this type of conversions? Are there any other ways to achieve a copy in a different PixelFormat?
Thanks,
I read, people have problem like you. Try this way:
Change PixelFormat, while you try clone, system have problem.
Biggest reason is new definition of Bitmap.
Bitmap clone = new Bitmap(gray.Width, gray.Height, PixelFormat.Format32bppArgb);
using (Graphics gr = Graphics.FromImage(clone)) {
gr.DrawImage(color, new Rectangle(0, 0, clone.Width, clone.Height));
}
Or without DrawImage function:
using (Bitmap color = gray.Clone(new Rectangle(0, 0, gray.Width, gray.Height), PixelFormat.Format32bppArgb))
{
// color is now in the desired format.
}

Applying BackwardQuadrilateralTransformation to defined GRAFT points

I have a small piece of C# code that uses a Kinect to detect up to 4 glyphs and draws and polygon between them on a canvas, as seen here:
I've tried to follow along to this in order to implement 2D augmented reality and project an image within the created polygon. I've read in a source image and tried to apply the BackwardQuadrilateralTransformation to it but can't seem to display the transformed image. I am probably using the wrong method but I have tried to convert the image and paint it onto a canvas with no luck. I'm not sure if I'm just massively misunderstanding the method and maybe it isn't possible, any help would be greatly appreciated. I can supply more sample code if required.
private void GlyphBackQuad(List<IntPoint> quadpoints)
{
Bitmap srcImage = new Bitmap( // my sample image filepath );
UnmanagedImage sourceImage = UnmanagedImage.FromManagedImage(srcImage);
BackwardQuadrilateralTransformation filter = new BackwardQuadrilateralTransformation(sourceImage, quadpoints);
filter.Apply(sourceImage);
Bitmap bmp = sourceImage.ToManagedImage();
ImageBrush ib = new ImageBrush();
ib.ImageSource = ConvertDrawingImage2MediaImageSource(bmp);
PolyCanvas.Background = ib;
}
After a further play around with the code I think I have developed a partial solution, it still needs some work but hopefully this is more helpful for people to read/debug.
private void GlyphBackQuad(List<IntPoint> quadpoints, Bitmap bmp)
{
// Read in bitmap source image and clone it to the same format as destination
Bitmap srcImage = AForge.Imaging.Image.Clone(new Bitmap( // my sample filepath), System.Drawing.Imaging.PixelFormat.Format24bppRgb);
System.Drawing.Imaging.BitmapData bitmapData = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
// Convert to unmanaged image
UnmanagedImage unmanagedImage = new UnmanagedImage( bitmapData );
// Filter
BackwardQuadrilateralTransformation filter = new BackwardQuadrilateralTransformation(srcImage, quadpoints);
filter.ApplyInPlace(unmanagedImage);
// Convert back to managed image and save
Bitmap managedImage = unmanagedImage.ToManagedImage();
managedImage.Save( // my save filepath, System.Drawing.Imaging.ImageFormat.Png);
}

Possible to reproduce the "resize image" quality of Paint.NET?

I have been having a tough time creating a thumbnail that is not horrible quality. So far the best code i've come up with is:
Bitmap bmp = new Bitmap(width, height);
Graphics graphic = Graphics.FromImage(bmp);
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.HighQuality;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.CompositingQuality = CompositingQuality.HighQuality;
graphic.DrawImage(photo, 0, 0, width, height);
return imageToByteArray(bmp);
Which produces this gem:
If I resize the same image in Paint.NET i get this:
Which is WAY better. Everything I've found on line points me to some variation of the code I have above. I know Paint.NET was open source at one point. Does anyone know what magic they were doing to create such nice resize functionality and if that functionality can be reproduced in C#?
UPDATE:
The original image from this example was a jpg
GIFs
I recalled reading that .NET has issues with palette-based formats, like GIF, so I dug up a few articles.
This article describes how to quantize (pick an optimum palette) to improve quality: http://msdn.microsoft.com/en-us/library/aa479306.aspx, as does this (badly formatted) article.
In brief, I believe GDI+ picks a non-optimum palette when performing the resize.
PNGs
PNGs are palette-based, so they may be prone to the same issues as GIFs. I'm not sure if it matters that the palette can be much larger.
JPEG-friendly example
This code should work fine on JPEGs (but does not render GIFs smoothly). If you try it and it pixelates a JPEG, then there is probably something else going on.
private static byte[] GetScaledImage( byte[] inputBytes, int width, int height ) {
Image img = null;
using( MemoryStream ms = new MemoryStream() ) {
ms.Write( inputBytes, 0, inputBytes.Length );
img = Image.FromStream( ms );
}
using( MemoryStream ms = new MemoryStream() ) {
using( Image newImg = new Bitmap( width, height ) ) {
using( Graphics g = Graphics.FromImage( newImg ) ) {
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage( img, 0, 0, width, height );
newImg.Save( ms, img.RawFormat );
return ms.GetBuffer();
}
}
}
}
since Bitmap(int,int) is effectively Bitmap(int,int,PixelFormat.Format32bppArgb) I think the problem is in source image. Try to create another intermediate copy of the image of the same size as source image if using palette, then use that 32bppArgb image source for your resize function.

How to convert 32-bit RGBA Image to Grayscale preserving alpha

I'd like to, in code and on demand, convert a 32-bit RGBA Image object (originally a 32-bit PNG) to its 32-bit grayscale counterpart.
I've already read several other questions here, as well as many articles online. I've tried using ColorMatrix to do it, but it doesn't seem to handle the alpha very well. Pixels that are entirely opaque grayscale perfectly. Any pixel that is partially transparent seems not to translate well as there are still tinges of color in those pixels. It is enough to be noticeable.
The ColorMatrix I use is as follows:
new System.Drawing.Imaging.ColorMatrix(new float[][]{
new float[] {0.299f, 0.299f, 0.299f, 0, 0},
new float[] {0.587f, 0.587f, 0.587f, 0, 0},
new float[] {0.114f, 0.114f, 0.114f, 0, 0},
new float[] { 0, 0, 0, 1, 0},
new float[] { 0, 0, 0, 0, 1}
});
This is, as I've read, a pretty standard NTSC weighted matrix. I then use it, along with Graphics.DrawImage, but as I said the partially transparent pixels are still colored. I should point out this is displaying the Image object via a WinForms PictureBox on a white background. Could it be perhaps just the way PictureBox's draw their images and handle the transparent parts? The background colors are not affecting it (the tinge of color is from the original image for sure), but perhaps PictureBox isn't redrawing the transparent pixels correctly?
I've seen some methods that use a FormatConvertedBitmap along with an OpacityMask. I haven't tried it, mainly because I'd really prefer not to have to import PresentationCore.dll (not to mention that means it won't work in .NET 2.0 limited apps). Surely the basic System.Drawing.* stuff can do this simple procedure? Or not?
Are you by any chance painting the image onto itself using a ColorMatrix? That won't work of course (because if you paint something semi-transparent-gray over a green pixel, some green will shine through). You need to paint it onto a new, empty bitmap containing only transparent pixels.
Thanks to danbystrom's idle curiosity, I was indeed redrawing on top of the original. For anyone interested, here's the corrected method I used:
using System.Drawing;
using System.Drawing.Imaging;
public Image ConvertToGrayscale(Image image)
{
Image grayscaleImage = new Bitmap(image.Width, image.Height, image.PixelFormat);
// Create the ImageAttributes object and apply the ColorMatrix
ImageAttributes attributes = new System.Drawing.Imaging.ImageAttributes();
ColorMatrix grayscaleMatrix = new ColorMatrix(new float[][]{
new float[] {0.299f, 0.299f, 0.299f, 0, 0},
new float[] {0.587f, 0.587f, 0.587f, 0, 0},
new float[] {0.114f, 0.114f, 0.114f, 0, 0},
new float[] { 0, 0, 0, 1, 0},
new float[] { 0, 0, 0, 0, 1}
});
attributes.SetColorMatrix(grayscaleMatrix);
// Use a new Graphics object from the new image.
using (Graphics g = Graphics.FromImage(grayscaleImage))
{
// Draw the original image using the ImageAttributes created above.
g.DrawImage(image,
new Rectangle(0, 0, grayscaleImage.Width, grayscaleImage.Height),
0, 0, grayscaleImage.Width, grayscaleImage.Height,
GraphicsUnit.Pixel,
attributes);
}
return grayscaleImage;
}
If you convert the image to TGA, an uncompressed imaeg format you can use "RubyPixels" to edit the pixel data directly, doing whatever you please. You can then convert it back to PNG.
I recomend doing to conversion with ImageMagick, also from ruby.

How to create Bitmap from System.Window.media.PixelFormats.Gray16

I am successfully drawn images from their raw pixel data.(only 8 bit images).
here is the code for doing the same thing.
PixelFormat format = PixelFormat.Format8bppIndexed;
Bitmap bmp = new Bitmap(Img_Width, Img_Height, format);
Rectangle rect = new Rectangle(0, 0, Img_Width, Img_Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, format);
Marshal.Copy(rawPixel, 0, bmpData.Scan0, rawPixel.Length);
bmp.UnlockBits(bmpData);
Now as you all know PixelFormat.format16bppGrayscale is not supported by c# 2.0 GDI+.
I googled and got 3.0/3.5 framework support this.
So i installed both.
The class which is support is System.windows.media.PixelFormats.
PixelFormats.Gray16
Now my problem is how to create a bitmap and get a image for display by passing this parameter.
i got something BitmapSource class there but i am very new in C#3.0.
Please help me.
Try this:
private static Bitmap changePixelFormat(Bitmap input, PixelFormat format)
{
Bitmap retval=new Bitmap(input.Width, input.Height, format);
retval.SetResolution(input.HorizontalResolution, input.VerticalResolution);
Graphics g = Graphics.FromImage(retval);
g.DrawImage(input, 0, 0);
g.Dispose();
return retval;
}
Take a look at the Greyscale filters in AForge.net. You can find source here.
EDIT:
As a note on that source I linked, it is using an 'old' version of AForge.NET, but the concepts are the same.

Categories

Resources