Заставить BufferedImage использовать меньше RAM?

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

BufferedImage background
background = ImageIO.read(file)

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

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

Существует ли способ иметь его, хранят изображение в поршне в меньшем формате? Я нахожусь в ситуации, где я имею более слабый на стороне ЦП, чем RAM, таким образом, небольшой хит производительности для возвращения размера объекта изображения вниз к jpeg сжатию определенно стоил бы того.

14
задан Electrons_Ahoy 20 July 2010 в 21:11
поделиться

5 ответов

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

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

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

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

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

Так что вам придется выбирать:

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

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

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

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

[*] Нет НИКАКОГО СЛУЧАЯ, что вы уменьшите изображение с помощью встроенного API Java так же быстро и с таким же качеством, как это делает ImageMagick, для начала.

3
ответ дан 1 December 2019 в 13:21
поделиться

В одном из моих проектов я просто уменьшаю выборку изображения, поскольку оно читается из 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, чтобы отслеживать, какая часть изображения была прочитана на данный момент. Это полезно для отображения индикатора выполнения, если изображение, например, считывается по сети ... Не обязательно, вы можете просто указать null.

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

11
ответ дан 1 December 2019 в 13:21
поделиться

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

1
ответ дан 1 December 2019 в 13:21
поделиться

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

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

2
ответ дан 1 December 2019 в 13:21
поделиться

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

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

int[] pixels = background.getRaster().getPixels(
    0, 
    0, 
    imageBuffer.getWidth(), 
    imageBuffer.getHeight(), 
    (int[]) null
);
0
ответ дан 1 December 2019 в 13:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: