问题
I need to shrink every image I get that is more than 10MB. File types are png, jpg and gif. I saw that ImageSharp has an option to resize an image:
Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(maxFileSize)
}
I saw a lot of examples using the resize function with width and height, but none using this one size option, and no documentation explain exactly what "size" means.
I've tried the following: Shrinking an image of 22.2MB using maxFileSize=1024
yielded a picture of 527.9MB.
"Shrinking" the same image with maxFileSize=1024*2*10
yielded a 47.4MB picture.
How can I shrink an image to a size of roughly 10MB (can be a bit less)? My goal here is to limit to 10MB, and, if exceeded, reduce the image to the maximal possible size under 10MB without affecting the ratio.
回答1:
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 least0
.ESTIMATED_MAXIMUM_WIDTH
: The estimated upper bound for the perfect image width. The image will never become larger than this. Should be at mostimage.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 toimage.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.
回答2:
After looking some more into the documentation I now see the single int constructor simply insert the same number to both width and height.
For now, I shrink the image way too much using the following:
using (var image = Image.Load(imageFile))
{
var proportion = (double)imageFile.Length / maxFileSize;
var maxWidth = (int)(image.Width / proportion);
var maxHeight = (int)(image.Height / proportion);
image.Mutate(x => x
.Resize(new ResizeOptions
{
Mode = ResizeMode.Max,
Size = new Size(maxWidth, maxHeight)
}));
// Save the Image
}
If anyone has better Idea I would love to hear it.
The goal is to have the image shrink to just under 10MB (as close as possible).
来源:https://stackoverflow.com/questions/60209376/shrink-image-file-using-imagesharp