Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Acme Photo Resizer is a C# Program for Resizing Images in Bulk

0.00/5 (No votes)
20 Jan 2016 1  
In this article I will show how simple programs can help improve your photography work flow. Acme Photo resizer resizes JPG images in bulk. It can also add a copyright notice and the photograph's file name to each image. It was written to help me resize race photographs so that I could post them to

Introduction

Acme Photo Resizer is a simple image processing program designed to resize programs in a bulk operation.

Background

As a way to support the local running community, I take photographs of local races where there is no official photographer. I post them to race web sites and Facebook. When you are posting hundreds of images, you need to reduce the file size to a size that is appropriate for the web-site. Facebook has gotten much better of late, and will display images 2048px wide. What I needed was an easy way to resize hundreds of images at once. While I don't charge for my race photographs, I do like to put a copyright notive and filename on each photograph. That was my motivation for writing this program.

I'm indebted to Code Project contributor Lev Danielyan for his EXIF metadata extraction library. I use the EXIF orientation tag to rotate images shot in portrait mode.

Photograph resized to 600px wide.

Choosing Images

Acme Photo Resizer has a very basic interface. You specify your resize option, enter an optional Copyright Holder name, choose a set of JPG files and click the process button. The program shows a progress bar and lists the files processed in a list box. It places the resized images into a subfolder named "Resized" in the folder containing the selected JPG files.

The "Orientation" tag in the EXIF tag is used to determine the orientation of the photograph. The rotation is to ensure the photograph is resized with the correct orientation.

Performance and multi-threading

As I was writing this article, it occurred to me that I could improve performamce using multi-threading. Because the program iterates through a set of files, it was fairly straight forward to convert it to use the Parallel class’s ForEach method.

Task t = Task.Factory.StartNew(() =>
{
   if (Parallel.ForEach(_Files, f =>
   {
      if (f.EndsWith(".jpg")) {
         ResizeImageFile(f, dir, perCent, pixelWidth, pixelHeight, copyrightHolder, flipHorizontal, flipVertical);
         ReportProgress(f);
      }
   }).IsCompleted) {
      Complete();
   }
});

The tricky part is updating controls in a multi-threaded environment. Controls can only be accessed on the thread that created them. Attempting to access them directly from inside a thread raises an exception. I had a method that updated a progress bar and a list box and I wanted to keep those features. I came across this code project article by Pablo Grisafi and it provided an elegant solution, so I adopted his technique. I appended the following extension method class to the form code file.

static class FormExtensions
{
    static public void UIThread(this Form form, MethodInvoker code)
    {
        if (form.InvokeRequired)
        {
            form.Invoke(code);
            return;
        }
        code.Invoke();
    }
}

That enabled me to implement ReportProgress quite simply.

private void ReportProgress(string f)
{
   this.UIThread(delegate
   {
      proBar.Increment(1);
      lstResized.Items.Add(f);
   });
}

I also needed a way to clean up once all the threads had completeted. I used the same technique.

private void Complete()
{
   this.UIThread(delegate
   {
      //
      // Ensure progres bar is completed
      proBar.Value = proBar.Maximum;
      //
      // Reset cursor and re-enable form.
      Cursor.Current = Cursors.Default;
      this.Enabled = true;
   });
}

Resizing an Image

The method that does the resizing uses standard .Net libraries.

using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Drawing.Text;

It is rather large. I built it using various examples I found on the web. I always say "Google is my manual".

