#java #android #image-processing
#java #Android #обработка изображений
Вопрос:
Я работаю над приложением для Android, и пользователь сделает снимок с помощью камеры. Размер результирующей фотографии может варьироваться от < 1 МБ до более чем 3-4 МБ. Мне нужно убедиться, что размер фотографии меньше 1 МБ, чтобы сохранить ее на сервере. Итак, для изображений размером более 1 МБ я хочу уменьшить его до менее чем 1 МБ, но сохранить как можно более высокое качество. Меня не беспокоит потеря (некоторого) качества.
Как я могу этого добиться? Насколько я знаю, большинство решений включают уменьшение изображения до целевых размеров (ширина X высота), но в моем случае меня интересует только результирующий размер уменьшенного изображения.
И очень важно, чтобы результат был меньше 1 МБ, если присутствует какая-либо погрешность, она должна быть на меньшем размере файла, если это имеет смысл.
Есть ли способ рассчитать, какими должны быть ширина и высота для достижения целевого размера файла? Или, что еще лучше, уменьшить его без указания ширины и высоты?
Ответ №1:
Как я могу этого добиться?
Основные три варианта:
- Уменьшить разрешение (ширину и высоту)
- Уменьшить качество JPEG (значение 0-100, которое используется при сжатии растрового изображения в формат JPEG)
- Уменьшите разрядность (например, с ARGB888 до RGB565), хотя это в основном что-то для PNG, и я подозреваю, что это действительно испортит качество вашей фотографии
Есть ли способ рассчитать, какими должны быть ширина и высота для достижения целевого размера файла?
Нет, поскольку это немного зависит от содержимого изображения и от того, насколько хорошо оно сжимается с использованием алгоритмов сжатия JPEG.
Или, что еще лучше, уменьшить его без указания ширины и высоты?
Для увеличения с 3-4 МБ до менее чем 1 МБ вам почти гарантированно потребуется уменьшить разрешение.
Однако, поскольку только вы знаете, что считаете приемлемым результатом, вам нужно будет поэкспериментировать самостоятельно, чтобы определить, как наилучшим образом применить эти три варианта.
Комментарии:
1. Извините, я изо всех сил старался изложить все свои требования. На самом деле их не так много. Я мог бы использовать любой из первых двух ваших вариантов. Но вопрос в том, на сколько мне нужно уменьшить разрешение или на сколько мне нужно уменьшить качество JPEG. Есть ли способ рассчитать на основе размера файла? Как я уже сказал, качество меня не волнует, цель состоит в том, чтобы уменьшить размер как можно меньше, чтобы получить меньше 1 мб.
2. @jefferson: «Есть ли способ рассчитать на основе размера файла?» — как я уже писал, нет, поскольку это немного зависит от содержимого изображения и от того, насколько хорошо оно сжимается с использованием алгоритмов сжатия JPEG. Вы можете запустить несколько тестов с некоторыми образцами фотографий и попытаться придумать эвристику, которую вы можете использовать. Например, предположим, что размер вашей фотографии составляет 4 МБ. Уменьшение вдвое разрешения по каждой оси (половина ширины и половина высоты) уменьшит количество пикселей до 25% от исходного, что должно приблизиться к 1 МБ. Однако фактическое изображение все еще может быть больше или меньше 1 МБ, в зависимости от того, как работает сжатие.
3. Хорошо, это имеет смысл, спасибо. Если размер файла уменьшается с разрешением в линейной зависимости, это именно то, что я пытался выяснить.
4. @jefferson: Опять же, он не будет уменьшаться точно линейным способом. Но разрешение, безусловно, является наиболее важным компонентом размера файла JPEG.
5. Да, я понимаю. Нужно будет проверить теорию и, возможно, установить нижний предел, который допускает некоторую погрешность.
Ответ №2:
Я сделал это около 15 лет назад, используя Swing и javax.image
и единственным жизнеспособным решением было применить разные уровни качества к кодировщику JPG, чтобы найти наилучшую настройку качества, при которой изображение все еще было меньше запрошенного максимального размера. Код выглядел примерно так (код здесь синтезирован из фактического кода, никогда не тестировался в этой версии …):
/** Create JPG version of some image with given JPG quality setting (0..1), helper method */
private byte[] createResultBytes(BufferedImage losslessimage,float lossyquality) {
Iterator<ImageWriter> iter = ImageIO.getImageWritersByFormatName("JPG");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (iter.hasNext()) {
ImageWriter writer = iter.next();
ImageWriteParam iwp = writer.getDefaultWriteParam();
iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
iwp.setCompressionQuality(lossyquality);
MemoryCacheImageOutputStream mcios = new MemoryCacheImageOutputStream(baos);
writer.setOutput(mcios);
// workaround for alpha-channel problems with lossy images: Seems to be non-functional for some pictures, anyway.
BufferedImage img2=new BufferedImage(losslessimage.getWidth(),losslessimage.getHeight(),BufferedImage.TYPE_INT_RGB);
Graphics g=img2.getGraphics();
g.drawImage(new ImageIcon(losslessimage).getImage(),0,0,null);
g.dispose();
IIOImage iioimg = new IIOImage(img2, null, null);
try {
writer.write(null, iioimg, iwp);
mcios.close();
}
catch (IOException ioe) {}
}
return baos.toByteArray();
}
/** Create JPG version of some image with best JPG quality where the file size is still not larger than maxsize. */
public byte[] getLossyImage(BufferedImage losslessimage,int maxsize,float maxquality) {
byte[] resultbytes=createResultBytes(losslessimage,maxquality);
if (resultbytes.length>size) {
int minsize=(int)(size*.9f);
float newq=maxquality/2f;
resultbytes=createResultBytes(losslessimage,newq);
float qdiff=newq/2f;
while ((resultbytes.length<minsize || resultbytes.length>size) amp;amp; diff>.02f) {
newq =(resultbytes.length<minsize?qdiff:-qdiff);
resultbytes=createResultBytes(losslessimage,newq);
qdiff=qdiff/2f;
}
}
return resultbytes;
}
Обратите внимание, что я изменил размер файла только с помощью настройки качества JPG, а не путем автоматического изменения размера изображения. Если вы хотите пойти этим путем, вам сначала нужно применить эти изменения размера.
Комментарии:
1. Интересный подход. Спасибо, что поделились, я тоже это протестирую.