プログレッシブJPEG

携帯ではSoftBankの一部端末を除きプログレッシブ方式JPEGに対応していませんので、変換が必要になることが多くあります。

プログレッシブかの判定とベースラインJPEGへの変換を行うツールをJavaで書いてみました。もっと効率の良い方法があるかも知れませんが...。

(不具合があったので 2008/03/22 に一部修正しています)

import java.util.*;
import java.io.*;

import javax.imageio.*;
import javax.imageio.metadata.*;
import javax.imageio.stream.*;

import org.w3c.dom.*;
import javax.xml.xpath.*;

public class Progressive {

    public static void main(String[] args) throws Exception {

        if (args.length == 1) {
            System.out.println(
                "progressive(interlace)=" +
                    new Progressive().isProgressive(new File(args[0])));
        }
        else if (args.length == 2) {
            new Progressive().toNonProgressive(
                new File(args[0]), new File(args[1]));
        }
        else {
            String ls = System.getProperty("line.separator");
            System.err.println(
                "Usage: " + ls +
                " java " + Progressive.class.getName() + " file" + ls +
                " java " + Progressive.class.getName() + " src-file dst-file");
            System.exit(1);
        }
    }

    public boolean isProgressive(File file) throws IOException {

        ImageInputStream iis = null;
        ImageReader ir = null;

        boolean progressive = false;

        try {
            iis = ImageIO.createImageInputStream(file);
            Iterator it = ImageIO.getImageReaders(iis);
            ir = it.hasNext()? (ImageReader)it.next() : null;
            if (ir == null{
                throw new UnsupportedOperationException("no readers.");
            }

            ir.setInput(iis);

            int n = ir.getNumImages(true);
            for (int i=0; i < n; i++{
                if (isProgressive(ir.getImageMetadata(i))) {
                    progressive = true;
                    break;
                }
            }
        }
        finally {
            if (iis != null{
                try { iis.close(); } catch(IOException ie) {}
            }
            if (ir != null{
                ir.dispose();
            }
        }
        return progressive;
    }

    public boolean toNonProgressive(File src, File dst) throws IOException {

        ImageInputStream iis = null;
        ImageOutputStream ios = null;
        ImageReader ir = null;
        ImageWriter iw = null;

        try {
            iis = ImageIO.createImageInputStream(src);
            Iterator it = ImageIO.getImageReaders(iis);
            ir = it.hasNext()? (ImageReader)it.next() : null;
            if (ir == null{
                throw new UnsupportedOperationException("no readers.");
            }

            ir.setInput(iis, falsetrue);
            IIOMetadata streamMeta = ir.getStreamMetadata();

            int n = ir.getNumImages(true);

            List images = new ArrayList();
            boolean progressive = false;
            for (int i=0; i < n; i++{
                IIOImage image = ir.readAll(i, null);
                if (isProgressive(image.getMetadata())) {
                    progressive = true;
                }
                images.add(image);
            }

            if (! progressive) {
                return false;
            }

            iw = ImageIO.getImageWriter(ir);
            if (iw == null{
                throw new UnsupportedOperationException("no writers.");
            }

            ImageWriteParam iwp = iw.getDefaultWriteParam();
            iwp.setProgressiveMode(ImageWriteParam.MODE_DISABLED);

            ios = ImageIO.createImageOutputStream(dst);
            iw.setOutput(ios);
            if (images.size() == 1){
                iw.write(streamMeta, (IIOImage)images.get(0), iwp);
            }
            else{
                iw.prepareWriteSequence(streamMeta);
                for (int i=0, cnt=images.size(); i < cnt; i++){
                    iw.writeToSequence((IIOImage)images.get(i), iwp);
                }
                iw.endWriteSequence();
            }
            return true;
        }
        finally{
            if (iis != null){
                try{ iis.close(); } catch(IOException ie) {}
            }
            if (ios != null){
                try{ ios.close(); } catch(IOException ie) {}
            }
            if (ir != null{ ir.dispose(); }
            if (iw != null{ iw.dispose(); }
        }
    }

    private static final String EXP = "Compression/NumProgressiveScans/@value";

    private boolean isProgressive(IIOMetadata root) {

        if (! root.isStandardMetadataFormatSupported()) {
            throw new UnsupportedOperationException("metadata format");
        }

        try {
            Node node = root.getAsTree(
                IIOMetadataFormatImpl.standardMetadataFormatName);

            XPathFactory xpf = XPathFactory.newInstance();
            XPath xpath = xpf.newXPath();
            String val = xpath.evaluate(EXP, node);

            return val != null && !val.equals("1");
        }
        catch(XPathExpressionException xpee) {
            throw new Error(xpee.toString());
        }
    }
}

使い方

プログレッシブ(インターレース)かの判定

$ java Progressive progressive.jpg
progressive(interlace)=true

ベースラインJPEGへの変換

$ java Progressive progressive.jpg noneprogressive.jpg

一応インターレースGIF、インターレースPNGにも対応しています。