2011-06-12 15 views
10

在Windows上運行的某些Java代碼中,我正在從磁盤讀取一些大的RGB數據塊,並希望儘快將其顯示在屏幕上。 RGB數據每個通道8位,不含任何字母。目前我有如下代碼來創建BufferedImage。在BufferedImage中快速加載和繪製RGB數據

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); 
} 

問題是,將其渲染到屏幕的性能很慢。大約250 - 300毫秒。我已經閱讀過,爲了獲得最佳性能,您需要在與屏幕兼容的BufferedImage中顯示。爲此,我將從上述方法返回的緩衝圖像傳遞給像這樣的方法。

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; 
} 

這種方法基本上是將其轉換從RGB到ARGB在Windows上,它真的加速了顯示,但這種方法大約需要300毫秒爲1600×1200的RGB數據塊。所以現在我基本上把繪圖問題的性能問題轉化爲轉換問題。

300ms與從磁盤加載RGB數據所用的時間大致相同。我想我可以做得更快。

有沒有更好的方法可以做轉換?或者,如果我預先修改了RGB數據並添加了一個Alpha通道,它會有幫助嗎?如果是這樣,我的Raster和ColorModel會是什麼樣子。此外,由於我的RGB數據不包含透明度,我可以通過使用預乘alpha或其他東西來獲得任何性能改進?

對不起,有點在這個ColorModel,光柵的東西有點失落。

謝謝!

回答

11

玩了這個之後,我有一個體面的答案,適用於Windows,如果當前的圖形配置使用ARGB整數打包柵格。

我所做的是先創建兼容的BufferedImage,然後手動將我的RGB字節數組轉換爲ARGB int數組。然後,我從兼容的BufferedImage中獲取Raster,並將其ARGB整數寫入其中。這要快得多。

我還有一個類,用於檢查兼容的BufferedImage是否處於我期望的格式,如果不是,則默認使用較慢的方法。

這是班級。希望它可以幫助你。

/** 
* 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 
     } 
    } 

} 
+0

2意見建議:1)更改源文件中的'Compatabile' - >'Compatible'。 2)將您的答案標記爲「正確」。 – 2011-06-13 15:33:47

+1

+1 ...我想提及的是,ColorModel的智能延遲確實減慢了一切(高達** 3在一些OS X版本**上的幅度,是的,超過1000倍的速度**!**),並提供基本上什麼都沒有(嚴肅地說,有多少專業的圖像/視頻處理編輯用Java編寫出來?這是正確的,零......)與直接使用* int [] *進行比較。這是*「zomg我們擁有物品,讓我們無緣無故地將所有東西過度包裝和包裝」*。 * int [] *對勝利進行原始操作:當速度是一個問題時,您必須這樣做。 – SyntaxT3rr0r 2011-06-13 16:07:00

+0

是的,我的加速時間從300ms左右降到15ms以下。 – awinbra 2011-06-13 16:21:55

19

我意識到這是一個非常古老的問題,我只是張貼這對任何人就這個問題尋找更多的選擇誰可能絆倒。我最近遇到了一個問題,那就是我試圖佔用一個大的(720p)RGB字節[]並將其渲染到BufferedImage。我用的是原始的實現看起來是這樣的(這裏的簡化):

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); 
} 

即使像創建BufferedImageComponentSampleModel一次和重用他們優化,呼籲BufferedImagesetData的最後一步依然在採取對50-60毫秒的順序,這是不可接受的。

我最終意識到的是,至少在我的場景中,您可以直接寫入BufferedImage的備份字節數組,並繞過大部分中間處理(假設圖像的備份元數據已經正確) 。所以我改變了我的代碼看起來像這樣:

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); 
} 

如果這樣做的,我的表現了大約爲20因素提高我現在處理在3-5毫秒同一幀,而不是50-60毫秒。

這可能不適用於所有情況,但我認爲我會分享以防其他人發現它有用。

+0

非常好!對我來說工作得很好,我準備繼承'BufferedImage'來讓這個笨笨的'setData()'工作得更快,你救了我,謝謝!:) – Matthieu 2013-01-02 18:26:36

+0

I很困惑......這裏究竟發生了什麼?那'System.arraycopy()似乎覆蓋了'imgData'。難道不是相反嗎? – 2014-09-01 13:09:08

+0

自從我發佈這個版本以來,已經有一段時間了,所以如果缺少它,請諒解......'imgData'是對實際的'byte []'(作爲BufferedImage的WritableRaster的成員變量存儲)的引用,用於實際通過'BufferedImage'類渲染圖像。 'frame'是我想要通過BufferedImage渲染的'byte []'。因此,System.arraycopy(...)將我構建的圖像(包含在'frame'中)複製到'BufferedImage'的腸腔內的背景'byte []'。現在,當呈現'BufferedImage'時,它使用'frame'中的數據完成,我將其複製到'BufferedImage'本身 – 2014-09-01 22:42:14