Este es un código funcional para reducir una imagen a un tamaño específico más pequeño. Pero tiene varias cosas que no son buenas:Cambiar el tamaño de la imagen JPEG al tamaño especificado
- es lento
- que puede hacer varias iteraciones antes de obtener la imagen escalada
- cada vez que tiene que determinar el tamaño que tiene que cargar toda la imagen en un memoryStream
Me gustaría mejorarlo. ¿Puede haber alguna forma de obtener una mejor estimación inicial para evitar tantas iteraciones? ¿Me estoy equivocando? Mis motivos para crearlo es aceptar cualquier imagen de tamaño desconocido y escalarla a un cierto tamaño. Esto permitirá una mejor planificación de las necesidades de almacenamiento. Cuando se escala a una cierta altura/ancho, el tamaño de la imagen puede variar demasiado para nuestras necesidades.
Necesitará hacer una referencia a System.Drawing.
//Scale down the image till it fits the given file size.
public static Image ScaleDownToKb(Image img, long targetKilobytes, long quality)
{
//DateTime start = DateTime.Now;
//DateTime end;
float h, w;
float halfFactor = 100; // halves itself each iteration
float testPerc = 100;
var direction = -1;
long lastSize = 0;
var iteration = 0;
var origH = img.Height;
var origW = img.Width;
// if already below target, just return the image
var size = GetImageFileSizeBytes(img, 250000, quality);
if (size < targetKilobytes * 1024)
{
//end = DateTime.Now;
//Console.WriteLine("================ DONE. ITERATIONS: " + iteration + " " + end.Subtract(start));
return img;
}
while (true)
{
iteration++;
halfFactor /= 2;
testPerc += halfFactor * direction;
h = origH * testPerc/100;
w = origW * testPerc/100;
var test = ScaleImage(img, (int)w, (int)h);
size = GetImageFileSizeBytes(test, 50000, quality);
var byteTarg = targetKilobytes * 1024;
//Console.WriteLine(iteration + ": " + halfFactor + "% (" + testPerc + ") " + size + " " + byteTarg);
if ((Math.Abs(byteTarg - size)/(double)byteTarg) < .1 || size == lastSize || iteration > 15 /* safety measure */)
{
//end = DateTime.Now;
//Console.WriteLine("================ DONE. ITERATIONS: " + iteration + " " + end.Subtract(start));
return test;
}
if (size > targetKilobytes * 1024)
{
direction = -1;
}
else
{
direction = 1;
}
lastSize = size;
}
}
public static long GetImageFileSizeBytes(Image image, int estimatedSize, long quality)
{
long jpegByteSize;
using (var ms = new MemoryStream(estimatedSize))
{
SaveJpeg(image, ms, quality);
jpegByteSize = ms.Length;
}
return jpegByteSize;
}
public static void SaveJpeg(Image image, MemoryStream ms, long quality)
{
((Bitmap)image).Save(ms, FindEncoder(ImageFormat.Jpeg), GetEncoderParams(quality));
}
public static void SaveJpeg(Image image, string filename, long quality)
{
((Bitmap)image).Save(filename, FindEncoder(ImageFormat.Jpeg), GetEncoderParams(quality));
}
public static ImageCodecInfo FindEncoder(ImageFormat format)
{
if (format == null)
throw new ArgumentNullException("format");
foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders())
{
if (codec.FormatID.Equals(format.Guid))
{
return codec;
}
}
return null;
}
public static EncoderParameters GetEncoderParams(long quality)
{
System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.Quality;
//Encoder encoder = new Encoder(ImageFormat.Jpeg.Guid);
EncoderParameters eparams = new EncoderParameters(1);
EncoderParameter eparam = new EncoderParameter(encoder, quality);
eparams.Param[0] = eparam;
return eparams;
}
//Scale an image to a given width and height.
public static Image ScaleImage(Image img, int outW, int outH)
{
Bitmap outImg = new Bitmap(outW, outH, img.PixelFormat);
outImg.SetResolution(img.HorizontalResolution, img.VerticalResolution);
Graphics graphics = Graphics.FromImage(outImg);
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(img, new Rectangle(0, 0, outW, outH), new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
graphics.Dispose();
return outImg;
}
Al llamar a este creará una segunda imagen que está cerca en tamaño al valor solicitado:
var image = Image.FromFile(@"C:\Temp\test.jpg");
var scaled = ScaleDownToKb(image, 250, 80);
SaveJpeg(scaled, @"C:\Temp\test_REDUCED.jpg", 80);
Para este ejemplo específico:
- tamaño del archivo original: 628 kB
- Tamaño de archivo solicitado: 250 kB
- Tamaño del archivo a escala: 238 kB
Hice algo similar a su sugerencia. Funciona de cierta manera, pero luego hay ocasiones en las que, por ejemplo, el tamaño objetivo era 250 kB y la imagen escalada tenía 440 kB (tamaño original 628 kB) porque había más detalles en la imagen o algo así. Eso es demasiado error. Gracias por la sugerencia. – jbobbins