Качество изменения размера изображения (Java)

У меня есть приложение с открытым исходным кодом, которое загружает фотографии в Facebook. Чтобы сохранить пропускную способность, фотографии автоматически изменяются перед загрузкой (Facebook устанавливает ограничение по максимальному размеру). Несколько человек жаловались на качество фотографий, и на самом деле вы можете увидеть разницу (см. Эту проблему для некоторых демонстрационных изображений).

Поэтому мой вопрос заключается в том, каков "лучший" способ уменьшения изображений (т.е. фотографий) в Java без потери качества или, по крайней мере, с минимальными потерями качества / артефактами?

Вы можете увидеть текущий код у меня здесь (изменить размер кода через эту страницу).

7 ответов

Решение

Я попробовал все это - включая уловки здесь, и все, что я могу сказать, что вам лучше использовать ImageMagick с любым интерфейсом, библиотеки изображений Javas просто не работают, когда дело доходит до этого. Вам нужно поддерживать так много форматов и алгоритмов, чтобы сделать это правильно.

Фил, я не знаю, какое решение вы в конечном итоге использовали, но масштабирование изображений в Java может выглядеть довольно неплохо, если вы:

  • Избегайте типов BufferedImage, которые не очень хорошо поддерживаются JDK.
  • Использовать пошаговое масштабирование
  • Придерживайтесь бикубического при использовании инкрементального масштабирования

Я провел значительную долю тестирования с этими методами, и ключевое значение имело добавочное масштабирование, а также привязка к хорошо поддерживаемым типам изображений - я вижу, что Александр упомянул, что ему все еще не повезло с этим, что является обломом.

Я выпустил библиотеку imgscalr (Apache 2) около 6 месяцев назад, чтобы решить вопрос: "Я хочу хорошо выглядящие уменьшенные копии этого изображения, СДЕЛАЙТЕ СЕЙЧАС!" после прочтения примерно 10 вопросов вроде этого на SO.

Стандартное использование выглядит так:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, 640);

2-й аргумент - это ограничивающие ширину и высоту, которые imgscalr будет использовать для масштабирования изображения - сохраняя правильные пропорции, даже если вы передали неверные размеры - есть много более подробных методов, но это простейшее использование.

Вариант использования, который вам нужен, например, если Facebook ограничил изображения до 800x600 пикселей, будет выглядеть следующим образом:

BufferedImage img = ImageIO.read(...); // load image
BufferedImage scaledImg = Scalr.resize(img, Method.QUALITY, 800, 600);

Это гарантирует, что изображение остается в наилучшем поддерживаемом типе изображения и масштабируется с использованием метода самого высокого качества, который может использовать Java.

В моем собственном тестировании с высоким разрешением я не заметил каких-либо явных расхождений с масштабированными изображениями, использующими эту библиотеку / эти методы, ЗА ИСКЛЮЧЕНИЕМ, когда загрузчик ImageIO переводит ваше изображение в плохо поддерживаемый тип изображения - например, это часто случается с GIF-файлами, Если вы оставляете их такими и не выводите их из этих плохо поддерживаемых типов, то получается, что они выглядят действительно испорченными и ужасными.

Причина этого заключается в том, что команда Java2D на самом деле имеет различные аппаратно ускоренные конвейеры для всех различных типов BufferedImages, которые может обрабатывать JDK - подмножество тех типов изображений, которые являются менее распространенными, прибегают к использованию того же конвейера рендеринга программного обеспечения под охватывает в Java2D, что приводит к плохим, а иногда и совершенно некорректно выглядящим изображениям. Это была такая PIA, чтобы объяснить и попытаться выяснить, что я просто написал эту логику прямо в библиотеку.

Два наиболее поддерживаемых типа: BufferedImage.TYPE_INT_RGB и _ARGB, если вам интересно.

Две наиболее популярные библиотеки с открытым исходным кодом, специализирующиеся на изменении размера изображения в Java:

Кроме того, есть путь JDK с Java Graphics2D( см. этот вопрос о том, как это сделать), что, как известно, приводит к плохим результатам, особенно при уменьшении масштаба. Существует также Java-интерфейс для ImageMagick, который здесь будет опущен, поскольку для него требуется внешний инструмент.

Визуальное качество

Вот сравнение результатов изменения размера / уменьшения580x852PNG для145x213, В качестве ссылки используется Photoshop CS5 "Сохранить для веб". Примечание: результаты 1:1, что созданные библиотеки просто скопированы вместе.Масштабирование не использует никакой фильтрации, просто простой алгоритм ближайшего соседа. Здесь вы можете найти оригинальное изображение.

сравнение

  1. Thumbnailator 0.4.8 с настройками по умолчанию, без настройки размеров
  2. Photoshop CS5 с бикубическим алгоритмом
  3. imgscalr 4.2 с настройкой ULTRA_QUALITY, без настройки размера
  4. Graphics2D (Java 8) с подсказками рендеринга VALUE_INTERPOLATION_BICUBIC, VALUE_RENDER_QUALITY, VALUE_ANTIALIAS_ON

