What's the most efficient way to generate thumbnails? - c#

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.

Related

How to capture all screen in C# and change the resolution of the bitmap

I tried to capture my screen in C# (.NET 4.6.2 in console mode) to get a base64 string of the bitmap.
My issue is that the base64 is too big in my case so I want to decrease the resolution of the Bitmap, I tried this code:
public static string TakeScreenshotToBase64()
{
Bitmap memoryImage = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Size size = new Size(memoryImage.Width, memoryImage.Height);
Graphics memoryGraphics = Graphics.FromImage(memoryImage);
memoryGraphics.CopyFromScreen(0, 0, 0, 0, size);
MemoryStream ms = new MemoryStream();
memoryImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
return Convert.ToBase64String(ms.ToArray());
}
If I try to edit the width and the height values, I got a smaller picture but I don't have the full picture sadly.
So I tried to create a new Bitmap from my existing Bitmap with another resolution, but it doesn't work, any suggestions? thank you for your help.
As requested, i hope this helps. I can explain better if the comments are not enough.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
namespace BitMapResize
{
public partial class Form1 : Form
{
/// <summary>
/// Function to Create the images.
/// </summary>
/// <param name="resizeWidth">If it is null, the application will create an image with the original size.</param>
/// <param name="resizeHeight">If it is null, the application will create an image with the original size.</param>
public static void TakeScreenshotToBase64(float? resizeWidth, float? resizeHeight)
{
using (MemoryStream ms = new MemoryStream())
{
/* First things first, creating a bitmap of the original data to the memorystream, so we can use later.*/
Bitmap memoryImage = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
Size size = new Size(memoryImage.Width, memoryImage.Height);
using (Graphics memoryGraphics = Graphics.FromImage(memoryImage))
{
memoryGraphics.CopyFromScreen(0, 0, 0, 0, size); // Persisting the full screen resolution.
memoryImage.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); // Saving in the memory stream...
}
string imageName = "Print"; // This won't change if the size is the original.
// This conditions checks if the resize parameters are not null. If they are not null, a resizing process begins.
if (!(resizeWidth is null) || !(resizeHeight is null))
{
var bmp = new Bitmap((int)resizeWidth.Value, (int)resizeHeight.Value); // Create a new bitmap with the new dimensions.
using (Graphics memoryGraphics = Graphics.FromImage(bmp)) // Using a Graphics that operates on the new bitmap.
{
// Defining scale factor. This way we maintain aspect ratio.
float scale = Math.Min(resizeWidth.Value / memoryImage.Width, resizeHeight.Value / memoryImage.Height);
int scaleWidth = (int)(memoryImage.Width * scale);
int scaleHeight = (int)(memoryImage.Height * scale);
// Optional: Set this for better output quality.
memoryGraphics.InterpolationMode = InterpolationMode.High;
memoryGraphics.CompositingQuality = CompositingQuality.HighQuality;
memoryGraphics.SmoothingMode = SmoothingMode.AntiAlias;
// Here, choose the background color for the image.
/* Why the background?
* As long as we keep the aspect ratio, if the given dimension does not respect the scale of the original dimension,
* an area of the new image will not be filled. So we can set a background color for these empty areas.
*/
Brush baseColor = new SolidBrush(Color.White);
memoryGraphics.FillRectangle(baseColor, new RectangleF(0, 0, resizeWidth.Value, resizeHeight.Value));
// Process the resize, based on the in-memory buffer that we wrote earlier.
memoryGraphics.DrawImage(Image.FromStream(ms), ((int)resizeWidth.Value - scaleWidth) / 2, ((int)resizeHeight.Value - scaleHeight) / 2, scaleWidth, scaleHeight);
}
imageName = "ResizedPrint";
// Not requested, but now can save this in a file.
using (FileStream fs = new FileStream(Application.StartupPath + "/" + imageName + ".jpg", FileMode.Create))
{
bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
else
{
using (FileStream fs = new FileStream(Application.StartupPath + "/" + imageName + ".jpg", FileMode.Create))
{
// If no resize was done, save the original data.
memoryImage.Save(fs, System.Drawing.Imaging.ImageFormat.Jpeg);
}
}
}
}
// Print event start.
private void button1_Click(object sender, EventArgs e)
{
TakeScreenshotToBase64(1024, 768);
//TakeScreenshotToBase64(null, null);
}
}
}

C# Crop & resize large images

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

Loosing transparency in System.Drawing.Image when using ImageResizer for resizing

For my current WPF appliaction I have to down scale some System.Drawing.Image ( objects which I load from PNG files (some of them with transparent background). I've tried multiple approaches for the resizing of the images and all of them worked fine in terms of having a smaller image afterwards. But unfortunately they all make the images loose their transparency.
My last try was to use ImageResizer an external library to get the job done as I expected it to handle that problem easily, but I still have the same issue: Original image is displayed with transparent background; Resized image displayed is displayed with black background.
Here's my code for the usage of the ImageResizer library:
ImageResizer.Instructions inst = new ImageResizer.Instructions("width=" + newWidth.ToString() + ";height=" + newHeight.ToString() + ";format=png;mode=max");
ImageResizer.ImageJob job = new ImageResizer.ImageJob(originalImage, typeof(System.Drawing.Bitmap), inst);
job.Build();
return job.Result as System.Drawing.Image;
These are my other two approaches, which also basically deliver the same result (Image resized: yet; Transparency preserved: Nope):
return originalImage.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero); // Transparency gets lost
return new System.Drawing.Bitmap(originalImage, new System.Drawing.Size(newWidth, newHeight)); // Transparency gets lost
Any ideas on what I have to do in order preserve the transparency while resizing?
Regards
Ralf
ImageResizer always preserves transparency.
You're losing transparency during the encoding (or display) of the image, which happens after you've taken control away from ImageResizer. Instead of passing in typeof(System.Drawing.Bitmap), pass in an output path or output stream.
var i = new Instructions(){ Width = newWidth,Height = newHeight, OutputFormat= OutputFormat.Png, Mode= FitMode.Max};
new ImageJob(originalImage, "output.png", i).Build();
ImageResizer can't control how an image is encoded if you take a raw Bitmap from it instead.
You didn't specific how you're using the results, which is very important. If you are not writing them to disk or a stream, but are instead displaying them, then you're looking for the problem in the wrong place. It's likely that the code responsible for compositing the results onto the display surface is failing to treat the image as a 32-bit image, and is instead ignoring the alpha channel.
Even though you're using WPF, you're working with System.Drawing.Image objects, so you can do this:
public static Bitmap ResizeImage(Image imgToResize, int newHeight)
{
int sourceWidth = imgToResize.Width;
int sourceHeight = imgToResize.Height;
float nPercentH = ((float)newHeight / (float)sourceHeight);
int destWidth = Math.Max((int)Math.Round(sourceWidth * nPercentH), 1); // Just in case;
int destHeight = newHeight;
Bitmap b = new Bitmap(destWidth, destHeight);
using (Graphics g = Graphics.FromImage((Image)b))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
}
return b;
}
Afterwards, be sure to save it with the PNG encoder:
public static System.Drawing.Imaging.ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
and then
codec = GetEncoder(ImageFormat.Png);
newBitmap.Save(newFile, codec, null);
(Note I'm using the standard .Net class libraries rather than a 3rd party library; hope that's OK.)
Update
Incidentally, since you are working in WPF, why not use WPF's image manipulation?
public static class BitmapHelper
{
public static void SaveToPng(this BitmapSource bitmap, string fileName)
{
var encoder = new PngBitmapEncoder();
SaveUsingEncoder(bitmap, fileName, encoder);
}
public static void SaveUsingEncoder(this BitmapSource bitmap, string fileName, BitmapEncoder encoder)
{
BitmapFrame frame = BitmapFrame.Create(bitmap);
encoder.Frames.Add(frame);
using (var stream = File.Create(fileName))
{
encoder.Save(stream);
}
}
public static void ImageLoadResizeAndSave(string inFile, string outFile, int newPixelHeight)
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = new Uri(inFile);
image.EndInit();
var newImage = BitmapHelper.ResizeImageToHeight(image, newPixelHeight);
BitmapHelper.SaveToPng(newImage, outFile);
}
/// <summary>
/// Resize the image to have the selected height, keeping the width proportionate.
/// </summary>
/// <param name="imgToResize"></param>
/// <param name="newHeight"></param>
/// <returns></returns>
public static BitmapSource ResizeImageToHeight(BitmapSource imgToResize, int newPixelHeight)
{
double sourceWidth = imgToResize.PixelWidth;
double sourceHeight = imgToResize.PixelHeight;
var nPercentH = ((double)newPixelHeight / sourceHeight);
double destWidth = Math.Max((int)Math.Round(sourceWidth * nPercentH), 1); // Just in case;
double destHeight = newPixelHeight;
var bitmap = new TransformedBitmap(imgToResize, new ScaleTransform(destWidth / imgToResize.PixelWidth, destHeight / imgToResize.PixelHeight));
return bitmap;
}
}
Maybe you're losing transparencies converting images in the older format to WPF format?

