Качество изменения размера изображения (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, который здесь будет опущен, поскольку для него требуется внешний инструмент.
Визуальное качество
Вот сравнение результатов изменения размера / уменьшения580x852
PNG для145x213
, В качестве ссылки используется Photoshop CS5 "Сохранить для веб". Примечание: результаты 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 ГБ оперативной памяти и 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, и результат почти такой же.