Szybkie Å‚adowanie i rysowanie danych RGB w BufferedImage

W jakimś kodzie Javy działającym pod Windows, odczytuję z dysku kilka dużych bloków danych RGB i chcę to jak najszybciej wyświetlić na ekranie. Dane RGB to 8 bitów na kanał bez żadnej Alfy. Obecnie mam kod podobny do poniższego, aby utworzyć BufferedImage.

BufferedImage getBufferedImage(File file, int width, int height) {

    byte[] rgbData = readRGBFromFile(file);

    WritableRaster raster = Raster.createInterleavedRaster(
        rgbData, width, height, 
        width * 3, // scanlineStride
        3, // pixelStride
        new int[]{0, 1, 2}, // bandOffsets
        null);

    ColorModel colorModel = new ComponentColorModel(
        ColorSpace.getInstance(ColorSpace.CS_sRGB), 
        new int[]{8, 8, 8}, // bits
        false, // hasAlpha
        false, // isPreMultiplied
        ComponentColorModel.OPAQUE, 
        DataBuffer.TYPE_BYTE);

    return new BufferedImage(colorModel, raster, false, null);
}

Problem polega na tym, że wydajność renderowania tego na ekranie jest dość powolna. Około 250-300 ms. czytałem, że aby uzyskać najlepszą wydajność trzeba wyświetlać w buforze, który jest kompatybilny z ekranem. Aby to zrobić, przekazuję buforowany obraz zwrócony z powyższej metody do metody takiej jak ta.

BufferedImage createCompatibleImage(BufferedImage image)
{
    GraphicsConfiguration gc = GraphicsEnvironment.
        getLocalGraphicsEnvironment().
        getDefaultScreenDevice().
        getDefaultConfiguration();

    BufferedImage newImage = gc.createCompatibleImage(
        image.getWidth(), 
        image.getHeight(), 
        Transparency.TRANSLUCENT);

    Graphics2D g = newImage.createGraphics();
    g.drawImage(image, 0, 0, null);
    g.dispose();

    return newImage;
}

Ta metoda zasadniczo konwertuje go z RGB NA ARGB w systemie Windows i naprawdę przyspiesza wyświetlanie, ale ta metoda zajmuje ~300 ms dla bloku danych 1600 x 1200 RGB. Więc teraz w zasadzie zamieniłem wydajność problemu z rysowaniem na problem z konwersją.

300ms to mniej więcej tyle samo czasu, ile potrzeba do załadowania danych RGB z dysku. Ja bym myślę, że mógłbym zrobić coś szybciej.

Czy jest lepszy sposób na konwersję? A może pomogłoby, gdybym wcześniej zmodyfikował dane RGB i sam dodał kanał alfa? Jeśli tak, jak wyglądałby mój Raster i ColorModel. Ponadto, ponieważ moje dane RGB nie zawierają przezroczystości, Czy Mogę uzyskać jakieś ulepszenia wydajności za pomocą wstępnie zwielokrotnionej Alfy lub czegoś takiego? Przepraszam, trochę się pogubiłem w tym Colormodelu, rastrze. Dzięki!
Author: awinbra, 2011-06-12

2 answers

Zdaję sobie sprawę, że to naprawdę stare pytanie, po prostu zamieszczam to dla każdego, kto może natknąć się na to pytanie, szukając więcej opcji. Ostatnio miałem problem, w którym próbowałem pobrać duży (720p) bajt RGB [] i wyrenderować go do BufferedImage. Oryginalna implementacja, której używałem, wyglądała mniej więcej tak (uproszczona tutaj):

public void processFrame(byte[] frame, int width, int height)
{
   DataBuffer videoBuffer = new DataBufferByte(frame,frame.length);
   BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
   ComponentSampleModel sampleModel = new ComponentSampleModel(DataBuffer.TYPE_BYTE,width,height,3,width*3,new int[] {2,1,0});
   Raster raster = Raster.createRaster(sampleModel,videoBuffer,null);
   currentImage.setData(raster);
}

Nawet z optymalizacjami, takimi jak tworzenie BufferedImage i ComponentSampleModel raz i ponowne ich użycie, ostatni krok wywołania {[5] } na BufferedImage był nadal przyjmowanie rzędu 50-60 milisekund, co jest niedopuszczalne.

W końcu zdałem sobie sprawę, że przynajmniej w moim scenariuszu można zapisać bezpośrednio do tablicy bajtów nośnych BufferedImage i ominąć większość przetwarzania pośredniego (zakładając, że metadane nośników dla obrazu są już poprawne). Więc zmieniłem mój kod, aby wyglądał tak:

public void processFrame(byte[] frame, int width, int height)
{
   BufferedImage currentImage = new BufferedImage(width,height,BufferedImage.TYPE_3BYTE_BGR);
   byte[] imgData = ((DataBufferByte)currentImage.getRaster().getDataBuffer()).getData();
   System.arraycopy(frame,0,imgData,0,frame.length);
}
Po prostu robiąc to, moja wydajność poprawiła się o około 20 razy. Teraz przetwarzam te same klatki W 3-5 milisekund zamiast 50-60 milisekund.

Może to nie dotyczy wszystkich przypadków, ale pomyślałem, że podzielę się, gdyby ktoś inny uznał to za przydatne.

 20
Author: Brent Writes Code,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-21 20:25:11

Po zabawie z tym mam przyzwoitą odpowiedź, która działa na Windows, Jeśli bieżąca konfiguracja Grafiki jest za pomocą ARGB integer packed rasters.

Najpierw tworzę kompatybilny BufferedImage, a następnie ręcznie konwertuję moją tablicę bajtów RGB na tablicę ARGB int. Następnie dostaję Raster z kompatybilnego BufferedImage i zapisuję do niego moje ARGB ints. To jest o wiele szybsze.

