blob: 39d8db1d226dbe093e985bd0b512dbaccee808a5 [file] [log] [blame]
/*
* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of The Linux Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.android.gallery3d.util;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.util.Base64;
import android.util.Xml;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.CountedDataInputStream;
import com.android.gallery3d.exif.JpegHeader;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
public class GDepth {
private static final String TAG = "GDepth";
private static final String GDEPTH = "http://ns.google.com/photos/1.0/depthmap/";
private static final String GIMAGE = "http://ns.google.com/photos/1.0/image/";
private static final String XMPNOTE = "http://ns.adobe.com/xmp/note/";
private static final String SIGNATURE_STD = "http://ns.adobe.com/xap/1.0/\0";
private static final String SIGNATURE_EXT = "http://ns.adobe.com/xmp/extension/\0";
public static class Parser {
private byte[] image;
private byte[] ddm;
private int roiX = 0;
private int roiY = 0;
private int roiWidth = 0;
private int roiHeight = 0;
public boolean parse(Context context, Uri uri) {
InputStream is = null;
if (uri == null) return valid();
try {
is = context.getContentResolver().openInputStream(uri);
XMPStream stream = new XMPStream(is);
parse(stream);
if (stream.prepareExtended()) {
parse(stream);
}
} catch (IOException ignored) {
} finally {
Utils.closeSilently(is);
}
return valid();
}
public boolean valid() {
return ddm != null && image != null;
}
private void parse(XMPStream stream) throws IOException {
XmlPullParser parser = Xml.newPullParser();
try {
parser.setInput(stream, null);
parser.nextTag();
int event = parser.next();
while (event != XmlPullParser.END_DOCUMENT) {
if (event == XmlPullParser.START_TAG && "Description".equals(parser.getName())) {
int count = parser.getAttributeCount();
for (int i = 0; i < count; ++i) {
String ns = parser.getAttributeNamespace(i);
if (GDEPTH.equals(ns)) {
String name = parser.getAttributeName(i);
String value = parser.getAttributeValue(i);
if ("Data".equals(name)) {
ddm = parseData(value);
} else if ("RoiX".equals(name)) {
roiX = Integer.parseInt(value);
} else if ("RoiY".equals(name)) {
roiY = Integer.parseInt(value);
} else if ("RoiWidth".equals(name)) {
roiWidth = Integer.parseInt(value);
} else if ("RoiHeight".equals(name)) {
roiHeight = Integer.parseInt(value);
}
} else if (GIMAGE.equals(ns)) {
if ("Data".equals(parser.getAttributeName(i))) {
image = parseData(parser.getAttributeValue(i));
}
} else if (XMPNOTE.equals(ns)) {
if ("HasExtendedXMP".equals(parser.getAttributeName(i))) {
stream.setExtended(parser.getAttributeValue(i));
}
}
}
}
event = parser.next();
}
} catch (XmlPullParserException ignored) {
}
}
private byte[] parseData(String base64data) {
return Base64.decode(base64data, Base64.DEFAULT);
}
// decode base64 encoded (and compressed) data into Bitmaps
public Image decode() {
Image img = new Image();
img.bitmap = BitmapFactory.decodeByteArray(image, 0, image.length);
Log.d(TAG, "bitmap: " + img.bitmap.getWidth() + "x" + img.bitmap.getHeight());
image = null;
img.depthMap = decodeDepthMap();
Log.d(TAG, "ddm: " + img.depthMap.getWidth() + "x" + img.depthMap.getHeight());
ddm = null;
if (roiWidth == 0 || roiHeight == 0) {
roiWidth = img.bitmap.getWidth();
roiHeight = img.bitmap.getHeight();
}
img.roi = new int[]{roiX, roiY, roiWidth, roiHeight};
return img;
}
private Bitmap decodeDepthMap() {
Bitmap bitmap = BitmapFactory.decodeByteArray(ddm, 0, ddm.length);
int width = bitmap.getWidth(), height = bitmap.getHeight();
int[] pixels = new int[width * height];
bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
Bitmap out = Bitmap.createBitmap(width, height, Bitmap.Config.ALPHA_8);
for (int i = 0; i < pixels.length; ++i) {
int p = pixels[i];
pixels[i] = (p & 0xff) << 24;
}
out.setPixels(pixels, 0, width, 0, 0, width, height);
return out;
}
}
public static class Image {
public Bitmap bitmap;
public Bitmap depthMap;
public int[] roi;
public boolean valid() {
return bitmap != null && depthMap != null && roi != null;
}
}
private static class XMPStream extends InputStream {
private static final int START = 0;
private static final int STANDARD = 1;
private static final int EXTEND = 2;
private static final int END = 3;
private CountedDataInputStream stream;
private String extended;
private int status = START;
private int chunk = 0;
XMPStream(InputStream is) {
stream = new CountedDataInputStream(is);
}
private boolean next() throws IOException {
while (true) {
short marker = stream.readShort();
if (marker == JpegHeader.EOI || JpegHeader.isSofMarker(marker)) {
status = END;
return false;
}
if (marker == JpegHeader.APP1) {
int length = stream.readUnsignedShort();
if (status == STANDARD) {
if (length < SIGNATURE_STD.length()) {
stream.skipOrThrow(length - 2);
} else {
String s = stream.readString(SIGNATURE_STD.length());
if (SIGNATURE_STD.equals(s)) {
chunk = length - SIGNATURE_STD.length() - 2;
return true;
} else {
stream.skipOrThrow(length - 2 - SIGNATURE_STD.length());
}
}
} else {
if (length < SIGNATURE_EXT.length()) {
stream.skipOrThrow(length - 2);
} else {
String s = stream.readString(SIGNATURE_EXT.length());
if (SIGNATURE_EXT.equals(s)) {
if (extended == null) return false;
String guid = stream.readString(32);
if (!extended.equals(guid)) continue;
// skip 2 32-bit integers
stream.skipOrThrow(8);
chunk = length - SIGNATURE_EXT.length() - 42;
return true;
} else {
stream.skipOrThrow(length - 2 - SIGNATURE_EXT.length());
}
}
}
}
}
}
void setExtended(String guid) {
extended = guid;
}
boolean prepareExtended() {
if (extended != null) {
status = EXTEND;
}
return extended != null;
}
@Override
public int read() throws IOException {
byte[] b = new byte[1];
int r = read(b, 0, 1);
return r == -1 ? r : b[0];
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (chunk > 0) {
int l = chunk > len ? len : chunk;
chunk -= l;
return stream.read(b, off, l);
}
if (status == START) {
if (stream.readShort() != JpegHeader.SOI) return -1;
status = STANDARD;
} else if (status == STANDARD || status == END) {
return -1;
}
if (next() && chunk > 0) {
int l = chunk > len ? len : chunk;
chunk -= l;
return stream.read(b, off, l);
}
return -1;
}
}
}