Create thumbnail image - c#

I want to display thumbnail image in a gridview from file location. How to generate that of .jpeg file?
I am using C# language with asp.net.

You have to use GetThumbnailImage method in the Image class:
https://msdn.microsoft.com/en-us/library/8t23aykb%28v=vs.110%29.aspx
Here's a rough example that takes an image file and makes a thumbnail image from it, then saves it back to disk.
Image image = Image.FromFile(fileName);
Image thumb = image.GetThumbnailImage(120, 120, ()=>false, IntPtr.Zero);
thumb.Save(Path.ChangeExtension(fileName, "thumb"));
It is in the System.Drawing namespace (in System.Drawing.dll).
Behavior:
If the Image contains an embedded thumbnail image, this method
retrieves the embedded thumbnail and scales it to the requested size.
If the Image does not contain an embedded thumbnail image, this method
creates a thumbnail image by scaling the main image.
Important: the remarks section of the Microsoft link above warns of certain potential problems:
The GetThumbnailImage method works well when the requested thumbnail
image has a size of about 120 x 120 pixels. If you request a large
thumbnail image (for example, 300 x 300) from an Image that has an
embedded thumbnail, there could be a noticeable loss of quality in the
thumbnail image.
It might be better to scale the main image (instead
of scaling the embedded thumbnail) by calling the DrawImage method.

The following code will write an image in proportional to the response, you can modify the code for your purpose:
public void WriteImage(string path, int width, int height)
{
Bitmap srcBmp = new Bitmap(path);
float ratio = srcBmp.Width / srcBmp.Height;
SizeF newSize = new SizeF(width, height * ratio);
Bitmap target = new Bitmap((int) newSize.Width,(int) newSize.Height);
HttpContext.Response.Clear();
HttpContext.Response.ContentType = "image/jpeg";
using (Graphics graphics = Graphics.FromImage(target))
{
graphics.CompositingQuality = CompositingQuality.HighSpeed;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.DrawImage(srcBmp, 0, 0, newSize.Width, newSize.Height);
using (MemoryStream memoryStream = new MemoryStream())
{
target.Save(memoryStream, ImageFormat.Jpeg);
memoryStream.WriteTo(HttpContext.Response.OutputStream);
}
}
Response.End();
}

