Как конвертировать и писать большие изображения, не вызывая ошибки OOM? - PullRequest
0 голосов
/ 22 сентября 2011

У меня есть изображения, хранящиеся в базе данных в виде ImageIcons, которые я хотел бы использовать на нашей веб-странице, однако для больших изображений у меня возникают исключения из памяти.

Вот как я сейчас это делаю,

[Редактировать] Я расширил свои ImageUtilities, чтобы обеспечить непрозрачный BufferedImage, который упрощает код,

BufferedImage rgbbi = ImageUtilities.toBufferedImage(icon.getImage());

ServletOutputStream out = null;
try {
    // Get the Servlets output stream.
    out = responseSupplier.get().getOutputStream();

    // write image to our piped stream
    ImageIO.write(rgbbi, "jpg", out);

} catch (IOException e1) {
    logger.severe("Exception writing image: " + e1.getMessage());
} finally {
    try {
        out.close();
    } catch (IOException e) {
        logger.info("Error closing output stream, " + e.getMessage());
    }
}

Выдаются следующие исключения:

Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
    at java.awt.image.DataBufferInt.<init>(DataBufferInt.java:41)
    at java.awt.image.Raster.createPackedRaster(Raster.java:458)
    at java.awt.image.DirectColorModel.createCompatibleWritableRaster(DirectColorModel.java:1015)
    at sun.awt.image.ImageRepresentation.createBufferedImage(ImageRepresentation.java:230)
    at sun.awt.image.ImageRepresentation.setPixels(ImageRepresentation.java:484)
    at sun.awt.image.ImageDecoder.setPixels(ImageDecoder.java:120)
    at sun.awt.image.JPEGImageDecoder.sendPixels(JPEGImageDecoder.java:97)
at sun.awt.image.JPEGImageDecoder.readImage(Native Method)
at sun.awt.image.JPEGImageDecoder.produceImage(JPEGImageDecoder.java:119)
at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:246)
at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:172)
at sun.awt.image.ImageFetcher.run(ImageFetcher.java:136)
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
Exception in thread "Image Fetcher 0" java.lang.OutOfMemoryError: Java heap space
...

Есть ли способ, которым я могу переписать это для потоковой передачи вывода ImageIO.write и каким-то образом ограничить его размер буфера?

[Изменить] Я не могу просто увеличить размер кучи, изображения, которые мне нужно обслуживать, находятся в диапазоне 10000x7000 пикселей, в виде байтового массива, который работает (10000px x 7000px x 24bits) 280MB. Я думаю, что это необоснованный размер кучи, выделяемый для преобразования изображений в сервлете.

Пример изображения Большой

Ответы [ 4 ]

1 голос
/ 23 сентября 2011

Как отмечается в комментариях, хранение 10000x7000 изображений в базе данных, как ImageIcons, и подача их через сервлет, пахнет плохим дизайном.Тем не менее, я указываю на эту библиотеку PNGJ (отказ от ответственности: я ее кодировал), которая позволяет читать, записывать изображения в формате PNG последовательно, построчно.Конечно, это будет полезно, только если вы храните свои большие изображения в этом формате.

1 голос
/ 22 сентября 2011

Я предполагаю, что на вашем экране недостаточно пикселей для отображения полного изображения.Поскольку вам, кажется, нужна некомпрессированная версия в ОЗУ для дисплея, вам понадобится ровно столько кучи, сколько предполагает размер изображения.Сказав это, есть много лучших способов.

Я написал свою дипломную работу по эффективному отображению нескольких больших изображений с разрешением до 40000x40000 пикселей одновременно.В итоге мы реализовали LOD с многоуровневым кешем.Это означает, что размер изображения был изменен, и каждый размер был разделен на квадратные куски, в результате чего образовалась пирамида .Нам пришлось немного экспериментировать, чтобы найти оптимальный размер куска.Он варьируется от системы к системе, но можно смело предположить, что он находится где-то между 64x64 и 256x256 пикселей.

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

«Многоуровневый» означает, что фрагменты изображения были загружены в VRAM видеокарты с ОЗУ в качестве кэша L1 и HD в качествеКэш L2 (при условии, что изображение находится в сети), но эта оптимизация может быть чрезмерной в вашем случае.

В общем, на это нужно обратить внимание, пока вы просто просили контроль памяти.Однако, если это крупный проект, внедрение LOD является правильным инструментом для работы.

0 голосов
/ 22 сентября 2011

Больше памяти, кажется, единственный ответ для конвертации, без необходимости писать свои собственные.

Тогда я решил просто не преобразовывать изображения и использовать метод, описанный в этом ответе, чтобы получить тип изображения mime для возможности установки заголовка.

0 голосов
/ 22 сентября 2011

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

Вам нужно сделать это один раз?

Возможно, вы застряли при необходимости написать все это самостоятельно, загрузив файл, обработав «пиксели» и записав его. Это был бы ЛУЧШИЙ способ сделать это, вместо того, чтобы загружать всю вещь, конвертировать (т.е. копировать) ее и записывать. Я не знаю, работают ли такие вещи, как Image Magick, на потоках или изображениях памяти.

Дополнения для AlexR:

Чтобы сделать это ПРАВИЛЬНО, ему нужно декодировать файл в какой-то формат, который можно преобразовать. Например, JPEG делит изображения на 8x8 блоков, сжимает их по отдельности, а затем выводит эти блоки. Во время потоковой передачи сами блоки сжимаются (поэтому, если у вас было 10 черных блоков, вы получаете 1 черный блок со счетом 10).

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

Для простоты, особенно с множеством различных форматов, проще "загрузить все изображение" в память, поработать над его необработанной битовой картой, скопировать эту битовую карту при преобразовании, а затем записать необработанное изображение обратно во что угодно формат (скажем, преобразование цветного JPEG в PNG с серой шкалой).

Для больших изображений, с которыми имеет дело этот человек, это оказывается ОЧЕНЬ дорого с памятью.

Итак, ОПТИМАЛЬНО, он писал бы определенный код для чтения файла по частям, то есть передавал его по потоку, конвертировал каждый маленький бит и возвращал его обратно. Это заняло бы совсем немного памяти, но ему, вероятно, пришлось бы выполнять большую часть работы сам.

Так что, да, он может "просто читать изображение побайтно", но обработка и алгоритмы, скорее всего, будут довольно сложными.

...