Convert Bitmap to unindexed pixelformat without compression/qualityloss

Is it possible to convert a indexed Bitmap to a unindexed without losing any quality?
I currently use this code to convert:
public Bitmap CreateNonIndexedImage(Image src)
{
Bitmap newBmp = new Bitmap(src.Width, src.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics gfx = Graphics.FromImage(newBmp))
{
gfx.DrawImage(src, 0, 0);
}
return newBmp;
}
Here is the indexed bitmap: http://puu.sh/6VO1N.png
And this is the converted image: http://puu.sh/6VO2Q.png
I need the unindexed to be exactly the same as the indexed but I donĀ“t know what to do.
The image was resampled. That's normally a Good Thing, it helps to get rid of the dithering artifacts in the original image. But you don't like it so you have to turn interpolation off. You also should use the DrawImage() overload that prevents rescaling due to the resolution of the original image. Not actually a problem with the image you've got but it could be one with another. Thus:
public static Bitmap CreateNonIndexedImage(Image src) {
Bitmap newBmp = new Bitmap(src.Width, src.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics gfx = Graphics.FromImage(newBmp)) {
gfx.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
gfx.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
gfx.DrawImage(src, new Rectangle(0, 0, src.Width, src.Height));
}
return newBmp;
}

C# generating thumbnails filesize

