C# Rotating JPG without losing too much quality - c#

So I'm reading in files from a directory, figuring out which way they need to be rotated. Rotating and then saving. That part works... The issue is, after it saves the file it gets recompressed and I go from 1.5meg images to 250k images. I need to keep the file size around the original. I tried using jhead.exe and calling it from a command line but couldn't get any of my arguments to pass in correctly. Here's my code snipit to detect, rotate, and save.
foreach (FileInfo f in dir.GetFiles("*.jpg"))
{
try
{
string ExportName = "";
Bitmap originalImage = new Bitmap(f.FullName.ToString());
Info inf = new Info(originalImage);
gma.Drawing.ImageInfo.Orientation orientation = gma.Drawing.ImageInfo.Orientation.TopLeft;
try
{
orientation = inf.Orientation;
}
catch
{
orientation = gma.Drawing.ImageInfo.Orientation.TopLeft;
}
originalImage = CheckRotation(originalImage, orientation);
progressBar.Value = progressBar.Value + 1;
originalImage.Save(f.FullName.ToString(), ImageFormat.Jpeg);
Application.DoEvents();
}
private Bitmap CheckRotation(Bitmap inputImage, gma.Drawing.ImageInfo.Orientation orientation)
{
Bitmap rotatedImage = inputImage;
switch (orientation)
{
case gma.Drawing.ImageInfo.Orientation.LeftBottom:
rotatedImage.RotateFlip(RotateFlipType.Rotate90FlipXY);
break;
case gma.Drawing.ImageInfo.Orientation.RightTop:
rotatedImage.RotateFlip(RotateFlipType.Rotate90FlipNone);
break;
default:
break;
}
return rotatedImage;
}

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);
originalImage.Save(f.FullName.ToString(), ici, ep);
This will use 100% quality - but beware, jpegs are still lossy compression - try using a png if you need loss-less quality.

The key for lossless jpeg edit is to use always same QualityLevel and BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile with BitmapCacheOption.None.
And be aware that even if you use QualityLevel 100, quality will go down. With this method goes down just first time, because it goes from unknown QualityLevel to 80, but every other jpeg edit is lossless.
RotateJpeg(#"d:\!test\TestInTest\20160209_143609.jpg", 80, Rotation.Rotate90);
public bool RotateJpeg(string filePath, int quality, Rotation rotation) {
var original = new FileInfo(filePath);
if (!original.Exists) return false;
var temp = new FileInfo(original.FullName.Replace(".", "_temp."));
const BitmapCreateOptions createOptions = BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile;
try {
using (Stream originalFileStream = File.Open(original.FullName, FileMode.Open, FileAccess.Read)) {
JpegBitmapEncoder encoder = new JpegBitmapEncoder {QualityLevel = quality, Rotation = rotation};
//BitmapCreateOptions.PreservePixelFormat | BitmapCreateOptions.IgnoreColorProfile and BitmapCacheOption.None
//is a KEY to lossless jpeg edit if the QualityLevel is the same
encoder.Frames.Add(BitmapFrame.Create(originalFileStream, createOptions, BitmapCacheOption.None));
using (Stream newFileStream = File.Open(temp.FullName, FileMode.Create, FileAccess.ReadWrite)) {
encoder.Save(newFileStream);
}
}
}
catch (Exception) {
return false;
}
try {
temp.CreationTime = original.CreationTime;
original.Delete();
temp.MoveTo(original.FullName);
}
catch (Exception) {
return false;
}
return true;
}

Easy...
public static void Rotate90(string fileName)
{
Image Pic;
string FileNameTemp;
Encoder Enc = Encoder.Transformation;
EncoderParameters EncParms = new EncoderParameters(1);
EncoderParameter EncParm;
ImageCodecInfo CodecInfo = GetEncoderInfo("image/jpeg");
// load the image to change
Pic = Image.FromFile(fileName);
// we cannot store in the same image, so use a temporary image instead
FileNameTemp = fileName + ".temp";
// for rewriting without recompression we must rotate the image 90 degrees
EncParm = new EncoderParameter(Enc,(long)EncoderValue.TransformRotate90);
EncParms.Param[0] = EncParm;
// now write the rotated image with new description
Pic.Save(FileNameTemp,CodecInfo,EncParms);
Pic.Dispose();
Pic = null;
// delete the original file, will be replaced later
System.IO.File.Delete(fileName);
System.IO.File.Move(FileNameTemp, fileName);
}
See reference at:
https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-transforming-a-jpeg-image-without-loss-of-information-use

Related

How to save a Jpeg with ImageSharp with parameters for better quality

I need to convert the following code based on System.Drawing to use ImageSharp.
The method saves a JPG image.
internal static void SaveToJpg(MemoryStream ms, string fileName)
{
using (var image = System.Drawing.Image.FromStream(ms))
{
EncoderParameters myEncoderParameters = new EncoderParameters(3);
ImageCodecInfo ici = null;
// Use codec for better quality.
myEncoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
myEncoderParameters.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.ScanMethod, (int) EncoderValue.ScanMethodInterlaced);
myEncoderParameters.Param[2] = new EncoderParameter(System.Drawing.Imaging.Encoder.RenderMethod, (int) EncoderValue.RenderProgressive);
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.MimeType == "image/jpeg")
ici = codec;
}
try
{
image.Save(fileName, ici, myEncoderParameters);
}
finally
{
for (int i = 0; i < myEncoderParameters.Param.Length; i++)
{
myEncoderParameters.Param[i].Dispose();
}
myEncoderParameters.Dispose();
}
}
}
Are there any options to set the same parameters in ImageSharp? I've only found the JpegEncoder.Quality property, but the output image has a worse quality compared with the one saved with System.Drawing
internal static void SaveToJpg(MemoryStream ms, string fileName)
{
using (var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(ms))
{
JpegEncoder jp = new JpegEncoder() { Quality = 100 };
image.SaveAsJpeg(fileName, jp);
}
}

