package net.dev123.commons; import java.io.File; import java.net.URL; import java.nio.ByteBuffer; import java.nio.ByteOrder; import net.dev123.commons.http.HttpRequestHelper; import net.dev123.commons.util.FileUtil; import net.dev123.exception.LibException; public class ImageInfo { /** * Image formats */ public enum Format { PNG, JPEG, GIF, TIFF, BMP, ICO, WEBP } private static final int EOI_MARKER = 0xd9; private static final int RST_MARKER_START = 0xd0; private static final int RST_MARKER_END = 0xd7; private static final int TEM_MARKER = 0x01; private static final int HUFFMAN_TABLE_MARKER = 0xc4; private static final int ARITHMETIC_CODING_CONDITIONING_MARKER = 0xcc; private URL url; private Format format; private byte[] imageData; private int width; private int height; private long size; public ImageInfo(URL url) { this.url = url; } public int getWidth() { return width; } public void setWidth(int width) { this.width = width; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public long getSize() { return size; } public String getDisplaySize() { return FileUtil.byteCountToDisplaySize(size); } public void setSize(long size) { this.size = size; } public Format getFormat() { return format; } public void setFormat(Format format) { this.format = format; } public URL getUrl() { return url; } /* public void setImageData(byte[] imageData) { if (imageData == null) { throw new IllegalArgumentException("imageData must not be null"); } if (imageData.length == 0) { throw new IllegalArgumentException("imageData must not be empty"); } this.imageData = imageData.clone(); this.width = -1; this.height = -1; this.format = null; } */ /** * Updates the dimension fields of the image. * */ private void updateDimensions() { if (imageData == null) { throw new UnsupportedOperationException( "No image data is available."); } if (imageData.length < 8) { throw new IllegalArgumentException( "imageData must be a valid image"); } if (imageData[0] == 'G' && imageData[1] == 'I' && imageData[2] == 'F') { updateGifDimensions(); format = Format.GIF; } else if (imageData[0] == (byte) 0x89 && imageData[1] == 'P' && imageData[2] == 'N' && imageData[3] == 'G' && imageData[4] == 0x0d && imageData[5] == 0x0a && imageData[6] == 0x1a && imageData[7] == 0x0a) { updatePngDimensions(); format = Format.PNG; } else if (imageData[0] == (byte) 0xff && imageData[1] == (byte) 0xd8) { updateJpegDimensions(); format = Format.JPEG; } else if ((imageData[0] == 'I' && imageData[1] == 'I' && imageData[2] == 0x2a && imageData[3] == 0x00) || (imageData[0] == 'M' && imageData[1] == 'M' && imageData[2] == 0x00 && imageData[3] == 0x2a)) { updateTiffDimensions(); format = Format.TIFF; } else if (imageData[0] == 'B' && imageData[1] == 'M') { updateBmpDimensions(); format = Format.BMP; } else if (imageData[0] == 0x00 && imageData[1] == 0x00 && imageData[2] == 0x01 && imageData[3] == 0x00) { updateIcoDimensions(); format = Format.ICO; } else if (imageData.length > 16 && imageData[0] == 'R' && imageData[1] == 'I' && imageData[2] == 'F' && imageData[3] == 'F' && imageData[8] == 'W' && imageData[9] == 'E' && imageData[10] == 'B' && imageData[11] == 'P' && imageData[12] == 'V' && imageData[13] == 'P' && imageData[14] == '8') { updateWebpDimensions(); format = Format.WEBP; } else { throw new IllegalArgumentException( "imageData must be a valid image"); } } /** * Updates the dimension fields of the GIF image. Based on * http://www.w3.org/Graphics/GIF/spec-gif89a.txt. * */ private void updateGifDimensions() { if (imageData.length < 10) { throw new IllegalArgumentException("corrupt GIF format"); } ByteBuffer buffer = ByteBuffer.wrap(imageData); buffer.order(ByteOrder.LITTLE_ENDIAN); width = buffer.getChar(6) & 0xffff; height = buffer.getChar(8) & 0xffff; } /** * Updates the dimension fields of the PNG image. Based on * http://www.w3.org/TR/2003/REC-PNG-20031110/ sections 5 and 11.2.2. * */ private void updatePngDimensions() { if (imageData.length < 24) { throw new IllegalArgumentException("corrupt PNG format"); } ByteBuffer buffer = ByteBuffer.wrap(imageData); buffer.order(ByteOrder.BIG_ENDIAN); width = buffer.getInt(16); height = buffer.getInt(20); } /** * Updates the dimension fields of the JPEG image. Based on * http://www.w3.org/Graphics/JPEG/itu-t81.pdf. * */ private void updateJpegDimensions() { ByteBuffer buffer = ByteBuffer.wrap(imageData); buffer.order(ByteOrder.BIG_ENDIAN); if (extend(buffer.get()) != 0xff || extend(buffer.get()) != 0xd8) { throw new IllegalArgumentException( "corrupt JPEG format: Expected SOI marker"); } int code; try { while (true) { do { code = extend(buffer.get()); } while (code != 0xff); while (code == 0xff) { code = extend(buffer.get()); } if (isFrameMarker(code)) { buffer.position(buffer.position() + 3); height = extend(buffer.getShort()); width = extend(buffer.getShort()); return; } if (code == EOI_MARKER) { throw new IllegalArgumentException( "corrupt JPEG format: No frame sgements found."); } if (code >= RST_MARKER_START && code <= RST_MARKER_END) { continue; } if (code == TEM_MARKER) { continue; } int length = extend(buffer.getShort(buffer.position())); buffer.position(buffer.position() + length); } } catch (IllegalArgumentException ex) { throw new IllegalArgumentException("corrupt JPEG format"); } catch (java.nio.BufferUnderflowException ex) { throw new IllegalArgumentException("corrupt JPEG format"); } } private static boolean isFrameMarker(int code) { return ((code & 0xf0) == 0xc0) && code != HUFFMAN_TABLE_MARKER && code != ARITHMETIC_CODING_CONDITIONING_MARKER; } private static int extend(byte b) { return b & 0xFF; } private static int extend(short s) { return s & 0xFFFF; } /** * Updates the dimension fields of the TIFF image. Based on * http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf sections 2 * and 3. * */ private void updateTiffDimensions() { ByteBuffer buffer = ByteBuffer.wrap(imageData); if (imageData[0] == 'I') { buffer.order(ByteOrder.LITTLE_ENDIAN); } int offset = buffer.getInt(4); int ifdSize = buffer.getChar(offset) & 0xffff; offset += 2; for (int i = 0; i < ifdSize && offset + 12 <= imageData.length; i++) { int tag = buffer.getChar(offset) & 0xffff; if (tag == 0x100 || tag == 0x101) { int type = buffer.getChar(offset + 2) & 0xffff; int result; if (type == 3) { result = buffer.getChar(offset + 8) & 0xffff; } else if (type == 4) { result = buffer.getInt(offset + 8); } else { result = imageData[offset + 8]; } if (tag == 0x100) { width = result; if (height != -1) { return; } } else { height = result; if (width != -1) { return; } } } offset += 12; } if (width == -1 || height == -1) { throw new IllegalArgumentException("corrupt tiff format"); } } /** * Updates the dimension fields of the BMP image. Based on * http://msdn.microsoft.com/en-us/library/ms532290(VS.85).aspx * http://msdn.microsoft.com/en-us/library/ms532300(VS.85).aspx * http://msdn.microsoft.com/en-us/library/ms532331(VS.85).aspx for windows * versions. * */ private void updateBmpDimensions() { if (imageData.length < 18) { throw new IllegalArgumentException("corrupt BMP format"); } ByteBuffer buffer = ByteBuffer.wrap(imageData); buffer.order(ByteOrder.LITTLE_ENDIAN); width = buffer.get(6) & 0xff; height = buffer.get(7) & 0xff; int headerLength = buffer.getInt(14); if (headerLength == 12 && imageData.length >= 22) { width = buffer.getChar(18) & 0xffff; height = buffer.getChar(20) & 0xffff; } else if ((headerLength == 40 || headerLength == 108 || headerLength == 124 || headerLength == 64) && imageData.length >= 26) { width = buffer.getInt(18); height = buffer.getInt(22); } else { throw new IllegalArgumentException("corrupt BMP format"); } } /** * Updates the dimension fields of the ICO image. * */ private void updateIcoDimensions() { if (imageData.length < 8) { throw new IllegalArgumentException("corrupt ICO format"); } ByteBuffer buffer = ByteBuffer.wrap(imageData); buffer.order(ByteOrder.LITTLE_ENDIAN); width = buffer.get(6) & 0xff; height = buffer.get(7) & 0xff; if (width == 0) { width = 256; } if (height == 0) { height = 256; } } private void updateWebpDimensions() { if (imageData.length < 30) { throw new IllegalArgumentException("corrupt WEBP format"); } ByteBuffer buffer = ByteBuffer.wrap(imageData); buffer.order(ByteOrder.LITTLE_ENDIAN); int bits = buffer.get(20) | buffer.get(21) << 8 | buffer.get(22) << 16; boolean keyFrame = (bits & 1) == 0; if (!keyFrame) { throw new IllegalArgumentException( "corrupt WEBP format: not a key frame"); } int profile = (bits >> 1) & 7; int showFrame = (bits >> 4) & 1; if (profile > 3) { throw new IllegalArgumentException( "corrupt WEBP format: invalid profile"); } if (showFrame == 0) { throw new IllegalArgumentException( "corrupt WEBP format: frame is not visible"); } width = extend(buffer.getShort(26)); height = extend(buffer.getShort(28)); } public static ImageInfo getImageInfo(URL url) throws LibException { if (url == null) { return null; } ImageInfo info = null; String scheme = url.getProtocol(); if ("file".equalsIgnoreCase(scheme)) { File imageFile = FileUtil.toFile(url); info = new ImageInfo(url); info.imageData = FileUtil.readFileToByteArray(imageFile); info.setSize(FileUtil.sizeOf(imageFile)); // 设置文件大小 info.updateDimensions(); // 更新图片尺寸、格式信息 } else if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { info = HttpRequestHelper.getImageInfo(url.toString()); } return info; } public static ImageInfo getImageInfo(File file) throws LibException { if (file == null || !file.exists() || !file.isFile()) { return null; } ImageInfo info = null; info = new ImageInfo(null); info.imageData = FileUtil.readFileToByteArray(file); info.setSize(FileUtil.sizeOf(file)); // 设置文件大小 info.updateDimensions(); // 更新图片尺寸、格式信息 return info; } @Override public String toString() { return "ImageInfo [url=" + url + ", format=" + format + ", width=" + width + ", height=" + height + ", size=" + getDisplaySize() + "]"; } }