Change Size of Jpeg File in C# - c#

I need to transfer some images through Network, I saved images with Jpeg and 40% quality as following:
public void SaveJpeg(string path, Image image, int quality) {
if((quality < 0) || (quality > 100)) {
string error = string.Format("Jpeg image quality must be
between 0 and 100, with 100 being the highest quality. A value of {0} was
specified.", quality);
throw new ArgumentOutOfRangeException(error);
}
EncoderParameter qualityParam = new
EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
image.Save(path, jpegCodec, encoderParams);
}
But with this way the size of Jpeg files not enough small, Also I change the quality but that's not good appearance. Is there any way to save pictures with smaller file size and proper appearance? I don't know but is there any way to use System.Drawing.Graphics object, also I don't need to zip files, or change dimension of images, at now just the size of picture file is important.

With image compression, there's a fine line between creating a small file and creating a poor quality image. JPEG is a lossy compression format which means that data is removed when compressed, which is why constantly re-encoding a JPEG file will continually decrease its quality.
On the other hand, PNG files are lossless but may still result in bigger files. You could try encoding the file as a PNG using PngBitmapEncoder. This will ensure the quality remains high, but the size may or may not decrease enough for your program (it depends on the image).
If you're performing this on a local machine and don't need to do it too often (e.g. for many concurrent users), you could invoke an external program to do it for you. PNG Monster is very good at compressing PNG files without decreasing the quality. You could call this from your program and send the resulting PNG file. (You may want to check the licensing terms to ensure that it's compatible with your program).
There aren't many ways where you can maintain a high quality and perform a high compression at the same time, without manipulating the image (e.g. changing dimension).

I have a method for creating and saving the thumbnail of an uploaded picture, I think NewImageSize method might help you.It also handles the quality issue.
public Size NewImageSize(int OriginalHeight, int OriginalWidth, double FormatSize)
{
Size NewSize;
double tempval;
if (OriginalHeight > FormatSize && OriginalWidth > FormatSize)
{
if (OriginalHeight > OriginalWidth)
tempval = FormatSize / Convert.ToDouble(OriginalHeight);
else
tempval = FormatSize / Convert.ToDouble(OriginalWidth);
NewSize = new Size(Convert.ToInt32(tempval * OriginalWidth), Convert.ToInt32(tempval * OriginalHeight));
}
else
NewSize = new Size(OriginalWidth, OriginalHeight); return NewSize;
}
private bool save_image_with_thumb(string image_name, string path)
{
ResimFileUpload1.SaveAs(path + image_name + ".jpg"); //normal resim kaydet
///////Thumbnail yarat ve kaydet//////////////
try
{
Bitmap myBitmap;
myBitmap = new Bitmap(path + image_name + ".jpg");
Size thumbsize = NewImageSize(myBitmap.Height, myBitmap.Width, 100);
System.Drawing.Image.GetThumbnailImageAbort myCallBack = new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback);
// If jpg file is a jpeg, create a thumbnail filename that is unique.
string sThumbFile = path + image_name + "_t.jpg";
// Save thumbnail and output it onto the webpage
System.Drawing.Image myThumbnail = myBitmap.GetThumbnailImage(thumbsize.Width, thumbsize.Height, myCallBack, IntPtr.Zero);
myThumbnail.Save(sThumbFile);
// Destroy objects
myThumbnail.Dispose();
myBitmap.Dispose();
return true;
}
catch //yaratamazsa normal ve thumb iptal
{
return false;
}
///////////////////////////////////
}

Related

ImageProcessor.ImageFactory compression not reducing image file size

I am attempting to implement an image compression function to be used on images uploaded to my website. I want to take the original image and save 3 different sizes/quality levels. For this I am using ImageProcessor.ImageFactory. The three levels:
ISupportedImageFormat sm_format = new JpegFormat { Quality = 40 };
Size sm_size = new Size(150, 0);
ISupportedImageFormat md_format = new JpegFormat { Quality = 60 };
Size md_size = new Size(280, 0);
ISupportedImageFormat lg_format = new JpegFormat { Quality = 100 };
Size lg_size = new Size(1000, 0);
imageFactory.Load(or_directoryPath + "/" + fileName)
.Resize(sm_size)
.Format(sm_format)
.BackgroundColor(Color.Transparent)
.Save(Path.Combine(sm_directory, fileName));
// same for md and lg images
What's happening is that the medium and small images do not have the expected smaller filesize.
An example:
Original image is a .jpg 3000x3000 that is 3.7MB large.
The large image size is 2.96MB
The medium image size is 2.63MB
The small image size is 2.62MB
I tried the following on the small image to further compress it to 10% quality:
// Encoder parameter for image quality
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, 10);
// JPEG image codec
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
img.Save(path, jpegCodec, encoderParams);
The end result is significantly lower quality, but the image file size is still 2.62MB
Edit: uploaded original images shared on postimg
The medium image:
The small image:
The small image compressed:
The original image:
Upon further inspection of the image, the EXIF data really is the problem with that file. It contains a section with a custom color profile, and has data stored in it with a size of about 2.64 megabytes. This can be checked by uploading the image to http://regex.info/exif.cgi and clicking the "Show ICC profile data".
Stripping that weird profile data gets rid of the extreme overhead and brings down the filesize to 348 KB at 1000x1000 px.
As you already found out yourself, you must set the preserveExifData parameter in the constructor of the ImageFactory object to false to make it strip the data. Or call the default constructor as
ImageFactory imageFactory = new ImageFactory();
When using ImageProcessor it is possible to set an option in the processing.config
<processing preserveExifMetaData="true" fixGamma="false" interceptAllRequests="false" allowCacheBuster="true">
Setting preserveExifMetaData="false" to removes the EXIF data aswell.
You can also set the option in the ImageFactory constructor:
var imageFactory = new ImageFactory(preserveExifData:true);

