Более быстрая альтернатива ColorConvertOp

У меня есть метод преобразования BufferedImages, который вводит TYPE_CUSTOM в TYPE_INT_RGB. Я использую следующий код, однако мне бы очень хотелось найти более быстрый способ сделать это.

BufferedImage newImg = new BufferedImage(
    src.getWidth(), 
    src.getHeight(), 
    BufferedImage.TYPE_INT_RGB);

ColorConvertOp op = new ColorConvertOp(null);
op.filter(src, newImg);

Он отлично работает, однако он довольно медленный, и мне интересно, есть ли более быстрый способ сделать это преобразование.

ColorModel Before Conversion:

ColorModel: #pixelBits = 24 numComponents = 3 color space = [email protected] transparency = 1 has alpha = false isAlphaPre = false

ColorModel After Conversion:

DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=0

Спасибо!


Update:

Оказалось, что работа с необработанными пиксельными данными была наилучшим способом. Поскольку TYPE_CUSTOM на самом деле RGB-преобразование вручную является простым и примерно на 95% быстрее, чем ColorConvertOp.

public static BufferedImage makeCompatible(BufferedImage img) throws IOException {
    // Allocate the new image
    BufferedImage dstImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);

    // Check if the ColorSpace is RGB and the TransferType is BYTE. 
    // Otherwise this fast method does not work as expected
    ColorModel cm = img.getColorModel();
    if ( cm.getColorSpace().getType() == ColorSpace.TYPE_RGB && img.getRaster().getTransferType() == DataBuffer.TYPE_BYTE ) {
        //Allocate arrays
        int len = img.getWidth()*img.getHeight();
        byte[] src = new byte[len*3];
        int[] dst = new int[len];

        // Read the src image data into the array
        img.getRaster().getDataElements(0, 0, img.getWidth(), img.getHeight(), src);

        // Convert to INT_RGB
        int j = 0;
        for ( int i=0; i<len; i++ ) {
            dst[i] = (((int)src[j++] & 0xFF) << 16) | 
                     (((int)src[j++] & 0xFF) << 8) | 
                     (((int)src[j++] & 0xFF));
        }

        // Set the dst image data
        dstImage.getRaster().setDataElements(0, 0, img.getWidth(), img.getHeight(), dst);

        return dstImage;
    }

    ColorConvertOp op = new ColorConvertOp(null);
    op.filter(img, dstImage);

    return dstImage;
}

Ответ 1

BufferedImages болезненно медленны. У меня есть решение, но я не уверен, что вам это понравится. Самый быстрый способ обработки и преобразования буферизованных изображений - извлечение необработанного массива данных из BufferedImage. Вы делаете это, вызывая buffImg.getRaster() и преобразовывая его в определенный растровый. Затем вызовите raster.getDataStorage(). После того, как вы получите доступ к необработанным данным, можно написать быстрый код обработки изображений без всякой абстракции в BufferedImages, замедляющей его. Этот метод также требует глубокого понимания форматов изображений и некоторой обратной разработки с вашей стороны. Это единственный способ, которым я смог получить код обработки изображений, чтобы он работал достаточно быстро для моих приложений.

Пример:

ByteInterleavedRaster srcRaster = (ByteInterleavedRaster)src.getRaster();
byte srcData[] = srcRaster.getDataStorage();

IntegerInterleavedRaster dstRaster = (IntegerInterleavedRaster)dst.getRaster();
int dstData[] = dstRaster.getDataStorage();

dstData[0] = srcData[0] << 16 | srcData[1] << 8 | srcData[2];

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

Ответ 2

Я нашел рендеринг с использованием Graphics.drawImage() вместо ColorConvertOp в 50 раз быстрее. Я могу только предположить, что drawImage() ускорен GPU.

i.e это очень медленно, например 50 мс для прямоугольников размером 100х200

public void BufferdImage convert(BufferedImage input) {
   BufferedImage output= new BufferedImage(input.getWidht(), input.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CUSTOM_PALETTE);

   ColorConvertOp op = new ColorConvertOp(input.getColorModel().getColorSpace(), 
                                          output.getColorModel().getColorSpace());

   op.filter(input, output);
   return output;
}

i.e однако это регистрирует < 1 мс для тех же входов

public void BufferdImage convert(BufferedImage input) {
   BufferedImage output= new BufferedImage(input.getWidht(), input.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CUSTOM_PALETTE);

   Graphics graphics = output.getGraphics();
   graphics.drawImage(input, 0, 0, null);
   graphics.dispose();
   return output;
}

Ответ 3

Попробовали ли вы предоставить RenderingHints? Нет гарантий, но используя

ColorConvertOp op = new ColorConvertOp(new RenderingHints(
    RenderingHints.KEY_COLOR_RENDERING, 
    RenderingHints.VALUE_COLOR_RENDER_SPEED));

а не null в фрагменте кода может немного ускорить его.

Ответ 4

Я подозреваю, что проблема может заключаться в том, что ColorConvertOp() работает по пикселям (гарантируется "медленный" ).

Q: Возможно ли использовать gc.createCompatibleImage()?

В: Является ли ваш исходный растровый истинный цвет, или он использует цветную карту?

В: В противном случае вы согласитесь написать интерфейс JNI? Либо ваш собственный, пользовательский код C, либо внешнюю библиотеку, такую ​​как ImageMagick?

Ответ 5

Если у вас установлен JAI, вы можете попробовать удалить его, если хотите, или каким-либо другим способом отключить codecLib при загрузке JPEG. В прошлой жизни у меня были подобные проблемы (http://www.java.net/ node/660804), а ColorConvertOp был самым быстрым в то время.

Насколько я помню, основная проблема заключается в том, что Java2D вообще не оптимизирован для изображений TYPE_CUSTOM. Когда вы устанавливаете JAI, он поставляется с кодеком, который имеет декодер, который возвращает TYPE_CUSTOM и используется вместо значения по умолчанию. Список JAI может предоставить дополнительную помощь, это было несколько лет.

Ответ 6

возможно, попробуйте следующее:

Bitmap source = Bitmap.create(width, height, RGB_565);//don't remember exactly...
Canvas c = new Canvas(source);
// then 
c.draw(bitmap, 0, 0);

Тогда исходная растровая карта будет изменена.

Позже вы можете сделать:

onDraw(Canvas canvas){
canvas.draw(source, rectSrs,rectDestination, op);
}

если вы можете управлять всегда повторно использовать растровое изображение, чтобы вы могли получить лучшую производительность. Кроме того, вы можете использовать другие функции холста для рисования растрового изображения