I have a web application that allows users to upload images of various formats (PNG, JPEG, GIF). Unfortunately my users are not necessarily technical and end up uploading images that are of way too high quality (and size) than is required.
Is there a way for me to compress these images when serving them? By compress I mean lossy compression, reducing the quality and not necessarily the size. Should I be storing the file format as well if different formats are compressed differently?
Of course you can compress images on the fly using C# and .NET.
Here is a function to do so. First it sets up an Encoder object of type jpg and adds one parameter for the new quailty to it. This is then used to write the image with its new quality to a MemoryStream.
Then an image created from that stream is drawn onto itself with the new dimensions..:
//..
using System.Drawing.Imaging;
using System.IO;
//..
private Image compressImage(string fileName, int newWidth, int newHeight,
int newQuality) // set quality to 1-100, eg 50
{
using (Image image = Image.FromFile(fileName))
using (Image memImage= new Bitmap(image, newWidth, newHeight))
{
ImageCodecInfo myImageCodecInfo;
System.Drawing.Imaging.Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
myImageCodecInfo = GetEncoderInfo("image/jpeg");
myEncoder = System.Drawing.Imaging.Encoder.Quality;
myEncoderParameters = new EncoderParameters(1);
myEncoderParameter = new EncoderParameter(myEncoder, newQuality);
myEncoderParameters.Param[0] = myEncoderParameter;
MemoryStream memStream = new MemoryStream();
memImage.Save(memStream, myImageCodecInfo, myEncoderParameters);
Image newImage = Image.FromStream(memStream);
ImageAttributes imageAttributes = new ImageAttributes();
using (Graphics g = Graphics.FromImage(newImage))
{
g.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; //**
g.DrawImage(newImage, new Rectangle(Point.Empty, newImage.Size), 0, 0,
newImage.Width, newImage.Height, GraphicsUnit.Pixel, imageAttributes);
}
return newImage;
}
}
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo ici in encoders)
if (ici.MimeType == mimeType) return ici;
return null;
}
Setting the new dimensions is up to you. If you never want to change the dimensions, take out the parameters and set the values to the old dimensions in the code block If you only sometimes want to change you could pass them in as 0 or -1 and do the check inside..
Quality should be around 30-60%, depending on the motifs. Screenshots with text don't scale down well and need around 60-80% to look good and crispy.
This function returns a jpeg version of the file. If you want, you could create a different Encoder, but for scalable quality, jpeg usually is the best choice.
Obviously you could as well pass in an image instead of a filename or save the newImage to disk instead of returning it. (You should dispose of it in that case.)
Also: you could check the memStream.Length to see if the results are too big and adjust the quality..
Edit: Correction //**
Here is a small application written in .net 4.0 that compresses images in live.
Maybe it can help
https://github.com/user5934951/CompressImageLiveNet
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace export
{
class Program
{
static void Main(string[] args)
{
foreach (string file in Directory.EnumerateFiles(#"C:\Temp\Images"))
{
FileInfo file_info = new FileInfo(file);
var filePathOriginal = Path.Combine(#"C:\Temp\Images",
String.Format("{0}", file_info.Name));
if (File.Exists(filePathOriginal) && file_info.Name.Contains(".png"))
{
Task.Factory.StartNew(() => VaryQualityLevel(filePathOriginal));
Console.WriteLine("Compressed {0}. {1}", count, file_info.Name);
count++;
}
}
Console.WriteLine("End Compressing {0} Images{2}{2}Date: {1}{2}", count, DateTime.Now, Environment.NewLine);
Console.WriteLine("Done");
Console.ReadLine();
}
static void VaryQualityLevel(string file)
{
var img = new Bitmap(file);
try
{
SaveJpeg(img, file);
img.Dispose();
if (File.Exists(file))
File.Delete(file);
File.Move(file + 1, file);
}
catch (Exception ex)
{
// Keep going
}
}
static void SaveJpeg(Image img, string filename)
{
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, 100L);
ImageCodecInfo jpegCodec = GetEncoder(ImageFormat.Jpeg);
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
img.Save(filename + 1, jpegCodec, encoderParams);
}
static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
}
Related
I am making a function where I pass an Image and that will compress the image and return that image object. Following is my code:
public Image CompressImage(Image img)
{
ImageCodecInfo jpegCodec = null;
EncoderParameter imageQualitysParameter = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality, 50L);
ImageCodecInfo[] alleCodecs = ImageCodecInfo.GetImageEncoders();
EncoderParameters codecParameter = new EncoderParameters(1);
codecParameter.Param[0] = imageQualitysParameter;
for (int i = 0; i < alleCodecs.Length; i++)
{
if (alleCodecs[i].MimeType == "image/jpeg")
{
jpegCodec = alleCodecs[i];
break;
}
}
Image img1;
using (Stream memory = new MemoryStream())
{
img.Save(memory, jpegCodec, codecParameter);
img1 = Image.FromStream(memory);
memory.Close();
memory.Dispose();
}
return img1;
}
But When I am saving it in the memory stream, it is showing error
An unhandled exception of type 'System.ArgumentException' occurred in EnexolImageConversionApp.exe
Additional information: Parameter is not valid.
on line
img.Save(memory, jpegCodec, codecParameter);
Try this code - it selects proper ImageCodecInfo by comparing ImageFormat.Guid and ImageCodecInfo.FormatID:
using System.Drawing.Imaging;
using System.IO;
public static byte[] SaveImageToByteArray(Image image, int jpegQuality = 90)
{
using (var ms = new MemoryStream())
{
var jpegEncoder = GetEncoder(ImageFormat.Jpeg);
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)jpegQuality);
image.Save(ms, jpegEncoder, encoderParameters);
return ms.ToArray();
}
}
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
var codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Last step will be loading image again from received byte array.
For better issue understanding please provide exception details, including stacktrace.
I am currently working with C# and EmguCV (OpenCV) and want to compress/decompress an image with JPEG/PNG that is currently represented by the Mat class. I don't want to store it on disk, just compress it in-memory. The goal is to transfer it by TCP/IP later on.
Some code snippet for the following tasks highly appreciated:
A. Compress: Mat -> JPEG/PNG compressed byte[]
B. Decompress: JPEG/PNG compressed byte[] -> Mat
Here's the final code for compressing a Mat object with JPG returning a byte array for network transfer:
using Emgu.CV;
using System.Drawing.Imaging;
private VideoQuality quality;
private ImageCodecInfo codecInfo;
private EncoderParameters encoderParameters;
public byte[] compress(Mat image) {
using(MemoryStream memstream = new MemoryStream()) {
long tstart = Toolkit.CurrentTimeMillis();
image.Bitmap.Save(memstream, codecInfo, encoderParameters);
return memstream.ToArray();
}
}
private void setVideoQuality(long quality) {
this.codecInfo = getEncoder(ImageFormat.Jpeg);
this.encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
}
private ImageCodecInfo getEncoder(ImageFormat format) {
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach(ImageCodecInfo codec in codecs) {
if(codec.FormatID == format.Guid) {
return codec;
}
}
return null;
}
And here's the corresponding code for decoding the byte array:
private Image createImage(byte[] bytes) {
using (MemoryStream memstream = new MemoryStream(bytes, 0, bytes.Length)) {
memstream.Position = 0;
return Image.FromStream(memstream, true);
}
}
Hope this helps someone :-) Thanks for your help!
Update: Tried SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; and it's noticeable at full zoom, but it doesn't solve the problem, the problem is just anti-aliased now...
The TIFs are scanned documents, and contain things like lines for tables and text.
Current approach:
using System.Drawing;
using System.Drawing.Imaging;
var image = Image.FromFile(tifFileName);
Image bitmap = new Bitmap(image, (int)(image.Width), (int)(image.Height));
var imageFinal = new Bitmap(image.Width, image.Height);
var graphic = Graphics.FromImage(imageFinal);
graphic.DrawImage(image, 0, 0, image.Width, image.Height);
using(var imgStream = new MemoryStream())
{
imageFinal.Save(imgStream, ImageFormat.Png);
return new MemoryStream(imgStream.GetBuffer());
}
But, it ends up looking like garbage, for example any kind of slightly slanted line has a hint of a stair step, and other fine elements such as text look rough. Especially in comparison to using GIMP to save a TIF as a PNG, which looks great.
So, is there something I can add to make this work better? Or am I going to have to find another approach entirely?
My immediate impression is that you're going to too much trouble, since you aren't resizing:
var image = Image.FromFile(#"C:\Sample.tiff");
image.Save(#"C:\Sample.png", ImageFormat.Png);
If using the Image type doesn't solve your aliasing problems, try picking your encoder manually:
#region Using Directives
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
#endregion
namespace TiffToBitmap
{
internal class Program
{
private static void Main()
{
// Just save the image.
SaveImage(#"C:\Sample1.png", "image/tiff");
// Get a byte array from the converted image.
var imageBytes = GetBytes("image/tiff");
// Save it for easy comparison.
File.WriteAllBytes(#"C:\Sample2.png", imageBytes);
}
private static byte[] GetBytes(string mimeType)
{
var image = Image.FromFile(#"C:\Sample.tiff");
var encoders = ImageCodecInfo.GetImageEncoders();
var imageCodecInfo = encoders.FirstOrDefault(encoder => encoder.MimeType == mimeType);
if (imageCodecInfo == null)
{
return null;
}
using (var memoryStream = new MemoryStream())
{
var imageEncoderParams = new EncoderParameters(1);
imageEncoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
image.Save(memoryStream, imageCodecInfo, imageEncoderParams);
return memoryStream.GetBuffer();
}
}
private static void SaveImage(string path, string mimeType)
{
var image = Image.FromFile(#"C:\Sample.tiff");
var encoders = ImageCodecInfo.GetImageEncoders();
var imageCodecInfo = encoders.FirstOrDefault(encoder => encoder.MimeType == mimeType);
if (imageCodecInfo == null)
{
return;
}
var imageEncoderParams = new EncoderParameters(1);
imageEncoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
image.Save(path, imageCodecInfo, imageEncoderParams);
}
}
}
Here is my realization of image/tiff type conversion:
Convertion block:
/// <summary>
/// Convert Tiff image to another mime-type
/// </summary>
/// <param name="tiffImage">Source Tiff file</param>
/// <param name="mimeType">Desired result mime-type</param>
/// <returns>Converted image</returns>
public Bitmap ConvertTiffToBitmap(Image tiffImage, string mimeType)
{
var imageCodecInfo = ImageCodecInfo.GetImageEncoders().First(encoder => encoder.MimeType == MimeType.Tiff);
using (var memoryStream = new MemoryStream())
{
// Setting encode params
var imageEncoderParams = new EncoderParameters(1)
{
Param = {[0] = new EncoderParameter(Encoder.Quality, 100L)}
};
tiffImage.Save(memoryStream, imageCodecInfo, imageEncoderParams);
var converter = new ImageConverter();
// Reading stream data to new image
var tempTiffImage = (Image)converter.ConvertFrom(memoryStream.GetBuffer());
// Setting new result mime-type
imageCodecInfo = ImageCodecInfo.GetImageEncoders().First(encoder => encoder.MimeType == mimeType);
tempTiffImage?.Save(memoryStream, imageCodecInfo, imageEncoderParams);
return new Bitmap(Image.FromStream(memoryStream, true));
}
}
public static class MimeType
{
public const string Bmp = "image/bmp";
public const string Gif = "image/gif";
public const string Jpeg = "image/jpeg";
public const string Png = "image/png";
public const string Tiff = "image/tiff";
}
Usage:
var sourceImg = Image.FromFile(#"C:\MyImage.tif", true);
var codec = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == sourceImg.RawFormat.Guid);
if (codec.MimeType == MimeType.Tiff)
{
sourceImg = ConvertTiffToBitmap(sourceImg, MimeType.Jpeg);
}
Hope, it could be usefull
i am getting Invalid Parameter Error when calling System.Drawing.Image.Save function.
i google and found a few suggestions but nothing works.
what i am trying to do is that, when i upload an image and if it's lager than 100kb i would like to reduce the image size to half. please help.
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(realpath);
FullsizeImage = System.Drawing.Image.FromFile(realpath);
int fileSize = (int)new System.IO.FileInfo(realpath).Length;
while (fileSize > 100000) //If Larger than 100KB
{
SaveJpeg(realpath, FullsizeImage);
fileSize = (int)new System.IO.FileInfo(realpath).Length;
}
private static 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;
}
public static void SaveJpeg(string path, Image img)
{
Image NewImage = img;
img.Dispose();
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, 85L);
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
ImageCodecInfo jpegCodec = GetEncoderInfo(GetMimeType(path.Substring(path.LastIndexOf('.'), path.Length - path.LastIndexOf('.'))));
//THE ERROR IS HERE!!!!!!
NewImage.Save(path, jpegCodec, encoderParams);
//THE ERROR IS HERE!!!!!!
}
public static string GetMimeType(string extension)
{
if (extension == null)
{
throw new ArgumentNullException("extension");
}
if (!extension.StartsWith("."))
{
extension = "." + extension;
}
switch (extension.ToLower())
{
#region Big freaking list of mime types
// combination of values from Windows 7 Registry and
// from C:\Windows\System32\inetsrv\config\applicationHost.config
// some added, including .7z and .dat
case ".323": return "text/h323";
// more extension here..
#endregion
default:
// if you have logging, log: "No mime type is registered for extension: " + extension);
return "application/octet-stream";
}
}
EDIT : I modified the code as below, now the image is saving without any exception! Thanks! but another problem here. the file size is not getting reduced. which mean my while loop can never exit. please help and thanks again.
using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(realpath)))
{
using (Image FullsizeImage = Image.FromStream(ms))
{
//code here
int fileSize = (int)new System.IO.FileInfo(realpath).Length;
while (fileSize > 100000) //If Larger than 100KB
{
SaveJpeg(realpath, FullsizeImage, 85L);
fileSize = (int)new System.IO.FileInfo(realpath).Length;
}
}
}
Can someone help me please, my problem is not solved yet :(
Because you're disposing an image object.
public static void SaveJpeg(string path, Image img)
{
Image NewImage = img;
img.Dispose(); <------- Here
...
}
EDIT: Method Image.FromFile file opens a stream and that file wont be closed till your method is not terminated. Try to use MemoryStream.
using (MemoryStream ms = new MemoryStream(File.ReadAllBytes(realPath)))
{
using (Image img = Image.FromStream(ms))
{
ImageCodecInfo myImageCodecInfo;
Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
myImageCodecInfo = GetEncoderInfo("image/jpeg");
myEncoder = Encoder.Quality;
myEncoderParameters = new EncoderParameters(1);
myEncoderParameter = new EncoderParameter(myEncoder, 85L);
myEncoderParameters.Param[0] = myEncoderParameter;
img.Save(realPath, myImageCodecInfo, myEncoderParameters);
}
}
I have working code to rotate the image. But I have a requirement to preserve meta data, particularly color profile information.
public static void Rotate(string fileName,RotateFlipType rft, string targetMimeType)
{
ImageCodecInfo imageCodecInfo = ImageCodecInfo.GetImageEncoders().Single(i => i.MimeType == targetMimeType);
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
using( Image im = Image.FromFile(fileName, true))
{
im.RotateFlip(rft);
im.Save("rotated_"+fileName, imageCodecInfo, encoderParams);
}
}
How does that look?