I am creating a portfolio site for a photographer.
I am faced with the problem of resizing photos from large to small. If I reduce the size much of the quality is lost. How can I compress the picture in order not to lose quality?
My code:
using (var input = new Bitmap(imageFile.InputStream))
{
int width;
int height;
if (input.Width > input.Height)
{
width = 411 * input.Width / input.Height;
height = 411;
}
else
{
height = 411;
width = 411 * input.Width / input.Height;
}
using (var thumb = new Bitmap(width, height))
using (var graphic = Graphics.FromImage(thumb))
{
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = SmoothingMode.AntiAlias;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.DrawImage(input, 0, 0, width, height);
using (var output = System.IO.File.Create(imagePath))
{
thumb.Save(output, ImageFormat.Jpeg);
}
}
}
You may get better results doing gradual resizing (like 10-25% each step). Try also saving in loss-less format (like ImageFormat.Png).
If quality is extremely important manual conversion in proper photo editing tool is probably the right approach.
Related
I am using C#,MVC5 and I am uploading image from my web application but I realize that I have performance issues because I don't optimize them and I need to fix it and is important to keep the quality.
Below you can see the results of the report why is slow.
How can I do it?
I am saving the files into a path locally with the below code.
string imgpathvalue = ConfigurationManager.AppSettings["RestaurantPath"];
string path = System.IO.Path.Combine(Server.MapPath(imgpathvalue));
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
string pic = System.IO.Path.GetFileName(restaurantImg.FileName.Replace(" ", "_").Replace("%", "_"));
path = System.IO.Path.Combine(Server.MapPath(imgpathvalue), pic);
// file is uploaded
restaurantImg.SaveAs(path);
I have try the code below but I am getting the error "A generic error occurred in GDI+."
System.Drawing.Bitmap bmpPostedImage = new System.Drawing.Bitmap(restaurantImg.InputStream);
System.Drawing.Image objImage = ResizeImages.ScaleImage(bmpPostedImage, 81);
using (var ms = new MemoryStream())
{
objImage.Save(ms, objImage.RawFormat);
//ResizeImages.getImage(ms.ToArray());
}
public static System.Drawing.Image ScaleImage(System.Drawing.Image image, int maxHeight)
{
var ratio = (double)maxHeight / image.Height;
var newWidth = (int)(image.Width * ratio);
var newHeight = (int)(image.Height * ratio);
var newImage = new Bitmap(newWidth, newHeight);
using (var g = Graphics.FromImage(newImage))
{
g.DrawImage(image, 0, 0, newWidth, newHeight);
}
return newImage;
}
You are missing some of the code to resize your image correctly. Appending is a function that correctly resizes images depending on the Width and Height Values you give to it (in this example the image gets resized to 120*120 if possible).
Function Call:
ResizeImage("Path to the Image you want to resize",
"Path you want to save resizes copy into", 120, 120);
To make a function call like that possible we need to write our function. Which takes the image from the sourceImagePath and creates a new Bitmap.
Then it calculates the factor to resize the image and depending on if either the width or height is bigger it gets adjusted accordingly.
After that is done we create a new BitMap fromt he sourceImagePath and resize it. At the end we also need to dispose the sourceImage, the destImage and we also need to dispose of the Graphics Element g that we used for different Quality Settings.
Resize Function:
private void ResizeImage(string sourceImagePath, string destImagePath,
int wishImageWidth, int wishImageHeight)
{
Bitmap sourceImage = new Bitmap(sourceImagePath);
Bitmap destImage = null;
Graphics g = null;
int destImageWidth = 0;
int destImageHeight = 0;
// Calculate factor of image
double faktor = (double) sourceImage.Width / (double) sourceImage.Height;
if (faktor >= 1.0) // Landscape
{
destImageWidth = wishImageWidth;
destImageHeight = (int) (destImageWidth / faktor);
}
else // Port
{
destImageHeight = wishImageHeight;
destImageWidth = (int) (destImageHeight * faktor);
}
try
{
destImage = new Bitmap(sourceImage, destImageWidth, destImageHeight);
g = Graphics.FromImage(destImage);
g.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;
g.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.DrawImage(sourceImage, 0, 0, destImageWidth, destImageHeight);
// Making sure that the file doesn't already exists.
if (File.Exists(destImagePath)) {
// If it does delete the old version.
File.Delete(destImagePath);
}
destImage.Save(destImagePath);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("*** ERROR-Terror: " + ex.Message)
}
finally
{
if (g != null) { g.Dispose(); g = null; }
if (destImage != null) { destImage.Dispose(); destImage = null; }
}
sourceImage.Dispose();
sourceImage = null;
}
I got some very large building drawings, sometimes 22466x3999 with a bit depth of 24, or even larger.
I need to be able to resize these to smaller versions, and to be able to cut out sections of the image to smaller images.
I have been using the following code to resize the images, which I found here:
public static void ResizeImage(string OriginalFile, string NewFile, int NewWidth, int MaxHeight, bool OnlyResizeIfWider)
{
System.Drawing.Image FullsizeImage = System.Drawing.Image.FromFile(OriginalFile);
if (OnlyResizeIfWider)
{
if (FullsizeImage.Width <= NewWidth)
{
NewWidth = FullsizeImage.Width;
}
}
int NewHeight = FullsizeImage.Height * NewWidth / FullsizeImage.Width;
if (NewHeight > MaxHeight)
{
NewWidth = FullsizeImage.Width * MaxHeight / FullsizeImage.Height;
NewHeight = MaxHeight;
}
System.Drawing.Image NewImage = FullsizeImage.GetThumbnailImage(NewWidth, NewHeight, null, IntPtr.Zero);
FullsizeImage.Dispose();
NewImage.Save(NewFile);
}
And this code to crop the images:
public static MemoryStream CropToStream(string path, int x, int y, int width, int height)
{
if (string.IsNullOrWhiteSpace(path)) return null;
Rectangle fromRectangle = new Rectangle(x, y, width, height);
using (Image image = Image.FromFile(path, true))
{
Bitmap target = new Bitmap(fromRectangle.Width, fromRectangle.Height);
using (Graphics g = Graphics.FromImage(target))
{
Rectangle croppedImageDimentions = new Rectangle(0, 0, target.Width, target.Height);
g.DrawImage(image, croppedImageDimentions, fromRectangle, GraphicsUnit.Pixel);
}
MemoryStream stream = new MemoryStream();
target.Save(stream, image.RawFormat);
stream.Position = 0;
return stream;
}
}
My problem is that i get a Sytem.OutOfMemoryException when I try to resize the image, and that's because I can't load the full image in to FullsizeImage.
So what I would like to know, how do I resize an image without loading the entire image into memory?
There are chances the OutOfMemoryException is not because of the size of the images, but because you don't dispose all the disposables classes correctly :
Bitmap target
MemoryStream stream
System.Drawing.Image NewImage
are not disposed as they should. You should add a using() statement around them.
If you really encounter this error with just one image, then you should consider switch your project to x64. A 22466x3999 picture means 225Mb in memory, I think it shouldn't be an issue for x86. (so try to dispose your objects first).
Last but not least, Magick.Net is very efficient about resizing / cropping large pictures.
You can also force .Net to read the image directly from disk and stop memory caching.
Use
sourceBitmap = (Bitmap)Image.FromStream(sourceFileStream, false, false);
Instead of
...System.Drawing.Image.FromFile(OriginalFile);
see https://stackoverflow.com/a/47424918/887092
First I want to thank Bitmiracle for this great lib. Even while creating very big files, the memory footprint is very low.
A few days ago I ran into a problem where I wanted to create a tiff file bigger than 4GB. I created the tiled tiff file successfully, but it seems that the color of the tiles created beyond 4GB are somehow inverted.
Here the code relevant code:
Usage:
WriteTiledTiff("bigtiff.tiff",BitmapSourceFromBrush(new RadialGradientBrush(Colors.Aqua,Colors.Red), 256));
Methods:
public static BitmapSource BitmapSourceFromBrush(Brush drawingBrush, int size = 32, int dpi = 96)
{
// RenderTargetBitmap = builds a bitmap rendering of a visual
var pixelFormat = PixelFormats.Pbgra32;
RenderTargetBitmap rtb = new RenderTargetBitmap(size, size, dpi, dpi, pixelFormat);
// Drawing visual allows us to compose graphic drawing parts into a visual to render
var drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
// Declaring drawing a rectangle using the input brush to fill up the visual
context.DrawRectangle(drawingBrush, null, new Rect(0, 0, size, size));
}
// Actually rendering the bitmap
rtb.Render(drawingVisual);
return rtb;
}
public static void WriteTiledTiff(string fileName, BitmapSource tile)
{
const int PIXEL_WIDTH = 48000;
const int PIXEL_HEIGHT = 48000;
int iTile_Width = tile.PixelWidth;
int iTile_Height = tile.PixelHeight;
using (Tiff tiff = Tiff.Open(fileName, "w"))
{
tiff.SetField(TiffTag.IMAGEWIDTH, PIXEL_WIDTH);
tiff.SetField(TiffTag.IMAGELENGTH, PIXEL_HEIGHT);
tiff.SetField(TiffTag.COMPRESSION, Compression.NONE);
tiff.SetField(TiffTag.PHOTOMETRIC, Photometric.RGB);
tiff.SetField(TiffTag.ROWSPERSTRIP, PIXEL_HEIGHT);
tiff.SetField(TiffTag.XRESOLUTION, 96);
tiff.SetField(TiffTag.YRESOLUTION, 96);
tiff.SetField(TiffTag.BITSPERSAMPLE, 8);
tiff.SetField(TiffTag.SAMPLESPERPIXEL, 3);
tiff.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
tiff.SetField(TiffTag.TILEWIDTH, iTile_Width);
tiff.SetField(TiffTag.TILELENGTH, iTile_Height);
int tileC = 0;
for (int row = 0; row < PIXEL_HEIGHT; row += iTile_Height)
{
for (int col = 0; col < PIXEL_WIDTH; col += iTile_Width)
{
if (tile.Format != PixelFormats.Rgb24) tile = new FormatConvertedBitmap(tile, PixelFormats.Rgb24, null, 0);
int stride = tile.PixelWidth * ((tile.Format.BitsPerPixel + 7) / 8);
byte[] pixels = new byte[tile.PixelHeight * stride];
tile.CopyPixels(pixels, stride, 0);
tiff.WriteEncodedTile(tileC++, pixels, pixels.Length);
}
}
tiff.WriteDirectory();
}
}
The resulted file will be 6,47GB in size. I viewed it with a small tool called "vliv" vilv download
All LibTiff.Net versions including 2.4.500.0 are based on 3.x branch of the original libtiff.
Support for BigTIFF was introduced in 4.x branch of the original libtiff. Thus, at this time there are no LibTiff.Net versions designed to handle BigTiff files / files over 4GB on disk.
EDIT:
LibTiff.Net 2.4.508 adds support for BigTiff.
I have the following code
int oswidth = 0;
int osheight = 0;
if (comboBox3.SelectedIndex == 0)
{
oswidth = Convert.ToInt32(textBox5.Text.ToString());
osheight = Convert.ToInt32(textBox6.Text.ToString());
}
else if (comboBox3.SelectedIndex == 1)
{
oswidth = 38 * Convert.ToInt32(textBox5.Text.ToString());
osheight = 38 * Convert.ToInt32(textBox6.Text.ToString());
}
Bitmap oldimg = new Bitmap(pictureBox3.Image);
Bitmap objBitmap = new Bitmap(oldimg, new Size(oswidth, osheight));
objBitmap.Save(pictureBox3.ImageLocation.ToString(), ImageFormat.Jpeg);
The problem is when the selected index is 0 it works fine
but when the selected index is 1 i get a error "Parameter is not valid."
i tried different images but same error. is it the multiply by 32 thing
The Parameter is not valid error message when trying to create a Bitmap usually means that you are trying to allocate too much memory to it. The bitmap requires bit-depth*width*height/8 bytes of contiguous memory, and there just isn't enough available to satisfy that.
In this case, it looks like it's because you're multiplying its dimensions by 38 (and therefore multiplying the size in memory by 38^2).
You could utilize the following method:
private static void ResizeImage(string file, double vscale, double hscale, string output)
{
using(var source = Image.FromFile(file))
{
var width = (int)(source.Width * vscale);
var height = (int)(source.Height * hscale);
using(var image = new Bitmap(width, height, PixelFormat.Format24bppRgb))
using(var graphic = Graphics.FromImage(image))
{
graphic.SmoothingMode = SmoothingMode.AntiAlias;
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphic.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphic.DrawImage(source, new Rectangle(0, 0, width, height));
image.Save(output);
}
}
}
You can tailor this however you'd like, but it should meet your needs.
Important: The reason vscale and hscale are separate is to not follow scaling. You can easily combine them so you can scale accordingly. The other thing to remember, is your using a value of 32. Try using a value of .32 which will treat it more like a percent, which will scale. Also it won't increase the memory drastically causing your error.
I am developing a graphical application, and I need to keep a thumbnail for each page.
The challenge is how to generate a thumbnail file without loosing performance ??
Currently here is my code to do it:
VisualBrush VisualBrush = new VisualBrush(pageToGenerateThumbnailFor);
UIVisual.Background = VisualBrush;
RenderTargetBitmap = new RenderTargetBitmap((int)UIVisual.ActualWidth, (int)UIVisual.ActualHeight, 96, 96, PixelFormats.Pbgra32);
rtb.Render(UIVisual);
using (FileStream outStream = new FileStream(ThumbFileFullPath, FileMode.OpenOrCreate,
System.IO.FileAccess.ReadWrite))
{
PngBitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
pngEncoder.Save(outStream);
}
So, Is there a faster way to generate a thumbnail for a given Visual ?
Thanks
I did a bit of research recently for generating Image Thumbnails on the fly for an eCommerce site. I started off doing this myself generating a bitmap and then resizing etc. similar to the answer above. After problems with image size on disc and quality I looked in to http://imageresizing.net/ and I haven't looked back since. It can generate images from byte(), streams and physicals files all very quickly with one line of code:
ImageBuilder.Current.Build(New MemoryStream(bImage), sImageLocation + sFullFileName, New ResizeSettings("maxwidth=214&maxheight=238"))
I would definitely recommend this component rather than trying to reinvent the wheel...
The following class from a utility library that I've written performs well for me and produces good clear quality thumbnails...
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
namespace Simple {
public static class ThumbnailCreator {
private static readonly object _lock = new object();
public static Bitmap createThumbnail(Stream source, Int32 width, Int32 height) {
Monitor.Enter(_lock);
Bitmap output = null;
try {
using (Bitmap workingBitmap = new Bitmap(source)) {
// Determine scale based on requested height/width (this preserves aspect ratio)
Decimal scale;
if (((Decimal)workingBitmap.Width / (Decimal)width) > ((Decimal)workingBitmap.Height / (Decimal)height)) {
scale = (Decimal)workingBitmap.Width / (Decimal)width;
}
else {
scale = (Decimal)workingBitmap.Height / (Decimal)height;
}
// Calculate new height/width
Int32 newHeight = (Int32)((Decimal)workingBitmap.Height / scale);
Int32 newWidth = (Int32)((Decimal)workingBitmap.Width / scale);
// Create blank BitMap of appropriate size
output = new Bitmap(newWidth, newHeight, PixelFormat.Format32bppArgb);
// Create Graphics surface
using (Graphics g = Graphics.FromImage(output)) {
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
Rectangle destRectangle = new Rectangle(0, 0, newWidth, newHeight);
// Use Graphics surface to draw resized BitMap to blank BitMap
g.DrawImage(workingBitmap, destRectangle, 0, 0, workingBitmap.Width, workingBitmap.Height, GraphicsUnit.Pixel);
}
}
}
catch {
output = null;
}
finally {
Monitor.Exit(_lock);
}
return output;
}
}
}
it also retains the original image's aspect ratio.