I don't think that there is an easy (and reliable) way to compute the compressed size of an image, without, well, compressing it.
While ImageSharp doesn't seem to offer a native API for this, we can use existing functionality to find an optimal size which satisfies our constraints.
Idea:
- We can assume that smaller images take less space on disk, i.e., the file size is correlated with the number of pixels. This may not hold for some sophisticated compression algorithms on very similar image sizes; however, even then we should still able to find a good estimation for the perfect image size.
- Pick some rough lower and upper bounds for the image size. In the best case, our perfect image size lays somewhere in between.
- Perform a binary search between the aforementioned bounds, by repeatedly resizing and compressing our image, until it has our desired file size.
Corresponding C# code:
// Load original image
using Image image = Image.Load("image.jpg");
// Configure encoder
var imageEncoder = new JpegEncoder
{
Quality = 95,
Subsample = JpegSubsample.Ratio420
};
// Resize image, until it fits the desired file size and bounds
int min = ESTIMATED_MINIMUM_WIDTH;
int max = ESTIMATED_MAXIMUM_WIDTH;
int bestWidth = min;
using var tmpStream = new MemoryStream();
while(min <= max)
{
// Resize image
int width = (min + max) / 2;
using var resizedImage = image.Clone(op =>
{
op.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(width, MAXIMUM_HEIGHT)
});
});
// Compress image
tmpStream.SetLength(0);
resizedImage.Save(tmpStream, imageEncoder);
// Check file size of resized image
if(tmpStream.Position < MAXIMUM_FILE_SIZE)
{
// The current width satisfies the size constraint
bestWidth = width;
// Try to make image bigger again
min = width + 1;
}
else
{
// Image is still too large, continue shrinking
max = width - 1;
}
}
// Resize and store final image
image.Mutate(op =>
{
op.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(bestWidth, MAXIMUM_HEIGHT)
});
});
// Store resized image
image.Save("image-resized.jpg", imageEncoder);
This loads the image "image.jpg" and finds the width bestWidth
which yields a file size smaller than, but near to MAXIMUM_FILE_SIZE
.
The other constants are defined as follows:
ESTIMATED_MINIMUM_WIDTH
: The estimated lower bound for the perfect image width. The image will never become smaller than this. Should be at least 0
.
ESTIMATED_MAXIMUM_WIDTH
: The estimated upper bound for the perfect image width. The image will never become larger than this. Should be at most image.Width
.
MAXIMUM_HEIGHT
: The maximum image height. This is helpful if there are other constraints than the file size (e.g., the image must fit a certain screen size); else, this can be simply set to image.Height
.
While binary search offers good algorithmic complexity, this can still be quite slow for very large images. If time is important (the question doesn't specify this), the performance can be improved by using good initial estimations of the upper and lower bounds for bestWidth
, e.g., by linearly interpolating the file size through the ratio of pixels to bytes.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…