Resize of image as array of bytes - c#

I am currently trying to resize an image to a thumbnail, to show as a preview when its done uploading. I am using fineuploader plugin for the uploading part of the image. I consistently keep getting a "parameter is not valid". I've seen many posts related to this, and tried most of the solution, but have no success. Here is the snippet of the code:
public static byte[] CreateThumbnail(byte[] PassedImage, int LargestSide)
{
byte[] ReturnedThumbnail = null;
using (MemoryStream StartMemoryStream = new MemoryStream(),
NewMemoryStream = new MemoryStream())
{
StartMemoryStream.Write(PassedImage, 0, PassedImage.Length); //error being fire in this line
System.Drawing.Bitmap startBitmap = new Bitmap(StartMemoryStream);
int newHeight;
int newWidth;
double HW_ratio;
if (startBitmap.Height > startBitmap.Width)
{
newHeight = LargestSide;
HW_ratio = (double)((double)LargestSide / (double)startBitmap.Height);
newWidth = (int)(HW_ratio * (double)startBitmap.Width);
}
else
{
newWidth = LargestSide;
HW_ratio = (double)((double)LargestSide / (double)startBitmap.Width);
newHeight = (int)(HW_ratio * (double)startBitmap.Height);
}
System.Drawing.Bitmap newBitmap = new Bitmap(newWidth, newHeight);
newBitmap = ResizeImage(startBitmap, newWidth, newHeight);
newBitmap.Save(NewMemoryStream, System.Drawing.Imaging.ImageFormat.Jpeg);
ReturnedThumbnail = NewMemoryStream.ToArray();
}
return ReturnedThumbnail;
}
I'm out of ideas, any help is appreciated.

Your error is in the new Bitmap(startMemoryStream) line, not the line above.
The documentation states that this exception can occur when:
stream does not contain image data or is null.
-or-
stream contains a PNG image file with a single dimension greater than 65,535 pixels.
You should check that you have a valid PNG file in there. For example, write it to a file and try opening it in an image viewer.

That code is dangerous - every instance of a System.Drawing class must be placed in a using(){} clause.
Here's an alternate solution that uses the ImageResizer NuGet package and resizes the image safely.
var ms = new MemoryStream();
ImageResizer.Current.Build(PassedImage, ms, new ResizeSettings(){MaxWidth=LargestSide, MaxHeight=LargestSide});
return ImageResizer.ExtensionMethods.StreamExtensions.CopyToBytes(ms);

Related

C# Icon created looks fine, but Windows directory thumbnails don't look right