I have the following code to take an image and generate the thumbnail.
how can I alter the quality or compression to get smaller file sizes programatically?
Image thumbNail = image.GetThumbnailImage(Width, Height, null, new IntPtr());
If you truly need better control over the thumbnails produced, you will be better off producing your own by manually generating an image of smaller size and different quality. The GetThumbnailImage dows not give you much control.
See this article for how it's done.
http://www.switchonthecode.com/tutorials/csharp-tutorial-image-editing-saving-cropping-and-resizing
When saving the thumbNail with Image.Save you can specify the quality by passing a EncoderParameter. See: Reducing JPEG Picture Quality using C#
EncoderParameter epQuality = new EncoderParameter(
System.Drawing.Imaging.Encoder.Quality,
(int)numQual.Value);
...
newImage.Save(..., iciJpegCodec, epParameters);
You do not use the GetThumbnailImage API:
protected Stream ResizeImage(string source, int width, int height) {
using (System.Drawing.Bitmap bmp = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile(source))
using (System.Drawing.Bitmap newBmp = new System.Drawing.Bitmap(width, height))
using (System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage(newBmp))
{
graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphic.DrawImage(bmp, 0, 0, width, height);
MemoryStream ms = new MemoryStream();
newBmp.Save(ms, System.Drawing.Imaging.ImageFormat.Png);
return ms;
}
}

Categories

Resources