0

Trying to add multiple images to XSSFGroupShape. I have tried the following(modified for simplicity):

    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 retrieved properties and data of embedded images from group shape.

I understand the fact that an image cannot attached without an anchor. However, I couldn't find a way of adding an XSSFChildAnchor inside the XSSFGroupShape that would allow setting of properites of CTTransform2D object.

I also tried to add the image to the Workbook using int pictureIndex = sheet.getWorkbook().addPicture(imageData, picType) and use that index to set the CTPicture using shapeGroup.getCTGroupShape().insertNewPic(pictureIndex). However, by doing this the relationId that would point to that image is not being set resulting in no image being displayed.

Image of desired output

gutai_t
  • 3
  • 2

1 Answers1

1

XSSFShapeGroup is not complete until now (apache 5.0.0 May 2021).

But if pictures shall be in that group then those pictures first must be created at workbook level using Workbook.addPicture. There is no way around.

Having the picture ids then one can put the pictures in the group using XSSFShapeGroup.createPicture. There the XSSFClientAnchor is useless until now and can be simply new XSSFClientAnchor(). Nevertheless what else is set for that XSSFClientAnchor it will not have effect, neither for size of picture nor for position in the group. So after XSSFPicture picture = shapeGroup.createPicture(new XSSFClientAnchor(), pictureIdx1); the picture will be at position 0, 0 and will be of size 0, 0.

The size can be set using XSSFPicture.resize. If that is not possible, then it would must be calculated in unit EMU and set using low level ooxml classes. For example along the lines of

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

The position must be set using low level ooxml classes. For position X and Y also EMU is the measurement unit. For example:

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

So knowing that, let's provide a complete example. The following works (tested) using apache poi 5.0.0. Please note: The native size of the image files image1.jpeg and image2.jpeg should not be bigger than the anchor for the group frame.

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();

 }
}
Axel Richter
  • 56,077
  • 6
  • 60
  • 87