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