Wie zu komprimieren Bitmap als JPEG mit der geringsten Qualitätsverlust auf Android?
Dies ist nicht eine einfache problem, bitte Lesen!
Möchte ich Bearbeiten einer JPEG-Datei und speichern Sie es wieder als JPEG. Das problem ist, dass auch ohne manipulation gibt es signifikante (sichtbaren) Qualitätsverlust.
Frage: welche option oder API bin ich in der Lage sein zu re-compress JPEG-ohne Qualitätsverlust (ich weiß es nicht genau, aber ich denke, was ich weiter unten beschreiben ist nicht eine akzeptable Artefakte, vor allem mit Qualität=100).
Kontrolle
Lade ich es als Bitmap
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, eine Steuerung Bild zu vergleichen, zu speichern Sie nun die Ebene bytes Bitmap als PNG:
bitmap.compress(PNG, 100/*ignored*/, new FileOutputStream("/sdcard/image.png"));
Ich diese im Vergleich zu den original-JPEG-Bild auf meinem computer und es gibt keinen visuellen Unterschied.
Ich auch so gespeichert, raw - int[]
aus getPixels
geladen und es wie ein rohes ARGB-Datei auf meinem computer: es gibt keinen visuellen Unterschied zum original-JPEG noch PNG gespeichert von Bitmap.
Überprüfte ich die Bitmap mit den Abmessungen und der Konfiguration, die Sie mit die Quelle Bild und die Eingabe-Optionen: es decodiert werden, wie ARGB_8888
als erwartet.
Den obigen Kontrollen beweisen, dass die Pixel in der in-memory Bitmap korrekt sind.
Problem
Möchte ich JPEG-Dateien als Ergebnis, so dass die oben genannten PNG-und RAW-Ansätze nicht funktionieren würde, lassen Sie uns versuchen zu speichern als JPEG 100% erste:
//100% still expected lossy, but not this amount of artifacts
bitmap.compress(JPEG, 100, new FileOutputStream("/sdcard/image.jpg"));
Ich bin nicht sicher, ob seine Maßnahme ist Prozent, aber es ist einfacher zu Lesen und zu diskutieren, also werde ich es verwenden.
Ich bin mir bewusst, dass JPEG mit der Qualität von 100% ist immer noch verlustbehaftet, aber es sollte nicht so visuell verlustbehaftet, es fällt aus der Ferne. Hier ist ein Vergleich von zwei 100% Kompressionen der gleichen Quelle.
Öffnen Sie Sie in separaten Registerkarten und klicken Sie auf hin und her zwischen zu sehen, was ich meine. Der Unterschied Bilder wurden mit Gimp: original als untere Schicht, re-komprimiert-Mittelschicht mit "Grain extract" - Modus, top-layer, full white mit "Wert" - Modus zur Verbesserung der Schlechtigkeit.
Die folgenden Bilder sind hochgeladen auf Imgur, die auch komprimiert die Dateien, aber da die Bilder sind alle komprimiert die gleiche, die ursprüngliche unerwünschte Artefakte sichtbar bleiben, so wie ich es sehen, beim öffnen von meinem ursprünglichen Dateien.
Original [560k]:
Imgur ist der Unterschied zum original - (nicht relevant für das problem, nur um zu zeigen, dass es nicht verursacht extra Artefakte beim hochladen der Bilder):
IrfanView 100% [728k] (optisch identisch zum original):
IrfanView 100%'en Unterschied zum original (kaum etwas)
Android 100% [942k]:
Android 100%'en Unterschied zum original (Tönung, banding, schmieren)
IrfanView habe ich zu gehen, unter 50% [50k], um zu sehen, aus der Ferne ähnliche Effekte. Bei 70% [100k] in IrfanView gibt es keinen merklichen Unterschied, aber die Größe ist 9. Android.
Hintergrund
Erstellte ich eine app, die ein Bild von der Kamera-API, das Bild kommt als byte[]
und ist eine kodierte JPEG-blob. Ich speicherte diese Datei per OutputStream.write(byte[])
Methode, das war meine ursprüngliche Quelldatei. decodeByteArray(data, 0, data.length, options)
dekodiert die gleichen Pixel wie das Lesen aus einer Datei, getestet mit Bitmap.sameAs
es ist also irrelevant für das Problem.
War ich mit meinem Samsung Galaxy S4 mit Android 4.4.2 Dinge testen.
Edit: während der Untersuchung weiter ich habe auch versucht Android 6.0 und N Album Vorhören Emulatoren und Sie reproduzieren das gleiche Problem.
Vereinbart. AFAIK, "es ist was es ist" hier. Je nach den Manipulationen, die Sie tun möchten, könnten Sie nicht tun es in Java sowieso, aufgrund Speicher Einschränkungen, greifen Sie stattdessen auf die NDK. In diesem Fall, das Verhalten der
Bitmap.compress()
werden, fraglich. Epic Analyse, aber! 🙂die "Manipulationen" ich will tun, ist so einfach wie das zuschneiden einen Teil des Bildes, aber das bedeutet, dass ich zu re-codieren; und das will JPEG durch Speicherplatz betrifft. Hast du irgendwelche hintergrund auf, warum Android gehen würde mit einem sub-par-Implementierung des JPEG?
was ist Ihr Vorschlag? (auch in form einer Antwort 😉 ich weiß nicht wirklich umsetzen wollen, meine eigene encoder, aber es wäre schön, eine alternative haben. Meine Messung ist rein visuell und empirisch: ich habe eine Menge Bilder, wo das wirklich sichtbar ist, kann die oben nicht das beste Beispiel, aber ich denke, es ist ausreichend, um den Punkt durch.
Bitmap.compress()
gewesen wäre, die ursprünglich für single-core, ~500MHz CPUs und 192MB Gerät RAM. Das ist in etwa auf Augenhöhe mit 15-Jahr-alten PCs. Also, es war optimiert für geringe CPU-und low-RAM, nicht die max Qualität. Ich habe keine Ahnung, ob Sie es verbessert im Laufe der Zeit oder nicht, da dieses Gerät die Anforderungen wurden klettern. "Ich habe eine Menge Bilder, wo das wirklich sichtbar" -- beachten Sie, dass Sie möglicherweise empfindlicher auf die Frage, als andere. Ich bin notorisch miserabel, und wenn ich starre auf die Fotos, werde ich erkennen, minimale Unterschiede, aber das ist es.InformationsquelleAutor TWiStErRob | 2016-04-07
Du musst angemeldet sein, um einen Kommentar abzugeben.
Nach einigen Untersuchungen fand ich die Schuldige: Skia ist YCbCr-Konvertierung. Repro, code für die Untersuchung und Lösungen können gefunden werden bei TWiStErRob/AndroidJPEG.
Entdeckung
Nach nicht immer eine positive Antwort auf diese Frage (weder aus http://b.android.com/206128) ich begann, tiefer zu Graben. Fand ich zahlreiche halb-informiert SO Antworten, das half mir enorm bei der Entdeckung bits und Stücke. Eine solche Antwort war https://stackoverflow.com/a/13055615/253468 das machte mir bewusst
YuvImage
wandelt ein YUV NV21 byte-array in eine JPEG-komprimierte byte-array:Es gibt eine Menge von Freiheit geht in die Schaffung der YUV-Daten, die mit unterschiedlichen Konstanten und Präzision. Aus meine Frage, ist es klar, dass Android verwendet einen falschen Algorithmus.
Während der Wiedergabe, um mit den algorithmen und Konstanten, die ich online gefunden haben, bekam ich immer ein schlechtes image: entweder die Helligkeit verändert, oder hatte das gleiche banding-Probleme wie in der Frage.
, Tiefer zu Graben
YuvImage
ist eigentlich nicht die beim AufrufBitmap.compress
, hier ist der Stapel fürBitmap.compress
:jpeg_write_scanlines
(jcapistd.c:77)rgb2yuv_32
(SkImageDecoder_libjpeg.cpp:913)writer(=Write_32_YUV).write
(SkImageDecoder_libjpeg.cpp:961)[
WE_CONVERT_TO_YUV
bedingungslos definiert]SkJPEGImageEncoder::onEncode
(SkImageDecoder_libjpeg.cpp:1046)SkImageEncoder::encodeStream
(SkImageEncoder.cpp:15)Bitmap_compress
(Bitmap.cpp:383)Bitmap.nativeCompress
(Bitmap.java:1573)Bitmap.compress
(Bitmap.java:984)app.saveBitmapAsJPEG
()und der Stapel für die Verwendung
YuvImage
jpeg_write_raw_data
(jcapistd.c:120)YuvToJpegEncoder::compress
(YuvToJpegEncoder.cpp:71)YuvToJpegEncoder::encode
(YuvToJpegEncoder.cpp:24)YuvImage_compressToJpeg
(YuvToJpegEncoder.cpp:219)YuvImage.nativeCompressToJpeg
(YuvImage.java:141)YuvImage.compressToJpeg
(YuvImage.java:123)app.saveNV21AsJPEG
()Durch die Verwendung der Konstanten in
rgb2yuv_32
von derBitmap.compress
flow ich war in der Lage, neu zu erstellen die gleichen banding-Effekt mitYuvImage
, nicht eine Errungenschaft, nur eine Bestätigung, dass es tatsächlich die YUV-Konvertierung, die ist versaut. Ich doppelt überprüft, dass das problem nicht beiYuvImage
aufrufenlibjpeg
: durch die Umwandlung der ARGB-Bitmap nach YUV und wieder zurück nach RGB, dann dumping die daraus resultierende pixel-blob als ein raw-Bild, das banding war schon da.Während dies zu tun ich erkannte, dass die NV21/YUV420SP layout ist verlustbehaftet, wie es die Proben, die die Farbinformationen jedes 4. pixel, aber es hält den Wert (Helligkeit) der einzelnen pixel, was bedeutet, dass einige Farb-info ist verloren, aber die meisten der Informationen, die für die Augen der Menschen sind in der Helligkeit sowieso. Werfen Sie einen Blick auf die Beispiel auf wikipedia, die Cb-und Cr-Kanal macht kaum erkennbare Bilder, so lossy-sampling-es spielt keine Rolle, viel.
Lösung
So, an diesem Punkt wusste ich, dass libjpeg nicht die richtige Konvertierung, wenn es übergeben wird, werden die raw-Daten. Dies ist, wenn ich die NDK und integriert die neueste LibJPEG aus http://www.ijg.org. Ich war in der Lage zu bestätigen, dass in der Tat die übergabe der RGB-Daten aus der Bitmap-Pixel-array liefert das erwartete Ergebnis. Ich mag vermeiden Sie die Verwendung von einheitlichen Komponenten, wenn nicht absolut notwendig, so dass abgesehen von gehen für eine native Bibliothek kodiert, dass ein Bitmap-fand ich eine nette Abhilfe. Ich habe im Grunde genommen die
rgb_ycc_convert
Funktion vonjcolor.c
und schrieb es in Java das Skelett aus https://stackoverflow.com/a/13055615/253468. Die Tabelle ist nicht auf Geschwindigkeit optimiert, aber die Lesbarkeit einige Konstanten wurden aus Gründen der Kürze entfernt, finden Sie diese in libjpeg-code oder mein Beispiel-Projekt.Der Magische Schlüssel zu sein scheint
ONE_HALF - 1
der rest sieht schrecklich viel wie die Mathematik in Skia. Das ist eine gute Richtung für zukünftige Untersuchungen, aber für mich ist das oben genannte ist einfach genug, um eine gute Lösung für die Arbeit rund um Android builtin Skurrilität, wenn auch langsamer. Beachten Sie, dass diese Lösung nutzt die NV21 layout, das verliert 3/4 der Farb-info (von der Cr/Cb), aber dieser Verlust ist wesentlich kleiner als der Fehler durch Trennen der math. Beachten Sie auch, dassYuvImage
keine Unterstützung für odd-sized Bilder, für weitere Infos siehe NV21-format und ungerade Bild-Dimensionen.InformationsquelleAutor TWiStErRob
Bitte verwenden Sie die folgende Methode:
InformationsquelleAutor Ismaran Duwadi
Unten ist mein Code:
InformationsquelleAutor Er. Praful Parmar