C# setting ImageCodec and Encoder Parameters for Image.Save() - c#

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;

Related

Using .net c# to convert image from jpg to a specific TIFF format

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.

c# When converting TIFF to PNG how do I get the same compress I get by using MS Paint

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.

Reducing byte size of jpeg file

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;
}

How can I get better results when shrinking an image

I'm scaling images down in c#, and I've compared my methods with the best method in Photoshop cs5 and cannot replicate it.
In PS i'm using bicubic sharper, which looks really good. However, when trying to do the same in c# I don't get as high quality results. I've tried bicubic interpolation as well as HQ bicubic, smoothing mode HQ/None/AA. Composition modes, I've tried about 50 different variations and each one comes out pretty close to the image on the right.
You'll notice the pixelation on her back and around the title, as well as the authors name not coming out too well.
(Left is PS, right is c#.)
It seems that c# bicubic does too much smoothing even with smoothing set to none. I've been playing around with many variations of the following code:
g.CompositingQuality = CompositingQuality.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.None;
g.SmoothingMode = SmoothingMode.None;
Edit: As requested here is the starting image (1mb).
Perhaps I am missing something, but I have typically used the following code below to resize/compress JPEG Images. Personally, I think the result turned out pretty well based on your source image. The code doesn't handle a few edge cases concerning input parameters, but overall gets the job done (I have additional extension methods for Cropping, and Combining image transformations if interested).
Image Scaled to 25% original size and using 90% Compression. (~30KB output file)
Image Scaling Extension Methods:
public static Image Resize(this Image image, Single scale)
{
if (image == null)
return null;
scale = Math.Max(0.0F, scale);
Int32 scaledWidth = Convert.ToInt32(image.Width * scale);
Int32 scaledHeight = Convert.ToInt32(image.Height * scale);
return image.Resize(new Size(scaledWidth, scaledHeight));
}
public static Image Resize(this Image image, Size size)
{
if (image == null || size.IsEmpty)
return null;
var resizedImage = new Bitmap(size.Width, size.Height, image.PixelFormat);
resizedImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var g = Graphics.FromImage(resizedImage))
{
var location = new Point(0, 0);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(image, new Rectangle(location, size), new Rectangle(location, image.Size), GraphicsUnit.Pixel);
}
return resizedImage;
}
Compression Extension Method:
public static Image Compress(this Image image, Int32 quality)
{
if (image == null)
return null;
quality = Math.Max(0, Math.Min(100, quality));
using (var encoderParameters = new EncoderParameters(1))
{
var imageCodecInfo = ImageCodecInfo.GetImageEncoders().First(encoder => String.Compare(encoder.MimeType, "image/jpeg", StringComparison.OrdinalIgnoreCase) == 0);
var memoryStream = new MemoryStream();
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, Convert.ToInt64(quality));
image.Save(memoryStream, imageCodecInfo, encoderParameters);
return Image.FromStream(memoryStream);
}
}
Usage:
using(var source = Image.FromFile(#"C:\~\Source.jpg"))
using(var resized = source.Resize(0.25F))
using(var compressed = resized.Compress(90))
compressed.Save(#"C:\~\Output.jpg");
NOTE:
For anyone who may comment, you cannot dispose the MemoryStream created in the Compress method until after the image is disposed. If you reflect in to the implementation of Dispose on MemoryStream, it is actually save to not explicitly call dispose. The only alternative would be to wrap the image/memory stream in a custom implementation of a class that implements Image/IDisposable.
Looking at the amount of JPEG artifacts, especially at the top of the image, I think you set the jpg compression to high. That results in a smaller (filesize) file, but reduces image quality and seems to add more blur.
Can you try saving it in a higher quality? I assume the line containing CompositingQuality.HighQuality does this already, but maybe you can find an even higher quality mode. What are the differences in file size between Photoshop and C#? And how does the Photoshop image look after you saved it and reopened it? Just resizing in Photoshop doesn't introduce any jpg data loss. You will only notice that after you've saved the image as jpg and then closed and reopened it.
I stumbled upon this question.
I used this code to use no compression of the jpeg and it comes out like the PS version:
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
ImageCodecInfo ici = null;
foreach (ImageCodecInfo codec in codecs)
{
if (codec.MimeType == "image/jpeg")
ici = codec;
}
EncoderParameters ep = new EncoderParameters();
ep.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)100);

Generating a multipage TIFF is not working

I'm trying to generate a multipage TIFF file from an existing picture using code by Bob Powell:
picture.SelectActiveFrame(FrameDimension.Page, 0);
var image = new Bitmap(picture);
using (var stream = new MemoryStream())
{
ImageCodecInfo codecInfo = null;
foreach (var imageEncoder in ImageCodecInfo.GetImageEncoders())
{
if (imageEncoder.MimeType != "image/tiff") continue;
codecInfo = imageEncoder;
break;
}
var parameters = new EncoderParameters
{
Param = new []
{
new EncoderParameter(Encoder.SaveFlag, (long) EncoderValue.MultiFrame)
}
};
image.Save(stream, codecInfo, parameters);
parameters = new EncoderParameters
{
Param = new[]
{
new EncoderParameter(Encoder.SaveFlag, (long) EncoderValue.FrameDimensionPage)
}
};
for (var i = 1; i < picture.GetFrameCount(FrameDimension.Page); i++)
{
picture.SelectActiveFrame(FrameDimension.Page, i);
var img = new Bitmap(picture);
image.SaveAdd(img, parameters);
}
parameters = new EncoderParameters
{
Param = new[]
{
new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush)
}
};
image.SaveAdd(parameters);
stream.Flush();
}
But it's not working (only the first frame is included in the image) and I don't know why.
What I want to do is to change a particular frame of a TIFF file (add annotations to it).
I don't know if there's a simpler way to do it but what I have in mind is to create a multipage TIFF from the original picture and add my own picture instead of that frame.
[deleted first part after comment]
I'm working with multi-page TIFFs using LibTIFF.NET; I found many quicks in handling of TIFF using the standard libraries (memory related and also consistent crashes on 16-bit gray scale images).
What is your test image? Have you tried a many-frame tiff (preferably with a large '1' on the first frame, a '2 on the next etc; this could help you to be certain on the frame included in the file.
Another useful diagnosis may be tiffdump utility, as included in LibTiff binaries (also for windows). This will tell you exactly what frames you have.
See Using LibTiff from c# to access tiled tiff images
[Edit] If you want to understand the .NET stuff: I've found a new resource on multi-page tiffs using the standard .NET functionality (although I'll stick with LibTIFF.NET): TheCodeProject : Save images into a multi-page TIFF file... If you download it, the code snippet in Form1.cs function saveMultipage(..) is similar (but still slightly different) than your code. Especially the flushing at the end is done in a differnt way, and the file is deleted before the first frame...
[/Edit]
It seems that this process doesn't change image object but it changes the stream so I should get the memory stream buffer and build another image object:
var buffer=stream.GetBuffer();
using(var newStream=new MemoryStream(buffer))
{
var result=Image.FromStream(newStream);
}
Now result will include all frames.

Categories