Change image quality on the fly - c#

I have write this code to do crop and resize the image on the fly. I send the processed image to the browser like <img src="imagehandler.aspx?img=1.jpg">:
imagehandler.aspx:
<%# Page Language="C#"%>
<%# import Namespace="System.Drawing" %>
<%# import Namespace="System.IO" %>
<script runat="server">
System.Drawing.Image oldImage, newImage,cloned,tempImage;
void Page_Load(Object sender, EventArgs e) {
string strFileName = Convert.ToString(Request.QueryString["img"]);
oldImage = System.Drawing.Image.FromFile(Server.MapPath(strFileName));
rect= new Rectangle(0,50,100,100);
cloned = new Bitmap(oldImage ).Clone(rect, tempImage.PixelFormat);
newImage = new Bitmap(cloned);
cloned.Dispose();
Response.ContentType = "image/jpeg";
newImage.Save(Response.OutputStream, System.Drawing.Imaging.ImageFormat.Jpeg);
oldImage.Dispose();
newImage.Dispose();
oldImage = null;
newImage = null;
}
</script>
Now I want to add quality control to the output image and I found this Q/A This answer suggests a method which saves the image to the disk. I have tried to fit it to my purpose. Currently I can only save it on the disk and the method is void. I don't know how to pass the output to my own codes before streaming the result to the browser:
private void VaryQualityLevel(bmp1)
{
ImageCodecInfo jgpEncoder = GetEncoder(ImageFormat.Jpeg);
System.Drawing.Imaging.Encoder myEncoder= System.Drawing.Imaging.Encoder.Quality;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 50L);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp1.Save(#"c:\TestPhotoQualityFifty.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;
}

You can save your bitmap directly to MemoryStream and do whatever you want with it. Your encoder will be applied to image inside this stream. Instead of passing file path as first parameter of Save method just pass instance of MemoryStream. If I remember correctly there's also a way to pass this stream directly as a response to browser.
using(var ms = new MemoryStream())
{
bmp1.Save(ms, jgpEncoder, myEncoderParameters);
var bmp2 = new BitMap(ms);
//do whatever you want with this image
}
Keep in mind to use using statment or dispose method for sfream to avoid memory leak.
More details here:
https://learn.microsoft.com/en-us/dotnet/api/system.drawing.bitmap?view=netframework-4.7.2

Related

How can I convert a BMP to JPEG without saving to file?

I have a BMP image object which contains a BMP image, how can I convert it to JPEG format without saving to file?
All I need to know is the size of the JPEG file after the conversion, so If I have several BMP images, I want to convert each one of them into a JPEG and then see the size of each one.
How can I do that?
Thanks.
Just save it stream. and look size.
private ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
private void Form1_Load(object sender, EventArgs e)
{
Bitmap bmp1 = new Bitmap(#"C:\Users\ercan.acar\Pictures\Current CR Sticker.PNG");
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);
System.Drawing.Imaging.Encoder myEncoder =System.Drawing.Imaging.Encoder.Quality;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder,
50L);
myEncoderParameters.Param[0] = myEncoderParameter;
Stream stream=new MemoryStream();
bmp1.Save(stream, jpgEncoder,myEncoderParameters);
//bmp1.Save(#"C:\Users\ercan.acar\Documents\TestPhotoQualityFifty.jpg",);
var size = stream.Length;
}
How about using a nuget package like Magick.NET or similar.
Why reinventing the wheel and care about encoding, conversion adjustments, optimization if other have done this already for you.

Compress image without saving it

