.NET Saving jpeg with the same quality as it was loaded - c#

I have a cannon digital camera and I set it to take pictures with superfine quality and it outputs a .jpg file 3 mega in size.
If I load it like this in ASP.NET(this is useful to change it's dpi resolution or crop it or whaterver)
imgPicture = Image.FromFile(Config.WorkDirectory + this.TempPhotoName);
bmpPicture = new Bitmap(imgPicture);
and then I save it again like this:
bmpModified.Save(Config.WorkDirectory + this.TempPhotoName,System.Drawing.Imaging.ImageFormat.Jpeg);
it outputs a jpg that is only 700KB or so in size. There is a loss of quality.
I also tried saving it like this:
bmpPicture.Save(Config.WorkDirectory + this.TempPhotoName, codecJpeg, encparams);
where codecJpeg is
ImageCodecInfo codecJpeg = this.getEncoderInfo("image/jpeg");
private ImageCodecInfo 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 codecs[i];
return null;
}
and encparams:
EncoderParameters encparams = new EncoderParameters(1);
encparams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 97L);
This way the size(and I suspect also the quality) is maintained but I am inputing the quality by hand.
I want to ask:
Is there a way to save the image with the same quality as it was loaded without hardcoding the quality value?
Thank you in advance

So it sounds like you know how to set the quality, you really just need to know how to fetch the quality from the original image?
I suspect that Image.PropertyItems is your friend, if the quality is in the metadata to start with. (I don't know if there's even a standard scale for quality within JPEG encoders.)
EDIT: I've just checked, and a jpeg I downloaded didn't have any tags for quality.
One option might be to work out how big the file should end up to have roughly the same quality, and then save it several times, doing a binary search to work out the most appropriate quality. Icky, but it might just work.
I have a sneaking suspicion that there isn't a way to just preserve the original quality setting, although I don't have very good grounds for that suspicion...

Read here how to save image without re-encoding image: How-to: Re-encode a JPEG Image with Metadata.
However, if you do cropping or another image manipulation it impossible to do it without quality loss (well technically it is possible to do loss-less crop, if you work with boundaries that multiply of 16, but AFAIK it is cannot be done with libraries available in .net).

You must use the additional parameters which tell GDI+ to detect the color scheme.
For instance:
using (var img = Image.FromStream(fileupload.InputStream, true,true))
{
img.Save(fileOnDisk, ImageFormat.Jpeg);
}
using (var img = Image.FromFile("yourimage", true))
{
img.Save(fileOnDisk, ImageFormat.Jpeg);
}

Related

Image Quality Setting Not working in PNG and BMP Format Output

I'm using the following code to save image in different formats
public void saveJpeg(string path, Bitmap img, long quality)
{
System.Drawing.Imaging.EncoderParameter qualityParam = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
System.Drawing.Imaging.ImageCodecInfo Codec = this.getEncoderInfo(imgformat);
if (Codec == null)
return;
System.Drawing.Imaging.EncoderParameters encoderParams = new System.Drawing.Imaging.EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
img.Save(path + ext, Codec, encoderParams);
}
The quality setting works fine in case of JPEG Images.But when i set the quality to 20 and 100 for .bmp and .png formats i get files with the same size as output.
What is going wrong here?
Please advice.
"What is going wrong here?"
Nothing is going wrong here. The quality setting applies only to the JPEG encoder. PNG and BMP are both lossless, meaning that there's no quality/space trade-off to be made. You will always get exactly the same pixels out from those formats as went in.
Unfortunately, .NET does not offer much in the way of compression options for those formats, in any of its image-handling APIs. BMP does support a "run-length encoding" format, but to my knowledge there's no .NET API that will actually generate it. Likewise PNG, which actually has a lot more options that can be selected to tailor the compression used to the nature of the input image and the amount of time one wants to spend compressing the file.
But even if you had access to those options, they wouldn't affect the quality of the image. Just the final compressed size of the file (e.g. for PNG using the right compression algorithm and/or spending more time compressing the file will result in a smaller file, but still a file that is exactly the same as the image you started with).

Save .jpg image in grayscale using C# [duplicate]

We have a system that provides images in 8-bit grayscale either tiff or jpg formats. However, the component we have to process the images expects image to be in 8-bit jpg format.
When I use .Net to save the tiff images as jpg it convets it to 24-bit image.
Is there a way, hopefully simple and fast, to convert 8-bit grayscale tiff images to equivalent jpg?
I tried and tried just to conclude that I'm sorry: .Net library's Bitmap class DOES NOT save JPEG as 8bpp even when explicitly stated and data is in grayscale.
(note: although stated in some places, JPEG format DOES support 8bpp).
At Convert an image to grayscale you may find code snipet to convert to grayscale any Image.
Using that code, I was able to save a 8bpp grayscale Image instance with '.jpeg' extension, but stating ImageFormat.Gif... that's a cheat...
My findings show as solution an entirely different approach.
The FreeImage library offers powerful APIs, including the feature needed to solve your problem.
It's home page is at http://freeimage.sourceforge.net/faq.html
But, I could not easily compile it in my Win2008 + VS 2010 machine.
One ought to sweat a lot to make it run on modern environments.
Some hints on how to accomplish that are found at http://www.sambeauvois.be/blog/2010/05/freeimage-and-x64-projects-yes-you-can/
Good luck!
Image img = Image.FromFile(filePathOriginal);
Bitmap bmp = ConvertTo8bpp(img);
EncoderParameters parameters = new EncoderParameters();
parameters.Param[0] = new EncoderParameter(Encoder.ColorDepth, 8);
bmp.Save(filePathNew, jpgCodec, parameters);
bmp.Dispose();
img.Dispose();
...
private static Bitmap ConvertTo8bpp(Image img) {
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
}
return bmp;
}

