Wie komprimiere ich Bitmap als JPEG mit dem geringsten Qualitätsverlust unter Android?

Dies ist kein einfaches Problem, bitte lesen Sie durch!

Ich möchte eine JPEG-Datei bearbeiten und erneut als JPEG speichern. Das Problem ist, dass es auch ohne Manipulation einen signifikanten (sichtbaren) Qualitätsverlust gibt.Frag: Welche Option oder API fehlt mir, um JPEG ohne Qualitätsverlust erneut komprimieren zu können? (Ich weiß, dass dies nicht genau möglich ist, aber ich denke, was ich im Folgenden beschreibe, ist keine akzeptable Menge an Artefakten, insbesondere bei Qualität = 100.)

Steuerun

Ich lade es alsBitmap aus der Datei:

BitmapFactory.Options options = new BitmapFactory.Options();
// explicitly state everything so the configuration is clear
options.inPreferredConfig = Config.ARGB_8888;
options.inDither = false; // shouldn't be used anyway since 8888 can store HQ pixels
options.inScaled = false;
options.inPremultiplied = false; // no alpha, but disable explicitly
options.inSampleSize = 1; // make sure pixels are 1:1
options.inPreferQualityOverSpeed = true; // doesn't make a difference
// I'm loading the highest possible quality without any scaling/sizing/manipulation
Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/image.jpg", options);

Nun, um ein Kontrollbild zum Vergleichen zu haben, speichern wir die einfachen Bitmap-Bytes als PNG:

bitmap.compress(PNG, 100/*ignored*/, new FileOutputStream("/sdcard/image.png"));

Ich habe dies mit dem ursprünglichen JPEG-Bild auf meinem Computer verglichen und es gibt keinen visuellen Unterschied.

Ich habe auch das raw @ gespeicheint[] vongetPixels und lud es als unformatierte ARGB-Datei auf meinen Computer: Es gibt weder einen visuellen Unterschied zum ursprünglichen JPEG noch zum in Bitmap gespeicherten PNG.

Ich habe die Abmessungen und die Konfiguration der Bitmap überprüft. Sie stimmen mit dem Quellbild und den Eingabemöglichkeiten überein. Es wird als @ dekodierARGB_8888 wie erwartet

Die obigen Kontrollprüfungen beweisen, dass die Pixel in der speicherinternen Bitmap korrekt sind.

Proble

Als Ergebnis möchte ich JPEG-Dateien haben, daher funktionieren die obigen Ansätze für PNG und RAW nicht. Versuchen wir zunächst, diese als JPEG 100% zu speichern:

// 100% still expected lossy, but not this amount of artifacts
bitmap.compress(JPEG, 100, new FileOutputStream("/sdcard/image.jpg"));

Ich bin mir nicht sicher, ob das Maß in Prozent angegeben ist, aber es ist einfacher zu lesen und zu diskutieren, also werde ich es verwenden.

Mir ist bewusst, dass JPEG mit einer Qualität von 100% immer noch verlustbehaftet ist, aber es sollte nicht so verlustbehaftet sein, dass es von weitem auffällt. Hier ist ein Vergleich von zwei 100% igen Komprimierungen derselben Quelle.

Öffnen Sie sie in separaten Registerkarten und klicken Sie zwischen den Registerkarten hin und her, um zu sehen, was ich meine. Die Differenzbilder wurden mit Gimp erstellt: Original als unterste Ebene, neu komprimierte mittlere Ebene mit dem Modus "Getreideextrakt", obere Ebene vollweiß mit dem Modus "Wert", um die Schlechtigkeit zu verbessern.

Die folgenden Bilder werden in Imgur hochgeladen, wodurch auch die Dateien komprimiert werden. Da jedoch alle Bilder gleich komprimiert werden, bleiben die ursprünglichen unerwünschten Artefakte so sichtbar, wie ich sie beim Öffnen meiner Originaldateien sehe.

Original [560k]: Imgurs Unterschied zum Original (nicht relevant für das Problem, nur um zu zeigen, dass es keine @ verursacextr Artefakte beim Hochladen der Bilder):IrfanView 100% [728k] (optisch identisch mit dem Original):IrfanView 100% Unterschied zum Original (kaum etwas) Android 100% [942k]:Android 100% Unterschied zum Original (Abtönen, Streifenbildung, Verschmieren)

In IrfanView muss ich unter 50% [50k] gehen, um entfernt ähnliche Effekte zu sehen. Bei 70% [100k] in IrfanView gibt es keinen merklichen Unterschied, aber die Größe ist das Neunte von Android.

Hintergrun

Ich habe eine App erstellt, die ein Bild von der Kamera-API aufnimmt. Dieses Bild wird als @ angezeigbyte[] und ist ein codierter JPEG-Blob. Ich habe diese Datei über @ gespeicheOutputStream.write(byte[]) Methode, das war meine ursprüngliche Quelldatei.decodeByteArray(data, 0, data.length, options) decodiert die gleichen Pixel wie das Lesen aus einer Datei, getestet mitBitmap.sameAs so ist es für das Problem irrelevant.

Ich habe mein Samsung Galaxy S4 mit Android 4.4.2 zum Testen verwendet. Bearbeiten: während der weiteren Untersuchung habe ich auch versucht, Android 6.0 und N Vorschau-Emulatoren und sie reproduzieren das gleiche Problem.

Antworten auf die Frage(6)

Ihre Antwort auf die Frage