Mam również klasę, która sprawdza, czy zgodny BufferedImage jest w formacie I spodziewaj się, jeśli nie jest to domyślnie starsze wolniejsze podejście.

Oto Klasa. Mam nadzieję, że ci to pomoże.

/**
 * This class can read chunks of RGB image data out of a file and return a BufferedImage.
 * It may use an optimized technique for loading images that relies on assumptions about the 
 * default image format on Windows.
 */
public class RGBImageLoader
{
    private byte[] tempBuffer_;
    private boolean fastLoading_;

    public RGBImageLoader()
    {
        fastLoading_ = canUseFastLoadingTechnique();
    }

    private boolean canUseFastLoadingTechnique()
    {
        // Create an image that's compatible with the screen
        GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        BufferedImage image = gc.createCompatibleImage(100, 100, Transparency.TRANSLUCENT);

        // On windows this should be an ARGB integer packed raster. If it is then we can 
        // use our optimization technique

        if(image.getType() != BufferedImage.TYPE_INT_ARGB)
            return false;

        WritableRaster raster = image.getRaster();

        if(!(raster instanceof IntegerInterleavedRaster))
            return false;

        if(!(raster.getDataBuffer() instanceof DataBufferInt))
            return false;

        if(!(image.getColorModel() instanceof DirectColorModel))
            return false;

        DirectColorModel colorModel = (DirectColorModel) image.getColorModel();

        if(!(colorModel.getColorSpace() instanceof ICC_ColorSpace) ||
             colorModel.getNumComponents() != 4 ||
             colorModel.getAlphaMask() != 0xff000000 ||
             colorModel.getRedMask() != 0xff0000 ||
             colorModel.getGreenMask() != 0xff00 ||
             colorModel.getBlueMask() != 0xff)
            return false;

        if(raster.getNumBands() != 4 ||
           raster.getNumDataElements() != 1 ||
           !(raster.getSampleModel() instanceof SinglePixelPackedSampleModel))
            return false;

        return true;
    }

    public BufferedImage loadImage(File file, int width, int height, long imageOffset) throws IOException
    {
        if(fastLoading_)
            return loadImageUsingFastTechnique(file, width, height, imageOffset);
        else
            return loadImageUsingCompatibleTechnique(file, width, height, imageOffset);
    }

    private BufferedImage loadImageUsingFastTechnique(File file, int width, int height, long imageOffset) throws IOException
    {
        int sizeBytes = width * height * 3;

        // Make sure buffer is big enough
        if(tempBuffer_ == null || tempBuffer_.length < sizeBytes)
            tempBuffer_ = new byte[sizeBytes];

        RandomAccessFile raf = null;
        try
        {
            raf = new RandomAccessFile(file, "r");

            raf.seek(imageOffset);

            int bytesRead = raf.read(tempBuffer_, 0, sizeBytes);
            if (bytesRead != sizeBytes)
                throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);

            GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
            BufferedImage image = gc.createCompatibleImage(width, height, Transparency.TRANSLUCENT);
            WritableRaster raster = image.getRaster();
            DataBufferInt dataBuffer = (DataBufferInt) raster.getDataBuffer();

            addAlphaChannel(tempBuffer_, sizeBytes, dataBuffer.getData());

            return image;
        }
        finally
        {
            try
            {
                if(raf != null)
                raf.close();
            }
            catch(Exception ex)
            {
            }
        }
    }

    private BufferedImage loadImageUsingCompatibleTechnique(File file, int width, int height, long imageOffset) throws IOException
    {
        int sizeBytes = width * height * 3;

        RandomAccessFile raf = null;
        try
        {
            raf = new RandomAccessFile(file, "r");

            // Lets navigate to the offset
            raf.seek(imageOffset);

            DataBufferByte dataBuffer = new DataBufferByte(sizeBytes);
            byte[] bytes = dataBuffer.getData();

            int bytesRead = raf.read(bytes, 0, sizeBytes);
            if (bytesRead != sizeBytes)
                throw new IOException("Invalid byte count. Should be " + sizeBytes + " not " + bytesRead);

            WritableRaster raster = Raster.createInterleavedRaster(dataBuffer, // dataBuffer
                            width, // width
                            height, // height
                            width * 3, // scanlineStride
                            3, // pixelStride
                            new int[]{0, 1, 2}, // bandOffsets
                            null); // location

            ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), // ColorSpace
                            new int[]{8, 8, 8}, // bits
                            false, // hasAlpha
                            false, // isPreMultiplied
                            ComponentColorModel.OPAQUE, DataBuffer.TYPE_BYTE);

            BufferedImage loadImage = new BufferedImage(colorModel, raster, false, null);

            // Convert it into a buffered image that's compatible with the current screen.
            // Not ideal creating this image twice....
            BufferedImage image = createCompatibleImage(loadImage);

            return image;
        }
        finally
        {
            try
            {
                if(raf != null)
                raf.close();
            }
            catch(Exception ex)
            {
            }
        }
    }

    private BufferedImage createCompatibleImage(BufferedImage image)
    {
        GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();

        BufferedImage newImage = gc.createCompatibleImage(image.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);

        Graphics2D g = newImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();

        return newImage;
    }


    private void addAlphaChannel(byte[] rgbBytes, int bytesLen, int[] argbInts)
    {
        for(int i=0, j=0; i<bytesLen; i+=3, j++)
        {
            argbInts[j] = ((byte) 0xff) << 24 |                 // Alpha
                        (rgbBytes[i] << 16) & (0xff0000) |      // Red
                        (rgbBytes[i+1] << 8) & (0xff00) |       // Green
                        (rgbBytes[i+2]) & (0xff);               // Blue
        }
    }

}
 11
Author: awinbra,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-13 15:57:28