Set image resolution

I have array of image bytes and I would like to set resolution. Original image can be JPEG, PNG, BMP. Output - PNG. I am using ImageMagic to convert image and do some manipulations.
using (var image = this.Convert(originalImage, height, width))
using (var stream = new MemoryStream())
{
image.Quality = 90;
image.Write(stream, MagickFormat.Png);
return stream.GetBuffer();
}
I tryed to modify image.GetExifProfile, but has no success (at least for PNG images).
I can't use any comandline tool (like ImageMagic or ExifTool) here.
There are 3 exiff tags I need to modify
XResolution
YResolution
ResolutionUnit
I can successfully achieve this with bitmap, but it also resource overhead (need to create MemoryStream ...).
I have found some Pdf specification, but it will consume time to make it all work.
Does any can point me to right direction?
Thanks.

HttpPostedFileBase to Resized Image

I have the following line of code which takes my HttpPostedFileBase and converts it to an image.
I have messed around with Encoder Parameteres to try and resize the image but can't seem to do this.
What is the best way to resize the image to 250x250?
I would also prefer it to take the middle of the image as 250x250 rectangle rather than somewhere random.
What is the most space efficent way to convert and save the image as it will be going in the database?
Please note that model.Image is of type HttpPostedFileBase.
var image = Image.FromStream(model.Image.InputStream, true, true);
ImageCodecInfo jpgInfo = ImageCodecInfo.GetImageEncoders()
.Where(codecInfo => codecInfo.MimeType == "image/jpeg").First();
using (EncoderParameters encParams = new EncoderParameters(1))
{
encParams.Param[0] = new EncoderParameter(Encoder.Quality, (long)50);
//quality should be in the range [0..100]
using (var ms = new MemoryStream())
{
// Convert Image to byte[]
image.Save(ms, ImageFormat.Jpeg);
byte[] imageBytes = ms.ToArray();
// Convert byte[] to Base64 String
string base64String = Convert.ToBase64String(imageBytes);
image64 = base64String;
}
}
The best way is to use an established library like ImageResizer; that way, you avoid re-inventing the wheel. If you are concerned about space efficiency (file size), use the JPEG format with maximum compression. Note that using JPEG's lossy encoding will drastically reduce image quality. If you want to use a lossless encoding, try PNG. Compressing PNG images is quite difficult in .NET, in my experience. I searched in vain awhile back for a single library that would do it and all I found were thick-client Windows applications.

How can I get .NET to save this image?