Convert TIFF to 1bit

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

Image uploading / Setting DPI but ensuring file-size <=200k

I have a form (using MVC2) which has an image-upload script, but the rules for the final image stored on the server are pretty strict. I can force the file to the dimensions I want but it always ends up exceeding the file-size required... so I can allow a sub-200k image but once my code has processed it ends up slightly bigger.
These are the rules I have to adhere to:
Photographs should be in colour
The permitted image types for the
photograph are .JPG or .GIF
The maximum size of the image is 200kb
The dimensions of the photograph on the badge will be 274 pixels
(wide) x 354 pixels (high) # 200dpi (depth of pixels per inch)
This is what I have currently:
[HttpPost]
public ActionResult ImageUpload(HttpPostedFileBase fileBase)
{
ImageService imageService = new ImageService();
if (fileBase != null && fileBase.ContentLength > 0 && fileBase.ContentLength < 204800 && fileBase.ContentType.Contains("image/"))
{
string profileUploadPath = "~/Resources/images";
Path.GetExtension(fileBase.ContentType);
var newGuid = Guid.NewGuid();
var extension = Path.GetExtension(fileBase.FileName);
if (extension.ToLower() != ".jpg" && extension.ToLower() != ".gif") // only allow these types
{
return View("WrongFileType", extension);
}
EncoderParameters encodingParameters = new EncoderParameters(1);
encodingParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 70L); // Set the JPG Quality percentage
ImageCodecInfo jpgEncoder = imageService.GetEncoderInfo("image/jpeg");
var uploadedimage = Image.FromStream(fileBase.InputStream, true, true);
Bitmap originalImage = new Bitmap(uploadedimage);
Bitmap newImage = new Bitmap(originalImage, 274, 354);
Graphics g = Graphics.FromImage(newImage);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);
var streamLarge = new MemoryStream();
newImage.Save(streamLarge, jpgEncoder, encodingParameters);
var fileExtension = Path.GetExtension(extension);
var ImageName = newGuid + fileExtension;
newImage.Save(Server.MapPath(profileUploadPath) + ImageName);
//newImage.WriteAllBytes(Server.MapPath(profileUploadPath) + ImageName, streamLarge.ToArray());
originalImage.Dispose();
newImage.Dispose();
streamLarge.Dispose();
return View("Success");
}
return View("InvalidImage");
}
Just to add:
The images are going off to print on a card so the DPI is important. But I realise that 200k is not a lot for a printed image.. none of these are my business rules! As it stands with this code an image uploaded that is pretty much 200k, ends up costing 238k(ish)
It's very difficult to calculate the size of a jpeg in advance. Having said that, you don't need to compress it much.
Let's just look at some metrics:
274 * 354 = 96996 pixels. If you have 8 bits per pixel and 3 colour
channels (i.e. 24bit colour) then you have:
274* 354 * 8 * 3 = 2,327,904 bits = 290988 bytes = 284.17 kb.
200 / 284.17 ~ 0.70.
You only need to reduce it to 70% of its original size.
Sadly, it's at this point we get to the limit of my knowledge in this area! But I reckon that by saving as a jpeg it will be in the right size range anyway, even if saving at the highest quality setting.
I would guess at setting the quality to 70 and see what happens.
EDIT: DPI settings
Apparently you only need to change the EXIF data. See this answer: https://stackoverflow.com/a/4427411/234415
You should experiment with the JPEG quality setting. You currently have it set to 90, 80 might be sufficient and will result in a smaller file.
I see some problems with the code:
You are using GetThumbnailImage to create a thumbnail, but that is not intended for such large thumbnails. It works up to about 120x120 pixels. If the image has an embedded thumbnail, that will be used instead of scaling down the full image, so you will be scaling up a smaller image, with obvious quality problems.
You are saving the thumbnail to a memory stream, which you then just throw away.
You are saving the thumbnail to file without specifying the encoder, which means that it will either be saved as a low compressed JPEG image or a PNG image, that's why you get a larger file size.
You never dispose the uploadedImage object.
Note: The resolution (PPI/DPI) has no relevance when you display images on the web.

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

RAM saving when loading many Bitmap objects

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

Categories