如何将多个图像添加到 XSSFGroupShape

How to add multiple images to XSSFGroupShape

正在尝试向 XSSFGroupShape 添加多张图片。我尝试了以下(为简单起见进行了修改):

    import lombok.Getter;
    import lombok.Setter;
    import lombok.experimental.Accessors;

    private void addImagesToGroupShape(Sheet sheet, List<XSSFGroupImage> groupImages) throws Exception{
        // XSSFGroupShape dimension
        int dx1 = 180975;
        int dy1 = 409575;
        int dx2 = 219075;
        int dy2 = 638175;
        int row1 = 9;
        int col1 = 8;
        int row2 = 38;
        int col2 = 19;
        XSSFDrawing drawing = (XSSFDrawing) sheet.createDrawingPatriarch();
        XSSFClientAnchor anchor = drawing.createAnchor(dx1, dy1, dx2, dy2, col1, row1, col2, row2);
        XSSFShapeGroup shapeGroup = drawing.createGroup(anchor);
        
        // XSSFGroupImage is a custom class
        // Included below
        for (XSSFGroupImage groupImage : groupImages) {
            byte[] imageData = Base64.getDecoder().decode(groupImage.data().getBytes());
            
            int picType = 1;
            switch (groupImage.extension()) {
            case "jpeg":
                picType = Workbook.PICTURE_TYPE_JPEG;
                break;
            case "png":
                picType = Workbook.PICTURE_TYPE_PNG;
                break;
            case "emf":
                picType = Workbook.PICTURE_TYPE_EMF;
                break;
            default:
                var msg = "Extension not supported";
                throw new Exception(msg);
            }

            int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType);
           
            // shapeGroup.createPicture(anchor, pictureIndex);

            CTPicture ctPic = shapeGroup.getCTGroupShape().insertNewPic(pictureIndex);

            CTTransform2D ct2D = ctPic.addNewSpPr().addNewXfrm();
            ct2D.addNewOff();
            ct2D.addNewExt();

            ct2D.getOff().setX(groupImage.offsetX());
            ct2D.getOff().setY(groupImage.offsetY());
            ct2D.getExt().setCx(Long.parseLong(groupImage.extentsCx()));
            ct2D.getExt().setCy(Long.parseLong(groupImage.extentsCy()));

            XSSFChildAnchor childAnchor = new XSSFChildAnchor(Integer.parseInt(groupImage.offsetX()),
                    Integer.parseInt(groupImage.offsetY()), Integer.parseInt(groupImage.extentsCx()),
                    Integer.parseInt(groupImage.extentsCy()));

            // shapeGroup.getCTGroupShape().addNewPic();
            // shapeGroup.createPicture(childAnchor, picIndex);

            // ctPic.addNewBlipFill().addNewBlip().setEmbed("");
        }
    }

    @Accessors(fluent = true)
    public class XSSFGroupImage {
        @Getter
        @Setter
        private String data;
        @Getter
        @Setter
        private String extension;
        @Getter
        @Setter
        private String offsetX;
        @Getter
        @Setter
        private String offsetY;
        @Getter
        @Setter
        private String extentsCx;
        @Getter
        @Setter
        private String extentsCy;
    }

Added images outside group and .

我了解没有 anchor 就无法附加图像这一事实。但是,我找不到在 XSSFGroupShape 中添加 XSSFChildAnchor 的方法,该方法允许设置 CTTransform2D 对象的属性。

我还尝试使用 int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType) 将图像添加到 Workbook,并使用该索引使用 shapeGroup.getCTGroupShape().insertNewPic(pictureIndex) 设置 CTPicture。但是,通过这样做,将指向该图像的 relationId 未被设置,导致没有图像被显示。

Image of desired output

XSSFShapeGroup 直到现在(apache 5.0.0 2021 年 5 月)才完成。

但如果图片应属于该组,则必须首先使用 Workbook.addPicture 在工作簿级别创建这些图片。没有办法。

有了图片 ID,就可以使用 XSSFShapeGroup.createPicture 将图片放入组中。那里 XSSFClientAnchor 直到现在都没有用,可以简单地 new XSSFClientAnchor()。尽管如此,为 XSSFClientAnchor 设置的其他内容将不会生效,无论是图片大小还是组中的位置。所以在 XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1); 之后 picture 将位于位置 0, 0 并且大小为 0, 0.

