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.
- A drop-down box is used to set the quality factor.
- File(s) are dropped onto the designated area.
- 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";
public int Quality
{
get { return (int) Registry.GetValue(RegistryRoot, "Quality", DefaultQuality); }
set { Registry.SetValue(RegistryRoot, "Quality", value); }
}
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.
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);
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.
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
.
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.