package com.doumee.core.annotation.excel; import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ooxml.POIXMLDocumentPart; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.*; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; /** * 读取 Excel 工作表中按行列锚定的内嵌图片 */ public class ExcelEmbeddedImageReader { private ExcelEmbeddedImageReader() { } public static Map read(String filePath, int sheetIndex, int headerNum, int mainImageCol, int multiImageCol) throws IOException { try (InputStream in = new FileInputStream(filePath)) { return read(in, sheetIndex, headerNum, mainImageCol, multiImageCol); } } public static Map read(InputStream in, int sheetIndex, int headerNum, int mainImageCol, int multiImageCol) throws IOException { try (Workbook workbook = WorkbookFactory.create(in)) { Sheet sheet = workbook.getSheetAt(sheetIndex); if (sheet instanceof XSSFSheet) { return readXssf((XSSFSheet) sheet, headerNum, mainImageCol, multiImageCol); } if (sheet instanceof HSSFSheet) { return readHssf((HSSFSheet) sheet, headerNum, mainImageCol, multiImageCol); } return Collections.emptyMap(); } } private static Map readXssf(XSSFSheet sheet, int headerNum, int mainImageCol, int multiImageCol) { Map> cellPictures = new HashMap<>(); collectXssfDrawings(sheet, cellPictures); return buildRowImages(cellPictures, headerNum, mainImageCol, multiImageCol); } private static void collectXssfDrawings(XSSFSheet sheet, Map> cellPictures) { XSSFDrawing drawing = sheet.getDrawingPatriarch(); if (drawing != null) { appendXssfPictures(drawing, cellPictures); } for (POIXMLDocumentPart part : sheet.getRelations()) { if (part instanceof XSSFDrawing && part != drawing) { appendXssfPictures((XSSFDrawing) part, cellPictures); } } } private static void appendXssfPictures(XSSFDrawing drawing, Map> cellPictures) { if (drawing == null) { return; } for (XSSFShape shape : drawing.getShapes()) { if (!(shape instanceof XSSFPicture)) { continue; } XSSFPicture picture = (XSSFPicture) shape; ClientAnchor anchor = picture.getClientAnchor(); if (anchor == null) { continue; } PictureData pictureData = picture.getPictureData(); if (pictureData == null || pictureData.getData() == null || pictureData.getData().length == 0) { continue; } addCellPicture(cellPictures, anchor, pictureData); } } private static Map readHssf(HSSFSheet sheet, int headerNum, int mainImageCol, int multiImageCol) { Map> cellPictures = new HashMap<>(); HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); if (patriarch != null) { for (HSSFShape shape : patriarch.getChildren()) { if (!(shape instanceof HSSFPicture)) { continue; } HSSFPicture picture = (HSSFPicture) shape; ClientAnchor anchor = picture.getClientAnchor(); if (anchor == null) { continue; } PictureData pictureData = picture.getPictureData(); if (pictureData == null || pictureData.getData() == null || pictureData.getData().length == 0) { continue; } addCellPicture(cellPictures, anchor, pictureData); } } return buildRowImages(cellPictures, headerNum, mainImageCol, multiImageCol); } private static void addCellPicture(Map> cellPictures, ClientAnchor anchor, PictureData pictureData) { int sortKey = anchorSortKey(anchor); String ext = normalizeExtension(pictureData.suggestFileExtension()); byte[] data = pictureData.getData(); int minRow = anchor.getRow1(); int minCol = anchor.getCol1(); int key = cellKey(minRow, minCol); cellPictures.computeIfAbsent(key, k -> new ArrayList<>()) .add(new CellPicture(minRow, minCol, data, ext, sortKey)); } private static Map buildRowImages(Map> cellPictures, int headerNum, int mainImageCol, int multiImageCol) { Map result = new HashMap<>(); int dataStartRow = headerNum + 1; Set rows = new TreeSet<>(); for (CellPicture pic : flatten(cellPictures)) { if (pic.row >= dataStartRow) { rows.add(pic.row); } } for (int row : rows) { RowEmbeddedImages rowImages = new RowEmbeddedImages(); List mainList = cellPictures.getOrDefault(cellKey(row, mainImageCol), Collections.emptyList()); mainList.stream() .sorted(Comparator.comparingInt(CellPicture::getSortKey)) .findFirst() .ifPresent(pic -> rowImages.setMainImage(toItem(pic))); List multi = new ArrayList<>(); for (CellPicture pic : cellPictures.getOrDefault(cellKey(row, multiImageCol), Collections.emptyList())) { multi.add(toItem(pic)); } multi.sort(Comparator.comparingInt(RowEmbeddedImages.EmbeddedImageItem::getSortKey)); rowImages.setMultiImages(multi); result.put(row, rowImages); } return result; } private static List flatten(Map> cellPictures) { List all = new ArrayList<>(); for (List list : cellPictures.values()) { all.addAll(list); } return all; } private static RowEmbeddedImages.EmbeddedImageItem toItem(CellPicture pic) { return new RowEmbeddedImages.EmbeddedImageItem(pic.data, pic.extension, pic.sortKey); } private static int cellKey(int row, int col) { return row * 1000 + col; } private static int anchorSortKey(ClientAnchor anchor) { return anchor.getRow1() * 1_000_000 + anchor.getCol1() * 10_000 + anchor.getDx1() + anchor.getDy1(); } private static String normalizeExtension(String ext) { if (ext == null || ext.isEmpty()) { return ".jpg"; } return ext.startsWith(".") ? ext : "." + ext; } private static class CellPicture { private final int row; private final int col; private final byte[] data; private final String extension; private final int sortKey; private CellPicture(int row, int col, byte[] data, String extension, int sortKey) { this.row = row; this.col = col; this.data = data; this.extension = extension; this.sortKey = sortKey; } private int getSortKey() { return sortKey; } } }