Это не простая проблема, пожалуйста, прочитайте!
Я хочу манипулировать файлом JPEG и сохранять его снова как JPEG. Проблема в том, что даже без манипуляции наблюдается значительная (видимая) потеря качества. Вопрос: какой вариант или API мне не хватает, чтобы иметь возможность повторно сжимать JPEG без потери качества (я знаю, что это не совсем возможно, но я думаю, что то, что я описываю ниже, не является приемлемым уровнем артефактов, особенно с качеством = 100).
Control
Я загружаю его как 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 на своем компьютере, и нет визуальной разницы.
Я также сохранил raw 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]: Android 100% разница с оригиналом (тонирование, обвязка, размазывание)
В IrfanView я должен опуститься ниже 50% [50k], чтобы увидеть отдаленные аналогичные эффекты. При 70% [100k] в IrfanView нет заметной разницы, но размер составляет 9-й из Android.
Фон
Я создал приложение, которое делает снимок из Camera API, это изображение появляется как byte[]
и представляет собой закодированное JPEG-изображение. Я сохранил этот файл с помощью метода OutputStream.write(byte[])
, который был моим исходным исходным файлом. decodeByteArray(data, 0, data.length, options)
декодирует те же пиксели, что и чтение из файла, проверенный с помощью Bitmap.sameAs
, поэтому он не имеет отношения к проблеме.
Я использовал свой Samsung Galaxy S4 с Android 4.4.2, чтобы проверить все. Edit: при дальнейшем изучении я также пробовал эмуляторы предварительного просмотра Android 6.0 и N, и они воспроизводят ту же проблему.