So here is my problem
I've used a scanner to scan an object in greyscale and convert it into a JPEG format to be analyzed by a C# program. The image's pixelformat is 8BppIndexed.
When I import this image into C# and draw a histogram of it, I only see 16 grayscale values, like this:
All the values in between these peaks are 0.
This is what the normal histogram should look like (don't mind the colors, this histogram is made with another tool):
The first histogram (int[]) is formed with this code:
public static int[] GetHistogram(Bitmap b)
{
int[] myHistogram = new int[256];
for (int i = 0; i < myHistogram.Length; i++)
myHistogram[i] = 0;
BitmapData bmData = null;
try
{
//Lock it fixed with 32bpp
bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int scanline = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nWidth = b.Width;
int nHeight = b.Height;
for (int y = 0; y < nHeight; y++)
{
for (int x = 0; x < nWidth; x++)
{
long Temp = 0;
Temp += p[0]; // p[0] - blue, p[1] - green , p[2]-red
Temp += p[1];
Temp += p[2];
Temp = (int)Temp / 3;
myHistogram[Temp]++;
//we do not need to use any offset, we always can increment by pixelsize when
//locking in 32bppArgb - mode
p += 4;
}
}
}
b.UnlockBits(bmData);
}
catch
{
try
{
b.UnlockBits(bmData);
}
catch
{
}
}
return myHistogram;
}
To be sure this code is not the problem, I've tried using the AForge.Math.Histogram way and even a for - in - for loop to iterate through all pixels. Each time I get the same result.
Now here is the funny part(s):
When I draw the histogram with any other tool (used 3 others), I get
a normal histogram. This tells me that the information is within the image, but my code just can't get it out.
When I scan the exact same object and set the settings to export the image into a .bmp file, c# is able to draw a normal histogram
With another random .jpg image I found on my computer, c# is able to draw a normal
histogram.
These points tell me that there is probably something wrong with the way that I import the image into my code, so I tried different ways to import the image:
Bitmap bmp = (Bitmap)Bitmap.FromFile(path);
or
Bitmap bmp = AForge.Imaging.Image.FromFile(path);
or
Stream imageStreamSource = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
System.Windows.Media.Imaging.JpegBitmapDecoder decoder = new System.Windows.Media.Imaging.JpegBitmapDecoder(imageStreamSource, System.Windows.Media.Imaging.BitmapCreateOptions.PreservePixelFormat, System.Windows.Media.Imaging.BitmapCacheOption.Default);
System.Windows.Media.Imaging.BitmapSource bitmapSource = decoder.Frames[0];
System.Windows.Controls.Image image = new System.Windows.Controls.Image();
image.Source = bitmapSource;
image.Stretch = System.Windows.Media.Stretch.None;
MemoryStream ms = new MemoryStream();
var encoder = new System.Windows.Media.Imaging.BmpBitmapEncoder();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(image.Source as System.Windows.Media.Imaging.BitmapSource));
encoder.Save(ms);
ms.Flush();
System.Drawing.Image myImage = System.Drawing.Image.FromStream(ms);
Bitmap bmp = (Bitmap)Bitmap.FromStream(ms);
None of which gave a different histogram than the one with just 16 results.
I can not use the .bmp extension in my scanner, because I need to make a great many images and one .bmp image is around 200mb (yea, the images need a high resolution), while the .jpg is only around 30mb. Plus I've already made many .jpg images that can not be remade because the objects that have been scanned no longer exist.
NOTE: I know that using the .jpg extension is a lossy way to compress the images. That is not the current issue.
This is what a histogram, created with the exact same code as the first one, looks like with another random .jpg image from my computer:
Does this sound familiar to anyone? I feel like I've tried everything. Is there another way to solve this problem that I have not yet found?
EDIT
I thought I had found an extremely dirty way to fix my problem, but it does change the histogram:
Bitmap temp = (Bitmap)Bitmap.FromFile(m_sourceImageFileName);
if (temp.PixelFormat == PixelFormat.Format8bppIndexed ||
temp.PixelFormat == PixelFormat.Format4bppIndexed ||
temp.PixelFormat == PixelFormat.Format1bppIndexed ||
temp.PixelFormat == PixelFormat.Indexed)
{
//Change pixelformat to a format that AForge can work with
Bitmap tmp = temp.Clone(new Rectangle(0, 0, temp.Width, temp.Height), PixelFormat.Format24bppRgb);
//This is a super dirty way to make sure the histogram shows more than 16 grey values.
for (int i = 0; true; i++)
{
if (!File.Exists(m_sourceImageFileName + i + ".jpg"))
{
tmp.Save(m_sourceImageFileName + i + ".jpg");
tmp.Dispose();
temp = AForge.Imaging.Image.FromFile(m_sourceImageFileName + i + ".jpg");
File.Delete(m_sourceImageFileName + i + ".jpg");
break;
}
}
}
Bitmap properImage = temp;
This is the new histogram:
As you can see, it's not the same as what the histogram should look like.
I found out that the problem might be because the image is an 8bppIndexed jpeg image, and jpeg only supports 24bppRgb images. Any solutions?
I think the clue is in the type being "indexed" in your second line. There are probably only 16 colours in the lookup table. Can you post your original scanned image so we can see if there are really more shades in it? If not, try using ImageMagick to count the colours
Like this to get a histogram:
convert yourimage.jpg -format %c histogram:info:-
convert yourimage.jpg -colorspace rgb -colors 256 -depth 8 -format "%c" histogram:info:
Or count the unique colours like this:
identify -verbose yourimage.jpg | grep -i colors:
Or dump all the pixels like this:
convert yourimage.jpg -colorspace rgb -colors 256 -depth 8 txt:
Well, I solved it by opening the JPEG and saving it as bmp with the ImageJ library in java. I've made a .jar file from the code and I use this code to get the bmp into my c# code:
string extension = m_sourceImageFileName.Substring(m_sourceImageFileName.LastIndexOf("."), m_sourceImageFileName.Length - m_sourceImageFileName.LastIndexOf("."));
int exitcode;
ProcessStartInfo ProcessInfo;
Process process;
ProcessInfo = new ProcessStartInfo("java.exe", #"-jar ""C:\Users\stevenh\Documents\Visual Studio 2010\Projects\BlackSpotDetection V2.0\ConvertToBmp\dist\ConvertToBmp.jar"" " + extension + " " + m_sourceImageFileName + " " + m_addedImageName);
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
// redirecting standard output and error
ProcessInfo.RedirectStandardError = true;
ProcessInfo.RedirectStandardOutput = true;
process = Process.Start(ProcessInfo);
process.WaitForExit();
//Reading output and error
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
exitcode = process.ExitCode;
if (exitcode != 0)
{
statusLabel.Text = output;
MessageBox.Show("Error in external process: converting image to bmp.\n" + error);
//Exit code '0' denotes success and '1' denotes failure
return;
}
else
statusLabel.Text = "Awesomeness";
process.Close();
Bitmap realImage = AForge.Imaging.Image.FromFile(m_addedImageName);
File.Delete(m_addedImageName);
The jar will receive the extension, m_sourceImageFileName and m_addedImageFileName. It will open the sourceImage and save it under the name of m_addedImageFileName
I'm using the AForge library to open the image, because this library doesn't lock the image while it's opened, which makes me able to delete the 'home-made' image.
Related
I'm writing an application that has critical requirements for fast image processing. There will be a huge amount of images. I need to save them in fast-processible format. So, I decided to save colorful mipmaps with 8-bit color depth (using palette) and then save it as a byte array. So, I need the way to convert formats very fast and in-memory. Also, it must be cross-platform as far as possible (i write code on Windows and will deploy to AWS).
I have tried ImageSharp and .NET Core System.Drawing.Common packages. In the case of ImageSharp, I got almost an acceptable result: the quality of the resized image is perfect, but it works really slowly... I got 12 seconds for 4096x4096 24-bit full palette image. There are 256 distinct colors in the resulting palette, that is perfect for me.
In the case of System.Drawing.Common library I got much faster conversion speed:
900 ms for tiff format
2500 ms for gif format.
But, the number of distinct colors in the resulting palette:
for tiff: 224 (not so good for my solution)
for gif: 252 (good)
Also, the resulting image in tiff format has very poor quality, so probably it can't be used (maybe I am wrong).
System.Drawing.Common implementation:
var parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8L);
ImageCodecInfo bmpEncoder = ImageCodecInfo.GetImageEncoders()
.FirstOrDefault(x => x.FormatID == ImageFormat.Gif.Guid);
Stopwatch sw = Stopwatch.StartNew();
var allColors = new Bitmap(#"16777216colors.png");
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 462
using (MemoryStream ms = new MemoryStream())
{
allColors.Save(ms, bmpEncoder, parameters);
Console.WriteLine("Saved: " + sw.ElapsedMilliseconds); // 2104
ms.Position = 0;
Bitmap bitmap = new Bitmap(ms);
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 2383
// set to determine palette size
HashSet<int> set = new HashSet<int>();
// Temporary solution. It is needed to replace GetPixel to faster analog
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
var val = bitmap.GetPixel(i, j).ToArgb();
set.Add(val);
}
}
Console.WriteLine("Palette size: " + set.Count); // 252
Console.WriteLine("All done: " + sw.ElapsedMilliseconds); // 26345
}
/*
Output:
Loaded: 462
Saved: 2104
Loaded: 2383
Palette size: 252
All done: 26345
*/
ImageSharp implementation:
PngEncoder encoder = new PngEncoder()
{
ColorType = PngColorType.Palette,
BitDepth = PngBitDepth.Bit8,
CompressionLevel = 1
};
Image<Rgba32> img;
Stopwatch sw = Stopwatch.StartNew();
using (var file = File.OpenRead(#"16777216colors.png"))
{
img = Image.Load(file);
}
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 1302
using (MemoryStream ms = new MemoryStream())
{
img.Save(ms, encoder);
Console.WriteLine("Saved: " + sw.ElapsedMilliseconds); // 12608
ms.Position = 0;
img = Image.Load(ms);
Console.WriteLine("Loaded: " + sw.ElapsedMilliseconds); // 12813
}
// set to determine palette size
HashSet<uint> set = new HashSet<uint>();
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
var val = img[i, j].PackedValue;
set.Add(val);
}
}
Console.WriteLine("Palette size: " + set.Count); // 256
Console.WriteLine("All done: " + sw.ElapsedMilliseconds); // 15807
/*
Output:
Loaded: 1302
Saved: 12608
Loaded: 12813
Palette size: 256
All done: 15807
*/
So, the best way seems to be to use System.Drawing.Common implementation, but there is probably will be overhead related to slow pixel reading.
So, questions:
I expected high performance form ImageSharp, so I guess I doing
something wrong... It would be cool to know how to improve the performance! If anybody knows what I doing wrong please tell me.
Does it guaranteed that palette colors will be the
same for any image, so, I will be able to cache any-to-any color difference between these 256 colors?
Does anybody know the best way to solve my problem in other ways? I'll be glad to see suggestions. The solution must be cross-platform and fast as it possible up to byte array data representation
Try to use Accord for it. Its hard to say if it is fast. You Need to try it in your scenerio.
You want to have 8bpp image? So you want it to be GreyScale? Then, use this function from using Accord.Imaging.Filters;.
private Bitmap Create8bppGreyscaleImage(Bitmap bitmap)
=> Grayscale.CommonAlgorithms.BT709.Apply(bitmap);
This convertion on This jpg image take 500ms. Produced image look like this:
As you can see, Grayscale.CommonAlghoritms.BT709 takes Bitmap as argument. If you want to byte array, just use Marshall.Copy:
BitmapData bitmapData = this.Bitmap.LockBits(
new Rectangle(0, 0, this.Bitmap.Width, this.Bitmap.Height),
ImageLockMode.ReadWrite,
this.Bitmap.PixelFormat);
int pixels = bitmapData.Stride * this.Bitmap.Height;
byte[] bytes = new byte[pixels];
Marshal.Copy(bitmapData.Scan0, bytes, 0, pixels);
this.Bitmap.UnlockBits(bitmapData);
this.Bytes = bytes;
Loading image as 8bpp byte array by this method (image above) take 18ms.
It will save byte array to memory. Then, you can unlock bits to your bitmap and save it for example as .png:
BitmapData bitmapData = this.Bitmap.LockBits(
new Rectangle(0, 0, this.Bitmap.Width, this.Bitmap.Height),
ImageLockMode.ReadWrite,
this.Bitmap.PixelFormat);
int stride = bitmapData.Stride * this.Height;
Marshal.Copy(bytes, 0, bitmapData.Scan0, stride);
this.Bitmap.UnlockBits(bitmapData);
this.Bitmap.Save("test.png");
Tells me if my answer was helpful to you. You dont have reference to System.Drawing.dll out of the box in .NET Core, you need to add it on yourself
I want to read a dicom image using simpleitk, convert it into a bitmap and then display the result in a pictureBox. But when I'm trying to do this, an ArgumentException is thrown. How can I solve this?
Here is my code:
OpenFileDialog dialog = new OpenFileDialog();
dialog.Title = "Open";
dialog.Filter = "DICOM Files (*.dcm;*.dic)|*.dcm;*.dic|All Files (*.*)|*.*";
dialog.ShowDialog();
if (dialog.FileName != "")
{
using (sitk.ImageFileReader reader = new sitk.ImageFileReader())
{
reader.SetFileName(dialog.FileName);
reader.SetOutputPixelType(sitk.PixelIDValueEnum.sitkFloat32);
sitk.Image image = reader.Execute();
var castedImage = sitk.SimpleITK.Cast(image,
sitk.PixelIDValueEnum.sitkFloat32);
var size = castedImage.GetSize();
int length = size.Aggregate(1, (current, i) => current * (int)i);
IntPtr buffer = castedImage.GetBufferAsFloat();
// Declare an array to hold the bytes of the bitmap.
byte[] rgbValues = new byte[length];
// Copy the RGB values into the array.
Marshal.Copy(buffer, rgbValues, 0, length);
Stream stream = new MemoryStream(rgbValues);
Bitmap newBitmap = new Bitmap(stream);
//I have tried in this way, but it generated ArgumentException too
//Bitmap newBitmap = new Bitmap((int)image.GetWidth(), (int)image.GetHeight(), (int)image.GetDepth(), PixelFormat.Format8bppIndexed, buffer);
Obraz.pic.Image = newBitmap;
}
}
Thank you for your comments and attempts to help. After consultations and my own searching on the internet I solved this issue. The first problem was the inadequate representation of pixel image. I had to change Float32 to UInt8 to provide an eight-bit for pixel.
var castedImage = sitk.SimpleITK.Cast(image2, sitk.PixelIDValueEnum.sitkUInt8);
Then I would already create a Bitmap using the constructor that was was commented out in question, but with (int)image.GetWidth() instead of (int)image.GetDepth().
Bitmap newBitmap = new Bitmap((int)image.GetWidth(), (int)image.GetHeight(), (int)image.GetWidth(), PixelFormat.Format8bppIndexed, buffer);
Unfortunately, a new problem appeared. The image, that was supposed to be in gray scale, was displayed in strange colors. But I found the solution here
ColorPalette pal = newBitmap.Palette;
for (int i = 0; i <= 255; i++)
{
// create greyscale color table
pal.Entries[i] = Color.FromArgb(i, i, i);
}
newBitmap.Palette = pal; // you need to re-set this property to force the new ColorPalette
I'm developing a small C# tool that must be able to load a TIFF image, crop the image to a certain size, and save it as a PNG file.
I have large greyscale TIFF images of about 28000x256 pixels with 32-bit bit depth. When I try to process the images with my tool, it just outputs a blank white image.
Also, when I try to open the original TIFF images (not the ones processed with my tool) with the Windows Photo Viewer, it also shows a blank white image. Some other applications, e.g. ImageJ, display the image correctly. What is the problem here?
My code to load the images looks as follows:
Image image = Bitmap.FromFile(path.LocalPath);
int width = image.Width;
int height = image.Height;
Bitmap bmp = new Bitmap(width, height);
Graphics g = Graphics.FromImage(bmp);
The problem is that C# (or better said the underlying API) can't handle Greyscale images with a Colordepth greater than 8bit.
I'd suggest using LibTiff.NET for Handling TIFF images.
When i faced such an problem, i loaded the TIFF image raw Data into an array
using (var inputImage = Tiff.Open(image, "r"))
{
width = inputImage.GetField(TiffTag.IMAGEWIDTH)[0].ToInt();
height = inputImage.GetField(TiffTag.IMAGELENGTH)[0].ToInt();
inputImageData = new byte[width * height * bytePerPixel];
var offset = 0;
for (int i = 0; i < inputImage.NumberOfStrips(); i++)
{
offset += inputImage.ReadRawStrip(i, inputImageData, offset, (int)inputImage.RawStripSize(i));
}
}
The bytes then have to be converted into an array of uint (in my case, imagedata was only 16 bit, so i used ushort) Remember to take care of Endianness of the data!
// has to be done by hand to ensure endiannes is kept correctly.
var outputImageData = new ushort[inputImageData.Length / 2];
for (var i = 0; i < outputImageData.Length; i++)
{
outputImageData[i] = (ushort)((inputImageData[i * 2 + 1]) + (ushort)(inputImageData[i * 2] << 8));
}
You can then manipulate the image using normal Array Operations. I'd suggest you to use normal Array operations and not Lambda-Expressions, as they are much faster. (in My Scenario 100s vs 2s Runtime)
Finally you can save the image using LibTiff again
using (var output = Tiff.Open(imageout, "w"))
{
output.SetField(TiffTag.IMAGEWIDTH, width);
output.SetField(TiffTag.IMAGELENGTH, height);
output.SetField(TiffTag.SAMPLESPERPIXEL, 1);
output.SetField(TiffTag.BITSPERSAMPLE, 16);
output.SetField(TiffTag.ROWSPERSTRIP, height);
output.SetField(TiffTag.PHOTOMETRIC, Photometric.MINISBLACK);
output.SetField(TiffTag.FILLORDER, FillOrder.MSB2LSB);
// Transform to Byte-Array
var buffer = new byte[outputImageData.Length * sizeof(ushort)];
Buffer.BlockCopy(outputImageData, 0, buffer, 0, buffer.Length);
// Write it to Image
output.WriteRawStrip(0, buffer, buffer.Length);
}
I'm saving a bitmap to a file on my hard drive inside of a loop (All the jpeg files within a directory are being saved to a database). The save works fine the first pass through the loop, but then gives the subject error on the second pass. I thought perhaps the file was getting locked so I tried generating a unique file name for each pass, and I'm also using Dispose() on the bitmap after the file get saved. Any idea what is causing this error?
Here is my code:
private string fileReducedDimName = #"c:\temp\Photos\test\filePhotoRedDim";
...
foreach (string file in files)
{
int i = 0;
//if the file dimensions are big, scale the file down
Stream photoStream = File.OpenRead(file);
byte[] photoByte = new byte[photoStream.Length];
photoStream.Read(photoByte, 0, System.Convert.ToInt32(photoByte.Length));
Image image = Image.FromStream(new MemoryStream(photoByte));
Bitmap bm = ScaleImage(image);
bm.Save(fileReducedDimName + i.ToString() + ".jpg", ImageFormat.Jpeg);//error occurs here
Array.Clear(photoByte,0, photoByte.Length);
bm.Dispose();
i ++;
}
...
Thanks
Here's the scale image code: (this seems to be working ok)
protected Bitmap ScaleImage(System.Drawing.Image Image)
{
//reduce dimensions of image if appropriate
int destWidth;
int destHeight;
int sourceRes;//resolution of image
int maxDimPix;//largest dimension of image pixels
int maxDimInch;//largest dimension of image inches
Double redFactor;//factor to reduce dimensions by
if (Image.Width > Image.Height)
{
maxDimPix = Image.Width;
}
else
{
maxDimPix = Image.Height;
}
sourceRes = Convert.ToInt32(Image.HorizontalResolution);
maxDimInch = Convert.ToInt32(maxDimPix / sourceRes);
//Assign size red factor based on max dimension of image (inches)
if (maxDimInch >= 17)
{
redFactor = 0.45;
}
else if (maxDimInch < 17 && maxDimInch >= 11)
{
redFactor = 0.65;
}
else if (maxDimInch < 11 && maxDimInch >= 8)
{
redFactor = 0.85;
}
else//smaller than 8" dont reduce dimensions
{
redFactor = 1;
}
destWidth = Convert.ToInt32(Image.Width * redFactor);
destHeight = Convert.ToInt32(Image.Height * redFactor);
Bitmap bm = new Bitmap(destWidth, destHeight,
PixelFormat.Format24bppRgb);
bm.SetResolution(Image.HorizontalResolution, Image.VerticalResolution);
Graphics grPhoto = Graphics.FromImage(bm);
grPhoto.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
grPhoto.DrawImage(Image,
new Rectangle(0, 0, destWidth, destHeight),
new Rectangle(0, 0, Image.Width, Image.Height),
GraphicsUnit.Pixel);
grPhoto.Dispose();
return bm;
}
If I'm reading the code right, your i variable is zero every time through the loop.
It is hard to diagnose exactly what is wrong, I would recommend that you use using statements to ensure that your instances are getting disposed of properly, but it looks like they are.
I originally thought it might be an issue with the ScaleImage. So I tried a different resize function (C# GDI+ Image Resize Function) and it worked, but i is always set to zero at beginning of each loop. Once you move i's initialization outside of the loop your scale method works as well.
private void MethodName()
{
string fileReducedDimName = #"c:\pics";
int i = 0;
foreach (string file in Directory.GetFiles(fileReducedDimName, "*.jpg"))
{
//if the file dimensions are big, scale the file down
using (Image image = Image.FromFile(file))
{
using (Bitmap bm = ScaleImage(image))
{
bm.Save(fileReducedDimName + #"\" + i.ToString() + ".jpg", ImageFormat.Jpeg);//error occurs here
//this is all redundant code - do not need
//Array.Clear(photoByte, 0, photoByte.Length);
//bm.Dispose();
}
}
//ResizeImage(file, 50, 50, fileReducedDimName +#"\" + i.ToString()+".jpg");
i++;
}
}
when I create a bitmap like this:
var testImage = new Bitmap(320, 240);
var testDataLock = testImage.LockBits(new Rectangle(new Point(), testImage.Size),
System.Drawing.Imaging.ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
unsafe
{
var aaa = CamData.ToArray();
UInt32 lOffset = 0;
UInt32 lPos = 0;
byte* lDst = (byte*)testDataLock.Scan0;
byte bitshift = 8;
fixed (UInt16* lSrc = aaa)
{
while (lOffset < testImage.Width * testImage.Height)
{
lDst[lPos] = (byte)(lSrc[lOffset] >> bitshift);
lDst[lPos + 1] = lDst[lPos];
lDst[lPos + 2] = lDst[lPos];
lOffset++;
lPos += 3;
// take care of the padding in the destination bitmap
if ((lOffset % testImage.Width) == 0)
lPos += (UInt32)testDataLock.Stride - (uint)(testImage.Width * 3);
}
}
}
testImage.UnlockBits(testDataLock);
testImage.Save(#"H:\Test.bmp");
I alway get an error while trying to open this file with an visualisation lib:
Unknown file type! H:\test.bmp is not a Windows BMP file!
but in windows I can open the file with the viewer etc... there are no problems
does anybody know why I get this error?
thanks
you can save a System.Drawing.Bitmap to a valid windows .bmp like this:
//bmp is a System.Drawing.Bitmap
bmp.Save("MyBitmap.bmp", ImageFormat.Bmp);
The second parameter (which you did not include) specifies the format in which the bitmap must be saved.
Also, be sure to check if your visualisation lib supports 24Bit Per Pixel bitmaps,
since this is the format you are creating your bitmap in.
see:
PixelFormat.Format24bppRgb
As you can read at MSDN in the Remarks section your image will be saved as PNG if no encoder is specified.