Java сжимает файл изображения до целевого размера файла

#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. Интересный подход. Спасибо, что поделились, я тоже это протестирую.