Here is a complete example of how to create a smaller image (thumbnail). This snippet resizes the Image, rotates it when needed (if a phone was held vertically) and pads the image if you want to create square thumbs. This snippet creates a JPEG, but it can easily be modified for other file types. Even if the image would be smaller than the max allowed size the image will still be compressed and it's resolution altered to create images of the same dpi and compression level.
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
//set the resolution, 72 is usually good enough for displaying images on monitors
float imageResolution = 72;
//set the compression level. higher compression = better quality = bigger images
long compressionLevel = 80L;
public Image resizeImage(Image image, int maxWidth, int maxHeight, bool padImage)
{
int newWidth;
int newHeight;
//first we check if the image needs rotating (eg phone held vertical when taking a picture for example)
foreach (var prop in image.PropertyItems)
{
if (prop.Id == 0x0112)
{
int orientationValue = image.GetPropertyItem(prop.Id).Value[0];
RotateFlipType rotateFlipType = getRotateFlipType(orientationValue);
image.RotateFlip(rotateFlipType);
break;
}
}
//apply the padding to make a square image
if (padImage == true)
{
image = applyPaddingToImage(image, Color.Red);
}
//check if the with or height of the image exceeds the maximum specified, if so calculate the new dimensions
if (image.Width > maxWidth || image.Height > maxHeight)
{
double ratioX = (double)maxWidth / image.Width;
double ratioY = (double)maxHeight / image.Height;
double ratio = Math.Min(ratioX, ratioY);
newWidth = (int)(image.Width * ratio);
newHeight = (int)(image.Height * ratio);
}
else
{
newWidth = image.Width;
newHeight = image.Height;
}
//start the resize with a new image
Bitmap newImage = new Bitmap(newWidth, newHeight);
//set the new resolution
newImage.SetResolution(imageResolution, imageResolution);
//start the resizing
using (var graphics = Graphics.FromImage(newImage))
{
//set some encoding specs
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
}
//save the image to a memorystream to apply the compression level
using (MemoryStream ms = new MemoryStream())
{
EncoderParameters encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, compressionLevel);
newImage.Save(ms, getEncoderInfo("image/jpeg"), encoderParameters);
//save the image as byte array here if you want the return type to be a Byte Array instead of Image
//byte[] imageAsByteArray = ms.ToArray();
}
//return the image
return newImage;
}
//=== image padding
public Image applyPaddingToImage(Image image, Color backColor)
{
//get the maximum size of the image dimensions
int maxSize = Math.Max(image.Height, image.Width);
Size squareSize = new Size(maxSize, maxSize);
//create a new square image
Bitmap squareImage = new Bitmap(squareSize.Width, squareSize.Height);
using (Graphics graphics = Graphics.FromImage(squareImage))
{
//fill the new square with a color
graphics.FillRectangle(new SolidBrush(backColor), 0, 0, squareSize.Width, squareSize.Height);
//put the original image on top of the new square
graphics.DrawImage(image, (squareSize.Width / 2) - (image.Width / 2), (squareSize.Height / 2) - (image.Height / 2), image.Width, image.Height);
}
//return the image
return squareImage;
}
//=== get encoder info
private ImageCodecInfo getEncoderInfo(string mimeType)
{
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
for (int j = 0; j < encoders.Length; ++j)
{
if (encoders[j].MimeType.ToLower() == mimeType.ToLower())
{
return encoders[j];
}
}
return null;
}
//=== determine image rotation
private RotateFlipType getRotateFlipType(int rotateValue)
{
RotateFlipType flipType = RotateFlipType.RotateNoneFlipNone;
switch (rotateValue)
{
case 1:
flipType = RotateFlipType.RotateNoneFlipNone;
break;
case 2:
flipType = RotateFlipType.RotateNoneFlipX;
break;
case 3:
flipType = RotateFlipType.Rotate180FlipNone;
break;
case 4:
flipType = RotateFlipType.Rotate180FlipX;
break;
case 5:
flipType = RotateFlipType.Rotate90FlipX;
break;
case 6:
flipType = RotateFlipType.Rotate90FlipNone;
break;
case 7:
flipType = RotateFlipType.Rotate270FlipX;
break;
case 8:
flipType = RotateFlipType.Rotate270FlipNone;
break;
default:
flipType = RotateFlipType.RotateNoneFlipNone;
break;
}
return flipType;
}
//== convert image to base64
public string convertImageToBase64(Image image)
{
using (MemoryStream ms = new MemoryStream())
{
//convert the image to byte array
image.Save(ms, ImageFormat.Jpeg);
byte[] bin = ms.ToArray();
//convert byte array to base64 string
return Convert.ToBase64String(bin);
}
}
For the asp.net users a little example of how to upload a file, resize it and display the result on the page.
//== the button click method
protected void Button1_Click(object sender, EventArgs e)
{
//check if there is an actual file being uploaded
if (FileUpload1.HasFile == false)
{
return;
}
using (Bitmap bitmap = new Bitmap(FileUpload1.PostedFile.InputStream))
{
try
{
//start the resize
Image image = resizeImage(bitmap, 256, 256, true);
//to visualize the result, display as base64 image
Label1.Text = "<img src=\"data:image/jpg;base64," + convertImageToBase64(image) + "\">";
//save your image to file sytem, database etc here
}
catch (Exception ex)
{
Label1.Text = "Oops! There was an error when resizing the Image.<br>Error: " + ex.Message;
}
}
}