Я оставляю читателю выбирать лучший результат, так как это субъективно. Как правило, все имеют хороший результат, кроме Graphics2D, Thumbnailator генерирует более четкие изображения, очень похожие на вывод Photoshop, тогда как вывод imgscalr значительно мягче. Для значков / текста и т. Д. Требуется более четкий вывод, для изображений может потребоваться более плавный вывод.

Вычислительное время

Вот ненаучный тест с использованием этого инструмента и 114 изображений с размером от 96x96 вплоть до 2560x1440 рассматривая это как 425% создания изображений: 100%, 150%, 200%, 300% и 400% его масштабированных версий (т.е. операции масштабирования 114 * 5). Все библиотеки используют те же настройки, что и при сравнении качества (максимально возможное качество). Времена только масштабируют не весь процесс. Сделано на i5-2520M с 8 ГБ оперативной памяти и 5 запусками.

  • Thumbnailator: 7003.0ms | 6581,3мс | 6019,1мс | 6375,3мс | 8700.3ms
  • imgscalr: 25218,5мс | 25786,6мс | 25095,7мс | 25790,4 мс | 29296.3ms
  • Graphics2D: 7387,6мс | 7177,0 мс | 7048,2 мс | 7132,3мс | 7510.3ms

Вот код, используемый в этом тесте.

Интересно, что Thumbnailator также самый быстрый со средним временем 6,9 с, за которым следует Java2D с 7,2 с, оставляя imgscalr позади с плохими 26,2 с. Это, вероятно, не справедливо, так как imgscalr установлен в ULTRA_QUALITY который кажется очень дорогим; с QUALITY установив его в среднем на более конкурентоспособные 11,1 сек.

Чтобы изменить размер изображения с помощью пользовательского качества, используйте thumbnailator.jar.

Пример кода http://code.google.com/p/thumbnailator/wiki/Examples

Какую подсказку рендеринга вы используете? Обычно бикубическая повторная выборка будет лучшей. На фотографиях, на которые вы ссылаетесь, они очень неровные, что заставляет меня думать, что вы используете ближайшего соседа в качестве подсказки.

В классе PictureScaler, на который вы ссылаетесь, в paintComponent Метод, он использует шесть различных способов изменения размера изображения. Вы пробовали все шесть, чтобы увидеть, что дает лучший результат?

Я хотел, чтобы размер был наивысшего качества с сохранением пропорций. Перепробовал несколько вещей и прочитал несколько записей. Потерял два дня и в итоге я получил лучший результат с простым Java-методом (пробовал также библиотеки ImageMagick и java-image-scaling):

public static boolean resizeUsingJavaAlgo(String source, File dest, int width, int height) throws IOException {
  BufferedImage sourceImage = ImageIO.read(new FileInputStream(source));
  double ratio = (double) sourceImage.getWidth()/sourceImage.getHeight();
  if (width < 1) {
    width = (int) (height * ratio + 0.4);
  } else if (height < 1) {
    height = (int) (width /ratio + 0.4);
  }

  Image scaled = sourceImage.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING);
  BufferedImage bufferedScaled = new BufferedImage(scaled.getWidth(null), scaled.getHeight(null), BufferedImage.TYPE_INT_RGB);
  Graphics2D g2d = bufferedScaled.createGraphics();
  g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
  g2d.drawImage(scaled, 0, 0, width, height, null);
  dest.createNewFile();
  writeJpeg(bufferedScaled, dest.getCanonicalPath(), 1.0f);
  return true;
}


/**
* Write a JPEG file setting the compression quality.
*
* @param image a BufferedImage to be saved
* @param destFile destination file (absolute or relative path)
* @param quality a float between 0 and 1, where 1 means uncompressed.
* @throws IOException in case of problems writing the file
*/
private static void writeJpeg(BufferedImage image, String destFile, float quality)
      throws IOException {
  ImageWriter writer = null;
  FileImageOutputStream output = null;
  try {
    writer = ImageIO.getImageWritersByFormatName("jpeg").next();
    ImageWriteParam param = writer.getDefaultWriteParam();
    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    param.setCompressionQuality(quality);
    output = new FileImageOutputStream(new File(destFile));
    writer.setOutput(output);
    IIOImage iioImage = new IIOImage(image, null, null);
    writer.write(null, iioImage, param);
  } catch (IOException ex) {
    throw ex;
  } finally {
    if (writer != null) {
      writer.dispose();
    }
    if (output != null) {
      output.close();
    }
  }
}

После нескольких разочаровывающих экспериментов я нашел следующую оценку изменения размера и применил многопроходный подход в своем проекте.

Для этого я скопировал метод getScaledInstance() в свой класс генератора миниатюр, изменил свой подход к чтению изображений, чтобы использовать ImageIO (который возвращает BufferedImage), и теперь я очень счастлив!

Я сравнил результат с изменением размера, сделанным в Photoshop CS3, и результат почти такой же.

Другие вопросы по тегам