Using C#.
I have a jpeg.
When I load it into a bitmap object its pixelformat is Format24bppRgb.
If I 'convert' the bitmap to Format16bppRgb565 the byte size of the file is larger.
I thought by reducing the pixelformat I would reduce the size of the file. In fact doing this has resutled in the file actually increasing is size.
Am A fundamentally wrong in this expectation?
This is my code (which is just a quick workaround)...
//imgConverted is at 24bpp
Bitmap bmp24bit = imgConverted.Clone(new Rectangle(0, 0, img.Width, img.Height), PixelFormat.Format16bppRgb565);
This is the code I use to convert bitmap to a byte array to compare my byte sizes:
private byte[] ImageToByteArray(System.Drawing.Image imageIn)
{
byte[] data = null;
try
{
using (MemoryStream ms = new MemoryStream())
{
imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
imageIn.Dispose();
data = ms.ToArray();
}
}
catch (Exception ex)
{
}
finally
{
if (imageIn != null)
{
imageIn.Dispose();
}
}
return data;
}
The PixelFormat enumeration is concerned with how many bits each pixel takes up when the image data is in uncompressed bitmap form (e.g. in-memory on your computer when you load an image file). File formats like JPEG and PNG do not have a comparable concept of bits-per-pixel because they're compressed formats that don't necessarily store each pixel in 24 bits of space on-disk.
As an aside, JPEG always uses 24 bits to store colour information (to the extent it does store colour, JPEG's algorithm is quite complicated actually), and PNG supports at least indexed colour, 24-bit RGB and 32-bit ARGB colour depth. PNG also supports 48-bit colour depth too.
Back on-topic: you're seeing increased JPEG file sizes because JPEG is a lossy algorithm that has the side-effect of introducing compression artefacts. While saving an original photo to a highly compressed JPEG image causes information loss, these artefacts actually add (unwanted) new details to the image, so when re-saving these new artefacts you're increasing the amount of "information" that needs to be stored, hence why the file-size increases. This will also cause the image to progressively lose quality each time it's re-saved - as compressed-bits that should be used to represent the original image are now being wasted to represent the artefacts that were introduced by a previous re-encoding of the JPEG image.
(There are ways to losslessly re-save JPEG images without running JPEG's algorithm again, but most image editors nor GDI support them, search for "jpegtrans" and "jpegcrop" for more information).
If you want to reduce the file-size of JPEG images then use the "JPEG Quality" parameter, which is a special feature unique to the JPEG algorithm which allows subjective "quality" to be modified:
This is documented here: http://msdn.microsoft.com/en-us/library/bb882583(v=vs.110).aspx
Copy & paste:
private void VaryQualityLevel()
{
// Get a bitmap.
Bitmap bmp1 = new Bitmap(#"c:\TestPhoto.jpg");
ImageCodecInfo jgpEncoder = GetEncoder(ImageFormat.Jpeg);
// Create an Encoder object based on the GUID
// for the Quality parameter category.
System.Drawing.Imaging.Encoder myEncoder =
System.Drawing.Imaging.Encoder.Quality;
// Create an EncoderParameters object.
// An EncoderParameters object has an array of EncoderParameter
// objects. In this case, there is only one
// EncoderParameter object in the array.
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 50L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"c:\TestPhotoQualityFifty.jpg", jgpEncoder, myEncoderParameters);
myEncoderParameter = new EncoderParameter(myEncoder, 100L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"c:\TestPhotoQualityHundred.jpg", jgpEncoder, myEncoderParameters);
// Save the bitmap as a JPG file with zero quality level compression.
myEncoderParameter = new EncoderParameter(myEncoder, 0L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"c:\TestPhotoQualityZero.jpg", jgpEncoder, myEncoderParameters);
}
private ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Related
I'm writing a program the from some text file inputs outputs a large number of image files.
Currently these images are being created and saved with
Parallel.ForEach(set, c =>
{
using (Bitmap b = Generate_Image(c, Watermark))
{
//The encoder needs some set up to function properly
string s = string.Format("{0:0000}", c.Index);
string filepath = $#"{Directory}\{s}.png";
//best quality comes from manually configuring the codec and
//encoder used for the image saved
b.Save(filepath, ImageFormat.Png);
}
});
However I noticed that b.Save() has an overload for taking an ImageCodeInfo and an EncoderProperties, which should be able to produce a higher quality image output (the image quality is paramount to the program).
However, I haven't been able to find anywhere what needs to be done to create these objects to then pass in as parameters, at least not ones that work even in the Microsoft Documents, strangely enough, their samples didn't compile. So if I may ask how does one uses the method overload of Image.Save(Filepath,encoder,settings)?
Thank you in advance for any help offered.
I wrote a small method to retrieve that information in my own code:
private static ImageCodecInfo GetEncoderInfo(string mimeType)
{
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
if (codec.MimeType == mimeType)
return codec;
return null;
}
And this is how I use it to save a Jpeg:
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
if (jpegCodec == null)
return;
EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
image.Save(imagePath, jpegCodec, encoderParameters);
Its not used to save a Png:
image.Save(imagePath, ImageFormat.Png);
You'll need the following namespaces:
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
Also, I should mention, Png is a lossless image format. That is why there is no encoder parameters for it, because you cannot achieve better quality than lossless.
For the highest quality when working with the Graphics class, make sure you set the following properties:
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
I am trying to send images to a AS-400 and it only accepts TIFF images.
I am converting them but then it is complaining that
The error is complaining about "unrecognized tiff tags..." the first two are 317 & 531
Also: "For error codes X'8F0E' and X'8F0F', a bit pattern was detected that does not conform to the rules of the decompression algorithm. Further decompression is not possible. Verify the data integrity of the input data
stream and try the request again."
I have a tiff file that works, this is the details of it:
I am using code off of MSDN that I have modified.
the below code IS working, but I now need to have more than 1 parameter in the encoder.
Bitmap myBitmap;
ImageCodecInfo myImageCodecInfo;
Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
// Create a Bitmap object based on a BMP file.
myBitmap = new Bitmap(#"f:\testFromBlob.jpg");
// Get an ImageCodecInfo object that represents the TIFF codec.
myImageCodecInfo = GetEncoderInfo("image/tiff");
//do the actual work
myEncoder = Encoder.Compression;
myEncoderParameters = new EncoderParameters(1);
myEncoderParameter = new EncoderParameter(myEncoder,(long)EncoderValue.CompressionCCITT4);
myEncoderParameters.Param[0] = myEncoderParameter;
myBitmap.Save(#"f:\resultFromDotNet.tiff", myImageCodecInfo, myEncoderParameters);
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for (j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
I am out of my depth with image files.
Can anybody tell me how to do a conversion that matches the settings from my test image?
I have tried changing the bit depth to 1 as well as the compression, not sure but I think it may need both changed.
UPDATE - using Magick I am able to convert to a working format using
magick convert image01.jpg -compress Group4 tiff3.tiff
If that helps at all getting me on the right track for C# / .Net
Update 2: the above code is working but I need to know how to change multiple parameters vs just one. I think that will probably be the home run.
UPDATE 3: I have the multiple parameters working, posting it here in case it helps somebody else. Now just need to wait for the client to come in and see if this works for them!
For reference here is the link to the MS documentation
https://learn.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoder.colordepth?view=netframework-4.8
Bitmap myBitmap;
ImageCodecInfo myImageCodecInfo;
Encoder compressionEncoder;
Encoder colorDepthEncoder;
EncoderParameter compressionParameter;
EncoderParameter colorDepthParameter;
EncoderParameters myEncoderParameters;
// Create a Bitmap object based on a BMP file.
myBitmap = new Bitmap(#"f:\colorTest.jpg");
// Get an ImageCodecInfo object that represents the TIFF codec.
myImageCodecInfo = GetEncoderInfo("image/tiff");
//do the actual work
compressionEncoder = Encoder.Compression;
myEncoderParameters = new EncoderParameters(2);
compressionParameter = new EncoderParameter(compressionEncoder,(long)EncoderValue.CompressionCCITT4);
colorDepthEncoder = Encoder.ColorDepth;
colorDepthParameter = new EncoderParameter(colorDepthEncoder, 24L); //if needed this can be removed
myEncoderParameters.Param[0] = compressionParameter;
myEncoderParameters.Param[1] = colorDepthParameter;
myBitmap.Save(#"f:\resultFromDotNet1bit.tiff", myImageCodecInfo, myEncoderParameters);
In your original code sample - instead of Encoder.ColorDepth, you would want to set the parameter Encoder.Compression to EncoderValue.CompressionCCITT4.
This is the code I'm using to convert the TIFF to PNG.
var image = Image.FromFile(#"Test.tiff");
var encoders = ImageCodecInfo.GetImageEncoders();
var imageCodecInfo = encoders.FirstOrDefault(encoder => encoder.MimeType == "image/tiff");
if (imageCodecInfo == null)
{
return;
}
var imageEncoderParams = new EncoderParameters(1);
imageEncoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
image.Save(#"Test.png", imageCodecInfo, imageEncoderParams);
The TIFF file size is 46.8 MB (49,161,628 bytes) the PNG that is made using this code is 46.8 MB (49,081,870 bytes) but if I use MS paint the PNG file size is 6.69 MB (7,021,160 bytes).
So what do I change in the code to get the same compress I get by using MS Paint?
Without a good Minimal, Complete, and Verifiable code example, it's impossible to know for sure. But…
The code you posted appears to be getting a TIFF encoder, not a PNG encoder. Just because you name the file with a ".png" extension does not mean that you will get a PNG file. It's the encoder that determines the actual file format.
And it makes perfect sense that if you use the TIFF encoder, you're going to get a file that's exactly the same size as the TIFF file you started with.
Instead, try:
var imageCodecInfo = encoders.FirstOrDefault(encoder => encoder.MimeType == "image/png");
Note that this may or may not get you exactly the same compression used by Paint. PNG has a wide variety of compression "knobs" to adjust the exact way it compresses, and you don't get access to most of those through the .NET API. Paint may or may not be using the same values as your .NET program. But you should at least get a similar level of compression.
OK, after a lot of trial and error I came up with this.
var image = Image.FromFile(#"Test.tiff");
Bitmap bm = null;
PictureBox pb = null;
pb = new PictureBox();
pb.Size = new Size(image.Width, image.Height);
pb.Image = image;
bm = new Bitmap(image.Width, image.Height);
ImageCodecInfo png = GetEncoder(ImageFormat.Png);
EncoderParameters imageEncoderParams = new EncoderParameters(1);
imageEncoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
pb.DrawToBitmap(bm, pb.ClientRectangle);
bm.Save(#"Test.png", png, encodePars);
pb.Dispose();
And add this to my code.
private ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
if (codec.FormatID == format.Guid)
return codec;
return null;
}
By loading the TIFF in a PictureBox then saving it as a PNG the output PNG file size is 7.64 MB (8,012,608 bytes). Witch is a little larger then Paint But that is fine.
I wrote a desktop app which converts an 8bit TIFF to a 1bit but the output file cannot be opened in Photoshop (or other graphics software).
What the application does is
it iterates every 8 bytes (1 byte per pixel) of the original image
then converts each value to bool (so either 0 or 1)
saves every 8 pixels in a byte - bits in the byte are in the same order as the pixels in the original image
The TIFF tags I set: MINISBLACK, compression is NONE, fill order is MSB2LSB, planar config is contiguous. I'm using BitMiracle's LibTiff.NET for reading and writing the files.
What am I doing wrong that the output cannot be opened by popular software?
Input image: http://www.filedropper.com/input
Output image: http://www.filedropper.com/output
From your description of the byte manipulation part, it appears you are converting the image data from 8-bit to 1-bit correctly.
If that's the case, and you don't have specific reasons to do it from scratch using your own code, you can simplify the task of creating valid TIFF files by using System.Drawing.Bitmap and System.Drawing.Imaging.ImageCodecInfo. This allows you to save either uncompressed 1-bit TIFF or compressed files with different types of compression. The code is as follows:
// first convert from byte[] to pointer
IntPtr pData = Marshal.AllocHGlobal(imgData.Length);
Marshal.Copy(imgData, 0, pData, imgData.Length);
int bytesPerLine = (imgWidth + 31) / 32 * 4; //stride must be a multiple of 4. Make sure the byte array already has enough padding for each scan line if needed
System.Drawing.Bitmap img = new Bitmap(imgWidth, imgHeight, bytesPerLine, PixelFormat.Format1bppIndexed, pData);
ImageCodecInfo TiffCodec = null;
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
if (codec.MimeType == "image/tiff")
{
TiffCodec = codec;
break;
}
EncoderParameters parameters = new EncoderParameters(2);
parameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionLZW);
parameters.Param[1] = new EncoderParameter(Encoder.ColorDepth, (long)1);
img.Save("OnebitLzw.tif", TiffCodec, parameters);
parameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionCCITT4);
img.Save("OnebitFaxGroup4.tif", TiffCodec, parameters);
parameters.Param[0] = new EncoderParameter(Encoder.Compression, (long)EncoderValue.CompressionNone);
img.Save("OnebitUncompressed.tif", TiffCodec, parameters);
img.Dispose();
Marshal.FreeHGlobal(pData); //important to not get memory leaks
Is there any difference when you load into bitmap the same image from bmp or from png (or other format)? Does the original image format influence Bitmap object size in RAM?
Is there a way to archive Bitmap objects in order to make them less RAM resources consuming?
The size is only influenced by the size of the file, regardless of format (but obviously, certain formats result in smaller files than others).
One way to archive bitmaps, if you need to keep them as bitmaps, is simply to zip them. Alternatively, convert them to another image format that includes compression (ideally, lossless compression so not jpg). Sorry this was explaining archiving the files, not conserving live memory usage.
To stop bitmap objects using memory, you will need to let go of the item in memory and reload it when you want to use it again. Alternatively, though I've no experience with this, look into the new .NET 4 memory mapped files.
There are two ways to save the data in memory
Serialize and compress object with GZipStream in memory
Save images to temporary directory and read them to ram if only needed.
Image object size is not influenced with the original image format. but the size of the stream , that saves the object - does.
Here is the way how to get stream from the object:
public static Stream GetPNGBitmapStream(Image initial)
{
return GetBitmapStream(initial, "image/PNG");
}
public static Stream GetJPGBitmapStream(Image initial)
{
return GetBitmapStream(initial, "image/jpeg");
}
private static Stream GetBitmapStream(Image initial, string mimeType)
{
MemoryStream ms = new MemoryStream();
var qualityEncoder = Encoder.Quality;
var quality = (long)90;
var ratio = new EncoderParameter(qualityEncoder, quality);
var codecParams = new EncoderParameters(1);
codecParams.Param[0] = ratio;
ImageCodecInfo[] infos = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo jpegCodecInfo = null;
for (int i = 0; i < infos.Length; i++)
{
if (string.Compare(infos[i].MimeType, mimeType,true) == 0)
{
jpegCodecInfo = infos[i];
break;
}
}
if (jpegCodecInfo != null)
{
initial.Save(ms, jpegCodecInfo, codecParams);
MemoryStream ms2 = new MemoryStream(ms.ToArray());
ms.Close();
ms.Dispose();
return ms2;
}
return null;
}