Это непростая проблема, прочтите!
Я хочу изменить файл JPEG и снова сохранить его как JPEG. Проблема в том, что даже без манипуляций наблюдается значительная (видимая) потеря качества. Вопрос: какой параметр или API мне не хватает для повторного сжатия JPEG без потери качества (я знаю, что это не совсем возможно, но я думаю, что то, что я описываю ниже, не является приемлемым уровнем артефактов, особенно с качеством = 100).
Контроль
Я загружаю его как Bitmap
из файла:
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);
Теперь, чтобы иметь контрольное изображение для сравнения, давайте сохраним простые байты Bitmap как PNG:
bitmap.compress(PNG, 100/*ignored*/, new FileOutputStream("/sdcard/image.png"));
Я сравнил это с исходным изображением в формате JPEG на моем компьютере, и визуальной разницы нет.
Я также сохранил необработанный int[]
из getPixels
и загрузил его как необработанный файл ARGB на свой компьютер: нет визуальных отличий от исходного JPEG или PNG, сохраненного из Bitmap.
Я проверил размеры и конфигурацию растрового изображения, они соответствуют исходному изображению и параметрам ввода: он декодируется как ARGB_8888
, как и ожидалось.
Приведенные выше контрольные проверки подтверждают правильность пикселей в растровом изображении в памяти.
Проблема
В результате я хочу получить файлы JPEG, чтобы указанные выше подходы PNG и RAW не работали, давайте сначала попробуем сохранить как JPEG 100%:
// 100% still expected lossy, but not this amount of artifacts
bitmap.compress(JPEG, 100, new FileOutputStream("/sdcard/image.jpg"));
Я не уверен, что он измеряется в процентах, но его легче читать и обсуждать, поэтому я воспользуюсь им.
Я знаю, что JPEG с качеством 100% по-прежнему с потерями, но он не должен быть настолько визуально потерянным, чтобы это было заметно издалека. Вот сравнение двух 100% сжатий одного и того же источника.
Открывайте их на отдельных вкладках и щелкайте между ними, чтобы понять, что я имею в виду. Различия изображений были сделаны с использованием GIMP: оригинал в качестве нижнего слоя, повторно сжатый средний слой в режиме «Зернистость», верхний слой полностью белый в режиме «Значение» для улучшения плохих качеств.
Приведенные ниже изображения загружаются в Imgur, который также сжимает файлы, но поскольку все изображения сжимаются одинаково, исходные нежелательные артефакты остаются видимыми так же, как я вижу их при открытии исходных файлов.
Исходный [560k]: Отличие Imgur от оригинала (не имеет отношения к проблеме, просто чтобы показать, что это не вызывает каких-либо дополнительных артефактов при загрузке изображений):
IrfanView 100% [728k] (визуально идентичен оригиналу):
IrfanView 100% отличия от оригинала (почти ничего)
Android 100% [942k]:
100% отличия Android от оригинала (тонировка, полосы, смазывание)
В IrfanView мне нужно опуститься ниже 50% [50k], чтобы увидеть отдаленно похожие эффекты. При 70% [100k] в IrfanView заметной разницы нет, но размер составляет 9-ю часть от размера Android.
Задний план
Я создал приложение, которое делает снимок из Camera API, это изображение имеет вид byte[]
и представляет собой закодированный BLOB-объект в формате JPEG. Я сохранил этот файл методом OutputStream.write(byte[])
, это был мой исходный исходный файл. decodeByteArray(data, 0, data.length, options)
декодирует те же пиксели, что и чтение из файла, протестировано с Bitmap.sameAs
, поэтому не имеет отношения к проблеме.
Я использовал свой Samsung Galaxy S4 с Android 4.4.2 для проверки. Изменить: во время дальнейшего исследования я также попробовал эмуляторы предварительного просмотра Android 6.0 и N, и они воспроизводят ту же проблему.
Bitmap.compress()
будет спорным. Но эпический анализ! :-) - person CommonsWare   schedule 08.04.2016Bitmap.compress()
изначально был создан для одноядерных процессоров с тактовой частотой ~ 500 МГц и 192 МБ ОЗУ устройства. Это примерно на уровне компьютеров 15-летней давности. Следовательно, он был оптимизирован для низкого уровня ЦП и оперативной памяти, а не для максимального качества. Я понятия не имею, улучшали ли они его с течением времени или нет, учитывая, что требования к устройствам растут. У меня есть много изображений, на которых это действительно видно - имейте в виду, что вы можете быть более чувствительны к проблеме, чем другие. Я заведомо плохо разбираюсь в этом, и если я смотрю на фотографии, я обнаруживаю мельчайшие различия, но это все. - person CommonsWare   schedule 08.04.2016