Apache POI中Worksheet图片缩放功能失效问题排查
Apache POI 5.2.3 插入XLSM图片缩放异常问题解决
问题原因
- Apache POI的
Picture.resize()方法是基于图片原始DPI和Excel默认的96DPI来换算显示尺寸的。Pexels下载的图片大多是300DPI的高分辨率图,当你调用resize(scale)时,POI会按原始DPI计算实际显示大小,相当于额外做了一次反向缩放(300/96≈3.125倍),比如你设0.5的缩放比例,实际显示效果是0.5*(96/300)=0.16倍,自然会变成小点。 - 少数情况是图片插入时未正确绑定工作表的绘图锚点,但核心问题还是DPI不匹配。
正确缩放方案
方案1:直接设置图片目标宽高(推荐)
跳过resize(),直接用Picture.setWidth()和Picture.setHeight()指定Excel中显示的像素尺寸,单位是EMU,可以用Units.pixelToEMU()做转换。
示例代码:
import org.apache.poi.ss.usermodel.*; import org.apache.poi.util.Units; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class XLSMImageResize { public static void main(String[] args) throws IOException { // 创建默认字体为Calibri 11px的XLSM工作簿 Workbook workbook = new XSSFWorkbook(); Font defaultFont = workbook.createFont(); defaultFont.setFontName("Calibri"); defaultFont.setFontHeightInPoints((short)11); CellStyle defaultStyle = workbook.createCellStyle(); defaultStyle.setFont(defaultFont); workbook.setCellStyleAt(0, defaultStyle); Sheet sheet = workbook.createSheet("ImageSheet"); // 读取本地图片(已从Pexels下载好) String imgPath = "your-image-path.jpg"; byte[] imgBytes = new byte[(int) new java.io.File(imgPath).length()]; try (FileInputStream fis = new FileInputStream(imgPath)) { fis.read(imgBytes); } // 插入图片并设置尺寸 int pictureIdx = workbook.addPicture(imgBytes, Workbook.PICTURE_TYPE_JPEG); CreationHelper helper = workbook.getCreationHelper(); Drawing<?> drawing = sheet.createDrawingPatriarch(); ClientAnchor anchor = helper.createClientAnchor(); // 锚定到A1单元格 anchor.setCol1(0); anchor.setRow1(0); Picture pict = drawing.createPicture(anchor, pictureIdx); // 设置目标显示尺寸:400x300像素 int targetWidthPx = 400; int targetHeightPx = 300; pict.setWidth(Units.pixelToEMU(targetWidthPx)); pict.setHeight(Units.pixelToEMU(targetHeightPx)); // 保存工作簿 try (FileOutputStream fos = new FileOutputStream("output.xlsm")) { workbook.write(fos); } workbook.close(); } }
方案2:修正图片DPI后调用resize()
如果一定要用resize()方法,先把图片的原始DPI改成96(Excel默认值),再调用缩放方法。可以用ImageIO处理图片DPI:
示例代码片段:
import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileOutputStream; // 读取图片并修改DPI为96 BufferedImage img = ImageIO.read(new File(imgPath)); ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); ImageWriteParam param = writer.getDefaultWriteParam(); IIOMetadata metadata = writer.getDefaultImageMetadata(new javax.imageio.ImageTypeSpecifier(img), param); IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree("javax_imageio_jpeg_image_1.0"); IIOMetadataNode jpegVariety = (IIOMetadataNode) root.getElementsByTagName("JPEGvariety").item(0); IIOMetadataNode markerSequence = (IIOMetadataNode) jpegVariety.getElementsByTagName("markerSequence").item(0); IIOMetadataNode dpiNode = new IIOMetadataNode("appJFIF"); dpiNode.setAttribute("Xdensity", "96"); dpiNode.setAttribute("Ydensity", "96"); dpiNode.setAttribute("units", "1"); // 1代表像素/英寸 markerSequence.appendChild(dpiNode); // 写入临时文件 File tempImg = File.createTempFile("temp", ".jpg"); try (FileOutputStream fos = new FileOutputStream(tempImg)) { writer.setOutput(ImageIO.createImageOutputStream(fos)); writer.write(metadata, new javax.imageio.IIOImage(img, null, metadata), param); } // 后续读取临时文件插入图片,再调用resize(0.5)就能正常缩放
注意事项
- 针对XLSM(XSSF格式),使用通用的
Drawing和ClientAnchor即可,无需特意强转成XSSF专属类。 - 可以配合
sheet.setColumnWidth()和sheet.setRowHeight()调整单元格大小,让图片排版更合理。
内容的提问来源于stack exchange,提问作者Michał Mańkowski
相关产品推荐
相关产品推荐

