我在我的应用程序中生成了一些相当大的 .png
图像(例如,40000x10000 像素)。为了避免过多的内存使用,我利用了 ImageIO 逐行写入的事实,因此我只需要一次在内存中保留 40000 个像素(实际上我保留了更多 - 100 行,但是仍然不是完整的图像)。
然后,我使用以下代码将图像添加到 POI 工作簿:
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, "png", baos);
int pictureIdx = workbook.addPicture(baos.toByteArray, Workbook.PICTURE_TYPE_PNG);
CreationHelper helper = workbook.getCreationHelper();
Sheet sh = workbook.createSheet("Picture");
Drawing patriarch = sh.createDrawingPatriarch();
ClientAnchort anchor = helper.createClientAnchor();
anchor.setCol1(0);
anchor.setRow1(0);
Picture picture = patriarch.createPicture(anchor, pictureIdx);
picture.resize(); // here's the trouble :(
picture.resize()
调用是这里最大的问题。为了确定“首选”图像大小,它会尝试将整个图像加载到内存中 - 我们的示例图像在未压缩时大约需要 1.6GB 内存。尝试在某些用户机器上分配1.6GB内存会导致OOM异常。
如果我省略对 picture.resize()
的调用,则图像不会显示在生成的 xls 中 - 根据大小判断,它位于文件内部,但不可见在表中。
是否有某种方法可以跳过内存中整个图像的加载?也许我可以手动为图片提供首选图像尺寸?
最佳答案
我找到了解决此问题的方法 - 基本上是从 POI 源复制代码并删除对 getImageDimension()
的调用。请注意,此代码对 HSSF 内部结构假设太多,并且可能会在更新期间中断。
以下是我的解决方案(scala 语法):
/** Applies resizing to HSSFPicture - without loading the whole image into memory. */
private def safeResize(pic: HSSFPicture, width: Double, height: Double, sheet: HSSFSheet) {
val anchor = pic.getAnchor.asInstanceOf[HSSFClientAnchor]
anchor.setAnchorType(2)
val pref: HSSFClientAnchor = {
val PX_DEFAULT = 32f
val PX_MODIFIED = 36.56f
val PX_ROW = 15
def getPixelWidth(column: Int): Float = {
val default = sheet.getDefaultColumnWidth*256
val cw = sheet.getColumnWidth(column)
if (default == cw) PX_DEFAULT else PX_MODIFIED
}
def getColumnWidthInPixels(column: Int): Float = {
val cw = sheet.getColumnWidth(column)
cw / getPixelWidth(column)
}
def getRowHeightInPixels(i: Int): Float = {
val row = sheet.getRow(i)
val height: Float = if (row != null) row.getHeight else sheet.getDefaultRowHeight
height / PX_ROW
}
var w = 0f
//space in the leftmost cell
w += getColumnWidthInPixels(anchor.getCol1)*(1 - anchor.getDx1.toFloat/1024)
var col2 = (anchor.getCol1 + 1).toShort
var dx2 = 0
while(w < width){
w += getColumnWidthInPixels(col2)
col2 = (col2 + 1).toShort
}
if (w > width) {
//calculate dx2, offset in the rightmost cell
col2 = (col2 - 1).toShort
val cw = getColumnWidthInPixels(col2)
val delta = w - width
dx2 = ((cw-delta)/cw*1024).toInt
}
anchor.setCol2(col2)
anchor.setDx2(dx2)
var h = 0f
h += (1 - anchor.getDy1.toFloat/256)* getRowHeightInPixels(anchor.getRow1)
var row2 = anchor.getRow1 + 1
var dy2 = 0
while (h < height){
h += getRowHeightInPixels(row2)
row2+=1
}
if(h > height) {
row2-=1
val ch = getRowHeightInPixels(row2)
val delta = h - height
dy2 = ((ch-delta)/ch*256).toInt
}
anchor.setRow2(row2)
anchor.setDy2(dy2)
anchor
}
val row2 = anchor.getRow1 + (pref.getRow2 - pref.getRow1)
val col2 = anchor.getCol1 + (pref.getCol2 - pref.getCol1)
anchor.setCol2(col2.toShort)
anchor.setDx1(0)
anchor.setDx2(pref.getDx2)
anchor.setRow2(row2)
anchor.setDy1(0)
anchor.setDy2(pref.getDy2)
}
关于java - 有没有办法使用 Apache POI 将大图像添加到 xls 文件中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18226742/