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