Чтение прогрессивно кодированного 9000x9000 JPEG в Java занимает 1 минуту

При использовании javax.imageio.ImageIO для загрузки JPEG с большим разрешением (9000x9000) с диска это занимает более 1 минуты в моем приложении scala. Я попытался создать проект только для Java, но он по-прежнему занимает слишком много времени - около 30 секунд.

Вот как я загружаю изображение:

File file = new File("/Users/the21st/slow2.jpg");
BufferedImage image = ImageIO.read(file);

Есть ли способ повысить производительность при чтении прогрессивно закодированных JPEG с большим разрешением в Java?

Изображение, о котором идет речь, этот (модераторы, пожалуйста, не повторно загружайте на другой сайт хостинга, чтобы кодирование/качество не было" t)

Ответ 1

Хорошо, здесь мои выводы до сих пор (и, честно говоря, они немного беспокоятся...).

Использование стандартного JPEG-плагина для ImageIO в комплекте с Oracle JRE:

BufferedImage image = ImageIO.read(file); 

Считывает изображение примерно на 18 секунд на моем компьютере (MacBookPro/2.8 ГГц i7).

Используя мой JPEG-плагин для ImageIO, который использует несколько иной путь кода (т.е. вы, вероятно, можете получить те же результаты, получив ImageReader и вызывая метод readRaster(), затем создавая из него BufferedImage. Код является нетривиальным, поэтому, пожалуйста, обратитесь к странице проекта, если вам нравится код):

BufferedImage image = ImageIO.read(file); 

Считывает изображение примерно на 8 секунд на моем компьютере.

Используя мой класс BufferedImageFactory и AWT Toolkit:

BufferedImage image = new BufferedImageFactory(Toolkit.getDefaultToolkit().createImage(file.getAbsolutePat‌​h())).getBufferedImage();

Считывает изображение в ~ 2,5 секунды на моем компьютере.

Использование устаревшего класса JPEGImageDecoder из sun.awt.codec:

BufferedImage image = new JPEGImageDecoderImpl(new FileInputStream(file)).decodeAsBufferedImage();

Считывает изображение на ~ 1,7 секунды на моем компьютере.

Таким образом, это означает, что мы должны иметь возможность читать это изображение менее чем за 2 секунды даже на Java. В этом случае производительность от JPEGImageReader просто смешна, и мне очень нравится знать, почему. Как уже упоминалось, это похоже на прогрессивное декодирование, но все же оно должно быть лучше этого.

Update:

Просто для удовольствия, я создал быстрый плагин PoC ImageReader, поддерживаемый LibJPEG-Turbo Java API. Он еще не очень сложный, но он позволяет использовать код как:

BufferedImage image = ImageIO.read(file); 

Чтобы прочитать изображение в < 1,5 секунды на моем компьютере.

PS: Я использовал для обертки ImageIO для JMagick (аналогично коду, указанному @Jordan Doyle, но это позволит вам программировать против API ImageIO), однако я остановился, поскольку это было слишком много работы. Возможно, мне придется пересмотреть... По крайней мере, стоит проверить его решение, если вы не возражаете, полагаясь на установку JNI/native кода.

Ответ 2

Одна быстрая альтернатива ImageIO - ImageMagick, существуют различные оболочки для взаимодействия ImageMagick через Java, такие как JMagick

Чтобы получить BufferedImage из JMagick, вы должны сначала получить экземпляр MagickImage, который можно сделать следующим образом:

ImageInfo info = new ImageInfo(pathToImage);
MagickImage image = new MagickImage(info);

Теперь вы можете использовать метод, предоставленный нашим собственным Jacob Nordfalk 8 лет назад, чтобы прочитать изображение в BufferedImage здесь

public static BufferedImage magickImageToBufferedImage(MagickImage magickImage) throws Exception
{
    Dimension  dim = magickImage.getDimension();
    int size = dim.width * dim.height;
    byte[] pixels = new byte[size * 3];

    magickImage.dispatchImage(0, 0, dim.width, dim.height, "RGB", pixels);

    BufferedImage bimage = createInterleavedRGBImage(dim.width, dim.height, pixels);

    ColorModel cm = bimage.getColorModel();
    Raster raster = bimage.getData();
    WritableRaster writableRaster = null;

    writableRaster = (raster instanceof WritableRaster) ? (WritableRaster) raster : raster.createCompatibleWritableRaster();

    BufferedImage bufferedImage = new BufferedImage(cm, writableRaster, false, null);

    return bufferedImage;
}

Затем метод createInterleavedRGBImage:

public static BufferedImage createInterleavedRGBImage(int imageWidth, int imageHeight, byte data[])
{
    int[] numBits = new int[3];
    int[] bandoffsets = new int[3];

    for (int i = 0; i < 3; i++) {
        numBits[i] = 8;
        bandoffsets[i] = i;
    }

    ComponentColorModel ccm = new ComponentColorModel(
        ColorSpace.getInstance(ColorSpace.CS_sRGB),
        numBits,
        false,
        false, //Alpha pre-multiplied
        Transparency.OPAQUE,
        DataBuffer.TYPE_BYTE
    );

    PixelInterleavedSampleModel csm = new PixelInterleavedSampleModel(
        DataBuffer.TYPE_BYTE,
        imageWidth,
        imageHeight,
        3, //Pixel stride
        imageWidth * 3, // Scanline stride
        bandoffsets
    );

    DataBuffer dataBuf = new DataBufferByte(data, imageWidth * imageHeight * 3);
    WritableRaster wr = Raster.createWritableRaster(csm, dataBuf, new Point(0, 0));
    return new BufferedImage(ccm, wr, false, null);
}