0

我在我的应用程序中生成了一些相当大.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 中 - 它在文件中,从大小来看,但它在表中不可见。

有没有办法跳过将整个图像加载到内存中?也许我可以手动为图片提供首选图像尺寸?

4

1 回答 1

1

我找到了解决这个问题的方法——基本上是通过从 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)
}
于 2013-08-15T07:42:53.293 回答