I have this JPEG image, that opens fine in Picasa, Photoshop, web browser, etc., but in .NET it just refuses to work.
Image image = Image.FromFile(#"myimage.jpg");
image.Save(#"myimage2.jpg");
// ExternalException - A generic error occurred in GDI+.
Is there a way to recover it in .NET so I can work with it (I need to resize it), without fixing the problem at the source?
Full exception details:
source: System.Drawing
type: System.Runtime.InteropServices.ExternalException
message: A generic error occurred in GDI+.
stack trace:
at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
at System.Drawing.Image.Save(String filename, ImageFormat format)
at System.Drawing.Image.Save(String filename)
at ConsoleApplication20.Program.Main(String[] args) in C:\Users\sam\Desktop\S
ource\ConsoleApplication20\ConsoleApplication20\Program.cs:line 16
This issue is reproducible on Windows 7.
This seems to work:
using (Image image = Image.FromFile(#"c:\dump\myimage.jpg"))
using (Image clone = new Bitmap(image))
{
clone.Save(#"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
}
image is actually a Bitmap anyway, so it should be similar. Oddly myimage2 is 5k smaller - the joys of jpeg ;-p
A nice thing about this is that you can resize at the same time (your actual intent):
using (Image image = Image.FromFile(#"c:\dump\myimage.jpg"))
using (Image clone = new Bitmap(image,
new Size(image.Size.Width / 2, image.Size.Height / 2)))
{
clone.Save(#"c:\dump\myimage2.jpg", ImageFormat.Jpeg);
}
Try explicitly specifying the format:
using (Image image = Image.FromFile(#"test.jpg"))
{
image.Save(#"myimage2.gif", ImageFormat.Gif);
}
All ImageFormat.Png, ImageFormat.Bmp and ImageFormat.Gif work fine. ImageFormat.Jpeg throws an exception.
The original image is in the JFIF format as it starts with FF D8 FF E0 bytes.
This is a long-standing bug in the .NET framework itself that I don't expect to see fixed any time soon. There are several related bugs I've also come across that throw the 'generic error occurred in GDI+' including if you access the PropertyItem collection for some JPEGs (to examine the EXIF codes) then from that point on you won't be able to save the image. Also, for no rhyme or reason some JPEGs, like the one you have here, simply won't save at all. Note that just saving as a different format won't always work either, although it does in this case.
The workaround I've employed in an app of mine that consumes pictures from all over the web (and therefore sees more than its fair share of these types of problems) is to catch the ExternalException and copy the image into a new Bitmap as in one of the previous answers, however simply saving this as a new JPEG will drop the quality rather a lot so I use code much like that below to keep the quality high:
namespace ImageConversionTest
{
using System.Drawing;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.Globalization;
class Program
{
static void Main( string[] args )
{
using( Image im = Image.FromFile( #"C:\20128X.jpg" ) )
{
string saveAs = #"C:\output.jpg";
EncoderParameters encoderParams = null;
ImageCodecInfo codec = GetEncoderInfo( "image/jpeg" );
if( codec != null )
{
int quality = 100; // highest quality
EncoderParameter qualityParam = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, quality );
encoderParams = new EncoderParameters( 1 );
encoderParams.Param[0] = qualityParam;
}
try
{
if( encoderParams != null )
{
im.Save( saveAs, codec, encoderParams );
}
else
{
im.Save( saveAs, ImageFormat.Jpeg );
}
}
catch( ExternalException )
{
// copy and save separately
using( Image temp = new Bitmap( im ) )
{
if( encoderParams != null )
{
temp.Save( saveAs, codec, encoderParams );
}
else
{
temp.Save( saveAs, ImageFormat.Jpeg );
}
}
}
}
}
private static ImageCodecInfo GetEncoderInfo( string mimeType )
{
// Get image codecs for all image formats
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
// Find the correct image codec
foreach( ImageCodecInfo codec in codecs )
{
if( string.Compare( codec.MimeType, mimeType, true, CultureInfo.InvariantCulture ) == 0 )
{
return codec;
}
}
return null;
}
}
}
Note that you'll lose the EXIF information but I'll leave that for now (you'll generally still be able to read the EXIF codes, even when saving the source image fails). I have long since given up attempting to figure out what it is that .NET doesn't like about particular images (and have samples of various failure cases should anybody ever want to take up the challenge) but the approach above works, which is nice.
It seems to work fine on Windows XP and Vista, but not Windows 7.
I was able to find this issue on Microsoft Connect. It's not identical to your issue but it looks very similar - an ExternalException occurring when trying to resave a JPEG file. Currently at 16 repros including some comments that the issue still exists in the final release.
So it looks not to be a bug in the .NET Framework, but a bug in Windows 7 - specifically, a bug in the GdipSaveImageToStream API.
The workaround, as others have mentioned, is to force a conversion to another format. That's what both Marc and Darin's answers are doing. There is obviously some "extra" information in the JPEG file that is triggering the bug in Win7. When you convert to a different format or make a bitmap copy, that information (EXIF maybe?) is eliminated.
Try using WPF's BitmapSource instead of the WinForm's Image, it supports more pixel, image and file formats.
I tried yesterday on 32-bit Windows XP and can't reproduce the problem. Today I tried on 64-bit Windows 7 and get exactly the error you described, which is great for my debugging.
I investigated the header and the JPEG is a standard JPEG, but with an EXIF header. From what I read, it is not uncommon that the EXIF header maybe corrupt and some programs will just ignore it. In .NET it allows reading and (may even allow writing at some point), but not writing. You can read more about it in blog post GDI+ can’t handle some malformed JPG files.
One way to remove it is to clone the image like suggested by Marc, which will create the new image without an EXIF header, hence that explains why the file size is actually smaller. There are methods for removing an EXIF header programmatically, and some has been suggested in Stack Overflow
like Simple way to remove EXIF data from a JPEG with .NET.
The suggested problem with reading the byte marker and skip the stream will not work well consistently as we are dealing with a corrupted EXIF header. I tried using RemovePropertyItem is an option, but it still doesn't work either, and my guess is because there are corrupted property items that are being referred to (when I inspect the JPEG header there are six properties, and .NET only loads four). They are other library like exiv2net which can be explored, but I suspect the outcome will be similar.
In short, the answer will be to clone the image as suggested by Marc. This is perhaps not the solution, but an insight into the problem.
Try checking your permissions. Not being able to save can be caused by not having the right permission to write/modify, and could generate this error.
That error occurrs when you either have
a. no permissions to write the file
b. you are trying to write an image that is locked
(often, by a UI control ie. picturebox)
You'll need to dispose the original, and then save over it with your resized clone as in Marc's answer. I just figured I'd tell you why it's happening.

Categories

Resources