Converting to real 8bpp Jpeg - c#

I already searched and tried all the suggestions given it at SO, AForge, FreeImage and couple more websites, but I'm unable to transform an image into a real 8bpp one. I always get the JPEG saved as 24bpp instead of 8.
After the grayscale conversion I have a MemoryBMP (according to myImage.RawFormat), so when I save it like this
myImage.Save("image.jpg");
I get a 3Mb+ image (so I assume it's saved in BMP) which Windows tells me is 8bpp (but I need it to be JPEG). But if I save it like this
myImage.Save("image_JPEG.jpg", ImageFormat.Jpeg);
I get a 400Kb image (so I assume it's saved in JPEG) but it's in 24bpp.
Any ideas what can be causing this?
EDIT
As JYelton mentioned this is a limitation of Image.Save() method, so I changed to saving the image with the FreeImage library: FreeImage.SaveBitmap() works like a charm.

UPDATE: The Image.Save() method does not support 8 bit per pixel for JPEG format. You may want to use the FreeImage library instead, as mentioned in the comments below.
If you want to reduce color depth to 8 bits per pixel, typically that is the same as converting from 24-bit color to grayscale, where each color channel has 8 bits per pixel to begin with. (In other words, reducing 3 channels of color information to 1.)
The default encoder when using Image.Save() and specifying ImageFormat.Jpeg is 24 bpp, so you'll need to specify an encoder and supply some parameters:
ImageCodecInfo[] availableCodecs = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo jpgCodec = availableCodecs.FirstOrDefault(codec => codec.MimeType == "image/jpeg");
if (jpgCodec == null)
throw new NotSupportedException("Encoder for JPEG not found.");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8L);
myImage.Save("image_JPEG.jpg", jpgCodec, encoderParams);
This is a modified example from a longer explanation I found at aspnet-answers.com.

Here is one way to verify if ImageFormat.Jpeg supports Encoder.ColorDepth:
using System.Linq;
bool IsSupportedParameters(ImageFormat imageFormat, Encoder encoder)
{
var codec = ImageCodecInfo.GetImageEncoders().First(c => c.FormatID == imageFormat.Guid);
Bitmap bitmap = new Bitmap(1, 1);
EncoderParameters? paramList = bitmap.GetEncoderParameterList(codec.Clsid);
if (paramList != null)
{
return paramList.Param.Any(encParams => encParams.Encoder.Guid == encoder.Guid);
}
return false;
}
Lead to:
IsSupportedParameters(ImageFormat.Jpeg, Encoder.ColorDepth); // false
IsSupportedParameters(ImageFormat.Jpeg, Encoder.ChrominanceTable); // true
So you need to use another library to achieve what you want. For example see:
https://stackoverflow.com/a/31962503/136285
References:
How to: Determine the Parameters Supported by an Encoder
Encoder.ColorDepth Field

Related

C#/System.Drawing: Preserve COM marker in JPEG file (no EXIF)

I am doing a 90° rotation with minimal loss, using the following code:
System.Drawing.Image originalImage = System.Drawing.Image.FromFile("input.jpg");
ImageFormat sourceFormat = originalImage.RawFormat;
EncoderParameters encoderParams = null;
try
{
if (sourceFormat.Guid == ImageFormat.Jpeg.Guid)
{
encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Transformation,
(long)EncoderValue.TransformRotate90);
}
originalImage.Save("output.jpg", GetEncoder(sourceFormat), encoderParams);
}
finally
{
if (encoderParams != null)
encoderParams.Dispose();
}
However the Save function seems to create EXIF metadata from the original (pure, no EXIF) JPEG COM marker (0xFE). I do not want EXIF markers in the output JPEG. I also want to preserve the original COM marked. What C# API in my application can I use instead to save my rotated buffer ?
Using jpegdump (dicom3tools package):
$ jpegdump < input.jpg
[...]
Offset 0x0014 Marker 0xfffe COM Comment length variable 0x10
While:
$ jpegdump < output.jpg
[...]
Offset 0x0014 Marker 0xffe1 APP1 Reserved for Application Use length variable 0x5a
Turns out the only working solution I could come up with, was saving the JPEG to a MemoryStream and then post process this temporary Stream using the solution described at:
https://stackoverflow.com/a/1252097/136285
Pseudo code:
var jpegPatcher = new JpegPatcher();
FileStream outFile = new FileStream(fileName, FileMode.Open, FileAccess.Write);
jpegPatcher.PatchAwayExif(inStream, outFile);
I used the code from the blog, which does:
private void SkipAppHeaderSection(Stream inStream)
...
while (header[0] == 0xff && (header[1] >= 0xe0 && header[1] <= 0xef))
So the function name PatchAwayExif is a bit odd since it also remove the APP0 (aka JFIF) segment...but that was something I also needed.
I may be wrong but when the way you are rotating the image, you are not transforming the pixel matrix but simply changing the EXIF data to tell what the image orientation is.
So when you are saving it, it is just adding in the EXIF orientation flag to the original.
If you removed that EXIF data the image would lose its 90º rotation.
More info on rotation EXIF flag - here
Example of rotating an image by changing EXIF - here
If you do physically rotate the image using Image.RotateFlip you can remobve the exif data using originalImage.RemovePropertyItem(0x0112)