可以使用 XSSFPicture.resize 设置大小。如果那是不可能的,那么它必须以EMU为单位进行计算,并使用低级别ooxml 类进行设置。例如沿着

...
XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCx(widthInEMU);
picture.getCTPicture().getSpPr().getXfrm().getExt().setCy(heightInEMU);
...

必须使用低电平设置位置ooxml 类。对于位置 X 和 Y,EMU 也是测量单位。例如:

...
picture.getCTPicture().getSpPr().getXfrm().getOff().setX(xInEMU);
picture.getCTPicture().getSpPr().getXfrm().getOff().setY(yInEMU);
...

知道了这一点,让我们提供一个完整的例子。以下作品(已测试)使用 apache poi 5.0.0。请注意:图像文件 image1.jpegimage2.jpeg 的原始大小不应大于组框的锚点。

import org.apache.poi.xssf.usermodel.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Units;

import java.io.FileInputStream;
import java.io.FileOutputStream;

class CreateExcelXSSFShapeGroup {

 public static void main(String[] args) throws Exception{

  Workbook workbook = new XSSFWorkbook();

  CreationHelper helper = workbook.getCreationHelper();

  //add picture data to this workbook.
  FileInputStream is = new FileInputStream("./image1.jpeg");
  byte[] bytes = IOUtils.toByteArray(is);
  int pictureIdx1 = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
  is.close();
  is = new FileInputStream("./image2.jpeg");
  bytes = IOUtils.toByteArray(is);
  int pictureIdx2 = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);
  is.close();

  Sheet sheet = workbook.createSheet("Sheet1");

  Drawing drawing = sheet.createDrawingPatriarch();

  //From here on XSSF only.
  XSSFDrawing xssfdrawing = (XSSFDrawing)drawing;
  XSSFClientAnchor anchor = (XSSFClientAnchor)helper.createClientAnchor();

  int groupShapeStartsAtCol = 1;
  int groupShapeStartsAtRow = 1;
  anchor.setCol1(groupShapeStartsAtCol);
  anchor.setRow1(groupShapeStartsAtRow); 
  int groupWidthInCols = 8;
  anchor.setCol2(groupShapeStartsAtCol + groupWidthInCols);
  int groupHeightInRows = 20;
  anchor.setRow2(groupShapeStartsAtRow + groupHeightInRows);

  XSSFShapeGroup shapeGroup = xssfdrawing.createGroup(anchor); // group shape is now of size from anchor abowe

  XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1);
  //picture.resize(); // picture is now at pos 0, 0 (top left) of group shape and of it's native size
  picture.getCTPicture().getSpPr().getXfrm().getExt().setCx(1234567);
  picture.getCTPicture().getSpPr().getXfrm().getExt().setCy(1234567); // picture is now at pos 0, 0 (top left) of group shape and of size 1234567 x 1234567 EMU
  
  picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx2);
  picture.resize(); // picture is now at pos 0, 0 (top left) of group shape and of it's native size

  // picture shall be at position bottom right of group shape
  // so top left of picture must be at x = group width - picture width and y = group height - picture height
  int pictureWidthInPx = (int)Math.round(picture.getImageDimension().getWidth());
  int pictureHeightInPx = (int)Math.round(picture.getImageDimension().getHeight());
  int defaultColWidthInPx = Math.round(sheet.getColumnWidthInPixels(0));
  int defaultRowHeightInPx = Units.pointsToPixel(sheet.getDefaultRowHeightInPoints()); 
 
  int xInEMU = Units.pixelToEMU(groupWidthInCols * defaultColWidthInPx - pictureWidthInPx);
  picture.getCTPicture().getSpPr().getXfrm().getOff().setX(xInEMU);
  int yInEMU = Units.pixelToEMU(groupHeightInRows * defaultRowHeightInPx - pictureHeightInPx);
  picture.getCTPicture().getSpPr().getXfrm().getOff().setY(yInEMU);

  FileOutputStream out = new FileOutputStream("CreateExcelXSSFShapeGroup.xlsx");
  workbook.write(out);
  out.close();
  workbook.close();

 }
}