java - 有没有办法使用 Apache POI 将大图像添加到 xls 文件中?

标签 java image png apache-poi

我在我的应用程序中生成了一些相当大的 .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/

相关文章:

java - 使用 JUnit 测试树实现

java - KeyboardFocusManager.setCurrentKeyboardFocusManager() 打破焦点

jquery - 使用jquery,我如何选择div内的所有图像来更改其源

css - img 宽度 100% 显示 : box (css flexbox parent)

html - 背景图像不透明度和 :hover

c++ - 转换透明 png 会使 SDL/OpenGL 崩溃

java - Java多线程性能,创建新对象或者使用synchronized

java - 为 OPTIONS Http 方法禁用 Spring Security

html - 基于图像的链接上的工具提示和自定义光标问题

css - 带有 CSS 和 PNG 的假字体图标