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