¿Cómo comprimir Bitmap como JPEG con la menor pérdida de calidad en Android?

Este no es un problema sencillo, ¡por favor lea!

Quiero manipular un archivo JPEG y guardarlo nuevamente como JPEG. El problema es que, incluso sin manipulación, hay una pérdida de calidad significativa (visible).Pregunta: qué opción o API me falta para poder volver a comprimir JPEG sin pérdida de calidad (sé que no es exactamente posible, pero creo que lo que describo a continuación no es un nivel aceptable de artefactos, especialmente con calidad = 100).

Controlar

Lo cargo como unBitmap del archivo:

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);

Ahora, para tener una imagen de control con la que comparar, guardemos los bytes de mapa de bits simples como PNG:

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

Comparé esto con la imagen JPEG original en mi computadora y no hay diferencia visual.

También salvé el crudoint[] degetPixels y lo cargué como un archivo ARGB sin procesar en mi computadora: no hay diferencia visual en el JPEG original, ni en el PNG guardado en Bitmap.

Verifiqué las dimensiones y la configuración del mapa de bits, coinciden con la imagen de origen y las opciones de entrada: está decodificada comoARGB_8888 como se esperaba.

Lo anterior para controlar las comprobaciones demuestra que los píxeles en el mapa de bits en memoria son correctos.

Problema

Como resultado, quiero tener archivos JPEG, por lo que los enfoques PNG y RAW anteriores no funcionarían, intentemos guardar como JPEG 100% primero:

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

No estoy seguro de que su medida sea un porcentaje, pero es más fácil de leer y discutir, así que lo usaré.

Soy consciente de que JPEG con una calidad del 100% sigue siendo con pérdida, pero no debería ser tan visualmente con pérdida que se nota desde lejos. Aquí hay una comparación de dos compresiones 100% de la misma fuente.

Ábralos en pestañas separadas y haga clic de un lado a otro para ver a qué me refiero. Las imágenes de diferencia se hicieron usando Gimp: original como capa inferior, capa media re-comprimida con modo "Extracto de grano", capa superior completamente blanca con modo "Valor" para mejorar la maldad.

Las imágenes a continuación se cargan en Imgur, que también comprime los archivos, pero como todas las imágenes se comprimen de la misma manera, los artefactos no deseados originales permanecen visibles de la misma manera que lo veo al abrir mis archivos originales.

Original [560k]:La diferencia de Imgur con el original (no es relevante para el problema, solo para mostrar que no está causandoextra artefactos al cargar las imágenes):IrfanView 100% [728k] (visualmente idéntico al original):IrfanView 100% de diferencia con el original (casi nada)Android 100% [942k]:Android 100% de diferencia con el original (tintado, bandas, manchas)

En IrfanView tengo que ir por debajo del 50% [50k] para ver efectos remotamente similares. Al 70% [100k] en IrfanView no hay una diferencia notable, pero el tamaño es el noveno de Android.

Antecedentes

Creé una aplicación que toma una foto de Camera API, esa imagen viene comobyte[] y es un blob JPEG codificado. Guardé este archivo a través deOutputStream.write(byte[]) método, ese fue mi archivo fuente original.decodeByteArray(data, 0, data.length, options) decodifica los mismos píxeles que la lectura de un archivo, probado conBitmap.sameAs entonces es irrelevante para el problema.

Estaba usando mi Samsung Galaxy S4 con Android 4.4.2 para probar cosas. Editar: mientras investigaba más, también probé emuladores de vista previa de Android 6.0 y N y reproducen el mismo problema.

Respuestas a la pregunta(3)

Su respuesta a la pregunta