You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache POI 3.9.0开发Birt XLSX Emitter如何正确设置图片宽高

问题核心原因

你当前代码的适配逻辑存在4处关键错误,是图片异常的直接诱因:

  • 硬编码图片高度为116pt,注释掉了从imageContent读取实际高度的逻辑,导致高度计算完全不符合预期
  • 宽高缩放逻辑未考虑图片原始宽高比,仅基于给定宽度和单元格宽度计算比例,极易出现拉伸变形
  • ClientAnchor的结束行row2直接设置为和起始行row1相同,跨多行的图片会被强制截断
  • 行高调整逻辑仅修改当前所在行的高度,跨多行场景下总高度完全不足

修复后的核心代码

private void imageData( int type, byte[] data,
         int column, int colSpan, int rowSpan,
        int styleId,ImageData image )
{
    IImageContent imagecontent = image.getImageContent();
    this.cell  = this.row.getCell(column -1);
    
    if (this.cell == null) {
        this.cell = this.row.createCell(column -1);
    }
    this.cell.setCellType(Cell.CELL_TYPE_BLANK);
    String mimeType = imagecontent.getMIMEType();

    if( data != null ) {
        int imageIdx = wb.addPicture(data, imageType);
        Drawing drawing = this.sheet.createDrawingPatriarch();
        int startCol = this.cell.getColumnIndex();
        int startRow = this.cell.getRowIndex();
        int endCol = startCol + colSpan;
        int endRow = startRow + rowSpan;

        // 1. 计算目标单元格区域总可用宽高(单位mm)
        double totalColWidthMM = 0.0;
        for (int i = startCol; i < endCol; i++) {
            totalColWidthMM += widthUnits2Millimetres((short) this.sheet.getColumnWidth(i));
        }
        // 减去左右各2px边距 转mm
        totalColWidthMM -= pixels2Millimetres(4);

        double totalRowHeightPT = 0.0f;
        for (int i = startRow; i < endRow; i++) {
            Row row = this.sheet.getRow(i);
            if (row == null) row = this.sheet.createRow(i);
            totalRowHeightPT += row.getHeightInPoints();
        }
        // 减去上下各2px边距 转pt
        double totalRowHeightMM = points2Millimetres(totalRowHeightPT) - pixels2Millimetres(4);

        // 2. 获取图片原始宽高 计算原始宽高比
        double imgOriginalWidthMM = 0.0;
        double imgOriginalHeightMM = 0.0;
        if(isAbsolute(imagecontent.getWidth())) {
            imgOriginalWidthMM = imagecontent.getWidth().convertTo(DimensionType.UNITS_MM);
            imgOriginalHeightMM = imagecontent.getHeight().convertTo(DimensionType.UNITS_MM);
        } else if(isPixels(imagecontent.getWidth())) {
            imgOriginalWidthMM = pixels2Millimetres(imagecontent.getWidth().getMeasure());
            imgOriginalHeightMM = pixels2Millimetres(imagecontent.getHeight().getMeasure());
        }
        double imgAspectRatio = imgOriginalWidthMM / imgOriginalHeightMM;
        double targetAspectRatio = totalColWidthMM / totalRowHeightMM;

        // 3. 按原始比例缩放图片 适配单元格区域
        double targetImgWidthMM, targetImgHeightMM;
        if (imgAspectRatio > targetAspectRatio) {
            // 图片更宽 以宽度为准适配
            targetImgWidthMM = totalColWidthMM;
            targetImgHeightMM = targetImgWidthMM / imgAspectRatio;
        } else {
            // 图片更高 以高度为准适配
            targetImgHeightMM = totalRowHeightMM;
            targetImgWidthMM = targetImgHeightMM * imgAspectRatio;
        }

        // 4. 如果需要调整行高/列宽(无跨列/跨行的场景)
        if (colSpan == 0) {
            int targetColWidth = poiColumnWidthFromMM(targetImgWidthMM + pixels2Millimetres(4));
            this.sheet.setColumnWidth(startCol, targetColWidth);
            totalColWidthMM = targetImgWidthMM;
        }
        if (rowSpan == 0) {
            float targetRowHeightPT = millimetres2Points(targetImgHeightMM + pixels2Millimetres(4));
            this.cell.getRow().setHeightInPoints(targetRowHeightPT);
            totalRowHeightMM = targetImgHeightMM;
            endRow = startRow + 1;
        }

        // 5. 计算Anchor的偏移量
        int dx2 = (int) (targetImgWidthMM * 36000); // 1mm=36000EMU
        int dy2 = (int) (millimetres2Points(targetImgHeightMM) * XSSFShape.EMU_PER_POINT);

        ClientAnchor anchor = this.wb.getCreationHelper().createClientAnchor();
        anchor.setCol1(startCol);
        anchor.setRow1(startRow);
        anchor.setCol2(startCol);
        anchor.setRow2(startRow);
        // 边距偏移
        anchor.setDx1(2 * XSSFShape.EMU_PER_PIXEL);
        anchor.setDy1(2 * XSSFShape.EMU_PER_PIXEL);
        anchor.setDx2(dx2 + 2 * XSSFShape.EMU_PER_PIXEL);
        anchor.setDy2(dy2 + 2 * XSSFShape.EMU_PER_PIXEL);
        
        anchor.setAnchorType(ClientAnchor.MOVE_DONT_RESIZE);
        drawing.createPicture(anchor, imageIdx);
    }
}

// 补充需要的工具方法
private double points2Millimetres(float pt) {
    return pt * 0.352778;
}
private float millimetres2Points(double mm) {
    return (float) (mm / 0.352778);
}
private int poiColumnWidthFromMM(double mm) {
    // POI列宽单位是1/256个字符宽度 这里按默认字体换算即可 可根据实际字体调整
    return (int) (mm * 37.7953 / 0.352778);
}

关键修改说明

  • 优先读取图片原始宽高计算比例,完全删除硬编码116pt的逻辑,保证原始比例不被拉伸
  • 支持跨列跨行场景的总宽高计算,适配合并单元格的图片插入需求
  • 动态判断适配逻辑:宽图按宽适配,高图按高适配,自动保留边距
  • 修正Anchor偏移计算逻辑,避免图片被截断或者溢出单元格

内容的提问来源于stack exchange,提问作者user3458271

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.08 19:48:02