在处理图片资源时,有时候需要获取图片的分辨率
虽然Java提供了ImageIO.read方法获取包含图片大小、尺寸宽高等数据的BufferedImage对象,但它需要把图片完全加载到内存中,如果图片比较大,会占用较大的内存,其次它也不能读取PSD等源文件格式
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class ImageTest {
public static void main(String[] args) throws IOException {
String filePath = args[0];
BufferedImage image = ImageIO.read(new File(filePath));
int width = image.getWidth();
int height = image.getHeight();
System.out.println("width:" + width + " height:" + height);
}
}
当然也可以使用im4java等第三方包读取,调用时将工具地址传进来,例如:
import org.im4java.core.Info;
import org.im4java.core.InfoException;
public final class Im4JavaUtil {
private Im4JavaUtil() {}
/**
* 获取图像尺寸
*
* @param imagePath 图片路径
* @return 图像的尺寸
*/
public static int[] getImageInfo(String imagePath) throws InfoException {
int[] size = new int[2];
Info imageInfo = new Info(imagePath, true);
size[0] = imageInfo.getImageWidth();
size[1] = imageInfo.getImageHeight();
return size;
}
}
这种方式也有缺点,它会通过Process拉起子进程进行处理,不太方便对CPU等指标进行监控,如果同时处理的子进程过多,有可能会导致系统崩溃
因为这里我们只想要图片的分辨率,因此最好是通过解析不同图片格式协议的方式,只需要读取文件的前一些字节就能够获取到分辨率等数据,并且占用内存非常小
例如PSD文件头格式定义如下:
- 魔数:4字节,固定为8BPS
- 版本: 2字节,例如:1
- 保留字节: 6字节,固定为0
- 图像的通道数量 : 2字节
- 图像的高度: 4字节
- 图像的宽度:4字节
- 每通道的位数: 2字节
- 颜色模式 : 2字节
这里使用组合器的方式实现,这样扩展性更好,可以根据不同的业务需求添加自定义的解析器
1)首先创建一个解析器接口,图片的格式一般可以通过前4个字节确认
public interface ImageResolutionResolver {
/**
* 判断是否支持处理此类型
*
* @param c1 第一个字节
* @param c2 第二个字节
* @param c3 第三个字节
* @param c4 第四个字节
* @return 是否支持处理
*/
boolean support(int c1, int c2, int c3, int c4);
/**
* 获取图片分辨率
*
* @param fis 文件字节流
* @param c1 第一个字节
* @param c2 第二个字节
* @param c3 第三个字节
* @param c4 第四个字节
* @return 图片分辨率
*/
int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException;
/**
* 读取时需要区分大小端
*/
private static int readInt(InputStream is, int length, boolean bigEndian) throws IOException {
int ret = 0;
int sv = bigEndian ? ((length - 1) * 8) : 0;
int cnt = bigEndian ? -8 : 8;
for (int i = 0; i < length; i++) {
// 读取一个字节,如果是大端,则左移,这里的或等相当于相加运算
ret |= is.read() << sv;
sv += cnt;
}
return ret;
}
}
注意:
- 不同图片格式文件的字节序是不一样的,具体参考协议官方文档
- 因为大端存储时数据的高位存储在内存的低地址处,因此在解析时需要移位
2)创建各种不同图片格式的解析器,这些解析器都实现ImageResolutionResolver接口
Bmp格式解析器:
public class BmpResolutionResolver implements ImageResolutionResolver {
@Override
public boolean support(int c1, int c2, int c3, int c4) {
if (c1 == 66 && c2 == 77) {
return true;
}
return false;
}
@Override
public int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException {
fis.skip(14);
int width = readInt(fis, 2, false);
fis.skip(2);
int height = readInt(fis, 2, false);
return new int[]{width, height};
}
}
Gif格式解析器:
public class GifResolutionResolver implements ImageResolutionResolver {
@Override
public boolean support(int c1, int c2, int c3, int c4) {
if (c1 == 'G' && c2 == 'I' && c3 == 'F') {
return true;
}
return false;
}
@Override
public int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException {
fis.skip(2);
int width = readInt(fis, 2, false);
int height = readInt(fis, 2, false);
return new int[]{width, height};
}
}
JPG/JPEG格式解析器:
public class JpgResolutionResolver implements ImageResolutionResolver {
@Override
public boolean support(int c1, int c2, int c3, int c4) {
if (c1 == 0xFF && c2 == 0xD8) {
return true;
}
return false;
}
@Override
public int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException {
while (c3 == 255) {
int len = readInt(fis, 2, true);
if (c4 == 192 || c4 == 193 || c4 == 194) {
fis.skip(1);
int height = readInt(fis, 2, true);
int width = readInt(fis, 2, true);
return new int[]{width, height};
}
fis.skip(len - 2);
c3 = fis.read();
c4 = fis.read();
}
return null;
}
}
Png格式解析器:
public class PngResolutionResolver implements ImageResolutionResolver {
@Override
public boolean support(int c1, int c2, int c3, int c4) {
if (c1 == 137 && c2 == 80 && c3 == 78) {
return true;
}
return false;
}
@Override
public int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException {
fis.skip(14);
int width = readInt(fis, 2, true);
fis.skip(2);
int height = readInt(fis, 2, true);
return new int[]{width, height};
}
}
PSD格式解析器:
public class PsdResolutionResolver implements ImageResolutionResolver {
@Override
public boolean support(int c1, int c2, int c3, int c4) {
if (c1 == '8' && c2 == 'B' && c3 == 'P' && c4 == 'S') {
return true;
}
return false;
}
@Override
public int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException {
fis.skip(10);
int height = readInt(fis, 4, true);
int width = readInt(fis, 4, true);
return new int[] {width, height};
}
}
3)创建解析器组合器类,构造组合器时将需要的解析器传入
public class ImageResolutionResolverComposite {
private List<ImageResolutionResolver> resolvers = new LinkedList<>();
private boolean bigEndian = true;
public ImageResolutionResolverComposite() {
resolvers.add(new BmpResolutionResolver());
resolvers.add(new GifResolutionResolver());
resolvers.add(new JpgResolutionResolver());
resolvers.add(new PngResolutionResolver());
resolvers.add(new PsdResolutionResolver());
}
public int[] processImageSize(String filePath) throws IOException {
try (FileInputStream fis = new FileInputStream(filePath)) {
int c1 = fis.read();
int c2 = fis.read();
int c3 = fis.read();
int c4 = fis.read();
return resolve(fis, c1, c2, c3, c4);
}
}
private int[] resolve(FileInputStream fis, int c1, int c2, int c3, int c4) throws IOException {
for (ImageResolutionResolver resolver : resolvers) {
if (resolver.support(c1, c2, c3, c4)) {
return resolver.resolve(fis, c1, c2, c3, c4);
}
}
return null;
}
}
4)测试代码
public class ImageTest {
public static void main(String[] args) throws IOException {
String filePath = args[0];
ImageResolutionResolverComposite composite = new ImageResolutionResolverComposite();
int[] resolution = composite.processImageSize(filePath);
System.out.println("width:" + resolution[0] + " height:" + resolution[1]);
}
}