private static Image ScaleImage(Image imgPhoto, int? perCent, int? pixelWidth, int? pixelHeight, int orientation, ref Rectangle dest)
{
   int sourceWidth = imgPhoto.Width;
   int sourceHeight = imgPhoto.Height;
   int sourceX = 0;
   int sourceY = 0;
   int destWidth = 0;
   int destHeight = 0;
   int destX = 0;
   int destY = 0;
   //
   // Calculate the width and height of the resized photo
   // depending on which option was chosen.
   if (perCent != null) {
      if (perCent == 100) {
         return imgPhoto;
      }
      double nPercent = ((double)perCent / 100);
      destWidth = (int)(sourceWidth * nPercent);
      destHeight = (int)(sourceHeight * nPercent);
   }
   else if (pixelWidth != null) {
      if (sourceWidth == (int)(pixelWidth)) {
         return imgPhoto;
      }
      destWidth = (int)(pixelWidth);
      destHeight = (int)((destWidth * sourceHeight) / sourceWidth);
   }
   else {
      if (sourceHeight == (int)(pixelHeight)) {
         return imgPhoto;
      }
      destHeight = (int)pixelHeight;
      destWidth = (int)((destHeight * sourceWidth) / sourceHeight);
   }
   //
   // Create a bitmap for the resized photo
   Bitmap bmPhoto = new Bitmap(destWidth, destHeight, PixelFormat.Format24bppRgb);
   bmPhoto.SetResolution(imgPhoto.HorizontalResolution, imgPhoto.VerticalResolution);
   //
   // Create a graphics object from the bitmap and set attributes for highest quality
   Graphics grPhoto = Graphics.FromImage(bmPhoto);
   grPhoto.InterpolationMode = InterpolationMode.HighQualityBicubic;
   grPhoto.SmoothingMode = SmoothingMode.HighQuality;
   grPhoto.CompositingQuality = CompositingQuality.HighQuality;
   grPhoto.PixelOffsetMode = PixelOffsetMode.HighQuality;
   //
   // Draw the new image
   grPhoto.DrawImage(imgPhoto,
                     new Rectangle(destX, destY, destWidth, destHeight),
                     new Rectangle(sourceX, sourceY, sourceWidth, sourceHeight),
                     GraphicsUnit.Pixel);
   //
   // Reorient, if necessary
   if (orientation == 8) {
      bmPhoto.RotateFlip(RotateFlipType.Rotate270FlipNone);
   }
   else if (orientation == 6) {
      bmPhoto.RotateFlip(RotateFlipType.Rotate90FlipNone);
   }
   //
   // return the Bit map and save its dimensions
   dest.Width = bmPhoto.Width;
   dest.Height = bmPhoto.Height;
   grPhoto.Dispose();
   return bmPhoto;
}

Flipping Images

Sometimes you end up with images that are flipped horizontally or vertically. In my case, it was old slides and negatives that I photographed from the wrong side. My usual processing program, DxO Optics Pro, does not have a flip funcion, so I added the capability to Acme Photo Resizer. Flipping only takes one line of code. x.RotateFlip(RotateFlipType.RotateNoneFlipX);

Saving Images

The Microsoft Documentation says "GDI+ uses image encoders to convert the images stored in Bitmap objects to various file formats. Image encoders are built into GDI+ for the BMP, JPEG, GIF, TIFF, and PNG formats. An encoder is invoked when you call the Save or SaveAdd method of a Image object." I used the following methods to save files using the JPEG encoder.

private void SaveJpeg(string path, Bitmap img, long quality)
{
   // Encoder parameter for image quality
   EncoderParameter qualityParam =
       new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
   // Jpeg image codec
   ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg");
   if (jpegCodec == null)
      return;
   EncoderParameters encoderParams = new EncoderParameters(1);
   encoderParams.Param[0] = qualityParam;
   img.Save(path, jpegCodec, encoderParams);
}
private ImageCodecInfo GetEncoderInfo(string mimeType)
{
   // Get image codecs for all image formats
   ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
   // Find the correct image codec
   for (int i = 0; i < codecs.Length; i++) {
      if (codecs[i].MimeType == mimeType) {
         return codecs[i];
      }
   }
   return null;
}

I pass 90L to the quality parameter. That seems to give good quality and convenient file sizes.

Writing a Copyright notice

Writing to an image involves a few steps. This is the code I use.

//
// Get graphics object from bitmap
Graphics g = Graphics.FromImage(y);
g.SmoothingMode = SmoothingMode.AntiAlias;
//
// Figure out fontsize relative to image width
int fontSize = (int)(_FontSize * 350.0 / x.HorizontalResolution);
//
// Specify font. Currently hard-coded.
Font font = new Font("Lucida Handwriting", fontSize, FontStyle.Bold);
int picNameWidth = (int)g.MeasureString(picName, font).Width;
//
// Write out notice and picture name in black, then white offset by 2 pixels
g.DrawString(copyrightNotice, font, Brushes.Black, new Point(10, dest.Height - 50));
g.DrawString(copyrightNotice, font, Brushes.White, new Point(12, dest.Height - 48));
g.DrawString(picName, font, Brushes.Black, new Point(dest.Width - 10 - picNameWidth, dest.Height - 50));
g.DrawString(picName, font, Brushes.White, new Point(dest.Width - 8 - picNameWidth, dest.Height - 48));
g.Dispose();

I write the text in black and white to give a slight shadow effect. This means it will still be readable if the background is very light or very dark.

Help

I created a help file in HTML. The Help file is displayed when the Help button is clicked.

Using the code

This code uses intermediate C# coding techniques. It could serve as a starting point for applications that need to resize images. It also illustrates how to get EXIF data and how to rotate photographs to the correct orientation.

It is multi-threaded and is able to update a progress bar and list box without cross threading issues, thanks to Pablo Grisafi.

As a photographer, I found this program made it much easier to upload large image galleries to sites like Facebook" .

Points of Interest

Acme Photo Resizer illustrates how to resize, rotate, flip and save images. It uses the Parallel class to improve performance on computers with multicore processors.

History

First Version for Code-Project

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here