14cead8034aab0e20e402baee87cbe9129db00192Stephen Hinespackage com.android.rs.refocus;
2de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
3de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport android.graphics.Bitmap;
4de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport android.graphics.BitmapFactory;
5de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport android.util.Log;
6de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
7de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport com.adobe.xmp.XMPConst;
8de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport com.adobe.xmp.XMPException;
9de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport com.adobe.xmp.XMPIterator;
10de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport com.adobe.xmp.XMPMeta;
11de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport com.adobe.xmp.XMPMetaFactory;
12de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport com.adobe.xmp.properties.XMPPropertyInfo;
13de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
14de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport java.io.ByteArrayInputStream;
15de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport java.io.IOException;
16de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport java.io.InputStream;
17de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport java.io.UnsupportedEncodingException;
18de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport java.util.ArrayList;
19de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangimport java.util.List;
20de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
214cead8034aab0e20e402baee87cbe9129db00192Stephen Hinesimport com.android.rs.refocus.image.RangeInverseDepthTransform;
224cead8034aab0e20e402baee87cbe9129db00192Stephen Hinesimport com.android.rs.refocus.image.RangeLinearDepthTransform;
23de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
24de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
25de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang/**
26de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang * Created by hoford on 5/15/15.
27de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang */
28de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wangpublic class XmpDepthDecode {
29de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final String TAG = "XmpUtil";
30de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final String XMP_DEPTHMAP = "http://ns.google.com/photos/1.0/depthmap/";
31de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final String XMP_FOCUS = "http://ns.google.com/photos/1.0/focus/";
32de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final String XMP_HEADER = "http://ns.adobe.com/xap/1.0/\0";
33de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final String XMP_EXTENSION_HEADER =
34de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            "http://ns.adobe.com/xmp/extension/\0";
35de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final String XMP_HAS_EXTENSION = "HasExtendedXMP";
36de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final int XMP_EXTENSION_HEADER_GUID_SIZE =
37de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            XMP_EXTENSION_HEADER.length() + 32 + 1; // 32 byte GUID + 1 byte null termination.
38de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final int XMP_EXTENSION_HEADER_OFFSET = 7;
39de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
40de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final int M_SOI = 0xd8; // File start marker.
41de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final int M_APP1 = 0xe1; // Marker for EXIF or XMP.
42de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static final int M_SOS = 0xda; // Image data marker.
43de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
44de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final String mFormat;
45de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mFar;
46de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mNear;
47de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final Bitmap mDepthBitmap;
48de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mBlurAtInfinity;
49de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mFocalDistance;
50de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mDepthOfFiled;
51de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mFocalPointX;
52de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final double mFocalPointY;
53de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private final DepthTransform mDepthTransform;
54de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
55de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public XmpDepthDecode(InputStream is) throws IOException {
56de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        XMPMeta meta = read(is, false);
57de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        try {
58de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mFormat = meta.getPropertyString(XMP_DEPTHMAP, "GDepth:Format");
59de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
60de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mFar = Double.parseDouble(meta.getPropertyString(XMP_DEPTHMAP, "GDepth:Far"));
61de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mNear = Double.parseDouble(meta.getPropertyString(XMP_DEPTHMAP, "GDepth:Near"));
62de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
63de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            DepthTransform tDepthTransform = null;
64de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            String format = meta.getPropertyString(
65de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    XMP_DEPTHMAP, "GDepth:Format");
66de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (RangeInverseDepthTransform.FORMAT.equals(format)) {
67de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang              tDepthTransform = new RangeInverseDepthTransform((float)mNear, (float)mFar);
68de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            } else if (RangeLinearDepthTransform.FORMAT.equals(format)) {
69de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang              tDepthTransform = new RangeLinearDepthTransform((float)mNear, (float)mFar);
70de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            } else {
71de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang              Log.e(TAG, "Unknown GDepth format: " + format);
72de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
73de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mDepthTransform = tDepthTransform;
74de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
75de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            byte[] data = meta.getPropertyBase64(XMP_DEPTHMAP, "GDepth:Data");
76de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mDepthBitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(data));
77de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
78de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mBlurAtInfinity = Double.parseDouble(meta.getPropertyString(XMP_FOCUS, "GFocus:BlurAtInfinity"));
79de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mFocalDistance = Double.parseDouble(meta.getPropertyString(XMP_FOCUS, "GFocus:FocalDistance"));
80de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mDepthOfFiled = Double.parseDouble(meta.getPropertyString(XMP_FOCUS, "GFocus:DepthOfField"));
81de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mFocalPointX = Double.parseDouble(meta.getPropertyString(XMP_FOCUS, "GFocus:FocalPointX"));
82de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            mFocalPointY = Double.parseDouble(meta.getPropertyString(XMP_FOCUS, "GFocus:FocalPointY"));
83de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } catch (XMPException e) {
84de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            throw new IOException("XMP data missing");
85de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
86de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
87de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
88de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public Bitmap getDepthBitmap() {
89de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mDepthBitmap;
90de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
91de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
92de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public DepthTransform getDepthTransform() { return  mDepthTransform; }
93de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
94de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public String getFormat() {
95de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mFormat;
96de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
97de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
98de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getFar() {
99de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mFar;
100de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
101de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
102de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getNear() {
103de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mNear;
104de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
105de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
106de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getBlurAtInfinity() {
107de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mBlurAtInfinity;
108de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
109de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
110de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getFocalDistance() {
111de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mFocalDistance;
112de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
113de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
114de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getDepthOfField() { return mDepthOfFiled; }
115de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
116de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getFocalPointX() {
117de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mFocalPointX;
118de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
119de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
120de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    public double getFocalPointY() {
121de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return mFocalPointY;
122de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
123de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
124de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
125de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    // JPEG file is composed of many sections and image data. This class is used
126de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    // to hold the section data from image file.
127de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static class Section {
128de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        public int marker;
129de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        public int length;
130de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        public byte[] data;
131de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
132de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
133de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    static XMPMeta read(InputStream is, boolean skipExtendedContent) {
134de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        List<Section> sections = parse(is, true, skipExtendedContent);
135de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (sections == null) {
136de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return null;
137de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
138de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
139de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        XMPMeta xmpMeta = parseFirstValidXMPSection(sections);
140de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (xmpMeta == null ||
141de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                !xmpMeta.doesPropertyExist(XMPConst.NS_XMP_NOTE, XMP_HAS_EXTENSION)) {
142de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return xmpMeta;
143de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
144de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
145de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        String extensionName = null;
146de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        try {
147de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            extensionName = (String) xmpMeta.getProperty(
148de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    XMPConst.NS_XMP_NOTE, XMP_HAS_EXTENSION).getValue();
149de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } catch (XMPException e) {
150de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            e.printStackTrace();
151de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return null;
152de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
153de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
154de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (skipExtendedContent) {
155de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (!checkExtendedSectionExists(sections, extensionName)) {
156de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                // The main XMP section referenced an extended section that is not present.
157de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                // This is an error.
158de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                return null;
159de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
160de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return xmpMeta;
161de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
162de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
163de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        XMPMeta xmpExtended = parseExtendedXMPSections(sections, extensionName);
164de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (xmpExtended == null) {
165de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            // The main XMP section referenced an extended section that is not present.
166de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            // This is an error.
167de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return null;
168de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
169de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
170de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        // Merge the extended properties into the main one.
171de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        try {
172de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            XMPIterator iterator = xmpExtended.iterator();
173de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            while (true) {
174de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                XMPPropertyInfo info = (XMPPropertyInfo) iterator.next();
175de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (info.getPath() != null) {
176de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    xmpMeta.setProperty(info.getNamespace(), info.getPath(),
177de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                            info.getValue(), info.getOptions());
178de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
179de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
180de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } catch (Exception e) {
181de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            // Catch XMPException and NoSuchElementException.
182de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
183de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return xmpMeta;
184de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
185de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
186de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    /**
187de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Parses the JPEG image file. If readMetaOnly is true, only keeps the EXIF
188de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * and XMP sections (with marker M_APP1) and ignore others; otherwise, keep
189de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * all sections. The last section with image data will have -1 length.
190de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     *
191de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param is                  Input image data stream
192de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param readMetaOnly        Whether only reads the metadata in jpg
193de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param skipExtendedContent Whether to skip the content of extended sections
194de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @return The parse result
195de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     */
196de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static List<Section> parse(InputStream is, boolean readMetaOnly,
197de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                                       boolean skipExtendedContent) {
198de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        List<Section> sections = new ArrayList<Section>();
199de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (is == null) {
200de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return sections;
201de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
202de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
203de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        try {
204de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (is.read() != 0xff || is.read() != M_SOI) {
205de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                return sections;
206de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
207de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            int c;
208de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            while ((c = is.read()) != -1) {
209de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (c != 0xff) {
210de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return sections;
211de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
212de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                // Skip padding bytes.
213de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                while ((c = is.read()) == 0xff) {
214de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
215de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (c == -1) {
216de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return sections;
217de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
218de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int marker = c;
219de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (marker == M_SOS) {
220de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    // M_SOS indicates the image data will follow and no metadata after
221de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    // that, so read all data at one time.
222de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    if (!readMetaOnly) {
223de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        Section section = new Section();
224de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        section.marker = marker;
225de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        section.length = -1;
226de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        section.data = new byte[is.available()];
227de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        is.read(section.data, 0, section.data.length);
228de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        sections.add(section);
229de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    }
230de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return sections;
231de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
232de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int lh = is.read();
233de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int ll = is.read();
234de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (lh == -1 || ll == -1) {
235de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return sections;
236de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
237de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int length = lh << 8 | ll;
238de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (!readMetaOnly || marker == M_APP1) {
239de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    sections.add(readSection(is, length, marker, skipExtendedContent));
240de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                } else {
241de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    // Skip this section since all EXIF/XMP meta will be in M_APP1
242de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    // section.
243de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    is.skip(length - 2);
244de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
245de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
246de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return sections;
247de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } catch (IOException e) {
248de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            System.out.println("Could not parse file." + e);
249de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return sections;
250de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } finally {
251de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (is != null) {
252de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                try {
253de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    is.close();
254de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                } catch (IOException e) {
255de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    // Ignore.
256de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
257de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
258de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
259de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
260de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
261de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    /**
262de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Checks whether the byte array has XMP header. The XMP section contains
263de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * a fixed length header XMP_HEADER.
264de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     *
265de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param data   XMP metadata
266de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param header The header to look for
267de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     */
268de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static boolean hasHeader(byte[] data, String header) {
269de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (data.length < header.length()) {
270de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return false;
271de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
272de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        try {
273de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            byte[] buffer = new byte[header.length()];
274de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            System.arraycopy(data, 0, buffer, 0, header.length());
275de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (new String(buffer, "UTF-8").equals(header)) {
276de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                return true;
277de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
278de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } catch (UnsupportedEncodingException e) {
279de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return false;
280de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
281de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return false;
282de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
283de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
284de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static Section readSection(InputStream is, int length,
285de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                                       int marker, boolean skipExtendedContent) throws IOException {
286de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (length - 2 < XMP_EXTENSION_HEADER_GUID_SIZE || !skipExtendedContent) {
287de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            Section section = new Section();
288de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            section.marker = marker;
289de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            section.length = length;
290de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            section.data = new byte[length - 2];
291de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            is.read(section.data, 0, length - 2);
292de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return section;
293de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
294de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
295de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        byte[] header = new byte[XMP_EXTENSION_HEADER_GUID_SIZE];
296de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        is.read(header, 0, header.length);
297de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
298de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (hasHeader(header, XMP_EXTENSION_HEADER) && skipExtendedContent) {
299de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            Section section = new Section();
300de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            section.marker = marker;
301de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            section.length = header.length + 2;
302de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            section.data = header;
303de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            is.skip(length - 2 - header.length);
304de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return section;
305de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
306de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
307de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        Section section = new Section();
308de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        section.marker = marker;
309de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        section.length = length;
310de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        section.data = new byte[length - 2];
311de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        System.arraycopy(header, 0, section.data, 0, header.length);
312de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        is.read(section.data, header.length, length - 2 - header.length);
313de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return section;
314de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
315de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
316de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    /**
317de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Gets the end of the XMP meta content. If there is no packet wrapper,
318de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * return data.length, otherwise return 1 + the position of last '>'
319de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * without '?' before it.
320de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Usually the packet wrapper end is "<?xpacket end="w"?> but
321de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * javax.xml.parsers.DocumentBuilder fails to parse it in android.
322de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     *
323de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param data XMP metadata bytes
324de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @return The end of the XMP metadata content
325de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     */
326de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static int getXMPContentEnd(byte[] data) {
327de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        for (int i = data.length - 1; i >= 1; --i) {
328de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (data[i] == '>') {
329de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                if (data[i - 1] != '?') {
330de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return i + 1;
331de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
332de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
333de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
334de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        // It should not reach here for a valid XMP meta.
335de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return data.length;
336de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
337de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
338de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    /**
339de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Parses the first valid XMP section. Any other valid XMP section will be
340de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * ignored.
341de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     *
342de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param sections The list of sections parse
343de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @return The parsed XMPMeta object
344de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     */
345de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static XMPMeta parseFirstValidXMPSection(List<Section> sections) {
346de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        for (Section section : sections) {
347de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (hasHeader(section.data, XMP_HEADER)) {
348de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int end = getXMPContentEnd(section.data);
349de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                byte[] buffer = new byte[end - XMP_HEADER.length()];
350de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                System.arraycopy(
351de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                        section.data, XMP_HEADER.length(), buffer, 0, buffer.length);
352de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                try {
353de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    XMPMeta result = XMPMetaFactory.parseFromBuffer(buffer);
354de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return result;
355de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                } catch (XMPException e) {
356de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    System.out.println("XMP parse error " + e);
357de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    return null;
358de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                }
359de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
360de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
361de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return null;
362de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
363de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
364de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    /**
365de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Checks there is an extended section with the given name.
366de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     *
367de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param sections    The list of sections to parse
368de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param sectionName The name of the extended sections
369de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @return Whether there is an extended section with the given name
370de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     */
371de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static boolean checkExtendedSectionExists(List<Section> sections,
372de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                                                      String sectionName) {
373de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        String extendedHeader = XMP_EXTENSION_HEADER + sectionName + "\0";
374de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        for (Section section : sections) {
375de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (hasHeader(section.data, extendedHeader)) {
376de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                return true;
377de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
378de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
379de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return false;
380de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
381de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
382de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    /**
383de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * Parses the extended XMP sections with the given name. All other sections
384de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * will be ignored.
385de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     *
386de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param sections    The list of sections to parse
387de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @param sectionName The name of the extended sections
388de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     * @return The parsed XMPMeta object
389de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang     */
390de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    private static XMPMeta parseExtendedXMPSections(List<Section> sections,
391de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                                                    String sectionName) {
392de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        String extendedHeader = XMP_EXTENSION_HEADER + sectionName + "\0";
393de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
394de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        // Compute the size of the buffer to parse the extended sections.
395de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        List<Section> xmpSections = new ArrayList<Section>();
396de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        List<Integer> xmpStartOffset = new ArrayList<Integer>();
397de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        List<Integer> xmpEndOffset = new ArrayList<Integer>();
398de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        int bufferSize = 0;
399de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        for (Section section : sections) {
400de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            if (hasHeader(section.data, extendedHeader)) {
401de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int startOffset = extendedHeader.length() + XMP_EXTENSION_HEADER_OFFSET;
402de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                int endOffset = section.data.length;
403de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                bufferSize += Math.max(0, section.data.length - startOffset);
404de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                xmpSections.add(section);
405de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                xmpStartOffset.add(startOffset);
406de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                xmpEndOffset.add(endOffset);
407de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            }
408de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
409de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        if (bufferSize == 0) {
410de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return null;
411de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
412de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
413de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        // Copy all the relevant sections' data into a buffer.
414de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        byte buffer[] = new byte[bufferSize];
415de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        int offset = 0;
416de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        for (int i = 0; i < xmpSections.size(); ++i) {
417de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            Section section = xmpSections.get(i);
418de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            int startOffset = xmpStartOffset.get(i);
419de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            int endOffset = xmpEndOffset.get(i);
420de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            int length = endOffset - startOffset;
421de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            System.arraycopy(
422de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang                    section.data, startOffset, buffer, offset, length);
423de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            offset += length;
424de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
425de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang
426de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        XMPMeta xmpExtended = null;
427de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        try {
428de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            xmpExtended = XMPMetaFactory.parseFromBuffer(buffer);
429de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        } catch (XMPException e) {
430de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            System.out.println("Extended XMP parse error " + e);
431de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang            return null;
432de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        }
433de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang        return xmpExtended;
434de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang    }
435de2f182fdb522689e05280e449a39ec2c1b53e1fCindy Wang}
436