Title: Quickly pick JPG compression level in C#
The example Compress JPG files to a certain size in C# lets you adjust a file's JPG compression level until it is no bigger than a specified size. It does that by starting at a compression level of 100 and then decreasing the level until it finds a value that makes the file small enough.
This method is reasonably fast but it often causes a noticeable delay, particularly if the final compression level is small so the program has to make many attempts. In the worst case, if the program must use a compression level of 5, the program compresses and saves the file 20 times with compression levels 100, 95, 90, 85, ... 5.
This example uses the following code to perform a faster binary search for the best compression level.
// Save the file with the indicated maximum file size.
// Return the compression level used.
public static int SaveJpgAtFileSize(Image image,
string file_name, long max_size)
{
// We check levels that are multiples of 5.
const int delta = 5;
// le_level <= the correct level.
// Start with delta. This will be used if no level works.
int le_level = delta;
// gt_level > the correct level.
// Start with the first multiple of delta greater than 100.
int gt_level = (100 / delta) * delta + delta;
// Loop until there are no possibilities
// between le_level and gt_level.
for (; ; )
{
// Pick a test level in the middle.
int test_level = (le_level + gt_level) / 2;
// Make it a multiple of delta.
test_level = (test_level / delta) * delta;
// If test_level == le_level, then le_level is the winner.
if (test_level == le_level) break;
// Try saving at this compression level.
SaveJpg(image, file_name, test_level);
// See if the size is greater than or
// less than the target size.
if (GetFileSize(file_name) > max_size)
{
// The file is too big. Restrict to smaller sizes.
gt_level = test_level;
}
else
{
// The file is small enough. Consider larger sizes.
le_level = test_level;
}
}
// Save at the final size.
SaveJpg(image, file_name, le_level);
// Return the size.
return le_level;
}
The code uses the constant delta to determine the multiples it considers for the compression level. This version uses multiples of 5 so it will consider compression levels 5, 10, 15, 20, and so forth. The code makes this value a constant so it's easy to change the multiples. For example, you can set this to 1 to consider every possible compression level. (The algorithm used here is fast enough to make that practical. The previous example needed to save the file up to 20 times in the worst case. This version needs to save the file only 8 times to find the best compression level even if you set delta = 1.)
The program uses two key variables to control its search: le_level and gt_level. The value le_level is always less than or equal to the optimal level. The value gt_level is always strictly greater than the optimal level. As it runs through its loop, the program considers compression levels where le_level ≤= level < gt_level.
Initially the program sets le_level = delta and gt_level = the next multiple of delta greater than 100. In this example, that value is 105 so initially 5 ≤= level < 105. That makes sense because the method should always return a value between 5 and 100. (I made delta the smallest compression level the method will use even if the file is still bigger than desired.)
Having set up le_level and gt_level, the program enters an infinite loop. Within the loop, the code sets test_level to be the multiple of delta that is closest to halfway between le_level and gt_level. (This is the binary division step.)
If test_level == le_level, then the program has considered all of the possible level values so it breaks out of the loop. For example, suppose le_level = 30, gt_level = 35, and delta = 5. Then the code sets test_level = 30. At this point we know that 30 ≤ level < 35, so the result we want must be 30, which is the value of le_level.
If the loop doesn't end at this point, the program saves the file at compression level test_level and gets the file's size. If the file is too big, then the program lowers gt_level to test_level so it considers smaller levels in the next loop. If the file is not too big, then the program raises le_level to test_level so it considers bigger levels in the next loop.
The loop continues, lowering gt_level or raising le_level, until test_level == le_level and the code breaks out of the loop.
When the loop ends, the program probably just saved the file at the smallest level that makes the file too big, so it resaves the file at level le_level and returns le_level.
The main program simply calls the SaveJpgAtFileSize method to save the file with the size you entered on the form.
Download the example to experiment with it and to see additional details.
