Loaded bitmap differs from saved one in jpeg (c#) - c#

I'm using following methods to load bitmap from jpeg file (JpegToBitmap):
private static Bitmap JpegToBitmap(string fileName)
{
FileStream jpg = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read);
JpegBitmapDecoder ldDecoder = new JpegBitmapDecoder(jpg, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
BitmapFrame lfFrame = ldDecoder.Frames[0];
Bitmap lbmpBitmap = new Bitmap(lfFrame.PixelWidth, lfFrame.PixelHeight);
Rectangle lrRect = new Rectangle(0, 0, lbmpBitmap.Width, lbmpBitmap.Height);
BitmapData lbdData = lbmpBitmap.LockBits(lrRect, ImageLockMode.WriteOnly, (lfFrame.Format.BitsPerPixel == 24 ? PixelFormat.Format24bppRgb : PixelFormat.Format32bppArgb));
lfFrame.CopyPixels(System.Windows.Int32Rect.Empty, lbdData.Scan0, lbdData.Height * lbdData.Stride, lbdData.Stride);
lbmpBitmap.UnlockBits(lbdData);
return lbmpBitmap;
}
And to save bitmap to jpeg file:
private static void SaveToJpeg(Bitmap bitmap, string filePath)
{
EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
bitmap.Save(filePath, GetEncoder(ImageFormat.Jpeg), encoderParameters);
}
public static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
Bitmap pixels differs just a little bit, but this difference is significant for me:
Screenshot of values in matrix of colors of bitmap before save
Screenshot of values in same matrix of colors of bitmap after load
This matrixes are just 2-d arrays of Color got by standart bitmap.GetPixel(x,y) in a loop.
public ColorMatrix(Bitmap bitmap, int currHeight, int currWidth)
{
matrix = new Color[8, 8];
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
matrix[y, x] = bitmap.GetPixel(x + currWidth, y + currHeight);
}
}
}
So, the question is: How can i load saved bitmap (in jpeg/png or whatever format) correctly?

There are three reasons:
JPEG encoding and decoding processes use floating point arithmetic on integer input.
The Cb and Cr components are often subsampled relative to the Y component during compression.
During the quantization (fancy word for integer division) phase, DCT coefficients are usually discarded.

Related

System.Drawing.Imaging.Encoder.Quality file size changing strangely

I am trying out working with images and i came up to some strange behavior when using Encoder.Quality
I am having code like this:
try
{
Image myImage = Image.FromStream(file.OpenReadStream());
string final = "";
MemoryStream ts = new MemoryStream();
SaveJpeg(ts, myImage, 100);
final += (ts.Capacity * 0.0009765625).ToString("0") + ", ";
for (int i = 0; i <= 10; i++)
{
MemoryStream testStream = new MemoryStream();
SaveJpeg(testStream, myImage, i * 10);
final += (testStream.Capacity * 0.0009765625).ToString("0") + ", ";
}
return Json(final);
}
catch (Exception ex)
{
return View("Error", ex.ToString());
}
public static void SaveJpeg(string path, Image img, int quality)
{
if (quality < 0)
quality = 0;
if (quality > 100)
quality = 100;
// Encoder parameter for image quality
EncoderParameter qualityParam = new EncoderParameter(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);
}
public static void SaveJpeg(Stream stream, Image img, int quality)
{
if (quality < 0)
quality = 0;
if (quality > 100)
quality = 100;
// Encoder parameter for image quality
EncoderParameter qualityParam = new EncoderParameter(Encoder.Quality, quality);
// JPEG image codec
ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
EncoderParameters encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = qualityParam;
img.Save(stream, jpegCodec, encoderParams);
}
/// <summary>
/// Returns the image codec with the given mime type
/// </summary>
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;
}
It is asp.net core but i think that dosn't matter.
What matters here is that file i upload is "jpg" and it's size is 201kb but output from this code is 512, 29, 41, 53, 128, 128, 128, 128, 128, 128, 256, 512, (first number is quality 100, others are 0, 10, 20... 100)
When i store it into MemoryStream without changing quality i get same file size as my file.
Here i have two questions.
Is quality level 90-100 somehow "enchancing" image quality?
Why is image size from level 40-90 all same size, does it make any difference?

c# How to resize any image to specific kilobytes while retaining image type