I wrote some code to create ico files from any png, jpg, etc. images. The icons seem to be getting created correctly, and looks almost like the original image, when opened in Paint3d. Here is how it looks:
But when setting the image as a thumbnail to a folder, it looks weird and shiny.
Here is how it looks in windows file explorer:
Firstly, I would like to know if this is an issue in Windows itself, or is it code related? If this is Windows related, the code doesn't matter. If not, here it is:
I picked up a couple of code snippets from across the internet, so probably some non-optimized code, but here is the meat of my code:
//imagePaths => all images which I am converting to ico files
imagePaths.ForEach(imgPath => {
//create a temp png at this path after changing the original img to a squared img
var tempPNGpath = Path.Combine(icoDirPath, imgName.Replace(ext, ".png"));
var icoPath = tempPNGpath.Replace(".png", ".ico");
using (FileStream fs1 = File.OpenWrite(tempPNGpath)) {
Bitmap b = ((Bitmap)Image.FromFile(imgPath));
b = b.CopyToSquareCanvas(Color.Transparent);
b.Save(fs1, ImageFormat.Png);
fs1.Flush();
fs1.Close();
ConvertToIco(b, icoPath, 256);
}
File.Delete(tempPNGpath);
});
public static void ConvertToIco(Image img, string file, int size) {
Icon icon;
using (var msImg = new MemoryStream())
using (var msIco = new MemoryStream()) {
img.Save(msImg, ImageFormat.Png);
using (var bw = new BinaryWriter(msIco)) {
bw.Write((short)0); //0-1 reserved
bw.Write((short)1); //2-3 image type, 1 = icon, 2 = cursor
bw.Write((short)1); //4-5 number of images
bw.Write((byte)size); //6 image width
bw.Write((byte)size); //7 image height
bw.Write((byte)0); //8 number of colors
bw.Write((byte)0); //9 reserved
bw.Write((short)0); //10-11 color planes
bw.Write((short)32); //12-13 bits per pixel
bw.Write((int)msImg.Length); //14-17 size of image data
bw.Write(22); //18-21 offset of image data
bw.Write(msImg.ToArray()); // write image data
bw.Flush();
bw.Seek(0, SeekOrigin.Begin);
icon = new Icon(msIco);
}
}
using (var fs = new FileStream(file, FileMode.Create, FileAccess.Write))
icon.Save(fs);
}
In the Extension class, the method goes:
public static Bitmap CopyToSquareCanvas(this Bitmap sourceBitmap, Color canvasBackground) {
int maxSide = sourceBitmap.Width > sourceBitmap.Height ? sourceBitmap.Width : sourceBitmap.Height;
Bitmap bitmapResult = new Bitmap(maxSide, maxSide, PixelFormat.Format32bppArgb);
using (Graphics graphicsResult = Graphics.FromImage(bitmapResult)) {
graphicsResult.Clear(canvasBackground);
int xOffset = (maxSide - sourceBitmap.Width) / 2;
int yOffset = (maxSide - sourceBitmap.Height) / 2;
graphicsResult.DrawImage(sourceBitmap, new Rectangle(xOffset, yOffset, sourceBitmap.Width, sourceBitmap.Height));
}
return bitmapResult;
}
The differences in scaling are the result of the fact you're not doing the scaling yourself.
The icon format technically only supports images up to 256x256. You have code to make a square image out of the given input, but you never resize it to 256x256, meaning you end up with an icon file in which the header says the image is 256x256, but which is really a lot larger. This is against the format specs, so you are creating a technically corrupted ico file. The strange differences you're seeing are a result of different downscaling methods the OS is using in different situations to remedy this situation.
So the solution is simple: resize the image to 256x256 before putting it into the icon.
If you want more control over any smaller display sizes for the icon, you can add code to resize it to a number of classic used formats, like 16x16, 32x32, 64x64 and 128x128, and put them all in an icon file together. I have written an answer to another question that details the process of putting multiple images into a single icon:
A: Combine System.Drawing.Bitmap[] -> Icon
There are quite a few other oddities in your code, though:
I see no reason to save your in-between image as png file. That whole fs1 stream serves no purpose at all. You never use or load the temp file; you just keep using the b variable, which does not need anything written to disk.
There is no point in first making the icon in a MemoryStream, then loading that as Icon class through its file loading function, and then saving that to a file. You can just write the contents of that stream straight to a file, or, heck, use a FileStream right away.
As I noted in the comments, Bitmap is a disposable class, so any bitmap objects you create should be put in using statements as well.
The adapted loading code, with the temp png writing removed, and the using statements and resizes added:
public static void WriteImagesToIcons(List<String> imagePaths, String icoDirPath)
{
// Change this to whatever you prefer.
InterpolationMode scalingMode = InterpolationMode.HighQualityBicubic;
//imagePaths => all images which I am converting to ico files
imagePaths.ForEach(imgPath =>
{
// The correct way of replacing an extension
String icoPath = Path.Combine(icoDirPath, Path.GetFileNameWithoutExtension(imgPath) + ".ico");
using (Bitmap orig = new Bitmap(imgPath))
using (Bitmap squared = orig.CopyToSquareCanvas(Color.Transparent))
using (Bitmap resize16 = squared.Resize(16, 16, scalingMode))
using (Bitmap resize32 = squared.Resize(32, 32, scalingMode))
using (Bitmap resize48 = squared.Resize(48, 48, scalingMode))
using (Bitmap resize64 = squared.Resize(64, 64, scalingMode))
using (Bitmap resize96 = squared.Resize(96, 96, scalingMode))
using (Bitmap resize128 = squared.Resize(128, 128, scalingMode))
using (Bitmap resize192 = squared.Resize(192, 192, scalingMode))
using (Bitmap resize256 = squared.Resize(256, 256, scalingMode))
{
Image[] includedSizes = new Image[]
{ resize16, resize32, resize48, resize64, resize96, resize128, resize192, resize256 };
ConvertImagesToIco(includedSizes, icoPath);
}
});
}
The CopyToSquareCanvas remains the same, so I didn't copy it here. The Resize function is fairly simple: just use Graphics.DrawImage to paint the picture on a different-sized canvas, after setting the desired interpolation mode.
public static Bitmap Resize(this Bitmap source, Int32 width, Int32 height, InterpolationMode scalingMode)
{
Bitmap result = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(result))
{
// Set desired interpolation mode here
g.InterpolationMode = scalingMode;
g.PixelOffsetMode = PixelOffsetMode.Half;
g.DrawImage(source, new Rectangle(0, 0, width, height), new Rectangle(0, 0, source.Width, source.Height), GraphicsUnit.Pixel);
}
return result;
}
And, finally, the above-linked Bitmap[] to Icon function, slightly tweaked to write to a FileStream directly instead of loading the result into an Icon object:
public static void ConvertImagesToIco(Image[] images, String outputPath)
{
if (images == null)
throw new ArgumentNullException("images");
Int32 imgCount = images.Length;
if (imgCount == 0)
throw new ArgumentException("No images given!", "images");
if (imgCount > 0xFFFF)
throw new ArgumentException("Too many images!", "images");
using (FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
using (BinaryWriter iconWriter = new BinaryWriter(fs))
{
Byte[][] frameBytes = new Byte[imgCount][];
// 0-1 reserved, 0
iconWriter.Write((Int16)0);
// 2-3 image type, 1 = icon, 2 = cursor
iconWriter.Write((Int16)1);
// 4-5 number of images
iconWriter.Write((Int16)imgCount);
// Calculate header size for first image data offset.
Int32 offset = 6 + (16 * imgCount);
for (Int32 i = 0; i < imgCount; ++i)
{
// Get image data
Image curFrame = images[i];
if (curFrame.Width > 256 || curFrame.Height > 256)
throw new ArgumentException("Image too large!", "images");
// for these three, 0 is interpreted as 256,
// so the cast reducing 256 to 0 is no problem.
Byte width = (Byte)curFrame.Width;
Byte height = (Byte)curFrame.Height;
Byte colors = (Byte)curFrame.Palette.Entries.Length;
Int32 bpp;
Byte[] frameData;
using (MemoryStream pngMs = new MemoryStream())
{
curFrame.Save(pngMs, ImageFormat.Png);
frameData = pngMs.ToArray();
}
// Get the colour depth to save in the icon info. This needs to be
// fetched explicitly, since png does not support certain types
// like 16bpp, so it will convert to the nearest valid on save.
Byte colDepth = frameData[24];
Byte colType = frameData[25];
// I think .Net saving only supports colour types 2, 3 and 6 anyway.
switch (colType)
{
case 2: bpp = 3 * colDepth; break; // RGB
case 6: bpp = 4 * colDepth; break; // ARGB
default: bpp = colDepth; break; // Indexed & greyscale
}
frameBytes[i] = frameData;
Int32 imageLen = frameData.Length;
// Write image entry
// 0 image width.
iconWriter.Write(width);
// 1 image height.
iconWriter.Write(height);
// 2 number of colors.
iconWriter.Write(colors);
// 3 reserved
iconWriter.Write((Byte)0);
// 4-5 color planes
iconWriter.Write((Int16)0);
// 6-7 bits per pixel
iconWriter.Write((Int16)bpp);
// 8-11 size of image data
iconWriter.Write(imageLen);
// 12-15 offset of image data
iconWriter.Write(offset);
offset += imageLen;
}
for (Int32 i = 0; i < imgCount; i++)
{
// Write image data
// png data must contain the whole png data file
iconWriter.Write(frameBytes[i]);
}
iconWriter.Flush();
}
}

Confusion over using Stream vs byte[] for transferring file content

What I want to achieve is to read a JPEG image from disk, resize it (reduce the resolution) and return the resulting image to a different module which is going to save the image to an AWS S3 bucket... I am confused whether I should return the resulting image in a byte[] or MemoryStream.
I have seen this tutorial and have written the following function which reads an image, resize it and returns the resulting image in a byte[].
public byte[] GetResizedImage(string folderPath, string fileName)
{
// read the original image from disk
FileInfo originalImage = ReadFileFromDisk(folderPath, fileName);
// resize the image, set width to 640px and respect original aspect ratio
using (var image = Image.FromStream(originalImage.OpenRead()))
{
int newWidth = image.Width;
int newHeight = image.Height;
float aspectRatio = image.Width / image.Height;
if (image.Width > 640)
{
newWidth = 640;
newHeight = Convert.ToInt32(GlobalConstants.NoOfPixelsForImageResizing / aspectRatio);
}
// resize image
Image thumbnail = image.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
using (var thumbnailStream = new MemoryStream())
{
thumbnail.Save(thumbnailStream, ImageFormat.Jpeg);
return thumbnailStream.ToArray(); // <-- return image in byte[]
}
}
}
I am not clear if it makes any difference to change the above code and return MemoryStream instead of byte[]? If I change the code to return MemoryStream then I need to change the last 3 lines to:
var thumbnailStream = new MemoryStream();
thumbnail.Save(thumbnailStream, ImageFormat.Jpeg);
return thumbnailStream;
This way, I won't be able to dispose of MemoryStream... not sure if this would be a bad practice?
The reason for this question is that my other module which saves the reduced image to an S3 bucket, accepts the file input as Stream:
SavetoS3bucket(Stream image, string name)
{
// save image to S3 bucket
}
So I am not clear if I am better off passing a Stream or byte[] to the above method?

Combining Frames of a Tiff into a single png gives Out of Memory Exception C#

First, I save the frames of a tiff into a list of bitmaps:
public Bitmap SaveTiffAsTallPng(string filePathAndName)
{
byte[] tiffFile = System.IO.File.ReadAllBytes(filePathAndName);
string fileNameOnly = Path.GetFileNameWithoutExtension(filePathAndName);
string filePathWithoutFile = Path.GetDirectoryName(filePathAndName);
string filename = filePathWithoutFile + "\\" + fileNameOnly + ".png";
if (System.IO.File.Exists(filename))
{
return new Bitmap(filename);
}
else
{
List<Bitmap> bitmaps = new List<Bitmap>();
int pageCount;
using (Stream msTemp = new MemoryStream(tiffFile))
{
TiffBitmapDecoder decoder = new TiffBitmapDecoder(msTemp, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
pageCount = decoder.Frames.Count;
for (int i = 0; i < pageCount; i++)
{
System.Drawing.Bitmap bmpSingleFrame = Worker.BitmapFromSource(decoder.Frames[i]);
bitmaps.Add(bmpSingleFrame);
}
Bitmap bmp = ImgHelper.MergeImagesTopToBottom(bitmaps);
EncoderParameters eps = new EncoderParameters(1);
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 16L);
ImageCodecInfo ici = Worker.GetEncoderInfo("image/png");
bmp.Save(filename, ici, eps);
return bmp;
}
}
}
Then I pass that list of bitmaps into a separate function to do the actual combining:
public static Bitmap MergeImagesTopToBottom(IEnumerable<Bitmap> images)
{
var enumerable = images as IList<Bitmap> ?? images.ToList();
var width = 0;
var height = 0;
foreach (var image in enumerable)
{
width = image.Width > width
? image.Width
: width;
height += image.Height;
}
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format16bppGrayScale);
Graphics g = Graphics.FromImage(bitmap);//gives Out of Memory Exception
var localHeight = 0;
foreach (var image in enumerable)
{
g.DrawImage(image, 0, localHeight);
localHeight += image.Height;
}
return bitmap;
}
But I usually get an Out of Memory Exception, depending on the number of frames in the tiff. Even just 5 images that are around 2550px by 3300px each are enough to cause the error. That's only around 42MB, which ends up saving as a png that is a total of 2,550px by 16,500px and is only 1.5MB on disk. I'm even using this setting in my web.config: <gcAllowVeryLargeObjects enabled=" true" /> Other details: I'm working on a 64bit Windows 7 with 16GB of RAM (and I normally run at around 65% of ram usage), and my code is running in Visual Studio 2013 in an asp.net MVC project. I'm running the project as 32 bit because I'm using Tessnet2 which is only available in 32 bit. Still, I figure I should have plenty of memory to handle much more than 5 images at a time. Is there a better way to go about this? I'd rather not have to resort to paid frameworks, if I can help it. I feel that this is something I should be able to do for free and with out-of-the-box .net code. Thanks!
TL;DR
Documentation for Graphics.FromImage says: This method also throws an exception if the image has any of the following pixel formats: Undefined, DontCare, Format16bppArgb1555, Format16bppGrayScale. So, use another format or try another Image library (which is not built on top of GDI+, like Emgu CV)
DETAILED
You're getting OutOfMemoryException, but it has nothing to do with a lack of memory. It's just how Windows GDI+ is working and how it's wrapped in .NET. We can see that Graphics.FromImage calls GDI+:
int status = SafeNativeMethods.Gdip.GdipGetImageGraphicsContext(new HandleRef(image, image.nativeImage), out gdipNativeGraphics);
And if status is not OK, it will throw an exception:
internal static Exception StatusException(int status)
{
Debug.Assert(status != Ok, "Throwing an exception for an 'Ok' return code");
switch (status)
{
case GenericError: return new ExternalException(SR.GetString(SR.GdiplusGenericError), E_FAIL);
case InvalidParameter: return new ArgumentException(SR.GetString(SR.GdiplusInvalidParameter));
case OutOfMemory: return new OutOfMemoryException(SR.GetString(SR.GdiplusOutOfMemory));
case ObjectBusy: return new InvalidOperationException(SR.GetString(SR.GdiplusObjectBusy));
.....
}
}
status in your scenario is equal to 3, so why it throws OutOfMemoryException.
To reproduce it I just tried to create Image from 16x16 Bitmap:
int w = 16;
int h = 16;
Bitmap bitmap = new Bitmap(w, h, PixelFormat.Format16bppGrayScale);
Graphics g = Graphics.FromImage(bitmap); // OutOfMemoryException here
I used windbg + SOSEX for .NET extension for analysis and here what I can see:
I do not have data to test with, so I can't test your code. However it seems clear that at least you can save memory in more than one obvious way here.
Make these changes:
First, don't read all bytes and use the MemoryStream: you're copying the whole file to memory, AND then creating the Decoder with BitmapCacheOption.Default, which should load the whole in-memory stream into the decoder yet again ...
Eliminate the tiffFile array. In the using statement, open a FileStream on the file; and create your decoder with BitmapCacheOption.None --- no in-memory store for the decoder.
Then you create a full list of BitMaps for every frame! Instead, get your target size by just iterating the frames on the Decoder (BitmapFrame has PixelWidth). Use this to create your target BitMap; and then iterate the frames and draw each one there: put each frame's BitMap in a using block and dispose each one right after you've drawn it to your target.
Move your local Bitmap bmp variable outside the using block so all of the prior gets freeable after you've created that BitMap. Then outside of the using block write that to your file.
It seems clear that you're making two whole copies of the whole image, plus making each BitMap for each frame, plus making the final BitMap ... That's a lot of brute-force copies. It's ALL inside the one using block, so nothing leaves memory until your finish writing the new file.
There may yet be a better way to pass Streams to decoders and make BitMaps with Streams also, that will not require dumping all of the bytes into memory.
public Bitmap SaveTiffAsTallPng(string filePathAndName) {
string pngFilename = Path.ChangeExtension(filePathAndName), "png");
if (System.IO.File.Exists(pngFilename))
return new Bitmap(pngFilename);
else {
Bitmap pngBitmap;
using (FileStream tiffFileStream = File.OpenRead(filePathAndName)) {
TiffBitmapDecoder decoder
= new TiffBitmapDecoder(
tiffFileStream,
BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.None);
int pngWidth = 0;
int pngHeight = 0;
for (int i = 0; i < decoder.Frames.Count; ++i) {
pngWidth = Math.Max(pngWidth, decoder.Frames[i].PixelWidth);
pngHeight += decoder.Frames[i].PixelHeight;
}
bitmap = new Bitmap(pngWidth, pngHeight, PixelFormat.Format16bppGrayScale);
using (Graphics g = Graphics.FromImage(pngBitmap)) {
int y = 0;
for (int i = 0; i < decoder.Frames.Count; ++i) {
using (Bitmap frameBitMap = Worker.BitmapFromSource(decoder.Frames[i])) {
g.DrawImage(frameBitMap, 0, y);
y += frameBitMap.Height;
}
}
}
}
EncoderParameters eps = new EncoderParameters(1);
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 16L);
pngBitmap.Save(
pngFilename,
Worker.GetEncoderInfo("image/png"),
eps);
return pngBitmap;
}
}

