Качество изменения размера (Java)
У меня есть приложение с открытым исходным кодом, которое загружает фотографии в Facebook. Чтобы сохранить пропускную способность, фотографии автоматически изменяются до загрузки (Facebook накладывает максимальный размер). Несколько человек жаловались на качество фотографии, и на самом деле вы можете увидеть разницу (см. эту проблему для некоторых демонстрационных изображений).
Итак, мой вопрос: каков наилучший способ масштабирования изображений (например, фотографий) на Java без потери качества или, по крайней мере, с минимальными потерями/артефактами качества?
Вы можете увидеть текущий код, который у меня есть здесь (изменить размер кода через на этой странице).
Ответы
Ответ 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, если вам интересно.
Ответ 3
Для изменения размера изображения с пользовательским качеством используйте файл thumbnailator.jar.
Пример кода http://code.google.com/p/thumbnailator/wiki/Examples
Ответ 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();
}
}
}