Access Violation Exception in System.Drawing.dll when saving jpeg

I have a camera which uses Emgucv Package to take photo in some parts of my code.
I randomly get AccessViolationException in System.Drawing.dll when trying to save a Jpeg file. As a result I have a corrupted Jpeg file.
My code is run on windows 10 and is written in Microsoft visual studio 2015.
This is my code that throws an exception randomly:
public Boolean SavePhoto(Bitmap image)
{try
{
MyGate.LogCamera("Start Save Photo");
string photoTime = PersianDateTime.Now.ToString("HHmmss") + "_"
+ DateTime.Now.Millisecond;
String photoPath = System.IO.Path.Combine(Path.UserImagePath , PersianDateTime.Now.ToString("yyyyMMdd"));
if (System.IO.Directory.Exists(photoPath)==false)
{
System.IO.Directory.CreateDirectory(photoPath);
}
photoPath = System.IO.Path.Combine(photoPath, photoTime + ".jpg");
MyGate.LogCamera("Just Before Save Photo");
SaveJpeg(photoPath, image, 90);
MyGate.LogCamera("End Save Photo");
return true;
}
catch(Exception e)
{
new NDCBaseClass().LogError(e.ToString());
return false;
}
}
public void SaveJpeg(string path, Image img, int quality)
{
try
{
// Encoder parameter for image quality
EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
// JPEG image codec
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
img.Save(path, jpegCodec, encoderParams);
}
catch(Exception e)
{
new NDCBaseClass().LogError(e.ToString());
}
}
calling GC.Collect after img.save method solves the problem.

C# How to reduce the size of the stream (image)

