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

Сделать BufferedImage использовать меньше оперативной памяти?

У меня есть java-программа, которая читает jpeg файл с жесткого диска и использует его в качестве фонового изображения для других вещей. Само изображение сохраняется в объекте BufferImage, например:

BufferedImage background
background = ImageIO.read(file)

Это отлично работает - проблема в том, что сам объект BufferedImage огромен. Например, 215k jpeg файл становится объектом BufferedImag e, который имеет 4 мегабайта и изменяется. В рассматриваемом приложении могут быть загружены довольно большие фоновые изображения, но в то время как jpegs не превышает мега или два, память, используемая для хранения BufferedImage, может быстро превышать 100 мегабайт.

Я предполагаю, что все это связано с тем, что изображение хранится в ram в качестве необработанных RGB-данных, а не сжато или оптимизировано каким-либо образом.

Есть ли способ сохранить изображение в ram в меньшем формате? Я в ситуации, когда у меня больше слабее на стороне процессора, чем в ОЗУ, поэтому небольшое повышение производительности, чтобы получить размер объекта изображения обратно в сторону сжатия jpeg, будет стоить того.

4b9b3361

Ответ 1

Один из моих проектов, который я просто сбрасываю, проецирует изображение, поскольку он считывается из ImageStream "на лету". Сэмплирование вниз уменьшает размеры изображения до требуемой ширины и высоты, не требуя дорогостоящих вычислений изменения размера или изменения изображения на диске.

Потому что я вниз - пример изображения меньшего размера, он также значительно снижает вычислительную мощность и оперативную память, необходимые для ее отображения. Для дополнительной оптимизации я визуализирую буферное изображение в плитках... Но это немного выходит за рамки этой дискуссии. Попробуйте следующее:

public static BufferedImage subsampleImage(
    ImageInputStream inputStream,
    int x,
    int y,
    IIOReadProgressListener progressListener) throws IOException {
    BufferedImage resampledImage = null;

    Iterator<ImageReader> readers = ImageIO.getImageReaders(inputStream);

    if(!readers.hasNext()) {
      throw new IOException("No reader available for supplied image stream.");
    }

    ImageReader reader = readers.next();

    ImageReadParam imageReaderParams = reader.getDefaultReadParam();
    reader.setInput(inputStream);

    Dimension d1 = new Dimension(reader.getWidth(0), reader.getHeight(0));
    Dimension d2 = new Dimension(x, y);
    int subsampling = (int)scaleSubsamplingMaintainAspectRatio(d1, d2);
    imageReaderParams.setSourceSubsampling(subsampling, subsampling, 0, 0);

    reader.addIIOReadProgressListener(progressListener);
    resampledImage = reader.read(0, imageReaderParams);
    reader.removeAllIIOReadProgressListeners();

    return resampledImage;
  }

 public static long scaleSubsamplingMaintainAspectRatio(Dimension d1, Dimension d2) {
    long subsampling = 1;

    if(d1.getWidth() > d2.getWidth()) {
      subsampling = Math.round(d1.getWidth() / d2.getWidth());
    } else if(d1.getHeight() > d2.getHeight()) {
      subsampling = Math.round(d1.getHeight() / d2.getHeight());
    }

    return subsampling;
  }

Чтобы получить ImageInputStream из файла, используйте:

ImageIO.createImageInputStream(new File("C:\\image.jpeg"));

Как вы можете видеть, эта реализация также учитывает исходный формат изображения. Вы можете дополнительно зарегистрировать IIOReadProgressListener, чтобы вы могли отслеживать, сколько из изображения было прочитано до сих пор. Это полезно для отображения индикатора выполнения, если изображение считывается по сети, например... Не требуется, но вы можете просто указать нуль.

Почему это имеет особое значение для вашей ситуации? Он никогда не читает все изображение в памяти, столько, сколько вам нужно, чтобы оно отображалось с требуемым разрешением. Хорошо работает для огромных изображений, даже тех, которые на диске 10 МБ.

Ответ 2

