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

Drag-and-Drop Tool to Reduce JPEG Quality (and file size)

0.00/5 (No votes)
14 Apr 2015 1  
Today's high-resolution cameras produce huge files, with quality you may be willing to sacrifice.

Introduction

This application is a simple single-class Windows Forms project that presents a file drop target that will reduce the quality setting of JPEG files, while maintaining the resolution.

There is no magic here, but beginners will find these concepts explained:

  • Drag-and-drop in Windows Forms
  • Drag-and-drop on an application icon
  • Use of the System.Drawing.Bitmap class to manipulate JPEG image quality
  • Using BackgroundWorker for simple threading

Background

Examples of typical compression
Quality Relative
Size
95 50%
90 30%
80 20%
70 15%
60 12%
50 10%
40 9%
30 8%
20 7%
10 5%
5 4%

I recently purchased a Nikon D3300 digital DSLR camera for use in technical documentation (one of my company's services). I always shoot at the maximum resolution (6000×4000px ≈ 24MP) at the fine setting. This gives me the detail required, should I need to enlarge a section of an image for reproduction.

Even when shooting less important things, I leave the resolution and quality settings the same. If I don't, I know I will regret it later when I forget to change the settings back. The problem is that every image is 7-13MB. The 125 pictures I shot yesterday occupy over 1GB of disk space, fine for my 3TB NAS, but too big to keep on a 64GB SSD.

JPEG Quality

For these less important pictures, I wanted to use a drag-and-drop tool to reduce the JPEG quality without downsampling the resolution, so I wrote JpegQuality.

JPEG images use a lossy compression algorithm, the goal of which is to reduce file size with the least amount of perceptable degredation to the image. The system uses a quality factor between 0 and 100. A slight reduction in JPEG quality can have a dramatic effect on file size (see table).

The values in the table are examples only, and actual values depend largely on the subject matter. I find that a quality factor of 70 for lower resolution images, or 60 for higher resolution image results in little perceptable loss in quality.

The JPEG algorithm works by grouping pixels in to 8×8 blocks and applying a discrete cosine transform to quantitize and discard frequencies of colour and brightness that humans have difficulty distinguishing.


A cell phone photo at quality 100, originally 2592×1944, cropped and scaled.


The same image converted to quality 20 before cropping and scaling.

How to Use It

There are two modes of operation. Each results in a lower quality copy of the source image(s) being written to the same directory as the source image, with a filename suffix indicating the quality factor.

Drop onto control

Opening the application normally presents a simple user interface.

  1. A drop-down box is used to set the quality factor.
  2. File(s) are dropped onto the designated area.
  3. A progress bar indicates how many files have been processed.

Drop onto application icon

Files can be dropped directly onto the application's icon.

How it Works

Settings

The quality factor, selected using a ComboBox, is saved to the registry by way of a the Quality property.

private const int DefaultQuality = 60; 
private const string RegistryRoot = @"HKEY_CURRENT_USER\Software\Red Cell Innovation Inc.\JpegQuality";
/// <summary>
/// Gets/sets the quality.
/// </summary>
/// <value>The quality.</value>
public int Quality
{
    get { return (int) Registry.GetValue(RegistryRoot, "Quality", DefaultQuality); }
    set { Registry.SetValue(RegistryRoot, "Quality", value); }
}
/// <summary>
/// Handles the SelectedIndexChanged event of the QualityBox control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void QualityBox_SelectedIndexChanged(object sender, EventArgs e)
{
    Quality = int.Parse((sender as ToolStripComboBox).Text);
}

Drop onto application icon

When files are dropped onto an application's icon, Windows passes their absolute paths the application as command-line arguments. These arguments are evaluated upon main form activation.

/// <summary>
/// Raises the <see cref="E:System.Windows.Forms.Form.Activated" /> event.
/// </summary>
/// <param name="e">An <see cref="T:System.EventArgs" /> that contains the event data.</param>
protected override void OnActivated(EventArgs e)
{
    base.OnActivated(e);
    try
    {
        var args = Environment.GetCommandLineArgs();
        if (args.Length > 1)
        {
            var list = new List<string>(args);
            list.RemoveAt(0); // First argument is path to exe.
            ProcessImagesAsync(this, new DoWorkEventArgs(list.ToArray()));
            Close();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, ex.GetType().Name, MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

Of course this means the application can also be used at the command line by passing it a space-separated list of filenames.

Drop onto control

When files are dropped into a control whose AllowDrop property is true, in our case the DropLabel control, the drop-related Windows Forms events are invoked. For simple drop operations like this, we handle the DragOver and DragDrop events.

The DragOver event occurs when the mouse is positioned over the control. Handling it lets us provide feedback to the user, indicating whether the control can receive the object being dragged. The DragEventArgs object, passed to the event handler, references everything we need to determine if we can receive the object. In this case we query the data using the GetData method to see if there is a FileDrop object, which is an array of absolute file paths as strings. We determine whether we can handle the dragged object using three criteria:

  • A FileDrop object is present.
  • All of the files in the FileDrop object have the .jpg or .jpeg file extension.
  • Our worker thread isn't already busy processing.

If these conditions are all true, we set DragEventArgs.Effect to DragDropEffects.Copy, which will indicate a successful condirion to the user by adding a + to the cursor.

/// <summary>
/// Handles the DragOver event of the DropLabel control.
/// </summary>
/// <param name="sender">The source of the event.</param>
/// <param name="e">The <see cref="DragEventArgs"/> instance containing the event data.</param>
private void DropLabel_DragOver(object sender, DragEventArgs e)
{
    var files = new List<string>(e.Data.GetData("FileDrop") as string[] ?? new string[0]);
    bool valid = !Worker.IsBusy;
    foreach (string file in files)
        if (!Regex.IsMatch(file, @"\.jpe?g$", RegexOptions.IgnoreCase))
            valid = false;

    e.Effect = valid ? DragDropEffects.Copy : DragDropEffects.None;
}

Changing the Quality

The image is opened using the static Bitmap.FromFile(string filename) constructor, and is then saved by invoking the Save method with an ImageEncoder.

/// <summary>
/// Processes the image.
/// </summary>
/// <param name="source">The source.</param>
/// <param name="destination">The destination.</param>
private void ProcessImage(string source, string destination)
{
    using (var original = Bitmap.FromFile(source))
    {
        var encoder =  ImageCodecInfo.GetImageEncoders().Single(e => e.MimeType == "image/jpeg");
        var options = new EncoderParameters(1);
        options.Param[0] = new EncoderParameter(Encoder.Quality, Quality);
        original.Save(destination, encoder, options);
    }
}

Threading

I wanted to target version 3.5 of the .NET Runtime, so BackgroundWorker is the easiest way to perform background operations on a separate thread. It was made with applications like this in mind, having built-in support for cancellation and progress reporting.

To use it we define the work to be performed by handling the DoWork event. This work will be started on a thread from the thread pool when the RunWorkerAsync method is invoked.

Progress reporting is implemented by invoking the ReportProgress method from the worker, and handling the ProgressChanged event on the main UI thread. Similarly cancellation is implemented by invoking the CancelAsync method on the UI thread, and checking the CancellationPending property in the worker loop.

History

  • January 24, 2015: Article published.
  • April 14, 2015: Example images added.
  • January 28, 2018: Minor formatting and grammar cleanup.

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