Using SharpAvi to save screenshots to an AVI produces 100 frames of blank video

My game takes a screenshot each game loop and stores it memory. The user can then press "print screen" to trigger "SaveScreenshot" (see code below) to store each screenshot as a PNG and also compile them into an AVI using SharpAvi. The saving of images works fine, and a ~2sec AVI is produced, but it doesn't show any video when played. It's just the placeholder VLC Player icon. I think this is very close to working, but I can't determine what's wrong. Please see my code below. If anyone has any ideas, I'd be very appreciative!
private Bitmap GrabScreenshot()
{
try
{
Bitmap bmp = new Bitmap(this.ClientSize.Width, this.ClientSize.Height);
System.Drawing.Imaging.BitmapData data =
bmp.LockBits(this.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
GL.ReadPixels(0, 0, this.ClientSize.Width, this.ClientSize.Height, PixelFormat.Bgr, PixelType.UnsignedByte,
data.Scan0);
bmp.UnlockBits(data);
bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
return bmp;
} catch(Exception ex)
{
// occasionally getting GDI generic exception when rotating the image... skip that one.
return null;
}
}
private void SaveScreenshots()
{
var directory = "c:\\helioscreenshots\\";
var rootFileName = string.Format("{0}_", DateTime.UtcNow.Ticks);
var writer = new AviWriter(directory + rootFileName + ".avi")
{
FramesPerSecond = 30,
// Emitting AVI v1 index in addition to OpenDML index (AVI v2)
// improves compatibility with some software, including
// standard Windows programs like Media Player and File Explorer
EmitIndex1 = true
};
// returns IAviVideoStream
var aviStream = writer.AddVideoStream();
// set standard VGA resolution
aviStream.Width = this.ClientSize.Width;
aviStream.Height = this.ClientSize.Height;
// class SharpAvi.KnownFourCCs.Codecs contains FOURCCs for several well-known codecs
// Uncompressed is the default value, just set it for clarity
aviStream.Codec = KnownFourCCs.Codecs.Uncompressed;
// Uncompressed format requires to also specify bits per pixel
aviStream.BitsPerPixel = BitsPerPixel.Bpp32;
var index = 0;
while (this.Screenshots.Count > 0)
{
Bitmap screenshot = this.Screenshots.Dequeue();
var screenshotBytes = ImageToBytes(screenshot);
// write data to a frame
aviStream.WriteFrame(true, // is key frame? (many codecs use concept of key frames, for others - all frames are keys)
screenshotBytes, // array with frame data
0, // starting index in the array
screenshotBytes.Length); // length of the data
// save it!
// NOTE: compared jpeg, gif, and png. PNG had smallest file size.
index++;
screenshot.Save(directory + rootFileName + index + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
// save the AVI!
writer.Close();
}
public static byte[] ImageToBytes(Image img)
{
using (var stream = new MemoryStream())
{
img.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
return stream.ToArray();
}
}
From what I see, you're providing the byte-array in png-encoding, yet the stream is configured as KnownFourCCs.Codecs.Uncompressed.
Furthermore, from the manual:
AVI expects uncompressed data in format of standard Windows DIB, that is bottom-up bitmap of the specified bit-depth. For each frame, put its data in byte array and call IAviVideoStream.WriteFrame()
Next, all encoders expect input image data in specific format. It's BGR32 top-down - 32 bits per pixel, blue byte first, alpha byte not used, top line goes first. This is the format you can often get from existing images. [...] So, you simply pass an uncompressed top-down BGR32
I would retrieve the byte-array directly from the Bitmap using LockBits and Marshal.Copy as described in the manual.

C# Image: To preserve image's checksum

I have the following codes to convert an image(bitmap) to byte array:
public byte[] ConvertImageToByteArray(Image imageToConvert, ImageFormat formatOfImage)
{
byte[] Ret;
try
{
using (MemoryStream ms = new MemoryStream())
{
imageToConvert.Save(ms, formatOfImage);
Ret = ms.ToArray();
}
}
catch (Exception)
{
throw;
}
return Ret;
}
and Convert byte array back to image(bitmap):
public Bitmap ConvertByteArrayToImage(byte[] myByteArray)
{
Image newImage;
using (MemoryStream ms = new MemoryStream(myByteArray, 0, myByteArray.Length))
{
ms.Write(myByteArray, 0, myByteArray.Length);
newImage = Image.FromStream(ms, true);
}
return newImage;
}
Here's my Main Program:
byte[] test = ConvertImageToByteArray(Image.FromFile("oldImage.bmp"), ImageFormat.Bmp);
Bitmap bmp = ConvertByteArrayToImage(test);
bmp.Save("newImage.bmp");
But when I compare both of the image files(old & new bitmap images), their checksum appeared to be different. Any reason for that happening? How to fix it to maintain its integrity?
Basically, there are many ways an identical image can be encoded in a BMP file. If I try your example on a random image I found, I see the .NET Bitmap class saves the file without filling the biSizeImage field in the BITMAPINFOHEADER structure in the BMP header (but the original image produced by IrfanView has it filled), which is a completely correct and documented possibility. (“This may be set to zero for BI_RGB bitmaps.”)
And this is definitely not the only variable thing in the BMP format. For instance, there are multiple possible orderings of pixel data in the image (top-to-bottom, bottom-to-top), specified in the header. (“If biHeight is positive, the bitmap is a bottom-up DIB and its origin is the lower-left corner. If biHeight is negative, the bitmap is a top-down DIB and its origin is the upper-left corner.”)
So, if you receive any BMP file from a source not under your control and really need to produce an image using exactly the same BMP variant, you have a lot work to do, and I don’t think you could use the standard .NET helper classes for that.
See also this question: Save bitmap to file has zero in image size field
After chatting a bit, you solution comes down to reading and writing bytes, take the image object out the equation and just deal with the raw bytes.
To read the file:
MemoryStream ms = new MemoryStream(File.ReadAllBytes("filename"));
To write the file:
File.WriteAllBytes("outputfile", ms.ToArray());

EncodeParameters deinterlace bmp

I'm looking for a way to take in a 32 bit bitmap and save it again however deinterlacing the frames. When the image is taken two fields are visible but only the last one is necessary. Is this possible to do using EncoderParameters. This is what I've tried so far:
using (Image source = Image.FromFile(#"C:\Users\Martin vanPutten\Desktop\test.bmp"))
{
ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders().First(c => c.MimeType == "image/bmp");
EncoderParameters parameters = new EncoderParameters(3);
parameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
parameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int)EncoderValue.LastFrame);
parameters.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int)EncoderValue.RenderNonProgressive);
source.Save(#"C:\Users\Martin vanPutten\Desktop\test2.bmp", codec, parameters);
}
Is there another way to do this? All I need to do is remove the second overlapping frame in an image.
Quick update, its not that it has two frames, but 2 fields in 1 frame.

Resize image while preserving mimetype using .Net

I am loading images from a database and want to dynamically resize them according to some input.
Code is something like this:
public ActionResult GetImage(string imageID, int? width, int? height, bool constrain)
{
ValidateImageInput(width, height, constrain);
ImageWithMimeType info = LoadFromDatabase(imageID);
if(info == null)
throw new HttpException(404, "Image with that name or id was not found.");
Resize(info.Bytedata, width, height, constrain, info.MimeType);
return File(info.Data, info.MimeType);
}
How would I implement Resize in a way that preserves encoding type etc? I've looked at Image resizing efficiency in C# and .NET 3.5 but don't see how it would preserve encoding - since creating a new Bitmap surely isn't encoded?
Fact is, I managed to solve it with some help of google eventually. Guess I was a bit too trigger happy with the question. Anyway, the basic bits is that I look up the proper ImageFormat from the mimetype using ImageCodecInfo.GetImageEncoders(), then save using the correct encoding, as following:
private ImageFormat GetEncoderInfo(string mimeType)
{
// Get image codecs for all image formats
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
// Find the correct image codec
for (int i = 0; i < codecs.Length; i++)
if (codecs[i].MimeType == mimeType)
return new ImageFormat(codecs[i].FormatID);
return null;
}
This is a slightly different version I made of the code on http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-saving-cropping-and-resizing
Using the ImageFormat I can simply do
image.Save(dest, GetEncoderInfo(mimetype));
To preserve the filetype you have to look at that filetype the original file has and when saving the file you specify the file format.
Bitmap b = new Bitmap("foo.jpg");
b.Save("bar.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
In your case you would probably save to an MemoryStream that you later convert to the byte array (guessing that your info.Data is of type byte[]).

Categories

Resources