All I have is the Stream of the file (the file is image/jpeg type). I want to reduce the quality of the image (example: original 2MB -> 200KB).
var contentStream = ImageSource.FromStream(() => { return _MediaFile.GetStream(); });
newImg.Source = contentStream;
_ReportModel.Add(new ReportPicture()
{
Id = layout.Id,
Path = _MediaFile.Path,
Content = _MediaFile.GetStream()
});
Before I upload the _ReportModel to my server using REST API, I want to reduce the size of the Content which contains the stream of the read image (type jpeg).
I need something like:
private Stream ReduceStreamSize(Stream oldStream)
{
var reducedStream = //doSomething(oldStream);
return reducedStream;
}
How can I do this?
Update
I have tried this:
public Stream CompressImage(string imagePath)
{
try
{
// BitmapFactory options to downsize the image
BitmapFactory.Options o = new BitmapFactory.Options();
o.InJustDecodeBounds = true;
o.InSampleSize = 6;
// factor of downsizing the image
FileStream inputStream = new FileStream(imagePath, FileMode.Open);
//Bitmap selectedBitmap = null;
BitmapFactory.DecodeStream(inputStream, null, o);
inputStream.Close();
// The new size we want to scale to
int REQUIRED_SIZE = 75;
// Find the correct scale value. It should be the power of 2.
int scale = 1;
while (o.OutWidth / scale / 2 >= REQUIRED_SIZE &&
o.OutHeight / scale / 2 >= REQUIRED_SIZE)
{
scale *= 2;
}
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.InSampleSize = scale;
inputStream = new FileStream(imagePath, FileMode.Open);
Bitmap selectedBitmap = BitmapFactory.DecodeStream(inputStream, null, o2);
inputStream.Close();
FileStream outputStream = new FileStream(imagePath, FileMode.Open);
selectedBitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, outputStream);
return outputStream;
}
catch (Exception e)
{
return null;
}
}
But the image is not showing. There must be an error while compressing it.

How do I create a thumbnail from an image using C# Win Mobile 6.5/Compact framework?

I am trying to make/use a smaller image than the one taken with the camera to display in parts of my program (due to memory issues...)
Unfortunately I don't seem to be able to find ways to make a smaller image/thumbnail on the mobile device - the way it is possible in normal windows...
Is there a way to make a smaller image on Win Mobile 6.5/Compact Framework?
For example - these do not work on Win Mobile
What is the "best" way to create a thumbnail using ASP.NET?
This looks promising - but i want to just put the image on a PictureBox - and not sure how to use this to make it work.
This code should be work. I have also implemented this code. For that I have used OpenNETCF.Drawing.dll.
barray = GetImage(filepath);
if (barray != null)
{
ms.Write(barray, 0, barray.Length - 1);
imageBitmap = CreateThumbnail(ms, new Size(PreviewImageWidth, PreviewImageHeight));
bm = ImageUtils.IBitmapImageToBitmap(imageBitmap);
// m_CurrentImageID = Convert.ToInt32(lstPic.ToList()[index].Id);
PictureBox ib = ((PictureBox)this.imagePanel.Controls[(nIndex * 2) + 1]);
ib.Image = bm;
}
private byte[] GetImage(string FilePath)
{
byte[] barray;
if (File.Exists(FilePath))
{
FileInfo _fileInfo = new FileInfo(FilePath);
long _NumBytes = _fileInfo.Length;
FileStream _FStream = new FileStream(FilePath, FileMode.Open, FileAccess.Read);
BinaryReader _BinaryReader = new BinaryReader(_FStream);
barray = _BinaryReader.ReadBytes(Convert.ToInt32(_NumBytes));
_FStream.Flush();
_FStream.Dispose();
return barray;
}
else
{
return null;
}
}
public IBitmapImage CreateThumbnail(Stream stream, Size size)
{
IBitmapImage imageBitmap;
ImageInfo ii;
IImage image;
ImagingFactory factory = new ImagingFactoryClass();
try
{
factory.CreateImageFromStream(new StreamOnFile(stream), out image);
image.GetImageInfo(out ii);
factory.CreateBitmapFromImage(image, (uint)size.Width, (uint)size.Height, ii.PixelFormat, InterpolationHint.InterpolationHintDefault, out imageBitmap);
return imageBitmap;
}
catch (Exception ex)
{
CreateLogFiles Err = new CreateLogFiles();
Err.ErrorLog(ex.Message);
return null;
}
finally
{
imageBitmap = null;
image = null;
factory = null;
}
}
This should work:
this.pictureBox1.Image = System.Drawing.Image.FromFile(#"\path to image").GetThumbnailImage(100, 100, null, IntPtr.Zero);
but the quality of thumbnail may not be as good. For quality thumbnail images you could refer to this post here

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