Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

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

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

Стандартное использование выглядит следующим образом:

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

Второй аргумент - ограничивающая ширина и высота 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, если вам интересно.

Ответ 4

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

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

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

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

сравнение

  • Thumbnailator 0.4.8 с настройками по умолчанию, без регулировки размера.
  • Photoshop CS5 с бикубическим алгоритмом
  • imgscalr 4.2 с настройкой ULTRA_QUALITY, без регулировки размера
  • 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 ГБ RAM и 5 прогонов.

  • Thumbnailator: 7003.0ms | 6581.3мс | 6019,1 мс | 6375.3мс | 8700.3ms
  • imgscalr: 25218.5ms | 25786.6мс | 25095,7ms | 25790.4мс | 29296.3ms
  • Graphics2D: 7387.6ms | 7177.0ms | 7048,2мс | 7132.3мс | 7510.3ms

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

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

Ответ 5

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

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

Ответ 6

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

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

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

Ответ 7

Я хотел, чтобы размер с высоким качеством сохранялся. Пробовал несколько вещей и прочитал несколько записей. Потерял два дня, и в итоге я получил лучший результат с помощью простого 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();
    }
  }
}