Here is a version based on the accepted answer. It fixes two problems...
Improper disposing of the images.
Maintaining the aspect ratio of the image.
I found this tool to be fast and effective for both JPG and PNG files.
private static FileInfo CreateThumbnailImage(string imageFileName, string thumbnailFileName)
{
const int thumbnailSize = 150;
using (var image = Image.FromFile(imageFileName))
{
var imageHeight = image.Height;
var imageWidth = image.Width;
if (imageHeight > imageWidth)
{
imageWidth = (int) (((float) imageWidth / (float) imageHeight) * thumbnailSize);
imageHeight = thumbnailSize;
}
else
{
imageHeight = (int) (((float) imageHeight / (float) imageWidth) * thumbnailSize);
imageWidth = thumbnailSize;
}
using (var thumb = image.GetThumbnailImage(imageWidth, imageHeight, () => false, IntPtr.Zero))
//Save off the new thumbnail
thumb.Save(thumbnailFileName);
}
return new FileInfo(thumbnailFileName);
}

Here is an example to convert high res image into thumbnail size-
protected void Button1_Click(object sender, EventArgs e)
{
//---------- Getting the Image File
System.Drawing.Image img = System.Drawing.Image.FromFile(Server.MapPath("~/profile/Avatar.jpg"));
//---------- Getting Size of Original Image
double imgHeight = img.Size.Height;
double imgWidth = img.Size.Width;
//---------- Getting Decreased Size
double x = imgWidth / 200;
int newWidth = Convert.ToInt32(imgWidth / x);
int newHeight = Convert.ToInt32(imgHeight / x);
//---------- Creating Small Image
System.Drawing.Image.GetThumbnailImageAbort myCallback = new System.Drawing.Image.GetThumbnailImageAbort(ThumbnailCallback);
System.Drawing.Image myThumbnail = img.GetThumbnailImage(newWidth, newHeight, myCallback, IntPtr.Zero);
//---------- Saving Image
myThumbnail.Save(Server.MapPath("~/profile/NewImage.jpg"));
}
public bool ThumbnailCallback()
{
return false;
}
Source-
http://iknowledgeboy.blogspot.in/2014/03/c-creating-thumbnail-of-large-image-by.html

This is the code I'm using. Also works for .NET Core > 2.0 using System.Drawing.Common NuGet.
https://www.nuget.org/packages/System.Drawing.Common/
using System;
using System.Drawing;
class Program
{
static void Main()
{
const string input = "C:\\background1.png";
const string output = "C:\\thumbnail.png";
// Load image.
Image image = Image.FromFile(input);
// Compute thumbnail size.
Size thumbnailSize = GetThumbnailSize(image);
// Get thumbnail.
Image thumbnail = image.GetThumbnailImage(thumbnailSize.Width,
thumbnailSize.Height, null, IntPtr.Zero);
// Save thumbnail.
thumbnail.Save(output);
}
static Size GetThumbnailSize(Image original)
{
// Maximum size of any dimension.
const int maxPixels = 40;
// Width and height.
int originalWidth = original.Width;
int originalHeight = original.Height;
// Return original size if image is smaller than maxPixels
if (originalWidth <= maxPixels || originalHeight <= maxPixels)
{
return new Size(originalWidth, originalHeight);
}
// Compute best factor to scale entire image based on larger dimension.
double factor;
if (originalWidth > originalHeight)
{
factor = (double)maxPixels / originalWidth;
}
else
{
factor = (double)maxPixels / originalHeight;
}
// Return thumbnail size.
return new Size((int)(originalWidth * factor), (int)(originalHeight * factor));
}
}
Source:
https://www.dotnetperls.com/getthumbnailimage

Related

Resize and optimize Image before upload C#

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;
}

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;
}

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);

Resizing and Watermark Image in C#

