{{ :software:jpeg_mehrfach_komprimieren:jpeg_kompression.jpg?nolink&3000 |}}
====== JPEG – bis zur Unkenntlichkeit komprimiert ======
Häufig bekommt man in den sozialen Medien und Messengern Bilder zu sehen, welche zu Tode komprimiert wurden.
Wie kommt dies Zustande?
Ich habe ein Testprogramm in C# unter Windows geschrieben, welches ein Bild immer und immer wieder komprimiert. Dies habe ich mit vier verschiedenen Qualitätsparametern ausprobiert:
* 50%
* 85%
* Zwischen 50% und 100% (Zufall für jedes Frame)
* Zwischen 85% und 100% (Zufall für jedes Frame)
Das ganze habe ich in ein Video gepackt:\\
{{:software:jpeg_mehrfach_komprimieren:jpeg_kompression.mp4|}}
Man sieht, dass die Bilder so schlecht werden, weil immer verschiedene Kompressionsstufen verwendet werden. Die Bilder, welche immer die gleiche Kompressionsstufe aufweisen, werden nur minimalst schlechter. In der Praxis kommt natürlich noch hinzu, dass die Bilder in jedem Speicherschritt nicht pixelgenau übereinander liegen. Gründe hierfür sind: Zuschneiden und Skalieren von Bildern (z.B. in Screenshots). Der Effekt dürfte ähnlich stark sein, wie das Nutzen unterschiedlicher Kompressionsstufen.
Im Detail nochmals der Vorher-Nachher-Vergleich im Differenzbild:\\
{{:software:jpeg_mehrfach_komprimieren:diff_kompression.jpg?direct&600|}}
Man sieht bei den Bildern ohne variable Kompressionsstufe kaum einen Unterschied (obwohl das Bild über 15000x komprimiert wurde). Das Bild mit fixer Kompressionsrate ist besser als das mit zufälliger (85%-100%).
===== Software =====
Diese ist in C# mithilfe von Visual Studio 2015 erstellt.
++++ imageHelper.cs |
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;
namespace jpg_qualitiy {
class imageHelper {
public static Bitmap getScreenshot() {
int screenLeft = SystemInformation.VirtualScreen.Left;
int screenTop = SystemInformation.VirtualScreen.Top;
int screenWidth = SystemInformation.VirtualScreen.Width;
int screenHeight = SystemInformation.VirtualScreen.Height;
Bitmap bmp = new Bitmap(screenWidth, screenHeight);
using (Graphics g = Graphics.FromImage(bmp)) {
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
}
return bmp;
}
private static ImageCodecInfo GetEncoder(ImageFormat format) {
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs) {
if (codec.FormatID == format.Guid) {
return codec;
}
}
return null;
}
public static MemoryStream saveJpegQuality(Bitmap bmp, long quality) {
MemoryStream ms = new MemoryStream();
ImageCodecInfo jgpEncoder = GetEncoder(ImageFormat.Jpeg);
System.Drawing.Imaging.Encoder myEncoder = System.Drawing.Imaging.Encoder.Quality;
EncoderParameters myEncoderParameters = new EncoderParameters(1);
EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, quality);
myEncoderParameters.Param[0] = myEncoderParameter;
bmp.Save(ms, jgpEncoder, myEncoderParameters);
return ms;
}
public static Bitmap bmpFromUrl(string url) {
System.Net.WebRequest request = System.Net.WebRequest.Create(url);
System.Net.WebResponse response = request.GetResponse();
System.IO.Stream responseStream = response.GetResponseStream();
return new Bitmap(responseStream);
}
}
}
++++
Darin sind Funktionen enthalten, um das Bild zu mittels JPEG zu komprimieren, Bilder von URL zu beziehen und einen Screenshot zu bekommen.
++++ Form1.cs |
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Threading.Tasks;
using System.Drawing.Imaging;
using System.IO;
namespace jpg_qualitiy {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
Bitmap[] bmps = new Bitmap[4];
private void button1_Click(object sender, EventArgs e) {
bmps[0] = new Bitmap("testbild.jpg");
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;
pictureBox1.Image = bmps[0];
bmps[1] = (Bitmap)bmps[0].Clone();
bmps[2] = (Bitmap)bmps[0].Clone();
bmps[3] = (Bitmap)bmps[0].Clone();
timer1.Interval = 20;
timer1.Enabled = true;
ct = 0;
}
int ct;
private void timer1_Tick(object sender, EventArgs e) {
while (!formClosed) {
ct++;
Stopwatch sw = new Stopwatch();
sw.Start();
int[] quality = new int[4];
Parallel.For(0, 4,
i => {
Random rnd = new Random();
switch (i) {
case 0:
quality[i] = rnd.Next(50, 100);
break;
case 1:
quality[i] = rnd.Next(85, 100);
break;
case 2:
quality[i] = 50;
break;
case 3:
quality[i] = 85;
break;
}
bmps[i] = new Bitmap(imageHelper.saveJpegQuality(bmps[i], quality[i]));
}
);
Bitmap bmp = new Bitmap(bmps[0].Size.Width, bmps[0].Size.Height);
using (Graphics g = Graphics.FromImage(bmp)) {
g.InterpolationMode = InterpolationMode.High;
g.SmoothingMode = SmoothingMode.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.CompositingQuality = CompositingQuality.HighQuality;
g.DrawImage(bmps[0], new Rectangle(0, 0, bmps[0].Width / 2, bmps[0].Height / 2), new Rectangle(0, 0, bmps[0].Width / 2, bmps[0].Height / 2), GraphicsUnit.Pixel);
g.DrawImage(bmps[1], new Rectangle(bmps[0].Width / 2, 0, bmps[0].Width / 2, bmps[0].Height / 2), new Rectangle(bmps[0].Width / 2, 0, bmps[0].Width / 2, bmps[0].Height / 2), GraphicsUnit.Pixel);
g.DrawImage(bmps[2], new Rectangle(0, bmps[0].Height/2, bmps[0].Width / 2, bmps[0].Height / 2), new Rectangle(0, bmps[0].Height / 2, bmps[0].Width / 2, bmps[0].Height / 2), GraphicsUnit.Pixel);
g.DrawImage(bmps[3], new Rectangle(bmps[0].Width / 2, bmps[0].Height/2, bmps[0].Width / 2, bmps[0].Height / 2), new Rectangle(bmps[0].Width / 2, bmps[0].Height / 2, bmps[0].Width / 2, bmps[0].Height / 2), GraphicsUnit.Pixel);
StringFormat format = new StringFormat();
format.LineAlignment = StringAlignment.Near;
format.Alignment = StringAlignment.Near;
GraphicsPath p = new GraphicsPath();
p.AddString("Quality: Random (50-100)\r\n" + quality[0], FontFamily.GenericSansSerif, (int)FontStyle.Regular, 50, new Point(10, 10), format);
g.DrawPath(new Pen(Brushes.Black, 10), p);
g.FillPath(Brushes.White, p);
p = new GraphicsPath();
format.LineAlignment = StringAlignment.Near;
format.Alignment = StringAlignment.Far;
p.AddString("Quality: Random (85-100)\r\n" + quality[1], FontFamily.GenericSansSerif, (int)FontStyle.Regular, 50, new Point(bmps[0].Width - 10, 10), format);
g.DrawPath(new Pen(Brushes.Black, 10), p);
g.FillPath(Brushes.White, p);
p = new GraphicsPath();
format.LineAlignment = StringAlignment.Far;
format.Alignment = StringAlignment.Near;
p.AddString("Quality: 50", FontFamily.GenericSansSerif, (int)FontStyle.Regular, 50, new Point(10, bmps[0].Height - 10), format);
g.DrawPath(new Pen(Brushes.Black, 10), p);
g.FillPath(Brushes.White, p);
p = new GraphicsPath();
format.LineAlignment = StringAlignment.Far;
format.Alignment = StringAlignment.Far;
p.AddString("Quality: 85", FontFamily.GenericSansSerif, (int)FontStyle.Regular, 50, new Point(bmps[0].Width - 10, bmps[0].Height - 10), format);
g.DrawPath(new Pen(Brushes.Black, 10), p);
g.FillPath(Brushes.White, p);
p = new GraphicsPath();
format.LineAlignment = StringAlignment.Far;
format.Alignment = StringAlignment.Center;
p.AddString(ct.ToString(), FontFamily.GenericSansSerif, (int)FontStyle.Regular, 50, new Point(bmps[0].Width/2, bmps[0].Height - 10), format);
g.DrawPath(new Pen(Brushes.Black, 10), p);
g.FillPath(Brushes.White, p);
}
sw.Stop();
label1.Text = "Dauer: " + sw.Elapsed.TotalMilliseconds + "ms" + "\r\n" + "Count: " + ct + "\r\n" + "Quality: " + quality[0] + " " + quality[1] + " " + quality[2] + " " + quality[3];
if (ct % 25 == 0|| ct == 1) {
pictureBox1.Image = bmp; //jedes 25. Bild
if (!Directory.Exists("out")) Directory.CreateDirectory("out");
//bmp.Save("out/" + ct.ToString("000000") + ".png", ImageFormat.Png);
File.WriteAllBytes("out/" + ct.ToString("000000") + ".jpg", imageHelper.saveJpegQuality(bmp, 90).ToArray());
}
timer1.Enabled = false;
Application.DoEvents();
}
}
bool formClosed;
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
formClosed = true;
}
}
}
++++
Es handelt sich um eine Form mit einem Button, einer Picturebox, einem Timer und einem Label. Die Bilder werden in den vier Kompressionsklassen komprimiert, zusammengesetzt und beschriftet. Anschließend wird jedes 25te Bild abgespeichert und angezeigt.
{{tag>[csharp jpeg kompression]}}
\\ ~~DISQUS~~