I've seen a ton of stackoverflow articles for reducing image size, but none of them maintain the original image type (or so I've found). They usually have steps to reduce pixel dimensions, reduce image quality, and convert to a specific type of image (usually jpeg).
I have a group of images that I need to resize. They have various image types, and the filenames are all stored in a database, which makes converting from one image type to another somewhat problematic. I can't just change the filename from png to jpg because then the database won't point at a real file.
Doe anyone have an example of how to resize / reduce images to '256 kilobytes' and maintain the original image type?
For examples, here is the code I'm currently fiddling with.
public static byte[] ResizeImageFile(Image oldImage, int targetSize) // Set targetSize to 1024
{
Size newSize = CalculateDimensions(oldImage.Size, targetSize);
using (Bitmap newImage = new Bitmap(newSize.Width, newSize.Height, PixelFormat.Format24bppRgb))
{
using (Graphics canvas = Graphics.FromImage(newImage))
{
canvas.SmoothingMode = SmoothingMode.AntiAlias;
canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
canvas.DrawImage(oldImage, new Rectangle(new Point(0, 0), newSize));
MemoryStream m = new MemoryStream();
newImage.Save(m, ImageFormat.Jpeg);
return m.GetBuffer();
}
}
}
Maybe there is a way I can get file fileinfo or mime type first and then switch on the .Save for the type of image?
Here is what I came up with (based on some examples that I found online that weren't 100% complete.
private void EnsureImageRequirements(string filePath)
{
try
{
if (File.Exists(filePath))
{
// If images are larger than 300 kilobytes
FileInfo fInfo = new FileInfo(filePath);
if (fInfo.Length > 300000)
{
Image oldImage = Image.FromFile(filePath);
ImageFormat originalFormat = oldImage.RawFormat;
// manipulate the image / Resize
Image tempImage = RefactorImage(oldImage, 1200); ;
// Dispose before deleting the file
oldImage.Dispose();
// Delete the existing file and copy the image to it
File.Delete(filePath);
// Ensure encoding quality is set to an acceptable level
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
// Set encoder to fifty percent compression
EncoderParameters eps = new EncoderParameters
{
Param = { [0] = new EncoderParameter(Encoder.Quality, 50L) }
};
ImageCodecInfo ici = (from codec in encoders where codec.FormatID == originalFormat.Guid select codec).FirstOrDefault();
// Save the reformatted image and use original file format (jpeg / png / etc) and encoding
tempImage.Save(filePath, ici, eps);
// Clean up RAM
tempImage.Dispose();
}
}
}
catch (Exception ex)
{
this._logger.Error("Could not resize oversized image " + filePath, ex);
}
}
private static Image RefactorImage(Image imgToResize, int maxPixels)
{
int sourceWidth = imgToResize.Width;
int sourceHeight = imgToResize.Height;
int destWidth = sourceWidth;
int destHeight = sourceHeight;
// Resize if needed
if (sourceWidth > maxPixels || sourceHeight > maxPixels)
{
float thePercent = 0;
float thePercentW = 0;
float thePercentH = 0;
thePercentW = maxPixels / (float) sourceWidth;
thePercentH = maxPixels / (float) sourceHeight;
if (thePercentH < thePercentW)
{
thePercent = thePercentH;
}
else
{
thePercent = thePercentW;
}
destWidth = (int)(sourceWidth * thePercent);
destHeight = (int)(sourceHeight * thePercent);
}
Bitmap tmpImage = new Bitmap(destWidth, destHeight, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(tmpImage);
g.InterpolationMode = InterpolationMode.HighQualityBilinear;
g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
g.Dispose();
return tmpImage;
}

PDFium Saving pdf as image specify color depth

I am trying to export pdfs as jpeg using pdfium viewer. How can I specify the color depth of the jpeg image? Do I have to edit the stream using System.Drawing.Imaging or can does anyone know of a way to do it with pdfium? https://github.com/pvginkel/PdfiumViewer is what I am using to render the pdf and create the image.
using (var document = PdfiumViewer.PdfDocument.Load(file.ToString()))
{
for (int page = 0; page < document.PageCount; page++)
{
var image = document.Render(page, 2550, 3300, 72, 72, false);
image.Save(filepath + "\\" + filename + "-" + (page + 1) + ".jpg", ImageFormat.Jpeg);
}
}
Have to set the image encoder properties to change them. This is showing from bump to tiff but explains what I was looking for.
using System;
using System.Drawing;
using System.Drawing.Imaging;
class Example_SetColorDepth
{
public static void Main()
{
Bitmap myBitmap;
ImageCodecInfo myImageCodecInfo;
Encoder myEncoder;
EncoderParameter myEncoderParameter;
EncoderParameters myEncoderParameters;
// Create a Bitmap object based on a BMP file.
myBitmap = new Bitmap(#"C:\Documents and Settings\All Users\Documents\My Music\music.bmp");
// Get an ImageCodecInfo object that represents the TIFF codec.
myImageCodecInfo = GetEncoderInfo("image/tiff");
// Create an Encoder object based on the GUID
// for the ColorDepth parameter category.
myEncoder = Encoder.ColorDepth;
// Create an EncoderParameters object.
// An EncoderParameters object has an array of EncoderParameter
// objects. In this case, there is only one
// EncoderParameter object in the array.
myEncoderParameters = new EncoderParameters(1);
// Save the image with a color depth of 24 bits per pixel.
myEncoderParameter =
new EncoderParameter(myEncoder, 24L);
myEncoderParameters.Param[0] = myEncoderParameter;
myBitmap.Save("Shapes24bpp.tiff", myImageCodecInfo, myEncoderParameters);
}
private static ImageCodecInfo GetEncoderInfo(String mimeType)
{
int j;
ImageCodecInfo[] encoders;
encoders = ImageCodecInfo.GetImageEncoders();
for(j = 0; j < encoders.Length; ++j)
{
if(encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
}

How to reduce the size of image (in KB) on uploading and scaling?

I am uploading an image and reducing the dimensions. One of the reasons to do it is that I want also to reduce the size so it is better optimised and does not load long time.
But instead of downsizing the image has been enlarged despite the dimensions have halved.
This is my function:
public Image ScaleProportionally(Image imgPhoto, int shortestEdge = 0)
{
float sourceWidth = imgPhoto.Width;
float sourceHeight = imgPhoto.Height;
float destHeight = 0;
float destWidth = 0;
int sourceX = 0;
int sourceY = 0;
int destX = 0;
int destY = 0;
if (shortestEdge > 0)
{
if(sourceWidth < sourceHeight)
{
destWidth = shortestEdge;
destHeight = (float)(sourceHeight * shortestEdge / sourceWidth);
}
else
{
destWidth = (float)(shortestEdge * sourceWidth) / sourceHeight;
destHeight = shortestEdge;
}
Bitmap bmPhoto = new Bitmap((int)destWidth, (int)destHeight,
PixelFormat.Format32bppPArgb);
bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);
Graphics grPhoto = Graphics.FromImage(bmPhoto);
grPhoto.InterpolationMode = InterpolationMode.Low;
grPhoto.CompositingQuality = CompositingQuality.AssumeLinear;
grPhoto.DrawImage(imgPhoto,
new Rectangle(destX, destY, (int)destWidth, (int)destHeight),
new Rectangle(sourceX, sourceY, (int)sourceWidth, (int)sourceHeight),
GraphicsUnit.Pixel);
grPhoto.Dispose();
return bmPhoto;
}
else
{
return imgPhoto;
}
}
I originally had this below for InterpolationMode and CompositingQuality but changing them as above didn't really reduced the size of scaled photo:
grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
grPhoto.CompositingQuality = CompositingQuality.HighSpeed;
Original image has 546KB while scaled has 1MB almost twice the size.
How to reduce the size on rescaling?
I am using .NET 4.5
EDIT
Save file function:
public int CreateAndSaveMedia(HttpPostedFileBase file, string fileName, string mediaType, string caption, int folderId, Dictionary<string,string> additionalProperties = null)
{
// Create temp mediaitem folder
string mediaDirectory = System.Web.HttpContext.Current.Server.MapPath($"~/media/");
var path = Path.Combine(mediaDirectory, fileName);
file.SaveAs(path);
Image imgOriginal = Image.FromFile(path);
//pass in whatever value you want
Image imgActual = _imageService.ScaleProportionally(imgOriginal, 450);
imgOriginal.Dispose();
imgActual.Save(path);
imgActual.Dispose();
// Open file and assign media url to media content
FileStream s = new FileStream(path, FileMode.Open);
// Save media content
IMedia media = _mediaService.CreateMedia(caption, folderId, mediaType);
_mediaService.Save(media);
media.SetValue("umbracoFile", Path.GetFileName(path), s);
if (additionalProperties != null)
{
foreach (var itm in additionalProperties)
{
media.SetValue(itm.Key, itm.Value);
}
}
_mediaService.Save(media);
// Get media Id
int mediaId = media.Id;
s.Close();
System.IO.File.Delete(path);
return mediaId;
}
EDIT
Following #Nyerguds's comment I specifically set the new image type to jpg and it made a total difference:
imgActual.Save(path, System.Drawing.Imaging.ImageFormat.Jpeg);
Thanks!
You're not setting a type when saving, which makes the Image.Save default to either the detected type of the original image, or to PNG if it can't save in the exact format detected from the input.
The compression you get out of png depends a lot on what is in the image; png isn't very good at compressing complex images like photos, and resizing it to smaller size might in fact increase said complexity. Also, the .Net framework's png compression algorithms aren't as good as those in actual graphics manipulation programs.
For reducing size, you may want to specifically re-save as jpeg.
This can be done in two ways. The simple way is to just specify Jpeg as save type:
imgActual.Save(path, ImageFormat.Jpeg);
A more advanced method is to use the JPEG encoder. If you do this, you can set the save quality (inverse of compression rate, really) of the image, as percentage (values from 1 to 100). Unfortunately, though, there's no simple method to get it; you need to retrieve it by GUID by going over the full list of available encoders:
Int32 quality = 80;
ImageCodecInfo jpegEncoder = ImageCodecInfo.GetImageDecoders().First(c => c.FormatID == ImageFormat.Jpeg.Guid);
EncoderParameters encparams = new EncoderParameters(1);
encparams.Param[0] = new EncoderParameter(Encoder.Quality, quality);
imgActual.Save(path, jpegEncoder, encparams);

My image is not compressing

Loading code from filesystem:
System.Drawing.Image image = System.Drawing.Image.FromFile(<location of original image>););
Loading code from browser request:
var memoryStream = new MemoryStream();
using (memoryStream)
{
System.Web.HttpContext.Current.Request.Files[upload].InputStream.CopyTo(memoryStream);
memoryStream.ToArray();
}
byte[] bytes = memoryStream.GetBuffer();
// Get the image from the server
System.Drawing.Image image = new System.Drawing.Bitmap( System.Web.HttpContext.Current.Request.Files[upload].InputStream );
Resize image call:
System.Drawing.Image image = this.ResizeImage(
image,
originalImagePath,
ImageSizeType.Original,
null,
null)
Save image call:
image.Save(<location to save>);
The code that doesn't compress the image:
private System.Drawing.Image ResizeImage(System.Drawing.Image image, string filePath, string sizeType, int? _width, int? height )
{
...
System.Drawing.Bitmap b = new System.Drawing.Bitmap(width, resizeHeight);
b.SetResolution(72, 72);
System.Drawing.Graphics g = System.Drawing.Graphics.FromImage((System.Drawing.Image)b);
g.CompositingQuality = CompositingQuality.HighSpeed;
//g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.InterpolationMode = InterpolationMode.Low;
g.SmoothingMode = SmoothingMode.HighSpeed;
g.DrawImage(image, 0, 0, width, resizeHeight);
g.Dispose();
return (System.Drawing.Image)b;
}
No matter what I do to this image, when it saves, it saves at a really high kb.
For example... a jpg of 1024 x 768 # 300kb becomes 600 x 400 # 800kb
What am I doing wrong?
As Magnus rightly said, the drawing to the canvas makes no difference to size... of file...
It was the save file part that was being all noob... This is what it should be:
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;
}
...
if( mimeType.ToLower() == "image/jpeg")
{
ImageCodecInfo jpgEncoder = this.GetEncoderInfo("image/jpeg")
System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 80L);
myEncoderParameters.Param[0] = myEncoderParameter;
image.Save(systemFilePath, jpgEncoder, myEncoderParameters);
}
else
{
image.Save(systemFilePath);
}

Categories

Resources