Я предполагаю, что все это связано с тем, что изображение хранится в баране как необработанный RGB данных, не сжатых или оптимизированных в любым способом.

Точно... Скажем, 1920x1200 JPG может поместиться, скажем, в 300 Кбайт в памяти, в (типичном) RGB + alpha, 8 бит на компонент (следовательно, 32 бит на пиксель), он будет занимать в памяти:

1920 x 1200 x 32 / 8 = 9 216 000 bytes 

поэтому ваш файл размером 300 КБ станет изображением, требующим около 9 МБ ОЗУ (обратите внимание, что в зависимости от типа изображений, которые вы используете на Java, и в зависимости от JVM и ОС это иногда может быть ОЗУ GFX).

Если вы хотите использовать картинку в качестве фона рабочего стола 1920x1200, вам, вероятно, не нужно иметь изображение большего размера, чем в памяти (если вы не хотите какого-то особого эффекта, например, прореживания/-ализация/и т.д.).

Итак, у вас есть выбор:

  • делает ваши файлы менее широкими и менее высокими (в пикселях) на диске.
  • уменьшить размер изображения на лету

Я обычно перехожу с номером 2, потому что уменьшение размера файла на жестком диске означает, что вы теряете детали (изображение 1920x1200 менее детализировано, чем "тот же" на 3940x2400: вы "теряете информацию" путем уменьшения масштаба).

В настоящее время Java, вроде бы, сосет большие времена при манипулировании большими изображениями (как с точки зрения производительности, с точки зрения использования памяти, так и с точки зрения качества [*]). Еще в те дни, когда я назову ImageMagick из Java, сначала измените размер изображения на диске, а затем загрузите измененное изображение (например, установите размер экрана).

В настоящее время существуют Java-мосты/API-интерфейсы для непосредственного взаимодействия с ImageMagick.

[*] Существует NO WAY, вы уменьшаете изображение с помощью встроенного API Java как можно быстрее и с таким же качеством, как и для ImageMagick, для начала.

Ответ 3

Вам нужно использовать BufferedImage? Не могли бы вы написать собственную реализацию Image, в которой хранятся байты jpg в памяти, и при необходимости скрывается до BufferedImage, а затем удаляется?

Это применяется с некоторой логикой распознавания (перемасштабирование изображения с использованием JAI перед сохранением в вашем массиве байтов как jpg), сделает его быстрее, чем декодирование большого jpg каждый раз, и меньший размер, чем у вас в настоящее время (память обработки требования исключены).

Ответ 4

Использовать imgscalr:

http://www.thebuzzmedia.com/software/imgscalr-java-image-scaling-library/

Почему?

  • Следуйте рекомендациям
  • Глупый простой
  • Интерполяция, поддержка сглаживания
  • Таким образом, вы не загружаете свою собственную библиотеку масштабирования.

код:

BufferedImage thumbnail = Scalr.resize(image, 150);

or

BufferedImage thumbnail = Scalr.resize(image, Scalr.Method.SPEED, Scalr.Mode.FIT_TO_WIDTH, 150, 100, Scalr.OP_ANTIALIAS);

Также используйте image.flush() на своем большом изображении после преобразования, чтобы помочь с использованием памяти.

Ответ 5

Размер файла JPG на диске полностью не имеет значения.
Размер пикселя файла. Если ваше изображение составляет 15 мегапикселей, ожидайте, что он потребует дерьмовой загрузки ОЗУ для загрузки сырой несжатой версии.
Измените размеры ваших изображений так, как вам нужно, и это лучшее, что вы можете сделать, не переходя в менее насыщенное представление цветового пространства.

Ответ 6

Вы можете скопировать пиксели изображения в другой буфер и посмотреть, занимает ли это меньше памяти, чем объект BufferedImage. Возможно, что-то вроде этого:

BufferedImage background = new BufferedImage(
   width, 
   height, 
   BufferedImage.TYPE_INT_RGB
);

int[] pixels = background.getRaster().getPixels(
    0, 
    0, 
    imageBuffer.getWidth(), 
    imageBuffer.getHeight(), 
    (int[]) null
);