I am using the following code to compress an image and it does a nice job but I want to use the compressed image not save it. So right now I have to save the image then read it in again which is slow. Is there a way of compressing it with out saving it.
private void compress(System.Drawing.Image img, long quality, ImageCodecInfo codec)
{
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
img.Save("check1.jpg", codec, parameters);
}
private static ImageCodecInfo GetCodecInfo(string mimeType)
{
foreach (ImageCodecInfo encoder in ImageCodecInfo.GetImageEncoders())
if (encoder.MimeType == mimeType)
return encoder;
throw new ArgumentOutOfRangeException(
string.Format("'{0}' not supported", mimeType));
}
There is an overload that takes a Stream so you can save it straight to a MemoryStream and won't need to save to disk/reload.
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
var ms = new MemoryStream();
img.Save(ms, codec, parameters);
//Do whatever you need to do with the image
//e.g.
img = Image.FromStream(ms);
The reason you're getting the "Parameter not valid" exception you mention in the comments is because the image isn't being disposed of before you try to call FromStream, so you'll need to dispose it. Also, I don't know how you're calling this method, but you should probably update it to return the MemoryStream.
private MemoryStream compress(System.Drawing.Image img, long quality, ImageCodecInfo codec)
{
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(Encoder.Quality, quality);
var ms = new MemoryStream();
img.Save(ms, codec, parameters);
return ms;
}
public void MyMethod()
{
MemoryStream ms;
using(var img = Image.FromFile("myfilepath.img"))
{
ms = compress(img, /*quality*/, /*codec*/);
}
using(var compressedImage = Image.FromStream(ms))
{
//Use compressedImage
}
}
Notice how I return ms from compress and capture it. Also, more importantly, how we wrap the initial img in a using statement which will dispose the file handle correctly, and after that gets disposed create the second compressedImage which is also in a using so it will also get disposed of properly when you're done.

Image compression is not working

I have an operation on the site that takes crops an image, however the resultant, cropped image is coming out significantly larger in terms of file size (original is 24k and the cropped image is like 650k). So I found that I need to apply some compression to the image before saving it. I came up with the following:
public static System.Drawing.Image CropImage(System.Drawing.Image image, Rectangle cropRectangle, ImageFormat format)
{
var croppedImage = new Bitmap(cropRectangle.Width, cropRectangle.Height);
using (var g = Graphics.FromImage(croppedImage))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.DrawImage(
image,
new Rectangle(new Point(0,0), new Size(cropRectangle.Width, cropRectangle.Height)),
cropRectangle,
GraphicsUnit.Pixel);
return CompressImage(croppedImage, format);
}
}
public static System.Drawing.Image CompressImage(System.Drawing.Image image, ImageFormat imageFormat)
{
var bmp = new Bitmap(image);
var codecInfo = EncoderFactory.GetEncoderInfo(imageFormat);
var encoder = System.Drawing.Imaging.Encoder.Quality;
var parameters = new EncoderParameters(1);
var parameter = new EncoderParameter(encoder, 10L);
parameters.Param[0] = parameter;
using (var ms = new MemoryStream())
{
bmp.Save(ms, codecInfo, parameters);
var resultImage = System.Drawing.Image.FromStream(ms);
return resultImage;
}
}
I set the quality low just to see if there was any change at all. There isn't. The crop is being saved correctly appearance-wise but compression is a no joy. If I bypass CompressImage() altogether, neither the file size nor the image quality appear to be any different.
So, 2 questions. Why is nothing happening? Is there a simpler way to compress the resultant image to "web-optimize" similar to how photoshop saves web images (I thought it just stripped a lot of info out of it to reduce the size).
Your problem is you must 'compress' (really encode) the image as you save it, not before you save it. An Image object in your program is always uncompressed.
By saving to the MemoryStream and reading back out from the stream will encode the image and then decode it back to the same size again (with some quality loss in the process if you are using JPEG). However, if you save it to a file with the compression parameters, you will get a compressed image file.
Using this routine with JPEG quality level 90 on a 153 KB source image gives an output image of 102 KB. If you want a smaller file size (with more encoding artifacts) change the encoder parameter to something smaller than 90.
public static void SaveJpegImage(System.Drawing.Image image, string fileName)
{
ImageCodecInfo codecInfo = ImageCodecInfo.GetImageEncoders()
.Where(r => r.CodecName.ToUpperInvariant().Contains("JPEG"))
.Select(r => r).FirstOrDefault();
var encoder = System.Drawing.Imaging.Encoder.Quality;
var parameters = new EncoderParameters(1);
var parameter = new EncoderParameter(encoder, 90L);
parameters.Param[0] = parameter;
using (FileStream fs = new FileStream(fileName, FileMode.Create))
{
image.Save(fs, codecInfo, parameters);
}
}
I believe you shouldn't dispose of the MemoryStream while you are using an image created using Image.FromStream that refers to the stream. Creating a Bitmap directly from the stream also doesn't work.
Try this:
private static Image CropAndCompressImage(Image image, Rectangle rectangle, ImageFormat imageFormat)
{
using(Bitmap bitmap = new Bitmap(image))
{
using(Bitmap cropped = bitmap.Clone(rectangle, bitmap.PixelFormat))
{
using (MemoryStream memoryStream = new MemoryStream())
{
cropped.Save(memoryStream, imageFormat);
return new Bitmap(Image.FromStream(memoryStream));
}
}
}
}