C# Upload image and saving as Bitmap Increase size of the image

For uploading image I am using plupload on client side. Then in my controlled I have next logic:
public ActionResult UploadFile()
{
try
{
var file = Request.Files.Count > 0 ? Request.Files[0] : null;
using (var fileStream = new MemoryStream())
{
using (var oldImage = new Bitmap(file.InputStream))
{
var format = oldImage.RawFormat;
using (var newImage = ImageUtility.ResizeImage(oldImage, 800, 2000))
{
newImage.Save(fileStream, format);
}
byte[] bits = fileStream.ToArray();
}
}
{
catch (Exception ex)
{
}
}
ImageUtility.ResizeImage Method:
public static class ImageUtility
{
public static Bitmap ResizeImage(Bitmap image, int width, int height)
{
if (image.Width <= width && image.Height <= height)
{
return image;
}
int newWidth;
int newHeight;
if (image.Width > image.Height)
{
newWidth = width;
newHeight = (int)(image.Height * ((float)width / image.Width));
}
else
{
newHeight = height;
newWidth = (int)(image.Width * ((float)height / image.Height));
}
var newImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(newImage))
{
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphics.FillRectangle(Brushes.Transparent, 0, 0, newWidth, newHeight);
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
return newImage;
}
}
}
The issue which i have here that Image size is increased.
I uploaded image of 1.62MB and after this controller is called and it creates instance if Bitmap and then save Bitmap to filestream and read bits with "fileStream.ToArray();" I am getting 2.35MB in "bits".
Can anyone tell me what's the reason of increasing the image size after I save it as bitmap. I need Bitmap because I need to check with and height of uploaded image and resize it if I need.
The answer is simple, the bitmap takes up more memory the whatever format the image was in previously because it's uncompressed it stays in that uncompressed format after saving it.
jpeg, png, gif, etc. are compressed and therefore use less bytes tha a bitmap which is uncompressed.
If you just want to save the original image, just save file.InputStream.
If you need to resize, you can use a library to apply jpg/png/etc compression and then save the result.
What is the goal here? Are you merely trying to upload an image? Does it need to be validated as an image? Or are you just trying to upload the file?
If upload is the goal, without any regard to validation, just move the bits and save them with the name of the file. As soon as you do this ...
using (var oldImage = new Bitmap(file.InputStream))
... you are converting to a bitmap. Here is where you are telling the bitmap what format to use (raw).
var format = oldImage.RawFormat;
If you merely want to move the file (upload), you can run the memory stream to a filestream object and you save the bits.
If you want a few checks on whether the image is empty, etc, you can try this page (http://www.codeproject.com/Articles/1956/NET-Image-Uploading), but realize it is still putting it in an image, which is not your desire if you simply want to save "as is".

Resize image which is placed in byte[] array

Size image placed in byte[] array (don't know the type of image).
I have to produce another byte [] array, which size should be up to 50kB.
How can I do some kind of scaling?
Unless you want to get into some serious math, you need to load your byte array into a memory stream, load an image from that memory stream, and use the built-in GDI functions in the System.Drawing namespace.
Doing a 25%, or 50% scale is easy. Beyond that, you need to start doing interpolation and differencing to make anything look halfway decent in binary data manipulation. You'll be several days into it before you can match what's already available in GDI.
System.IO.MemoryStream myMemStream = new System.IO.MemoryStream(myBytes);
System.Drawing.Image fullsizeImage = System.Drawing.Image.FromStream(myMemStream);
System.Drawing.Image newImage = fullsizeImage .GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
System.IO.MemoryStream myResult = new System.IO.MemoryStream();
newImage.Save(myResult ,System.Drawing.Imaging.ImageFormat.Gif); //Or whatever format you want.
return myResult.ToArray(); //Returns a new byte array.
BTW - if you really need to figure out your source image type, see: How to check if a byte array is a valid image
Ok, so after some experiments, I have something like that:
public static byte[] Resize2Max50Kbytes(byte[] byteImageIn)
{
byte[] currentByteImageArray = byteImageIn;
double scale = 1f;
if (!IsValidImage(byteImageIn))
{
return null;
}
MemoryStream inputMemoryStream = new MemoryStream(byteImageIn);
Image fullsizeImage = Image.FromStream(inputMemoryStream);
while (currentByteImageArray.Length > 50000)
{
Bitmap fullSizeBitmap = new Bitmap(fullsizeImage, new Size((int)(fullsizeImage.Width * scale), (int)(fullsizeImage.Height * scale)));
MemoryStream resultStream = new MemoryStream();
fullSizeBitmap.Save(resultStream, fullsizeImage.RawFormat);
currentByteImageArray = resultStream.ToArray();
resultStream.Dispose();
resultStream.Close();
scale -= 0.05f;
}
return currentByteImageArray;
}
Has someone another idea? Unfortunatelly Image.GetThumbnailImage() was causing very dirty images.
The updated answer below works with Docker SixLabors.ImageSharp.
The solution for .net core 3.1 and greater:
Install System.Drawing.Common nuget:
Install-Package System.Drawing.Common
The code to change the size of the image from a byte array:
byte[] ReduceImage(byte[] bytes)
{
using var memoryStream = new MemoryStream(bytes);
using var originalImage = new Bitmap(memoryStream);
var resized = new Bitmap(newWidth, newHeight);
using var graphics = Graphics.FromImage(resized);
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.DrawImage(originalImage, 0, 0, reducedWidth, reducedHeight);
using var stream = new MemoryStream();
resized.Save(stream, ImageFormat.Png);
return stream.ToArray();
}
Update:
The approach above can don't work for Linux, so the universal solution is:
Install SixLabors.ImageSharp nuget:
Install-Package SixLabors.ImageSharp
Write the following code:
private static byte[] ReduceImage(byte[] bytes)
{
using var memoryStream = new MemoryStream(bytes);
using var image = Image.Load(memoryStream);
image.Mutate(x => x.Resize(ReducedWidth, ReducedHeight));
using var outputStream = new MemoryStream();
image.Save(outputStream, new PngEncoder() /*or another encoder*/);
return outputStream.ToArray();
}
Suppose you read a file from Drive
FileStream streamObj = System.IO.File.OpenRead(#"C:\Files\Photo.jpg");
Byte[] newImage=UploadFoto(streamObj);
public static Byte[] UploadFoto(FileStream fileUpload)
{
Byte[] imgByte = null;
imgByte = lnkUpload(fileUpload);
return imgByte;
}
private static Byte[] lnkUpload(FileStream img)
{
byte[] resizedImage;
using (Image orginalImage = Image.FromStream(img))
{
ImageFormat orginalImageFormat = orginalImage.RawFormat;
int orginalImageWidth = orginalImage.Width;
int orginalImageHeight = orginalImage.Height;
int resizedImageWidth = 60; // Type here the width you want
int resizedImageHeight = Convert.ToInt32(resizedImageWidth * orginalImageHeight / orginalImageWidth);
using (Bitmap bitmapResized = new Bitmap(orginalImage, resizedImageWidth, resizedImageHeight))
{
using (MemoryStream streamResized = new MemoryStream())
{
bitmapResized.Save(streamResized, orginalImageFormat);
resizedImage = streamResized.ToArray();
}
}
}
return resizedImage;
}
I have no definite implementation for you, but I would approach it that way:
You can store 51200 values (uncompressed). And you know the ratio from the original:
Calculate the dimensions with the ratio and the size of the new image:
x = y / ratio
size(51200) = x * y
y = size / x
x = (size / x) / ratio;
y = x * ratio
for the resampling of the values I would go for using a filter kernel:
http://en.wikipedia.org/wiki/Lanczos_resampling
Haven't used it yet, but sounds promising.
I am used this....
public static byte[] ImagenToByteArray(System.Drawing.Image imageIn)
{
MemoryStream ms = new MemoryStream();
imageIn.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
return ms.ToArray();
}

Categories