i want to watermark pictures with different sizes in one folder. the watermark has 3891 x 4118 pixels. the pictures i want to watermark have nearly the same size or way lower.
however, the watermark image should always have the same size on the images. therefore i take the width of image i want to watermark, multiple it by 0.2 (20%) and the height of the watermark image gets calculated by the ratio. (see code below).
after that, i resize the watermark image and put it on on the image i want to watermark. so far so good, but the problem is, that the image is way smaller than it should be. the calculation works fine, even if i say, put the image as is (3891 x 4118 pixel), it calculates it correctly, but the watermark doesn't get bigger. it stays on a size i didn't calulcate.
that's the button to load the folder.
private void DateiLaden_Click(object sender, RoutedEventArgs e)
{
FolderBrowserDialog fbd = new FolderBrowserDialog();
DialogResult result = fbd.ShowDialog();
try
{
string[] files = GetFiles(fbd.SelectedPath.ToString(), "*.jpg|*.jpeg"); //Only select JPG pictures, method below
int anzahlBilder = files.Length;
if (anzahlBilder <= 0)
{
System.Windows.Forms.MessageBox.Show("No images!");
} // if there are no images, stop processing...
else
{
var res = checkImageSize(files); //check if they have a minmal size, method below
if (res == "") //if everythings fine, continue
{
Directory.CreateDirectory(fbd.SelectedPath.ToString() + "\\watermarked"); //create new directory of the selected folder, folder so save the watermarked images
System.Drawing.Image brand = System.Drawing.Image.FromFile("../../watermark.png"); //load watermark
double imageHeightBrand = Convert.ToDouble(brand.Height); //get height (4118px)
double imageWidthBrand = Convert.ToDouble(brand.Width); //get width (3891px)
double ratioBrand = imageWidthBrand / imageHeightBrand; //get ratio (0.94487615347)
foreach (var file in files)
{
System.Drawing.Image img = System.Drawing.Image.FromFile(file); //load image to watermerk to get width and height
double imageHeightBild = Convert.ToDouble(img.Height); //height of the image to watermark
double imageWidthBild = Convert.ToDouble(img.Width); //width of the image to watermark
//Landscape
if (imageWidthBild > 640.0 && imageHeightBild > 425.0)
{
var imageWidthTmpBranding = imageWidthBild * 0.2; //the watermark width, but only 20% size of the image to watermark
var imageHeightTmpBranding = imageWidthTmpBranding / ratioBrand; //height of watermark, preserve aspect ratio
int imageWidthBranding = Convert.ToInt32(imageWidthTmpBranding); //convert in into int32 (see method below)
int imageHeightBranding = Convert.ToInt32(imageHeightTmpBranding); //convert in into int32 (see method below)
var watermark = ResizeImage(brand, imageWidthBranding, imageHeightBranding); //resize temporally the watermark image, method below
System.Drawing.Image image = System.Drawing.Image.FromFile(file); //get image to watermark
Graphics g = Graphics.FromImage(image); //load is as graphic
g.DrawImage(watermark, new System.Drawing.Point(50, 50)); //draw the watermark on it
image.Save(fbd.SelectedPath.ToString() + "\\watermarked\\" + returnOnlyImageName(file)); //save it in the folder with the same file name, see method below.
}
//Portrait
/* if(imageWidthBild > 350 && imageHeightBild > 520) {
}*/
}
}
else
{
System.Windows.Forms.MessageBox.Show(res);
}
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
"methods below"
private static string[] GetFiles(string sourceFolder, string filters)
{
return filters.Split('|').SelectMany(filter => System.IO.Directory.GetFiles(sourceFolder, filter)).ToArray();
}
private static string checkImageSize(string[] files)
{
string message = "The following pictures are too small:\n";
foreach (var file in files)
{
Bitmap img = new Bitmap(file);
var imageHeight = img.Height;
var imageWidth = img.Width;
if ((imageWidth < 640 && imageHeight < 425) || (imageWidth < 350 && imageHeight < 520))
{
message += returnOnlyImageName(file) + "\n";
}
}
if (message == "The following pictures are too small:\n")
{
return "";
}
else
{
message += "\nPlease change those pictures!";
return message;
}
}
private static string returnOnlyImageName(string file)
{
return file.Substring(file.LastIndexOf("\\")+1);
}
public static Bitmap ResizeImage(System.Drawing.Image image, int width, int height)
{
var destRect = new System.Drawing.Rectangle(0, 0, width, height);
var destImage = new Bitmap(width, height);
destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (var graphics = Graphics.FromImage(destImage))
{
graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
using (var wrapMode = new ImageAttributes())
{
wrapMode.SetWrapMode(WrapMode.TileFlipXY);
graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);
}
}
return destImage;
}
and is it also anyhow possible to save all the watermarks in the bottom right corner?
thanks in regards
Here you go...
void waterMarkOnBottomRight(Image img, Image watermarkImage, string saveFileName)
{
double imageHeightBrand = Convert.ToDouble(watermarkImage.Height);
double imageWidthBrand = Convert.ToDouble(watermarkImage.Width);
double ratioBrand = imageWidthBrand / imageHeightBrand;
double imageHeightBild = Convert.ToDouble(img.Height); //height of the image to watermark
double imageWidthBild = Convert.ToDouble(img.Width);
var imageWidthTmpBranding = imageWidthBild * 0.2; //the watermark width, but only 20% size of the image to watermark
var imageHeightTmpBranding = imageWidthTmpBranding / ratioBrand; //height of watermark, preserve aspect ratio
int imageWidthBranding = Convert.ToInt32(imageWidthTmpBranding); //convert in into int32 (see method below)
int imageHeightBranding = Convert.ToInt32(imageHeightTmpBranding);
int watermarkX = (int)(imageWidthBild - imageWidthBranding); // Bottom Right
int watermarkY = (int)(imageHeightBild - imageHeightBranding);
using (Graphics g = Graphics.FromImage(img))
g.DrawImage(watermarkImage,
new Rectangle(watermarkX, watermarkY, imageWidthBranding, imageHeightBranding),
new Rectangle(0, 0, (int)imageWidthBrand, (int)imageHeightBrand),
GraphicsUnit.Pixel);
img.Save(saveFileName);
}
This one's working for landscape (width > height).
Well.. in your code, you don't have to create separate instances for images for calculating, for creating graphics, add a new bitmap and paint using graphics and all. You do anything with the image, its in the memory. And its not going to affect the watermark image on your disk.

How to extract FlateDecoded Images from PDF with PDFSharp

how do I extract Images, which are FlateDecoded (such like PNG) out of a PDF-Document with PDFSharp?
I found that comment in a Sample of PDFSharp:
// TODO: You can put the code here that converts vom PDF internal image format to a
// Windows bitmap
// and use GDI+ to save it in PNG format.
// [...]
// Take a look at the file
// PdfSharp.Pdf.Advanced/PdfImage.cs to see how we create the PDF image formats.
Does anyone have a solution for this problem?
Thanks for your replies.
EDIT: Because I'm not able to answer on my own Question within 8 hours, I do it on that way:
Thanks for your very fast reply.
I added some Code to the Method "ExportAsPngImage", but I didn't get the wanted results. It is just extracting a few more Images (png) and they don't have the right colors and are distorted.
Here's my actual Code:
PdfSharp.Pdf.Filters.FlateDecode flate = new PdfSharp.Pdf.Filters.FlateDecode();
byte[] decodedBytes = flate.Decode(bytes);
System.Drawing.Imaging.PixelFormat pixelFormat;
switch (bitsPerComponent)
{
case 1:
pixelFormat = PixelFormat.Format1bppIndexed;
break;
case 8:
pixelFormat = PixelFormat.Format8bppIndexed;
break;
case 24:
pixelFormat = PixelFormat.Format24bppRgb;
break;
default:
throw new Exception("Unknown pixel format " + bitsPerComponent);
}
Bitmap bmp = new Bitmap(width, height, pixelFormat);
var bmpData = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pixelFormat);
int length = (int)Math.Ceiling(width * bitsPerComponent / 8.0);
for (int i = 0; i < height; i++)
{
int offset = i * length;
int scanOffset = i * bmpData.Stride;
Marshal.Copy(decodedBytes, offset, new IntPtr(bmpData.Scan0.ToInt32() + scanOffset), length);
}
bmp.UnlockBits(bmpData);
using (FileStream fs = new FileStream(#"C:\Export\PdfSharp\" + String.Format("Image{0}.png", count), FileMode.Create, FileAccess.Write))
{
bmp.Save(fs, System.Drawing.Imaging.ImageFormat.Png);
}
Is that the right way? Or should I choose another way? Thanks a lot!
I know this answer might be a few years to late, but maybe it will help others.
The disortion occurs in my case because image.Elements.GetInteger(PdfImage.Keys.BitsPerComponent) seems to not return the correct value. As Vive la déraison pointed out under your question, you get the BGR Format for using Marshal.Copy. So reversing the Bytes and rotating the Bitmap after executing Marshal.Copy will do the job.
The resulting code looks like this:
private static void ExportAsPngImage(PdfDictionary image, ref int count)
{
int width = image.Elements.GetInteger(PdfImage.Keys.Width);
int height = image.Elements.GetInteger(PdfImage.Keys.Height);
var canUnfilter = image.Stream.TryUnfilter();
byte[] decodedBytes;
if (canUnfilter)
{
decodedBytes = image.Stream.Value;
}
else
{
PdfSharp.Pdf.Filters.FlateDecode flate = new PdfSharp.Pdf.Filters.FlateDecode();
decodedBytes = flate.Decode(image.Stream.Value);
}
int bitsPerComponent = 0;
while (decodedBytes.Length - ((width * height) * bitsPerComponent / 8) != 0)
{
bitsPerComponent++;
}
System.Drawing.Imaging.PixelFormat pixelFormat;
switch (bitsPerComponent)
{
case 1:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format1bppIndexed;
break;
case 8:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format8bppIndexed;
break;
case 16:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format16bppArgb1555;
break;
case 24:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
break;
case 32:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
break;
case 64:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format64bppArgb;
break;
default:
throw new Exception("Unknown pixel format " + bitsPerComponent);
}
decodedBytes = decodedBytes.Reverse().ToArray();
Bitmap bmp = new Bitmap(width, height, pixelFormat);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
int length = (int)Math.Ceiling(width * (bitsPerComponent / 8.0));
for (int i = 0; i < height; i++)
{
int offset = i * length;
int scanOffset = i * bmpData.Stride;
Marshal.Copy(decodedBytes, offset, new IntPtr(bmpData.Scan0.ToInt32() + scanOffset), length);
}
bmp.UnlockBits(bmpData);
bmp.RotateFlip(RotateFlipType.Rotate180FlipNone);
bmp.Save(String.Format("exported_Images\\Image{0}.png", count++), System.Drawing.Imaging.ImageFormat.Png);
}
The code might need some optimisation, but it did export FlateDecoded Images correctly in my case.
To get a Windows BMP, you just have to create a Bitmap header and then copy the image data into the bitmap. PDF images are byte aligned (every new line starts on a byte boundary) while Windows BMPs are DWORD aligned (every new line starts on a DWORD boundary (a DWORD is 4 bytes for historical reasons)).
All information you need for the Bitmap header can be found in the filter parameters or can be calculated.
The color palette is another FlateEncoded object in the PDF. You also copy that into the BMP.
This must be done for several formats (1 bit per pixel, 8 bpp, 24 bpp, 32 bpp).
Here's my full code for doing this.
I'm extracting a UPS shipping label from a PDF so I know the format in advance. If your extracted image is of an unknown type then you'll need to check the bitsPerComponent and handle it accordingly. I also only handle the first image here on the first page.
Note: I'm using TryUnfilter to 'deflate' which uses whatever filter is applied and decodes the data in-place for me. No need to call 'Deflate' explicitly.
var file = #"c:\temp\PackageLabels.pdf";
var doc = PdfReader.Open(file);
var page = doc.Pages[0];
{
// Get resources dictionary
PdfDictionary resources = page.Elements.GetDictionary("/Resources");
if (resources != null)
{
// Get external objects dictionary
PdfDictionary xObjects = resources.Elements.GetDictionary("/XObject");
if (xObjects != null)
{
ICollection<PdfItem> items = xObjects.Elements.Values;
// Iterate references to external objects
foreach (PdfItem item in items)
{
PdfReference reference = item as PdfReference;
if (reference != null)
{
PdfDictionary xObject = reference.Value as PdfDictionary;
// Is external object an image?
if (xObject != null && xObject.Elements.GetString("/Subtype") == "/Image")
{
// do something with your image here
// only the first image is handled here
var bitmap = ExportImage(xObject);
bmp.Save(#"c:\temp\exported.png", System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
}
}
}
Using these helper functions
private static Bitmap ExportImage(PdfDictionary image)
{
string filter = image.Elements.GetName("/Filter");
switch (filter)
{
case "/FlateDecode":
return ExportAsPngImage(image);
default:
throw new ApplicationException(filter + " filter not implemented");
}
}
private static Bitmap ExportAsPngImage(PdfDictionary image)
{
int width = image.Elements.GetInteger(PdfImage.Keys.Width);
int height = image.Elements.GetInteger(PdfImage.Keys.Height);
int bitsPerComponent = image.Elements.GetInteger(PdfImage.Keys.BitsPerComponent);
var canUnfilter = image.Stream.TryUnfilter();
var decoded = image.Stream.Value;
Bitmap bmp = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(decoded, 0, bmpData.Scan0, decoded.Length);
bmp.UnlockBits(bmpData);
return bmp;
}
So far... my code... it works with many png files, but not the one that comes from adobe photoshop with colorspace indexed:
private bool ExportAsPngImage(PdfDictionary image, string SaveAsName)
{
int width = image.Elements.GetInteger(PdfSharp.Pdf.Advanced.PdfImage.Keys.Width);
int height = image.Elements.GetInteger(PdfSharp.Pdf.Advanced.PdfImage.Keys.Height);
int bitsPerComponent = image.Elements.GetInteger(PdfSharp.Pdf.Advanced.PdfImage.Keys.BitsPerComponent);
var ColorSpace = image.Elements.GetArray(PdfImage.Keys.ColorSpace);
System.Drawing.Imaging.PixelFormat pixelFormat= System.Drawing.Imaging.PixelFormat.Format24bppRgb; //24 just for initalize
if (ColorSpace is null) //no colorspace.. bufferedimage?? is in BGR order instead of RGB so change the byte order. Right now it works
{
byte[] origineel_byte_boundary = image.Stream.UnfilteredValue;
bitsPerComponent = (origineel_byte_boundary.Length) / (width * height);
switch (bitsPerComponent)
{
case 4:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppPArgb;
break;
case 3:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
break;
default:
{
MessageBox.Show("Unknown pixel format " + bitsPerComponent, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
break;
}
Bitmap bmp = new Bitmap(width, height, pixelFormat); //copy raw bytes to "master" bitmap so we are out of pdf format to work with
System.Drawing.Imaging.BitmapData bmd = bmp.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.WriteOnly, pixelFormat);
System.Runtime.InteropServices.Marshal.Copy(origineel_byte_boundary, 0, bmd.Scan0, origineel_byte_boundary.Length);
bmp.UnlockBits(bmd);
Bitmap bmp2 = new Bitmap(width, height, pixelFormat);
for (int indicex = 0; indicex < bmp.Width; indicex++)
{
for (int indicey = 0; indicey < bmp.Height; indicey++)
{
Color nuevocolor = bmp.GetPixel(indicex, indicey);
Color colorintercambiado = Color.FromArgb(nuevocolor.A, nuevocolor.B, nuevocolor.G, nuevocolor.R);
bmp2.SetPixel(indicex, indicey, colorintercambiado);
}
}
using (FileStream fs = new FileStream(SaveAsName, FileMode.Create, FileAccess.Write))
{
bmp2.Save(fs, System.Drawing.Imaging.ImageFormat.Png);
}
bmp2.Dispose();
bmp.Dispose();
}
else
{
// this is the case of photoshop... work needs to be done here. I ´m able to get the color palette but no idea how to put it back or create the png file...
switch (bitsPerComponent)
{
case 4:
pixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppArgb;
break;
default:
{
MessageBox.Show("Unknown pixel format " + bitsPerComponent, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
break;
}
if ((ColorSpace.Elements.GetName(0) == "/Indexed") && (ColorSpace.Elements.GetName(1) == "/DeviceRGB"))
{
//we need to create the palette
int paletteColorCount = ColorSpace.Elements.GetInteger(2);
List<System.Drawing.Color> paletteList = new List<Color>();
//Color[] palette = new Color[paletteColorCount+1]; // no idea why but it seams that there´s always 1 color more. ¿transparency?
PdfObject paletteObj = ColorSpace.Elements.GetObject(3);
PdfDictionary paletteReference = (PdfDictionary)paletteObj;
byte[] palettevalues = paletteReference.Stream.Value;
for (int index = 0; index < (paletteColorCount + 1); index++)
{
//palette[index] = Color.FromArgb(1, palettevalues[(index*3)], palettevalues[(index*3)+1], palettevalues[(index*3)+2]); // RGB
paletteList.Add(Color.FromArgb(1, palettevalues[(index * 3)], palettevalues[(index * 3) + 1], palettevalues[(index * 3) + 2])); // RGB
}
}
}
return true;
}
PDF may contain images with masks and with different colorspace options that is why simply decoding an image object may not work properly in some cases.
So the code also needs to check for image masks (/ImageMask) and other properties of image objects (to see if image should also use inverted colors or uses indexed colors) inside PDF to recreate the image similar to how it is displayed in PDF. See Image object, /ImageMask and /Decode dictionaries in the official PDF Reference.
Not sure if PDFSharp is capable of finding Image Mask objects inside PDF but iTextSharp is able to access image mask objects (see PdfName.MASK object types).
Commercial tools like PDF Extractor SDK are able to extract images in both original form and in "as rendered" form.
I work for ByteScout, maker of PDF Extractor SDK
Maybe not directly answer the question but another option to extract images from PDF is to use FreeSpire.PDF which can extract the image from pdf easily. It is available as Nuget package https://www.nuget.org/packages/FreeSpire.PDF/. They handle all the image format and can export as PNG. Their sample code is
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using Spire.Pdf;
namespace ExtractImagesFromPDF
{
class Program
{
static void Main(string[] args)
{
//Instantiate an object of Spire.Pdf.PdfDocument
PdfDocument doc = new PdfDocument();
//Load a PDF file
doc.LoadFromFile("sample.pdf");
List<Image> ListImage = new List<Image>();
for (int i = 0; i < doc.Pages.Count; i++)
{
// Get an object of Spire.Pdf.PdfPageBase
PdfPageBase page = doc.Pages[i];
// Extract images from Spire.Pdf.PdfPageBase
Image[] images = page.ExtractImages();
if (images != null && images.Length > 0)
{
ListImage.AddRange(images);
}
}
if (ListImage.Count > 0)
{
for (int i = 0; i < ListImage.Count; i++)
{
Image image = ListImage[i];
image.Save("image" + (i + 1).ToString() + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
System.Diagnostics.Process.Start("image1.png");
}
}
}
}
(code taken from https://www.e-iceblue.com/Tutorials/Spire.PDF/Spire.PDF-Program-Guide/How-to-Extract-Image-From-PDF-in-C.html)

Categories

Resources