Apache POI 5.4.1:如何在XSSFWorkbook中添加原始尺寸图片
解决Apache POI 5.4.1 XSSF图片缩放与resize(1.0)不显示问题
核心问题原因
Excel默认采用96DPI渲染图片,而多数图片的原生DPI是72(比如PNG/JPG常见值)。直接调用resize(1.0)时,POI会基于图片DPI和Excel的DPI做转换,导致计算出的尺寸异常,甚至图片被缩至不可见。另外,锚点设置不当也会让图片被单元格约束缩放。
正确实现步骤
1. 获取图片原生尺寸与DPI
先读取图片的真实宽度、高度和DPI值,避免依赖POI的自动计算:
// 示例:读取本地图片文件 File imageFile = new File("test.png"); BufferedImage img = ImageIO.read(imageFile); int imgWidth = img.getWidth(); int imgHeight = img.getHeight(); // 获取图片DPI(如果图片没有DPI元数据,默认用72) float dpiX = 72f; float dpiY = 72f; ImageReader reader = ImageIO.getImageReadersByFormatName("png").next(); try (ImageInputStream iis = ImageIO.createImageInputStream(imageFile)) { reader.setInput(iis, true); IIOMetadata metadata = reader.getImageMetadata(0); NodeList nodes = metadata.getAsTree("javax_imageio_1.0").getElementsByTagName("HorizontalPixelSize"); if (nodes.getLength() > 0) { String value = nodes.item(0).getAttributes().getNamedItem("value").getNodeValue(); dpiX = 25.4f / Float.parseFloat(value); // 转换为DPI(1英寸=25.4毫米) } nodes = metadata.getAsTree("javax_imageio_1.0").getElementsByTagName("VerticalPixelSize"); if (nodes.getLength() > 0) { String value = nodes.item(0).getAttributes().getNamedItem("value").getNodeValue(); dpiY = 25.4f / Float.parseFloat(value); } } catch (Exception e) { // 读取DPI失败时使用默认值 }
2. 计算适配Excel的尺寸
基于Excel的96DPI,将图片原生尺寸转换为Excel可识别的显示尺寸:
float excelDpi = 96f; // 转换图片像素为Excel渲染时的实际像素值 int displayWidth = (int) Math.round(imgWidth * excelDpi / dpiX); int displayHeight = (int) Math.round(imgHeight * excelDpi / dpiY);
3. 设置锚点并插入图片
使用ClientAnchor定义图片的占位范围,然后手动设置图片尺寸替代resize(1.0):
XSSFWorkbook workbook = new XSSFWorkbook(); XSSFSheet sheet = workbook.createSheet("ImageSheet"); // 读取图片到字节数组 byte[] imageBytes = Files.readAllBytes(imageFile.toPath()); int pictureIdx = workbook.addPicture(imageBytes, Workbook.PICTURE_TYPE_PNG); XSSFDrawing drawing = sheet.createDrawingPatriarch(); // 锚点设置为足够容纳图片的单元格范围,col1/row1是起始单元格,col2/row2是结束单元格 XSSFClientAnchor anchor = drawing.createAnchor(0, 0, 0, 0, 0, 0, 5, 10); // 创建图片并手动设置尺寸 XSSFPicture picture = drawing.createPicture(anchor, pictureIdx); picture.setWidth(displayWidth); picture.setHeight(displayHeight); // 写入文件 try (FileOutputStream fos = new FileOutputStream("output.xlsx")) { workbook.write(fos); }
4. 简化方案(已知图片DPI为96时)
如果你的图片原生DPI就是96,可以直接跳过DPI计算,直接设置原始像素尺寸:
XSSFPicture picture = drawing.createPicture(anchor, pictureIdx); picture.setWidth(imgWidth); picture.setHeight(imgHeight);
关键注意点
- 避免直接调用
resize(1.0),该方法的DPI转换逻辑容易导致尺寸计算偏差。 - 锚点的
col2/row2只需设置到能容纳图片的单元格位置即可,实际显示尺寸由setWidth和setHeight控制。 - 若图片无DPI元数据,默认用72计算可覆盖绝大多数常见图片场景。
内容的提问来源于stack exchange,提问作者squirrelInTheBarel




