2011-09-22 49 views
0

我有ImageIcons的形式存儲在數據庫中的圖像,我想要爲我們的網頁提供服務,但是對於大圖像,我將收回內存異常。如何轉換和寫入大圖像而不會導致OOM錯誤?

這是我現在怎麼做,

[編輯]我擴大了我的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。我認爲這是一個不合理的堆大小分配給一個servlet中的圖像轉換。

的示例圖像Large

+2

不好意思問這個顯而易見的問題,但是您是否試圖增加堆大小或將圖像切分爲更小的塊? – kostja

+0

@kostja我的圖像的一個例子是10368x6912像素大,壓縮其5Mb,未壓縮,我認爲在2Gb以下。另外,我怎樣才能剁一個ImageIcon? – Andrew

+1

所以你會發送2GB的數據從服務器?如果你有這種帶寬容量,你可以承擔更多的RAM和Java堆。 –

回答

1

正如評論中所指出的,要將10000x7000圖像存儲在數據庫中,作爲ImageIcons,並通過servlet來提供它們,會有異味。 儘管如此,我指出這PNGJ庫(免責聲明:我編碼它),允許您按順序逐行讀取/寫入PNG圖像。當然,這隻有在你以這種格式存儲你的大圖時纔有用。

+0

感謝您的相關回復。雖然不是我所用的解決方案,但它回答了我認爲最好的問題。沒有建立這樣做的方式,所以第三方庫或自己做,似乎是最好的方式。 – Andrew

+0

@leonbloy:你知道用於寫入JPEG文件的類似庫嗎?由於典型的16x16的JPEG塊大小,應該可以一次寫16行。無論如何,PNGJ似乎是一個不錯的圖書館。 2009年編寫我的項目編寫PNG文件類似的代碼。 – Robert

1

我假設你沒有你的屏幕上像素足以顯示一個完整的圖像。由於您似乎需要在RAM中使用未壓縮版本的顯示器,因此您將需要與圖像大小完全一樣多的堆。話雖如此,還有很多更好的方法。

我寫了我的學士論文,有效地同時顯示多達40000x40000像素的多個大型圖像。我們最終實現了具有多級緩存的LOD。這意味着圖像被調整大小,每個大小被切成方塊,導致image pyramid。爲了找到最佳塊大小,我們必須稍微修改一下。它隨系統而異,但可以安全地假設爲64x64和256x256像素之間。

接下來的事情是實現一個上傳正確chuncks的調度算法,以保持texel:pixel的比例爲1:1。爲了達到更好的質量,我們在金字塔的切片之間使用了三線性插值。

「多級」意味着圖像塊被上傳到顯卡的VRAM作爲L1緩存,HD作爲L2緩存(假設圖像在網絡上),但這種優化可能會過度在你的情況。總而言之,這是很多需要考慮的事情,而你只是要求內存控制。如果這是一個重大項目,實施LOD是這項工作的正確工具。

+0

大圖像在'ImageIO.write'調用中被轉換爲jpg圖像,這會產生合理大小的最終輸出。這是在這個問題之間創建的原始圖像。 – Andrew

0

你不能使用你正在使用的內置類來做到這一點,因爲它們被設計用於位圖批發。通過像Image Magick這樣的東西(或者現在的任何東西),你可能會更好地運行java。

你只需要這樣做一次?

你可能會被卡住,不得不自己寫所有這些,加載文件,處理「像素」並寫出來。這將是完成它的最佳方式,而不是加載整個事物,轉換(即複製)它並寫出來。我不知道Image Magick是否適用於流或內存映像。

附錄爲AlexR:

正確地做到這一點,他需要在文件中的一些可流格式進行解碼。例如,JPEG將圖像分成8x8塊,分別進行壓縮,然後將這些塊流出。當它將數據流傳送出去時,數據塊本身被壓縮(所以如果你有10個黑塊,你會得到1個黑塊,計數爲10)。

原始位圖比字節塊多一點,對於帶有alpha的高顏色空間,它是4個字節(紅色,綠色,藍色和Alpha各一個)。大多數色彩空間轉換都發生在像素級別。其他更復雜的濾鏡對像素和周圍像素起作用(高斯模糊就是一個簡單的例子)。

爲了簡單起見,特別是有很多不同的格式,將「加載整個圖像」存入內存,處理其原始位圖,在轉換它的同時複製該位圖,然後將原始圖像寫回任何格式(例如,將彩色JPEG轉換爲灰度PNG)。

對於大型圖像,就像這個人正在處理的那樣,它在內存中碰巧非常昂貴。

所以,最好的是,他會編寫特定的代碼來讀取文件的部分內容,將它流入,轉換每一個小小的一點,然後將其再次傳回。這隻需要很少的記憶,但他可能必須自己完成大部分工作。

所以,是的,他可以「只是逐字節讀取圖像」,但是處理和算法可能會相當複雜。

+0

這不是他所需要的,我想。注意'ImageUtilities.toBufferedTransparentImage' - 這是導致圖像透明的代碼,所以我認爲他不能從文件逐字節讀取圖像並將其寫入輸出流。 – AlexR

0

更多的記憶似乎是轉換的唯一答案,沒有我必須寫我自己的。

我的解決方案是隻是不轉換圖像,並使用this答案中描述的方法來檢索圖像MIME類型,以便能夠設置標題。

相關問題