为何无法读取BMP文件获取DPI?现有代码仅支持小尺寸JPEG
获取图像DPI:兼容BMP和JPEG的实用方案
嘿,我来帮你解决这个DPI读取的问题。你的现有代码只能处理小尺寸JPEG、读不了BMP,甚至用BufferedImage也没用,核心原因是:
- BMP的DPI信息根本不在ImageIO的通用元数据结构里,它存在文件头的专用区域;
BufferedImage只存储像素数据,完全不保留文件的元数据信息,所以从它那儿拿DPI本来就行不通。
下面我给你拆解问题,再给出能同时处理JPEG和BMP的解决方案:
为什么你的代码读不了BMP?
你的代码依赖于ImageIO的javax_imageio_1.0通用元数据树,但BMP的读取器并没有把DPI信息映射到这个结构里——BMP的DPI是存在BITMAPINFOHEADER中的biXPelsPerMeter和biYPelsPerMeter字段,得直接解析文件头才能拿到。
另外,你原代码里读取垂直DPI的逻辑有个小bug:它判断的是水平DPI节点的长度,这会导致如果水平DPI存在但垂直DPI不存在时,代码逻辑出错,我也一起给你修正了。
解决方案:分格式处理
1. 专门读取BMP的DPI
直接解析BMP文件头,读取每米像素数后转换成DPI(1英寸=0.0254米,所以DPI = 每米像素数 × 0.0254):
import java.io.RandomAccessFile; import java.io.IOException; public class BmpDpiReader { public static void getBmpDpi(String filePath) throws IOException { try (RandomAccessFile raf = new RandomAccessFile(filePath, "r")) { // 跳过14字节的BITMAPFILEHEADER文件头 raf.seek(14); // 读取BITMAPINFOHEADER的大小(标准是40字节) int infoHeaderSize = raf.readInt(); if (infoHeaderSize != 40) { System.out.println("xDPI: -"); System.out.println("yDPI: -"); return; } // 跳过biWidth、biHeight等16字节字段,直接定位到像素密度字段 raf.seek(14 + 4 + 16); // 读取biXPelsPerMeter(小端序,需要转成大端) int xPelsPerMeter = Integer.reverseBytes(raf.readInt()); // 读取biYPelsPerMeter int yPelsPerMeter = Integer.reverseBytes(raf.readInt()); // 转换为DPI并输出 int xDPI = Math.round(xPelsPerMeter * 0.0254f); int yDPI = Math.round(yPelsPerMeter * 0.0254f); System.out.println("xDPI: " + (xDPI > 0 ? xDPI : "-")); System.out.println("yDPI: " + (yDPI > 0 ? yDPI : "-")); } } }
2. 兼容JPEG和BMP的通用读取方法
先判断文件格式,再选择对应的读取逻辑,同时修正原JPEG读取代码的bug:
import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; import java.io.File; import java.io.IOException; import java.util.Iterator; import org.w3c.dom.NodeList; public class ImageDpiReader { public static void getImageDpi(String filePath) throws IOException { File file = new File(filePath); String format = getFileExtension(file); // 处理BMP格式 if ("bmp".equalsIgnoreCase(format)) { BmpDpiReader.getBmpDpi(filePath); return; } // 处理JPEG/JPG格式 if ("jpeg".equalsIgnoreCase(format) || "jpg".equalsIgnoreCase(format)) { try (ImageInputStream iis = ImageIO.createImageInputStream(file)) { Iterator<ImageReader> readers = ImageIO.getImageReaders(iis); if (!readers.hasNext()) { System.err.println("No available reader for JPEG format"); return; } ImageReader reader = readers.next(); reader.setInput(iis); IIOMetadata meta = reader.getImageMetadata(0); IIOMetadataNode root = (IIOMetadataNode) meta.getAsTree("javax_imageio_1.0"); // 读取水平DPI NodeList horizontalNodes = root.getElementsByTagName("HorizontalPixelSize"); int xDPI = -1; if (horizontalNodes.getLength() > 0) { IIOMetadataNode sizeNode = (IIOMetadataNode) horizontalNodes.item(0); String pixelSize = sizeNode.getAttributes().item(0).getNodeValue(); xDPI = Math.round(25.4f / Float.parseFloat(pixelSize)); } System.out.println("xDPI: " + (xDPI > 0 ? xDPI : "-")); // 读取垂直DPI(独立判断,修正原代码的bug) NodeList verticalNodes = root.getElementsByTagName("VerticalPixelSize"); int yDPI = -1; if (verticalNodes.getLength() > 0) { IIOMetadataNode sizeNode = (IIOMetadataNode) verticalNodes.item(0); String pixelSize = sizeNode.getAttributes().item(0).getNodeValue(); yDPI = Math.round(25.4f / Float.parseFloat(pixelSize)); } System.out.println("yDPI: " + (yDPI > 0 ? yDPI : "-")); } return; } // 其他格式暂不支持 System.out.println("Unsupported file format: " + format); } // 获取文件扩展名 private static String getFileExtension(File file) { String fileName = file.getName(); int dotIndex = fileName.lastIndexOf('.'); return dotIndex == -1 ? "" : fileName.substring(dotIndex + 1); } }
额外说明
- JPEG大文件问题:如果你的原代码处理大JPEG出错,建议用
try-with-resources包裹ImageInputStream(如上代码所示),确保资源正确释放,避免缓存溢出。 - BufferedImage的局限:记住,
BufferedImage只是像素数据的容器,不包含任何文件元数据,所以别指望从它那儿获取DPI、EXIF这类信息。
内容的提问来源于stack exchange,提问作者Raman Kaushal
相关产品推荐
相关产品推荐

