Ändern der Größe von jpeg-Bild auf bestimmte Größe
Dies ist funktionaler code um ein Bild zu reduzieren bis zu einer gewissen kleineren Größe. Aber es hat einige Dinge, die sind nicht gut:
- es langsam
- es können mehrere Iterationen, bevor man das Bild skaliert
- jedes mal, es hat zu bestimmen, die Größe hat es zu laden, wird das gesamte Bild in einen memoryStream
Ich möchte es verbessern. Kann es ein Weg sein, um eine bessere Schätzung zu verbieten, so viele Iterationen? Werde ich über das alles falsch? Meine Gründe für die Erstellung ist zu akzeptieren, jedes Bild von unbekannter Größe, und skalieren Sie es auf eine bestimmte Größe. Dies erlaubt eine bessere Planung für die Lagerung benötigt. Beim skalieren auf eine bestimmte Höhe/Breite, die Größe des Bildes variieren kann, viel zu viel für unsere Bedürfnisse.
Müssen Sie ein ref-System.Zeichnung.
//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;
}
Ruft dies erstellt ein 2. Bild, dass ist in der Nähe in der Größe der angeforderten Wert:
var image = Image.FromFile(@"C:\Temp\test.jpg");
var scaled = ScaleDownToKb(image, 250, 80);
SaveJpeg(scaled, @"C:\Temp\test_REDUCED.jpg", 80);
Diesem Beispiel:
- ursprünglichen Dateigröße: 628 kB
- gewünschte Dateigröße: 250 kB
- skaliert Dateigröße: 238 kB
Du musst angemeldet sein, um einen Kommentar abzugeben.
Ich denke, man kann davon ausgehen das lineare Wachstum (und Reduzierung) der Datei Größe, je nach Pixel zählen Wachstum. Das heißt, wenn, zum Beispiel, Sie haben 500 x 500 200 kb Bild und Sie müssen 50 kb, Bild, sollten Sie reduzieren Sie die Bildgröße, um 250x250 (4-mal weniger Pixel). Ich glaub das sollte Euch gewünschte Bild mit einem Durchlauf die meiste Zeit. Aber Sie können zwicken sogar noch weiter, durch die Einführung einiger Risiko Prozent (etwa 10%) zu reduzieren-Verhältnis oder sowas.
Anstatt das zu tun einen langsamen Satz der Iterationen, die für jedes Bild, machen Sie einen test mit einer Anzahl repräsentativer Bilder und Holen Sie sich eine Auflösung, geben Sie das gewünschte Datei-Größe durchschnittlich. Verwenden Sie dann die Auflösung die ganze Zeit.
@jbobbins: ich Stimme mit @xpda, wenn der erste Versuch zum ändern der Größe des Bildes, um die Ziel-Größe ist zu weit entfernt von der Schwelle, Sie können wiederholen Sie den Schritt noch einmal oder einfach fallen Sie zurück zu Ihrer vorherigen inneficient Algorithmus. Es wird konvergieren viel schneller als deine aktuelle Umsetzung. Die ganze Sache sollte durchgeführt werden in O(1) statt O(log n), wie Sie es jetzt tun.
Könnten Sie probieren Sie einige der JPEG-Kompression Verhältnisse und die Art von konstruieren Sie eine Tabelle, aus Experimentierfreude (ich weiß, es wird nicht perfekt sein, aber nahe genug), dass Ihnen eine sehr gute Näherung. Zum Beispiel (entnommen aus Wikipedia):
Meine Lösung für dieses problem war die Qualität verringern, bis die gewünschte Größe erreicht wurde. Unten ist meine Lösung für die Nachwelt.
NB: Dies könnte verbessert werden, indem Sie eine Art von Spekulation.