How to convert varBinary into image or video when retrieved from database in C#

I am using visual studio 2010, (desktop application) and using LINQ to SQL to save image/video or audio files to database in dataType VarBinary (MAX). This I can do... Problem is, I can't get them and display them in xaml because I can't get the converting part correct. Here is what I have so far (though its not working);
private void bt_Click (object sender, RoutedEventArgs e)
{
databaseDataContext context = new databaseDataContext();
var imageValue = from s in context.Images
where s.imageID == 2
select s.imageFile;
value = imageValue.Single().ToString();
//convert to string and taking down to next method to get converted in image
}
public string value { get; set; }
public object ImageSource //taking from http://stackoverflow.com/
{
get
{
BitmapImage image = new BitmapImage();
try
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
image.UriSource = new Uri(value, UriKind.Absolute);
image.EndInit();
Grid.Children.Add(image);
}
catch { return DependencyProperty.UnsetValue; } return image;
}
}
I not even sure if I am on the correct track? And I am assuming that video or audio is quite similar methods?
Since your image is stored in binary format in the database, you want to "stream" this into an image object by leveraging the MemoryStream object.
Looking at your code, your solution will look something like this:
BitmapImage bmpImage = new BitmapImage();
MemoryStream msImageStream = new MemoryStream();
msImageStream.Write(value, 0, value.Length);
bmpCardImage.BeginInit();
bmpCardImage.StreamSource = new MemoryStream(msImageStream.ToArray());
bmpCardImage.EndInit();
image.Source = bmpCardImage;
It's very easy, if you have a binary data and want to create an Image object, use this code:
public Image BinaryToImage(byte[] binaryData)
{
MemoryStream ms = new MemoryStream(binaryData);
Image img = Image.FromStream(ms);
return img;
}
If you already have the bytes, to verify that what you saved is correct you can save the bytes to a file and open it....
string tempFile = Path.GetTempFileName();
MemoryStream ms = new MemoryStream(bytes); //bytes that was read from the db
//Here I assume that you're reading a png image, you can put any extension you like is a file name
FileStream stream = new FileStream(tempFile + ".png", FileMode.Create);
ms.WriteTo(stream);
ms.Close();
stream.Close();
//And here we open the file with the default program
Process.Start(tempFile + ".png");
And later you can use the answer of Dillie-O and stream....
Dillie-O's code makes for a very nice extension method:
// from http://stackoverflow.com/questions/5623264/how-to-convert-varbinary-into-image-or-video-when-retrieved-from-database-in-c:
public static BitmapImage ToImage(this Binary b)
{
if (b == null)
return null;
var binary = b.ToArray();
var image = new BitmapImage();
var ms = new MemoryStream();
ms.Write(binary, 0, binary.Length);
image.BeginInit();
image.StreamSource = new MemoryStream(ms.ToArray());
image.EndInit();
return image;
}

Compress bitmap before sending over network

I'm trying to send a bitmap screenshot over network, so I need to compress it before sending it. Is there a library or method for doing this?
When you save an Image to a stream, you have to select a format. Almost all bitmap formats (bmp, gif, jpg, png) use 1 or more forms of compression. So just select an appropriate format, and make make sure that sender and receiver agree on it.
If you are looking for something to compress the image in quality, here it is-
private Image GetCompressedBitmap(Bitmap bmp, long quality)
{
using (var mss = new MemoryStream())
{
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
ImageCodecInfo imageCodec = ImageCodecInfo.GetImageEncoders().FirstOrDefault(o => o.FormatID == ImageFormat.Jpeg.Guid);
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = qualityParam;
bmp.Save(mss, imageCodec, parameters);
return Image.FromStream(mss);
}
}
Use it -
var compressedBmp = GetCompressedBitmap(myBmp, 60L);
Try the System.IO.DeflateStream class.
May be you can use:
private Bitmap compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//
int options = 100;
while ( baos.toByteArray().length / 1024>100) { //
baos.reset();
image.compress(Bitmap.CompressFormat.JPEG, options, baos);//
options -= 10;// 10
}
ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//
Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//
return bitmap;
}

Categories