`
TimerBin
  • 浏览: 355583 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JAVA快速精准获取图片类型及实现源码分析

    博客分类:
  • JAVA
阅读更多

关于在Java 程序中如何根据上传的图片流信息,获取上传的图片类型、宽、高这个问题一直纠结着我,终于抽出时间对这个问题分析

 

在JDK 中有提供现成的API 来查询图片的类型、宽、高。

ImageInputStream imageInputStream  = ImageIO.createImageInputStream(new File("D:\1.jpg"));
Iterator<ImageReader> iter = ImageIO.getImageReaders(imageInputStream);

if (null != iter && iter.hasNext()) {
      ImageReader reader = iter.next();
      format = reader.getFormatName(); //获得图片的类型

      reader.setInput(imageInputStream, true);

     reader.getWidth(0);  //获得图片的宽

     reader.getHeight(0); //获得图片的高
}

 

获取图片类型

重要源码分析

1、ImageIO.getImageReaders()方法,参数是ImageInputStream 或者其他。

/**
     * Returns an <code>Iterator</code> containing all currently
     * registered <code>ImageReader</code>s that claim to be able to
     * decode the supplied <code>Object</code>, typically an
     * <code>ImageInputStream</code>.
     *
     * <p> The stream position is left at its prior position upon
     * exit from this method.
     *
     * @param input an <code>ImageInputStream</code> or other
     * <code>Object</code> containing encoded image data.
     *
     * @return an <code>Iterator</code> containing <code>ImageReader</code>s.
     *
     * @exception IllegalArgumentException if <code>input</code> is
     * <code>null</code>.
     *
     * @see javax.imageio.spi.ImageReaderSpi#canDecodeInput
     */

public static Iterator<ImageReader> getImageReaders(Object input) {
        if (input == null) {
            throw new IllegalArgumentException("input == null!");
        }
        Iterator iter;
        // Ensure category is present
        try {
            iter = theRegistry.getServiceProviders(ImageReaderSpi.class,
                                              new CanDecodeInputFilter(input),
                                              true); //读取项目
        } catch (IllegalArgumentException e) {
            return new HashSet().iterator();
        }

        return new ImageReaderIterator(iter);
 }

 

仔细观察ImageReader对象发现它是一个抽象类,在抽象类中存在一个抽象的属性ImageReaderSpi    originatingProvider 。其实这个属性的实现类具有以下几种继承类:

BMPImageReaderSpi 、GIFImageReaderSpi、JPEGImageReaderSpi、PNGImageReaderSpi、WBMPImageReaderSpi ,这个抽象属性就是读取图片流信息的关键。

 

1.1 IIORegistry.getServiceProviders()方法用以获得ImageReader的确切实现类,参数1:抽象类的源文件,用于获取继承抽象类的集合,参数2:对继承抽象类的集合的过滤器CanDecodeInputFilter,参数3:boolean型 返回的结果是否是Key/Value返回结果。

 

/**
     * Returns an <code>Iterator</code> containing service provider
     * objects within a given category that satisfy a criterion
     * imposed by the supplied <code>ServiceRegistry.Filter</code>
     * object's <code>filter</code> method.
     *
     * <p> The <code>useOrdering</code> argument controls the
     * ordering of the results using the same rules as
     * <code>getServiceProviders(Class, boolean)</code>.
     *
     * @param category the category to be retrieved from.
     * @param filter an instance of <code>ServiceRegistry.Filter</code>
     * whose <code>filter</code> method will be invoked.
     * @param useOrdering <code>true</code> if pairwise orderings
     * should be taken account in ordering the returned objects.
     *
     * @return an <code>Iterator</code> containing service provider
     * objects from the given category, possibly in order.
     *
     * @exception IllegalArgumentException if there is no category
     * corresponding to <code>category</code>.
     */
    public <T> Iterator<T> getServiceProviders(Class<T> category,
            Filter filter,
            boolean useOrdering) {
        SubRegistry reg = (SubRegistry)categoryMap.get(category);
        if (reg == null) {
            throw new IllegalArgumentException("category unknown!");
        }
        Iterator iter = getServiceProviders(category, useOrdering);
        return new FilterIterator(iter, filter);
  }

 

 

getServiceProviders()方法用以获得ImageReaderSpi继承子类的迭代器BMPImageReaderSpi 、GIFImageReaderSpi、JPEGImageReaderSpi、PNGImageReaderSpi、WBMPImageReaderSpi的集合,这个方法是公用方法,暂未研究透彻因此不在这里丢人。

 

new FilterIterator(iter, filter);是调用传进来的CanDecodeInputFilter过滤器的filter方法进行过滤。代码如下:

public FilterIterator(Iterator<T> iter,
                          ServiceRegistry.Filter filter) {
        this.iter = iter;
        this.filter = filter;
        advance();
    }

    private void advance() {
        while (iter.hasNext()) {
            T elt = iter.next();
            if (filter.filter(elt)) {
                next = elt;
                return;
            }
        }

        next = null;
    }

 

filter.filter()方法如下所示:

 

 static class CanDecodeInputFilter
        implements ServiceRegistry.Filter {

        Object input;

        public CanDecodeInputFilter(Object input) {
            this.input = input;
        }

        public boolean filter(Object elt) {
            try {
                ImageReaderSpi spi = (ImageReaderSpi)elt;
                ImageInputStream stream = null;
                if (input instanceof ImageInputStream) {
                    stream = (ImageInputStream)input;
                }
                
                // Perform mark/reset as a defensive measure
                // even though plug-ins are supposed to take
                // care of it.
                boolean canDecode = false;
                if (stream != null) {
                    stream.mark();
                }
                canDecode = spi.canDecodeInput(input);
                if (stream != null) {
                    stream.reset();
                }
                
                return canDecode;
            } catch (IOException e) {
                return false;
            }
        }
    }

 

 

 CanDecodeInputFilter 过滤器会对ImageReaderSpi子类集合进行迭代循环并调用各自的重写canDecodeInput方法对stream进行检测是那种类型(stream是在new CanDecodeInputFilter时传输进来的ImageInputStream)。

 

 接下来我们来看下ImageReaderSpi的子类中对canDecodeInput方法的内容

1)BMPImageReaderSpi.canDecodeInput

public boolean canDecodeInput(Object source) throws IOException {
        if (!(source instanceof ImageInputStream)) {
            return false;
        }

        ImageInputStream stream = (ImageInputStream)source;
        byte[] b = new byte[2];
        stream.mark();
        stream.readFully(b);
        stream.reset();

        return (b[0] == 0x42) && (b[1] == 0x4d);
    }

 

2)GIFImageReaderSpi.canDecodeInput

 

public boolean canDecodeInput(Object input) throws IOException {
        if (!(input instanceof ImageInputStream)) {
            return false;
        }
        
        ImageInputStream stream = (ImageInputStream)input;
        byte[] b = new byte[6];
        stream.mark();
        stream.readFully(b);
        stream.reset();

        return b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' &&
            (b[4] == '7' || b[4] == '9') && b[5] == 'a';
    }

 

3)JPEGImageReaderSpi.canDecodeInput

 

public boolean canDecodeInput(Object source) throws IOException {
        if (!(source instanceof ImageInputStream)) {
            return false;
        }
        ImageInputStream iis = (ImageInputStream) source;
        iis.mark();
        // If the first two bytes are a JPEG SOI marker, it's probably
        // a JPEG file.  If they aren't, it definitely isn't a JPEG file.
        int byte1 = iis.read();
        int byte2 = iis.read();
        iis.reset();
        if ((byte1 == 0xFF) && (byte2 == JPEG.SOI)) {
            return true;
        }
        return false;
    }

 

4)PNGImageReaderSpi.canDecodeInput

 

public boolean canDecodeInput(Object source) throws IOException {
        if (!(source instanceof ImageInputStream)) {
            return false;
        }
        ImageInputStream stream = (ImageInputStream)source;
        byte[] b = new byte[3];
        stream.mark();
        stream.readFully(b);
        stream.reset();
        return ((b[0] == (byte)0) &&  // TypeField == 0
                b[1] == 0 && // FixHeaderField == 0xxx00000; not support ext header
                ((b[2] & 0x8f) != 0 || (b[2] & 0x7f) != 0));  // First width byte
                //XXX: b[2] & 0x8f) != 0 for the bug in Sony Ericsson encoder.
    }

 5)WBMPImageReaderSpi.canDecodeInput

 

 

 

public boolean canDecodeInput(Object source) throws IOException {
        if (!(source instanceof ImageInputStream)) {
            return false;
        }
        ImageInputStream stream = (ImageInputStream)source;
        byte[] b = new byte[3];
        stream.mark();
        stream.readFully(b);
        stream.reset();
        return ((b[0] == (byte)0) &&  // TypeField == 0
                b[1] == 0 && // FixHeaderField == 0xxx00000; not support ext header
                ((b[2] & 0x8f) != 0 || (b[2] & 0x7f) != 0));  // First width byte
                //XXX: b[2] & 0x8f) != 0 for the bug in Sony Ericsson encoder.
    }

 

重点说明:看到这里就应该明白了,相同图片类型的图片流内容中的固定多少位到多少位将会是相同的,具体图盘类型的公式可以如上所示。

 

1.2 new ImageReaderIterator(iter)方法,只是将解析出来的ImageReaderSpi 赋值到ImageReader对象的 originatingProvider 属性中。

 

 2、ImageReader.getFormatName();  方法用以读出图片的类型,

 public String getFormatName() throws IOException {
        return originatingProvider.getFormatNames()[0];
  }

 

 

上面的1如果看懂了这里就非常好理解了,只是调用第一步中获得ImageReaderSpi 子类中getFormatNames方法默认取第一个值。

 

说到这里我们就可以自己总结出一个公用方法来根据ImageInputStream来获取图片的类型信息(当然方法作用不大,不推荐使用,只是用于学习)。

 

返回的就是图片的类型信息

private static String checkImageType(ImageInputStream stream){
  String fileType="jpg";
  byte[] b;
  try {
   b = new byte[8];
   stream.mark();
   stream.readFully(b);
   stream.reset();
         int byte1 = stream.read();
         int byte2 = stream.read();
   if(b[0] == 'G' && b[1] == 'I' && b[2] == 'F' && b[3] == '8' &&
           (b[4] == '7' || b[4] == '9') && b[5] == 'a'){
    fileType ="GIF";
   }else if((b[0] == 0x42) && (b[1] == 0x4d)){
    fileType="BMP";
   }else if((byte1 == 0xFF) && (byte2 == JPEG.SOI)){
    fileType="JPG";
   }else if((b[0] == (byte)137 &&
                b[1] == (byte)80 &&
                b[2] == (byte)78 &&
                b[3] == (byte)71 &&
                b[4] == (byte)13 &&
                b[5] == (byte)10 &&
                b[6] == (byte)26 &&
                b[7] == (byte)10)){
    fileType ="PNG";
   }else if(((b[0] == (byte)0) &&  
                b[1] == 0 &&  
                ((b[2] & 0x8f) != 0 || (b[2] & 0x7f) != 0))){
    fileType = "wbmp";
   }
     } catch (Exception e) {
   e.printStackTrace();
  }
  return fileType;
 }

 

 

注:以上代码获得的“图片类型”并不是图片的后缀名信息,是图片的实际类型信息。

 

写到这里实际只是在做JDK源码分析,在实际应用中我们都已经获得了ImageInputStream,恐怕没人会闲着没事的重写JDK的提供好的方法 reader.getFormatName()获得图片类型的方法。

 

下面说一下我目前所应用场景,了解Spring MVC的就会对其中的MultipartFile感到熟悉,它是在Spring MVC中默认接受图片上传流的参数,在MultipartFile.getBytes() 方法获取的byte[]其实和ImageInputStream 所得到的byte[]是完全相同的,所以我们就完全可以使用MultipartFile.getBytes() 直接进行数组读取判断获取图片类型,代码如下所示:

 

private   String readPictureType(MultipartFile file) throws IOException {
        byte[] buffer = Arrays.copyOf(file.getBytes(), 8);
        String fileType="JPG";
        if(buffer[0] == 'G' && buffer[1] == 'I' && buffer[2] == 'F' && buffer[3] == '8' &&
                (buffer[4] == '7' || buffer[4] == '9') && buffer[5] == 'a'){
        	fileType ="GIF";
        }else if((buffer[0] == 0x42) && (buffer[1] == 0x4d)){
        	fileType="BMP";
        }else if((buffer[0] == (byte)137 &&
                     buffer[1] == (byte)80 &&
                     buffer[2] == (byte)78 &&
                     buffer[3] == (byte)71 &&
                     buffer[4] == (byte)13 &&
                     buffer[5] == (byte)10 &&
                     buffer[6] == (byte)26 &&
                     buffer[7] == (byte)10)){
        	fileType ="PNG";
        }else{
        	fileType="JPG";
        }
        return fileType;
    }

 从而避免繁琐的IO流的操作,节省效率。

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics