diff options
171 files changed, 6414 insertions, 5114 deletions
diff --git a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java index 837777e51..56adcb1e9 100644 --- a/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java +++ b/gallerycommon/src/com/android/gallery3d/common/ApiHelper.java @@ -170,6 +170,15 @@ public class ApiHelper { public static final boolean HAS_POST_ON_ANIMATION = Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + public static final boolean HAS_ANNOUNCE_FOR_ACCESSIBILITY = + Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; + + public static final boolean HAS_OBJECT_ANIMATION = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + + public static final boolean HAS_GLES20_REQUIRED = + Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; + public static int getIntFieldIfExists(Class<?> klass, String fieldName, Class<?> obj, int defaultVal) { try { diff --git a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java b/gallerycommon/src/com/android/gallery3d/common/ExifTags.java deleted file mode 100644 index 9b11fe416..000000000 --- a/gallerycommon/src/com/android/gallery3d/common/ExifTags.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.common; - -/** - * The class holds the EXIF tag names that are not available in - * {@link android.media.ExifInterface} prior to API level 11. - */ -public interface ExifTags { - static final String TAG_ISO = "ISOSpeedRatings"; - static final String TAG_EXPOSURE_TIME = "ExposureTime"; - static final String TAG_APERTURE = "FNumber"; -} diff --git a/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java b/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java deleted file mode 100644 index b3298e672..000000000 --- a/gallerycommon/src/com/android/gallery3d/common/LongSparseArray.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright (C) 2009 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.common; - - -// Copied from android.util.LongSparseArray for unbundling - -/** - * SparseArray mapping longs to Objects. Unlike a normal array of Objects, - * there can be gaps in the indices. It is intended to be more efficient - * than using a HashMap to map Longs to Objects. - */ -public class LongSparseArray<E> implements Cloneable { - private static final Object DELETED = new Object(); - private boolean mGarbage = false; - - private long[] mKeys; - private Object[] mValues; - private int mSize; - - /** - * Creates a new LongSparseArray containing no mappings. - */ - public LongSparseArray() { - this(10); - } - - /** - * Creates a new LongSparseArray containing no mappings that will not - * require any additional memory allocation to store the specified - * number of mappings. - */ - public LongSparseArray(int initialCapacity) { - initialCapacity = idealLongArraySize(initialCapacity); - - mKeys = new long[initialCapacity]; - mValues = new Object[initialCapacity]; - mSize = 0; - } - - @Override - @SuppressWarnings("unchecked") - public LongSparseArray<E> clone() { - LongSparseArray<E> clone = null; - try { - clone = (LongSparseArray<E>) super.clone(); - clone.mKeys = mKeys.clone(); - clone.mValues = mValues.clone(); - } catch (CloneNotSupportedException cnse) { - /* ignore */ - } - return clone; - } - - /** - * Gets the Object mapped from the specified key, or <code>null</code> - * if no such mapping has been made. - */ - public E get(long key) { - return get(key, null); - } - - /** - * Gets the Object mapped from the specified key, or the specified Object - * if no such mapping has been made. - */ - @SuppressWarnings("unchecked") - public E get(long key, E valueIfKeyNotFound) { - int i = binarySearch(mKeys, 0, mSize, key); - - if (i < 0 || mValues[i] == DELETED) { - return valueIfKeyNotFound; - } else { - return (E) mValues[i]; - } - } - - /** - * Removes the mapping from the specified key, if there was any. - */ - public void delete(long key) { - int i = binarySearch(mKeys, 0, mSize, key); - - if (i >= 0) { - if (mValues[i] != DELETED) { - mValues[i] = DELETED; - mGarbage = true; - } - } - } - - /** - * Alias for {@link #delete(long)}. - */ - public void remove(long key) { - delete(key); - } - - /** - * Removes the mapping at the specified index. - */ - public void removeAt(int index) { - if (mValues[index] != DELETED) { - mValues[index] = DELETED; - mGarbage = true; - } - } - - private void gc() { - // Log.e("SparseArray", "gc start with " + mSize); - - int n = mSize; - int o = 0; - long[] keys = mKeys; - Object[] values = mValues; - - for (int i = 0; i < n; i++) { - Object val = values[i]; - - if (val != DELETED) { - if (i != o) { - keys[o] = keys[i]; - values[o] = val; - values[i] = null; - } - - o++; - } - } - - mGarbage = false; - mSize = o; - - // Log.e("SparseArray", "gc end with " + mSize); - } - - /** - * Adds a mapping from the specified key to the specified value, - * replacing the previous mapping from the specified key if there - * was one. - */ - public void put(long key, E value) { - int i = binarySearch(mKeys, 0, mSize, key); - - if (i >= 0) { - mValues[i] = value; - } else { - i = ~i; - - if (i < mSize && mValues[i] == DELETED) { - mKeys[i] = key; - mValues[i] = value; - return; - } - - if (mGarbage && mSize >= mKeys.length) { - gc(); - - // Search again because indices may have changed. - i = ~binarySearch(mKeys, 0, mSize, key); - } - - if (mSize >= mKeys.length) { - int n = idealLongArraySize(mSize + 1); - - long[] nkeys = new long[n]; - Object[] nvalues = new Object[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - if (mSize - i != 0) { - // Log.e("SparseArray", "move " + (mSize - i)); - System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); - System.arraycopy(mValues, i, mValues, i + 1, mSize - i); - } - - mKeys[i] = key; - mValues[i] = value; - mSize++; - } - } - - /** - * Returns the number of key-value mappings that this LongSparseArray - * currently stores. - */ - public int size() { - if (mGarbage) { - gc(); - } - - return mSize; - } - - /** - * Given an index in the range <code>0...size()-1</code>, returns - * the key from the <code>index</code>th key-value mapping that this - * LongSparseArray stores. - */ - public long keyAt(int index) { - if (mGarbage) { - gc(); - } - - return mKeys[index]; - } - - /** - * Given an index in the range <code>0...size()-1</code>, returns - * the value from the <code>index</code>th key-value mapping that this - * LongSparseArray stores. - */ - @SuppressWarnings("unchecked") - public E valueAt(int index) { - if (mGarbage) { - gc(); - } - - return (E) mValues[index]; - } - - /** - * Given an index in the range <code>0...size()-1</code>, sets a new - * value for the <code>index</code>th key-value mapping that this - * LongSparseArray stores. - */ - public void setValueAt(int index, E value) { - if (mGarbage) { - gc(); - } - - mValues[index] = value; - } - - /** - * Returns the index for which {@link #keyAt} would return the - * specified key, or a negative number if the specified - * key is not mapped. - */ - public int indexOfKey(long key) { - if (mGarbage) { - gc(); - } - - return binarySearch(mKeys, 0, mSize, key); - } - - /** - * Returns an index for which {@link #valueAt} would return the - * specified key, or a negative number if no keys map to the - * specified value. - * Beware that this is a linear search, unlike lookups by key, - * and that multiple keys can map to the same value and this will - * find only one of them. - */ - public int indexOfValue(E value) { - if (mGarbage) { - gc(); - } - - for (int i = 0; i < mSize; i++) - if (mValues[i] == value) - return i; - - return -1; - } - - /** - * Removes all key-value mappings from this LongSparseArray. - */ - public void clear() { - int n = mSize; - Object[] values = mValues; - - for (int i = 0; i < n; i++) { - values[i] = null; - } - - mSize = 0; - mGarbage = false; - } - - /** - * Puts a key/value pair into the array, optimizing for the case where - * the key is greater than all existing keys in the array. - */ - public void append(long key, E value) { - if (mSize != 0 && key <= mKeys[mSize - 1]) { - put(key, value); - return; - } - - if (mGarbage && mSize >= mKeys.length) { - gc(); - } - - int pos = mSize; - if (pos >= mKeys.length) { - int n = idealLongArraySize(pos + 1); - - long[] nkeys = new long[n]; - Object[] nvalues = new Object[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - mKeys[pos] = key; - mValues[pos] = value; - mSize = pos + 1; - } - - private static int binarySearch(long[] a, int start, int len, long key) { - int high = start + len, low = start - 1, guess; - - while (high - low > 1) { - guess = (high + low) / 2; - - if (a[guess] < key) - low = guess; - else - high = guess; - } - - if (high == start + len) - return ~(start + len); - else if (a[high] == key) - return high; - else - return ~high; - } - - private static int idealByteArraySize(int need) { - for (int i = 4; i < 32; i++) - if (need <= (1 << i) - 12) - return (1 << i) - 12; - - return need; - } - - public static int idealLongArraySize(int need) { - return idealByteArraySize(need * 8) / 8; - } -} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java index 39eb57455..6e5c227d5 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java @@ -17,8 +17,12 @@ package com.android.gallery3d.exif; import java.nio.ByteOrder; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Calendar; +import java.util.TimeZone; /** * This class stores the EXIF header in IFDs according to the JPEG specification. @@ -27,6 +31,17 @@ import java.util.Arrays; * @see IfdData */ public class ExifData { + + private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; + private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"; + + private final DateFormat mDateTimeStampFormat = + new SimpleDateFormat(DATETIME_FORMAT_STR); + private final DateFormat mGPSDateStampFormat = + new SimpleDateFormat(GPS_DATE_FORMAT_STR); + private final Calendar mGPSTimeStampCalendar = Calendar.getInstance( + TimeZone.getTimeZone("UTC")); + private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; private byte[] mThumbnail; private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>(); @@ -34,6 +49,7 @@ public class ExifData { public ExifData(ByteOrder order) { mByteOrder = order; + mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); } IfdData getIfdData(int ifdId) { @@ -127,7 +143,9 @@ public class ExifData { } for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - if (!Util.equals(data.getIfdData(i), getIfdData(i))) return false; + IfdData ifd1 = data.getIfdData(i); + IfdData ifd2 = getIfdData(i); + if ((ifd1 != ifd2) && (ifd1 != null && !ifd1.equals(ifd2))) return false; } return true; } @@ -135,8 +153,9 @@ public class ExifData { } /** - * Adds {@link ExifTag#TAG_GPS_LATITUDE}, {@link ExifTag#TAG_GPS_LONGITUDE}, - * {@link ExifTag#TAG_GPS_LATITUDE_REF} and {@link ExifTag#TAG_GPS_LONGITUDE_REF} with the + * A convenient method to adds tags {@link ExifTag#TAG_GPS_LATITUDE}, + * {@link ExifTag#TAG_GPS_LONGITUDE}, {@link ExifTag#TAG_GPS_LATITUDE_REF} and + * {@link ExifTag#TAG_GPS_LONGITUDE_REF} at once with the * given latitude and longitude. */ public void addGpsTags(double latitude, double longitude) { @@ -167,6 +186,40 @@ public class ExifData { gpsIfd.setTag(longRefTag); } + /** + * A convenient method to add date or time related tags ( + * {@link ExifTag#TAG_DATE_TIME_DIGITIZED}, {@link ExifTag#TAG_DATE_TIME_ORIGINAL}, + * and {@link ExifTag#TAG_DATE_TIME}) with the given time stamp value. + * + */ + public void addDateTimeStampTag(short tagId, long timestamp, TimeZone timezone) { + if (tagId == ExifTag.TAG_DATE_TIME || + tagId == ExifTag.TAG_DATE_TIME_DIGITIZED || + tagId == ExifTag.TAG_DATE_TIME_ORIGINAL) { + mDateTimeStampFormat.setTimeZone(timezone); + addTag(tagId).setValue(mDateTimeStampFormat.format(timestamp)); + } else { + throw new IllegalArgumentException( + String.format("Tag %04x is not a supported date or time stamp tag", tagId)); + } + } + + /** + * A convenient method to add both {@link ExifTag#TAG_GPS_DATE_STAMP} + * and {@link ExifTag#TAG_GPS_TIME_STAMP}). + * Note that UTC timezone will be used as specified in the EXIF standard. + */ + public void addGpsDateTimeStampTag(long timestamp) { + addTag(ExifTag.TAG_GPS_DATE_STAMP).setValue(mGPSDateStampFormat.format(timestamp)); + + mGPSTimeStampCalendar.setTimeInMillis(timestamp); + addTag(ExifTag.TAG_GPS_TIME_STAMP). + setValue(new Rational[] { + new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1), + new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1), + new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)}); + } + private static Rational[] toExifLatLong(double value) { // convert to the format dd/1 mm/1 ssss/100 value = Math.abs(value); @@ -216,7 +269,8 @@ public class ExifData { } /** - * Adds a tag with the given tag ID. The original tag will be replaced by the new tag. For tags + * Adds a tag with the given tag ID. If the tag of the given ID already exists, + * the original tag will be returned. Otherwise, a new ExifTag will be created. For tags * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or * {@link #addThumbnailTag(short)} respectively. * @exception IllegalArgumentException if the tag ID is invalid. @@ -224,32 +278,43 @@ public class ExifData { public ExifTag addTag(short tagId) { int ifdId = ExifTag.getIfdIdFromTagId(tagId); IfdData ifdData = getOrCreateIfdData(ifdId); - ExifTag tag = ExifTag.buildTag(tagId); - ifdData.setTag(tag); + ExifTag tag = ifdData.getTag(tagId); + if (tag == null) { + tag = ExifTag.buildTag(tagId); + ifdData.setTag(tag); + } return tag; } /** - * Adds a thumbnail-related tag with the given tag ID. The original tag will be replaced - * by the new tag. + * Adds a thumbnail-related tag with the given tag ID. If the tag of the given ID + * already exists, the original tag will be returned. Otherwise, a new ExifTag will + * be created. * @exception IllegalArgumentException if the tag ID is invalid. */ public ExifTag addThumbnailTag(short tagId) { IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1); - ExifTag tag = ExifTag.buildThumbnailTag(tagId); - ifdData.setTag(tag); + ExifTag tag = ifdData.getTag(tagId); + if (tag == null) { + tag = ExifTag.buildThumbnailTag(tagId); + ifdData.setTag(tag); + } return tag; } /** - * Adds an interoperability-related tag with the given tag ID. The original tag will be - * replaced by the new tag. + * Adds an interoperability-related tag with the given tag ID. If the tag of the given ID + * already exists, the original tag will be returned. Otherwise, a new ExifTag will + * be created. * @exception IllegalArgumentException if the tag ID is invalid. */ public ExifTag addInteroperabilityTag(short tagId) { IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - ExifTag tag = ExifTag.buildInteroperabilityTag(tagId); - ifdData.setTag(tag); + ExifTag tag = ifdData.getTag(tagId); + if (tag == null) { + tag = ExifTag.buildInteroperabilityTag(tagId); + ifdData.setTag(tag); + } return tag; } @@ -258,4 +323,4 @@ public class ExifData { mStripBytes.clear(); mIfdDatas[IfdId.TYPE_IFD_1] = null; } -}
\ No newline at end of file +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java index b8db8e34c..51a30ffa2 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java @@ -215,15 +215,19 @@ public class ExifOutputStream extends FilterOutputStream { throws IOException { switch (tag.getDataType()) { case ExifTag.TYPE_ASCII: - dataOutputStream.write(tag.getString().getBytes()); - int remain = tag.getComponentCount() - tag.getString().length(); - for (int i = 0; i < remain; i++) { + byte buf[] = tag.getStringByte(); + if (buf.length == tag.getComponentCount()) { + buf[buf.length - 1] = 0; + dataOutputStream.write(buf); + } else { + dataOutputStream.write(buf); dataOutputStream.write(0); } break; case ExifTag.TYPE_LONG: + case ExifTag.TYPE_UNSIGNED_LONG: for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt(tag.getLong(i)); + dataOutputStream.writeInt((int) tag.getValueAt(i)); } break; case ExifTag.TYPE_RATIONAL: @@ -234,18 +238,13 @@ public class ExifOutputStream extends FilterOutputStream { break; case ExifTag.TYPE_UNDEFINED: case ExifTag.TYPE_UNSIGNED_BYTE: - byte[] buf = new byte[tag.getComponentCount()]; + buf = new byte[tag.getComponentCount()]; tag.getBytes(buf); dataOutputStream.write(buf); break; - case ExifTag.TYPE_UNSIGNED_LONG: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt((int) tag.getUnsignedLong(i)); - } - break; case ExifTag.TYPE_UNSIGNED_SHORT: for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeShort((short) tag.getUnsignedShort(i)); + dataOutputStream.writeShort((short) tag.getValueAt(i)); } break; } diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java index f1e52c5b3..2cff12a3d 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java @@ -16,8 +16,9 @@ package com.android.gallery3d.exif; +import android.util.Log; + import java.io.DataInputStream; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; @@ -64,6 +65,7 @@ import java.util.TreeMap; * </pre> */ public class ExifParser { + private static final String TAG = "ExifParser"; /** * When the parser reaches a new IFD area. Call * {@link #getCurrentIfd()} to know which IFD we are in. @@ -133,6 +135,10 @@ public class ExifParser { private static final int TAG_SIZE = 12; private static final int OFFSET_SIZE = 2; + private static final Charset US_ASCII = Charset.forName("US-ASCII"); + + private static final int DEFAULT_IFD0_OFFSET = 8; + private final CountedDataInputStream mTiffStream; private final int mOptions; private int mIfdStartOffset = 0; @@ -145,6 +151,9 @@ public class ExifParser { private ExifTag mJpegSizeTag; private boolean mNeedToParseOffsetsInCurrentIfd; private boolean mContainExifData = false; + private int mApp1End; + private byte[] mDataAboveIfd0; + private int mIfd0Position; private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>(); @@ -174,10 +183,17 @@ public class ExifParser { mTiffStream = new CountedDataInputStream(inputStream); mOptions = options; if (!mContainExifData) return; - if (mTiffStream.getReadByteCount() == 0) { - parseTiffHeader(); - long offset = mTiffStream.readUnsignedInt(); - registerIfd(IfdId.TYPE_IFD_0, offset); + + parseTiffHeader(); + long offset = mTiffStream.readUnsignedInt(); + if (offset > Integer.MAX_VALUE) { + throw new ExifInvalidFormatException("Invalid offset " + offset); + } + mIfd0Position = (int) offset; + registerIfd(IfdId.TYPE_IFD_0, offset); + if (offset != DEFAULT_IFD0_OFFSET) { + mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; + read(mDataAboveIfd0); } } @@ -225,33 +241,59 @@ public class ExifParser { int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; if (offset < endOfTags) { mTag = readTag(); + if (mTag == null) { + return next(); + } if (mNeedToParseOffsetsInCurrentIfd) { checkOffsetOrImageTag(mTag); } return EVENT_NEW_TAG; } else if (offset == endOfTags) { - long ifdOffset = readUnsignedLong(); // There is a link to ifd1 at the end of ifd0 if (mIfdType == IfdId.TYPE_IFD_0) { + long ifdOffset = readUnsignedLong(); if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) { if (ifdOffset != 0) { registerIfd(IfdId.TYPE_IFD_1, ifdOffset); } } } else { - if (ifdOffset != 0) { - throw new ExifInvalidFormatException("Invalid link to next IFD"); + int offsetSize = 4; + // Some camera models use invalid length of the offset + if (mCorrespondingEvent.size() > 0) { + offsetSize = mCorrespondingEvent.firstEntry().getKey() - + mTiffStream.getReadByteCount(); + } + if (offsetSize < 4) { + Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize); + } else { + long ifdOffset = readUnsignedLong(); + if (ifdOffset != 0) { + Log.w(TAG, "Invalid link to next IFD: " + ifdOffset); + } } } } while(mCorrespondingEvent.size() != 0) { Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry(); Object event = entry.getValue(); - skipTo(entry.getKey()); + try { + skipTo(entry.getKey()); + } catch (IOException e) { + Log.w(TAG, "Failed to skip to data at: " + entry.getKey() + + " for " + event.getClass().getName() + ", the file may be broken."); + continue; + } if (event instanceof IfdEvent) { mIfdType = ((IfdEvent) event).ifd; mNumOfTagInIfd = mTiffStream.readUnsignedShort(); mIfdStartOffset = entry.getKey(); + + if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) { + Log.w(TAG, "Invalid size of IFD " + mIfdType); + return EVENT_END; + } + mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd(); if (((IfdEvent) event).isRequested) { return EVENT_START_OF_IFD; @@ -290,8 +332,9 @@ public class ExifParser { if (mNeedToParseOffsetsInCurrentIfd) { while (offset < endOfTags) { mTag = readTag(); - checkOffsetOrImageTag(mTag); offset += TAG_SIZE; + if (mTag == null) continue; + checkOffsetOrImageTag(mTag); } } else { skipTo(endOfTags); @@ -342,7 +385,6 @@ public class ExifParser { * @see #read(byte[], int, int) * @see #readLong() * @see #readRational() - * @see #readShort() * @see #readString(int) * @see #readString(int, Charset) */ @@ -393,13 +435,7 @@ public class ExifParser { */ public int getStripSize() { if (mStripSizeTag == null) return 0; - if (mStripSizeTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - return mStripSizeTag.getUnsignedShort(mImageEvent.stripIndex); - } else { - // Cast unsigned int to int since the strip size is always smaller - // than the size of APP1 (65536) - return (int) mStripSizeTag.getUnsignedLong(mImageEvent.stripIndex); - } + return (int) mStripSizeTag.getValueAt(0); } /** @@ -408,9 +444,7 @@ public class ExifParser { */ public int getCompressedImageSize() { if (mJpegSizeTag == null) return 0; - // Cast unsigned int to int since the thumbnail is always smaller - // than the size of APP1 (65536) - return (int) mJpegSizeTag.getUnsignedLong(0); + return (int) mJpegSizeTag.getValueAt(0); } private void skipTo(int offset) throws IOException { @@ -425,7 +459,6 @@ public class ExifParser { * the tag may not contain the value if the size of the value is greater than 4 bytes. * When the value is not available here, call this method so that the parser will emit * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area where the value is located. - * @see #EVENT_VALUE_OF_REGISTERED_TAG */ public void registerForTagValue(ExifTag tag) { @@ -455,6 +488,12 @@ public class ExifParser { throw new ExifInvalidFormatException( "Number of component is larger then Integer.MAX_VALUE"); } + // Some invalid image file contains invalid data type. Ignore those tags + if (!ExifTag.isValidType(dataFormat)) { + Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat)); + mTiffStream.skip(4); + return null; + } ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType); int dataSize = tag.getDataSize(); if (dataSize > 4) { @@ -463,7 +502,16 @@ public class ExifParser { throw new ExifInvalidFormatException( "offset is larger then Integer.MAX_VALUE"); } - tag.setOffset((int) offset); + // Some invalid images put some undefined data before IFD0. + // Read the data here. + if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) { + byte[] buf = new byte[(int) numOfComp]; + System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET, + buf, 0, (int) numOfComp); + tag.setValue(buf); + } else { + tag.setOffset((int) offset); + } } else { readFullTagValue(tag); mTiffStream.skip(4 - dataSize); @@ -476,26 +524,30 @@ public class ExifParser { * caller is interested in, register the IFD or image. */ private void checkOffsetOrImageTag(ExifTag tag) { + // Some invalid formattd image contains tag with 0 size. + if (tag.getComponentCount() == 0) { + return; + } switch (tag.getTagId()) { case ExifTag.TAG_EXIF_IFD: if (isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_EXIF, tag.getUnsignedLong(0)); + registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); } break; case ExifTag.TAG_GPS_IFD: if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { - registerIfd(IfdId.TYPE_IFD_GPS, tag.getUnsignedLong(0)); + registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); } break; case ExifTag.TAG_INTEROPERABILITY_IFD: if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getUnsignedLong(0)); + registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); } break; case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT: if (isThumbnailRequested()) { - registerCompressedImage(tag.getUnsignedLong(0)); + registerCompressedImage(tag.getValueAt(0)); } break; case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: @@ -508,9 +560,9 @@ public class ExifParser { if (tag.hasValue()) { for (int i = 0; i < tag.getComponentCount(); i++) { if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - registerUncompressedStrip(i, tag.getUnsignedShort(i)); + registerUncompressedStrip(i, tag.getValueAt(i)); } else { - registerUncompressedStrip(i, tag.getUnsignedLong(i)); + registerUncompressedStrip(i, tag.getValueAt(i)); } } } else { @@ -528,7 +580,22 @@ public class ExifParser { } } - private void readFullTagValue(ExifTag tag) throws IOException { + void readFullTagValue(ExifTag tag) throws IOException { + // Some invalid images contains tags with wrong size, check it here + short type = tag.getDataType(); + if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED || + type == ExifTag.TYPE_UNSIGNED_BYTE) { + int size = tag.getComponentCount(); + if (mCorrespondingEvent.size() > 0) { + if (mCorrespondingEvent.firstEntry().getKey() < + mTiffStream.getReadByteCount() + size) { + Log.w(TAG, "Invalid size of tag."); + size = mCorrespondingEvent.firstEntry().getKey() + - mTiffStream.getReadByteCount(); + tag.setComponentCount(size); + } + } + } switch(tag.getDataType()) { case ExifTag.TYPE_UNSIGNED_BYTE: case ExifTag.TYPE_UNDEFINED: @@ -610,29 +677,36 @@ public class ExifParser { ExifInvalidFormatException { DataInputStream dataStream = new DataInputStream(inputStream); - // SOI and APP1 if (dataStream.readShort() != JpegHeader.SOI) { throw new ExifInvalidFormatException("Invalid JPEG format"); } short marker = dataStream.readShort(); - while(marker != JpegHeader.APP1 && marker != JpegHeader.EOI + while(marker != JpegHeader.EOI && !JpegHeader.isSofMarker(marker)) { int length = dataStream.readUnsignedShort(); - if ((length - 2) != dataStream.skip(length - 2)) { - throw new EOFException(); + // Some invalid formatted image contains multiple APP1, + // try to find the one with Exif data. + if (marker == JpegHeader.APP1) { + int header = 0; + short headerTail = 0; + if (length >= 8) { + header = dataStream.readInt(); + headerTail = dataStream.readShort(); + length -= 6; + if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { + mApp1End = length; + return true; + } + } + } + if (length < 2 || (length - 2) != dataStream.skip(length - 2)) { + Log.w(TAG, "Invalid JPEG format."); + return false; } marker = dataStream.readShort(); } - - if (marker != JpegHeader.APP1) return false; // No APP1 segment - - // APP1 length, it's not used for us - dataStream.readShort(); - - // Exif header - return (dataStream.readInt() == EXIF_HEADER - && dataStream.readShort() == EXIF_HEADER_TAIL); + return false; } /** @@ -650,27 +724,26 @@ public class ExifParser { } /** - * Reads a String from the InputStream with UTF8 charset. + * Reads a String from the InputStream with US-ASCII charset. + * The parser will read n bytes and convert it to ascii string. * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. */ public String readString(int n) throws IOException { - if (n > 0) { - byte[] buf = new byte[n]; - mTiffStream.readOrThrow(buf); - return new String(buf, 0, n - 1, "UTF8"); - } else { - return ""; - } + return readString(n, US_ASCII); } /** * Reads a String from the InputStream with the given charset. + * The parser will read n bytes and convert it to string. * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. */ public String readString(int n, Charset charset) throws IOException { - byte[] buf = new byte[n]; - mTiffStream.readOrThrow(buf); - return new String(buf, 0, n - 1, charset); + if (n > 0) { + byte[] buf = new byte[n]; + return mTiffStream.readString(n, charset); + } else { + return ""; + } } /** @@ -749,4 +822,4 @@ public class ExifParser { public ByteOrder getByteOrder() { return mTiffStream.getByteOrder(); } -}
\ No newline at end of file +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java index d8083b2dd..5bce9c496 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java @@ -16,6 +16,8 @@ package com.android.gallery3d.exif; +import android.util.Log; + import java.io.IOException; import java.io.InputStream; @@ -23,6 +25,7 @@ import java.io.InputStream; * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}. */ public class ExifReader { + private static final String TAG = "ExifReader"; /** * Parses the inputStream and and returns the EXIF data in an {@link ExifData}. * @throws ExifInvalidFormatException @@ -50,25 +53,28 @@ public class ExifReader { case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: tag = parser.getTag(); if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { - byte[] buf = new byte[tag.getComponentCount()]; - parser.read(buf); - tag.setValue(buf); + parser.readFullTagValue(tag); } exifData.getIfdData(tag.getIfd()).setTag(tag); break; case ExifParser.EVENT_COMPRESSED_IMAGE: byte buf[] = new byte[parser.getCompressedImageSize()]; - parser.read(buf); - exifData.setCompressedThumbnail(buf); + if (buf.length == parser.read(buf)) { + exifData.setCompressedThumbnail(buf); + } else { + Log.w(TAG, "Failed to read the compressed thumbnail"); + } break; case ExifParser.EVENT_UNCOMPRESSED_STRIP: buf = new byte[parser.getStripSize()]; - parser.read(buf); - exifData.setStripBytes(parser.getStripIndex(), buf); + if (buf.length == parser.read(buf)) { + exifData.setStripBytes(parser.getStripIndex(), buf); + Log.w(TAG, "Failed to read the strip bytes"); + } break; } event = parser.next(); } return exifData; } -}
\ No newline at end of file +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java index 49cb6edbc..cda67c2e2 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java @@ -18,6 +18,7 @@ package com.android.gallery3d.exif; import android.util.SparseArray; +import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; @@ -156,7 +157,7 @@ public class ExifTag { public static final short TAG_GPS_DEST_DISTANCE = 26; public static final short TAG_GPS_PROCESSING_METHOD = 27; public static final short TAG_GPS_AREA_INFORMATION = 28; - public static final short TAG_GPS_DATA_STAMP = 29; + public static final short TAG_GPS_DATE_STAMP = 29; public static final short TAG_GPS_DIFFERENTIAL = 30; // Interoperability tag @@ -827,12 +828,13 @@ public class ExifTag { (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); sTagInfo.put(TAG_GPS_AREA_INFORMATION, (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_GPS_DATA_STAMP, + sTagInfo.put(TAG_GPS_DATE_STAMP, (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 11); sTagInfo.put(TAG_GPS_DIFFERENTIAL, (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_SHORT << 16 | 11); } + private static Charset US_ASCII = Charset.forName("US-ASCII"); private final short mTagId; private final short mDataType; private final int mIfd; @@ -911,6 +913,13 @@ public class ExifTag { IfdId.TYPE_IFD_INTEROPERABILITY); } + static boolean isValidType(short type) { + return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII || + type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG || + type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED || + type == TYPE_LONG || type == TYPE_RATIONAL; + } + ExifTag(short tagId, short type, int componentCount, int ifd) { mTagId = tagId; mDataType = type; @@ -970,6 +979,15 @@ public class ExifTag { } /** + * Sets the component count of this tag. + * Call this function before setValue() if the length of value does not + * match the component count. + */ + public void setComponentCount(int count) { + mComponentCount = count; + } + + /** * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value * that links to the area where the actual value is located. * @@ -1171,18 +1189,37 @@ public class ExifTag { } /** - * Sets string values into this tag. + * Sets a string value into this tag. The value is treated as an ASCII string where we only + * preserve the lower byte of each character. The length of the string should be equal + * to either (component count -1) or (component count). A "0" byte will be appeneded while + * written to the EXIF file. If the length equals (component count), the final byte will be + * replaced by a "0" byte. + * * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII} - * or value.length() + 1 does NOT fit the definition of the component count in the - * EXIF standard. + * or the length of the string is not equal to (component count -1) and (component count) */ public void setValue(String value) { - checkComponentCountOrThrow(value.length() + 1); if (mDataType != TYPE_ASCII) { throwTypeNotMatchedException("String"); } - mComponentCount = value.length() + 1; - mValue = value; + + byte[] buf = new byte[value.length()]; + for (int i = 0, n = value.length(); i < n; i++) { + buf[i] = (byte) value.charAt(i); + } + + int count = buf.length; + if (mComponentCountDefined) { + if (mComponentCount != count && mComponentCount != count + 1) { + throw new IllegalArgumentException("Tag " + mTagId + ": Required " + + mComponentCount + " or " + (mComponentCount + 1) + + " components but was given " + count + + " component(s)"); + } + } else { + mComponentCount = buf[count - 1] == 0 ? count : count + 1; + } + mValue = buf; } /** @@ -1249,7 +1286,8 @@ public class ExifTag { setValue(value, 0, value.length); } - private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); + private static final SimpleDateFormat TIME_FORMAT = + new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); /** * Sets a timestamp to this tag. The method converts the timestamp with the format of @@ -1265,41 +1303,23 @@ public class ExifTag { setValue(TIME_FORMAT.format(new Date(time))); } } - - /** - * Gets the {@link #TYPE_UNSIGNED_SHORT} data. - * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_SHORT}. - */ - public int getUnsignedShort(int index) { - if (mDataType != TYPE_UNSIGNED_SHORT) { - throw new IllegalArgumentException("Cannot get UNSIGNED_SHORT value from " - + convertTypeToString(mDataType)); - } - return (int) (((long[]) mValue) [index]); - } - - /** - * Gets the {@link #TYPE_LONG} data. - * @exception IllegalArgumentException If the type is NOT {@link #TYPE_LONG}. - */ - public int getLong(int index) { - if (mDataType != TYPE_LONG) { - throw new IllegalArgumentException("Cannot get LONG value from " - + convertTypeToString(mDataType)); - } - return (int) (((long[]) mValue) [index]); - } - /** - * Gets the {@link #TYPE_UNSIGNED_LONG} data. - * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNSIGNED_LONG}. + * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG}, + * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE}, {@link #TYPE_UNSIGNED_LONG}, or + * {@link #TYPE_UNSIGNED_SHORT}. For {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, + * call {@link #getRational(int)} instead. + * + * @exception IllegalArgumentException if the data type is {@link #TYPE_RATIONAL} or + * {@link #TYPE_UNSIGNED_RATIONAL}. */ - public long getUnsignedLong(int index) { - if (mDataType != TYPE_UNSIGNED_LONG) { - throw new IllegalArgumentException("Cannot get UNSIGNED LONG value from " - + convertTypeToString(mDataType)); + public long getValueAt(int index) { + if (mValue instanceof long[]) { + return ((long[]) mValue) [index]; + } else if (mValue instanceof byte[]) { + return ((byte[]) mValue) [index]; } - return ((long[]) mValue) [index]; + throw new IllegalArgumentException("Cannot get integer value from " + + convertTypeToString(mDataType)); } /** @@ -1311,7 +1331,14 @@ public class ExifTag { throw new IllegalArgumentException("Cannot get ASCII value from " + convertTypeToString(mDataType)); } - return (String) mValue; + return new String((byte[]) mValue, US_ASCII); + } + + /* + * Get the converted ascii byte. Used by ExifOutputStream. + */ + byte[] getStringByte() { + return (byte[]) mValue; } /** @@ -1355,54 +1382,6 @@ public class ExifTag { } /** - * Returns a string representation of the value of this tag. - */ - public String valueToString() { - StringBuilder sbuilder = new StringBuilder(); - switch (getDataType()) { - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_UNSIGNED_BYTE: - byte buf[] = new byte[getComponentCount()]; - getBytes(buf); - for(int i = 0, n = getComponentCount(); i < n; i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(String.format("%02x", buf[i])); - } - break; - case ExifTag.TYPE_ASCII: - sbuilder.append(getString()); - break; - case ExifTag.TYPE_UNSIGNED_LONG: - for(int i = 0, n = getComponentCount(); i < n; i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(getUnsignedLong(i)); - } - break; - case ExifTag.TYPE_RATIONAL: - case ExifTag.TYPE_UNSIGNED_RATIONAL: - for(int i = 0, n = getComponentCount(); i < n; i++) { - Rational r = getRational(i); - if(i != 0) sbuilder.append(" "); - sbuilder.append(r.getNominator()).append("/").append(r.getDenominator()); - } - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - for(int i = 0, n = getComponentCount(); i < n; i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(getUnsignedShort(i)); - } - break; - case ExifTag.TYPE_LONG: - for(int i = 0, n = getComponentCount(); i < n; i++) { - if(i != 0) sbuilder.append(" "); - sbuilder.append(getLong(i)); - } - break; - } - return sbuilder.toString(); - } - - /** * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD}, * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT}, * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD} @@ -1415,6 +1394,15 @@ public class ExifTag { || tagId == TAG_INTEROPERABILITY_IFD; } + /** + * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD}, + * {@link #TAG_GPS_IFD}, {@link #TAG_INTEROPERABILITY_IFD} + */ + static boolean isSubIfdOffsetTag(short tagId) { + return tagId == TAG_EXIF_IFD + || tagId == TAG_GPS_IFD + || tagId == TAG_INTEROPERABILITY_IFD; + } @Override public boolean equals(Object obj) { if (obj instanceof ExifTag) { diff --git a/gallerycommon/src/com/android/gallery3d/exif/Rational.java b/gallerycommon/src/com/android/gallery3d/exif/Rational.java index 7d9026261..202c5d46d 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/Rational.java +++ b/gallerycommon/src/com/android/gallery3d/exif/Rational.java @@ -42,4 +42,13 @@ public class Rational { } return false; } + + @Override + public String toString() { + return mNominator + "/" + mDenominator; + } + + public double toDouble() { + return mNominator / (double) mDenominator; + } }
\ No newline at end of file diff --git a/gallerycommon/src/com/android/gallery3d/exif/Util.java b/gallerycommon/src/com/android/gallery3d/exif/Util.java deleted file mode 100644 index 594d6fc7f..000000000 --- a/gallerycommon/src/com/android/gallery3d/exif/Util.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.exif; - -import java.io.Closeable; - -class Util { - public static boolean equals(Object a, Object b) { - return (a == b) || (a == null ? false : a.equals(b)); - } - - public static void closeSilently(Closeable c) { - if (c == null) return; - try { - c.close(); - } catch (Throwable t) { - // do nothing - } - } -} diff --git a/src/com/android/gallery3d/anim/AlphaAnimation.java b/src/com/android/gallery3d/anim/AlphaAnimation.java index cb17527b8..f9f4cbd2c 100644 --- a/src/com/android/gallery3d/anim/AlphaAnimation.java +++ b/src/com/android/gallery3d/anim/AlphaAnimation.java @@ -17,7 +17,7 @@ package com.android.gallery3d.anim; import com.android.gallery3d.common.Utils; -import com.android.gallery3d.ui.GLCanvas; +import com.android.gallery3d.glrenderer.GLCanvas; public class AlphaAnimation extends CanvasAnimation { private final float mStartAlpha; diff --git a/src/com/android/gallery3d/anim/CanvasAnimation.java b/src/com/android/gallery3d/anim/CanvasAnimation.java index 4c8bcc825..cdc66c6ba 100644 --- a/src/com/android/gallery3d/anim/CanvasAnimation.java +++ b/src/com/android/gallery3d/anim/CanvasAnimation.java @@ -16,7 +16,7 @@ package com.android.gallery3d.anim; -import com.android.gallery3d.ui.GLCanvas; +import com.android.gallery3d.glrenderer.GLCanvas; public abstract class CanvasAnimation extends Animation { diff --git a/src/com/android/gallery3d/anim/StateTransitionAnimation.java b/src/com/android/gallery3d/anim/StateTransitionAnimation.java index cf04d2cc8..bf8a54405 100644 --- a/src/com/android/gallery3d/anim/StateTransitionAnimation.java +++ b/src/com/android/gallery3d/anim/StateTransitionAnimation.java @@ -20,9 +20,9 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; -import com.android.gallery3d.ui.GLCanvas; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.RawTexture; import com.android.gallery3d.ui.GLView; -import com.android.gallery3d.ui.RawTexture; import com.android.gallery3d.ui.TiledScreenNail; public class StateTransitionAnimation extends Animation { diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java index 88ac028e1..acfc033b7 100644 --- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java +++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java @@ -20,14 +20,17 @@ import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.res.Configuration; import android.os.Bundle; +import android.os.IBinder; import android.view.Menu; import android.view.MenuItem; import android.view.Window; @@ -40,8 +43,8 @@ import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRootView; -import com.android.gallery3d.util.ThreadPool; import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper; +import com.android.gallery3d.util.ThreadPool; public class AbstractGalleryActivity extends Activity implements GalleryContext { @SuppressWarnings("unused") @@ -71,6 +74,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext getWindow().setBackgroundDrawable(null); mPanoramaViewHelper = new PanoramaViewHelper(this); mPanoramaViewHelper.onCreate(); + doBindBatchService(); } @Override @@ -237,6 +241,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext } finally { mGLRootView.unlockRenderThread(); } + doUnbindBatchService(); } @Override @@ -308,4 +313,39 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext return (getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0; } + + private BatchService mBatchService; + private boolean mBatchServiceIsBound = false; + private ServiceConnection mBatchServiceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + mBatchService = ((BatchService.LocalBinder)service).getService(); + } + + public void onServiceDisconnected(ComponentName className) { + mBatchService = null; + } + }; + + private void doBindBatchService() { + bindService(new Intent(this, BatchService.class), mBatchServiceConnection, Context.BIND_AUTO_CREATE); + mBatchServiceIsBound = true; + } + + private void doUnbindBatchService() { + if (mBatchServiceIsBound) { + // Detach our existing connection. + unbindService(mBatchServiceConnection); + mBatchServiceIsBound = false; + } + } + + public ThreadPool getBatchServiceThreadPoolIfAvailable() { + if (mBatchServiceIsBound && mBatchService != null) { + return mBatchService.getThreadPool(); + } else { + // Fall back on the old behavior if for some reason the + // service is not available. + return getThreadPool(); + } + } } diff --git a/src/com/android/gallery3d/app/ActivityState.java b/src/com/android/gallery3d/app/ActivityState.java index cdd91ff4d..2f1e0c9d9 100644 --- a/src/com/android/gallery3d/app/ActivityState.java +++ b/src/com/android/gallery3d/app/ActivityState.java @@ -19,15 +19,13 @@ package com.android.gallery3d.app; import android.app.ActionBar; import android.app.Activity; import android.content.BroadcastReceiver; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.os.BatteryManager; import android.os.Bundle; -import android.provider.Settings; -import android.provider.Settings.SettingNotFoundException; +import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -36,9 +34,9 @@ import android.view.WindowManager; import com.android.gallery3d.R; import com.android.gallery3d.anim.StateTransitionAnimation; +import com.android.gallery3d.glrenderer.RawTexture; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.PreparePageFadeoutTexture; -import com.android.gallery3d.ui.RawTexture; import com.android.gallery3d.util.GalleryUtils; abstract public class ActivityState { @@ -62,9 +60,6 @@ abstract public class ActivityState { public Intent resultData; } - protected boolean mHapticsEnabled; - private ContentResolver mContentResolver; - private boolean mDestroyed = false; private boolean mPlugged = false; boolean mIsFinishing = false; @@ -92,7 +87,6 @@ abstract public class ActivityState { void initialize(AbstractGalleryActivity activity, Bundle data) { mActivity = activity; mData = data; - mContentResolver = activity.getAndroidContext().getContentResolver(); } public Bundle getData() { @@ -175,15 +169,20 @@ abstract public class ActivityState { protected void transitionOnNextPause(Class<? extends ActivityState> outgoing, Class<? extends ActivityState> incoming, StateTransitionAnimation.Transition hint) { - if (outgoing == PhotoPage.class && incoming == AlbumPage.class) { + if (outgoing == SinglePhotoPage.class && incoming == AlbumPage.class) { mNextTransition = StateTransitionAnimation.Transition.Outgoing; - } else if (outgoing == AlbumPage.class && incoming == PhotoPage.class) { + } else if (outgoing == AlbumPage.class && incoming == SinglePhotoPage.class) { mNextTransition = StateTransitionAnimation.Transition.PhotoIncoming; } else { mNextTransition = hint; } } + protected void performHapticFeedback(int feedbackConstant) { + mActivity.getWindow().getDecorView().performHapticFeedback(feedbackConstant, + HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + protected void onPause() { if (0 != (mFlags & FLAG_SCREEN_ON_WHEN_PLUGGED)) { ((Activity) mActivity).unregisterReceiver(mPowerIntentReceiver); @@ -231,13 +230,6 @@ abstract public class ActivityState { activity.registerReceiver(mPowerIntentReceiver, filter); } - try { - mHapticsEnabled = Settings.System.getInt(mContentResolver, - Settings.System.HAPTIC_FEEDBACK_ENABLED) != 0; - } catch (SettingNotFoundException e) { - mHapticsEnabled = false; - } - onResume(); // the transition store should be cleared after resume; diff --git a/src/com/android/gallery3d/app/AlbumDataLoader.java b/src/com/android/gallery3d/app/AlbumDataLoader.java index 0ee1b03af..28a822830 100644 --- a/src/com/android/gallery3d/app/AlbumDataLoader.java +++ b/src/com/android/gallery3d/app/AlbumDataLoader.java @@ -120,8 +120,7 @@ public class AlbumDataLoader { public MediaItem get(int index) { if (!isActive(index)) { - throw new IllegalArgumentException(String.format( - "%s not in (%s, %s)", index, mActiveStart, mActiveEnd)); + return mSource.getMediaItem(index, 1).get(0); } return mData[index % mData.length]; } diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java index ee7a107fd..dc2adf78f 100644 --- a/src/com/android/gallery3d/app/AlbumPage.java +++ b/src/com/android/gallery3d/app/AlbumPage.java @@ -24,8 +24,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; import android.provider.MediaStore; +import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -40,13 +40,15 @@ import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.MtpDevice; import com.android.gallery3d.data.Path; +import com.android.gallery3d.filtershow.CropExtras; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.glrenderer.FadeTexture; +import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.ui.ActionModeHandler; import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener; import com.android.gallery3d.ui.AlbumSlotRenderer; import com.android.gallery3d.ui.DetailsHelper; import com.android.gallery3d.ui.DetailsHelper.CloseListener; -import com.android.gallery3d.ui.FadeTexture; -import com.android.gallery3d.ui.GLCanvas; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.PhotoFallbackEffect; @@ -58,6 +60,7 @@ import com.android.gallery3d.util.Future; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.MediaSetUtils; + public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, SelectionManager.SelectionListener, MediaSet.SyncListener, GalleryActionBar.OnAlbumModeSelectedListener { @SuppressWarnings("unused") @@ -89,7 +92,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster private AlbumDataLoader mAlbumDataAdapter; protected SelectionManager mSelectionManager; - private Vibrator mVibrator; private boolean mGetContent; private boolean mShowClusterMenu; @@ -304,10 +306,10 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster startInFilmstrip); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, mMediaSet.isCameraRoll()); if (startInFilmstrip) { - mActivity.getStateManager().switchState(this, PhotoPage.class, data); + mActivity.getStateManager().switchState(this, FilmstripPage.class, data); } else { mActivity.getStateManager().startStateForResult( - PhotoPage.class, REQUEST_PHOTO, data); + SinglePhotoPage.class, REQUEST_PHOTO, data); } } } @@ -318,11 +320,11 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mData.getString(Gallery.EXTRA_CROP) != null) { // TODO: Handle MtpImagew Uri uri = dm.getContentUri(item.getPath()); - Intent intent = new Intent(CropImage.ACTION_CROP, uri) + Intent intent = new Intent(FilterShowActivity.CROP_ACTION, uri) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) .putExtras(getData()); if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) { - intent.putExtra(CropImage.KEY_RETURN_DATA, true); + intent.putExtra(CropExtras.KEY_RETURN_DATA, true); } activity.startActivity(intent); activity.finish(); @@ -371,7 +373,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false); mDetailsSource = new MyDetailsSource(); Context context = mActivity.getAndroidContext(); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); // Enable auto-select-all for mtp album if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { @@ -379,7 +380,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster } mLaunchedFromPhotoPage = - mActivity.getStateManager().hasStateClass(PhotoPage.class); + mActivity.getStateManager().hasStateClass(FilmstripPage.class); mInCameraApp = data.getBoolean(PhotoPage.KEY_APP_BRIDGE, false); mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { @@ -443,7 +444,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSelectionManager.leaveSelectionMode(); } mAlbumView.setSlotFilter(null); - + mActionModeHandler.pause(); mAlbumDataAdapter.pause(); mAlbumView.pause(); DetailsHelper.pause(); @@ -456,7 +457,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster mSyncTask = null; clearLoadingBit(BIT_LOADING_SYNC); } - mActionModeHandler.pause(); } @Override @@ -465,6 +465,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster if (mAlbumDataAdapter != null) { mAlbumDataAdapter.setLoadingListener(null); } + mActionModeHandler.destroy(); } private void initializeViews() { @@ -662,7 +663,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster switch (mode) { case SelectionManager.ENTER_SELECTION_MODE: { mActionModeHandler.startActionMode(); - if (mHapticsEnabled) mVibrator.vibrate(100); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; } case SelectionManager.LEAVE_SELECTION_MODE: { diff --git a/src/com/android/gallery3d/app/AlbumSetPage.java b/src/com/android/gallery3d/app/AlbumSetPage.java index cae606be1..4e89e91d0 100644 --- a/src/com/android/gallery3d/app/AlbumSetPage.java +++ b/src/com/android/gallery3d/app/AlbumSetPage.java @@ -23,7 +23,7 @@ import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Message; -import android.os.Vibrator; +import android.view.HapticFeedbackConstants; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -41,6 +41,8 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.FadeTexture; +import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.picasasource.PicasaSource; import com.android.gallery3d.settings.GallerySettings; import com.android.gallery3d.ui.ActionModeHandler; @@ -48,8 +50,6 @@ import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener; import com.android.gallery3d.ui.AlbumSetSlotRenderer; import com.android.gallery3d.ui.DetailsHelper; import com.android.gallery3d.ui.DetailsHelper.CloseListener; -import com.android.gallery3d.ui.FadeTexture; -import com.android.gallery3d.ui.GLCanvas; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.SelectionManager; @@ -92,7 +92,6 @@ public class AlbumSetPage extends ActivityState implements private boolean mShowClusterMenu; private GalleryActionBar mActionBar; private int mSelectedAction; - private Vibrator mVibrator; protected SelectionManager mSelectionManager; private AlbumSetDataLoader mAlbumSetDataAdapter; @@ -275,7 +274,7 @@ public class AlbumSetPage extends ActivityState implements data.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, true); data.putBoolean(PhotoPage.KEY_IN_CAMERA_ROLL, targetSet.isCameraRoll()); mActivity.getStateManager().startStateForResult( - PhotoPage.class, AlbumPage.REQUEST_PHOTO, data); + FilmstripPage.class, AlbumPage.REQUEST_PHOTO, data); return; } data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath); @@ -332,7 +331,6 @@ public class AlbumSetPage extends ActivityState implements mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE); mEyePosition = new EyePosition(context, this); mDetailsSource = new MyDetailsSource(); - mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); mActionBar = mActivity.getGalleryActionBar(); mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE, FilterUtils.CLUSTER_BY_ALBUM); @@ -353,8 +351,9 @@ public class AlbumSetPage extends ActivityState implements @Override public void onDestroy() { - cleanupCameraButton(); super.onDestroy(); + cleanupCameraButton(); + mActionModeHandler.destroy(); } private boolean setupCameraButton() { @@ -439,9 +438,9 @@ public class AlbumSetPage extends ActivityState implements public void onPause() { super.onPause(); mIsActive = false; - mActionModeHandler.pause(); mAlbumSetDataAdapter.pause(); mAlbumSetView.pause(); + mActionModeHandler.pause(); mEyePosition.pause(); DetailsHelper.pause(); // Call disableClusterMenu to avoid receiving callback after paused. @@ -655,7 +654,7 @@ public class AlbumSetPage extends ActivityState implements case SelectionManager.ENTER_SELECTION_MODE: { mActionBar.disableClusterMenu(true); mActionModeHandler.startActionMode(); - if (mHapticsEnabled) mVibrator.vibrate(100); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; } case SelectionManager.LEAVE_SELECTION_MODE: { diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java b/src/com/android/gallery3d/app/BatchService.java index 95edc5d15..98a1d8215 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetFX.java +++ b/src/com/android/gallery3d/app/BatchService.java @@ -14,32 +14,32 @@ * limitations under the License. */ -package com.android.gallery3d.filtershow.presets; +package com.android.gallery3d.app; -import android.graphics.Bitmap; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; -import com.android.gallery3d.filtershow.filters.ImageFilterFx; +import com.android.gallery3d.util.ThreadPool; -public class ImagePresetFX extends ImagePreset { - String name; - Bitmap fxBitmap; +public class BatchService extends Service { - @Override - public String name() { - return name; + public class LocalBinder extends Binder { + BatchService getService() { + return BatchService.this; + } } - public ImagePresetFX(Bitmap bitmap, String name) { - fxBitmap = bitmap; - this.name = name; - setup(); - } + private final IBinder mBinder = new LocalBinder(); + private ThreadPool mThreadPool = new ThreadPool(1, 1); @Override - public void setup() { - if (fxBitmap != null) { - mFilters.add(new ImageFilterFx(fxBitmap,name)); - } + public IBinder onBind(Intent intent) { + return mBinder; } + public ThreadPool getThreadPool() { + return mThreadPool; + } } diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java index ab43dada5..089872fa5 100644 --- a/src/com/android/gallery3d/app/CommonControllerOverlay.java +++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java @@ -154,7 +154,7 @@ public abstract class CommonControllerOverlay extends FrameLayout implements @Override public void showEnded() { mState = State.ENDED; - showMainView(mPlayPauseReplayView); + if (mCanReplay) showMainView(mPlayPauseReplayView); } @Override diff --git a/src/com/android/gallery3d/app/CropImage.java b/src/com/android/gallery3d/app/CropImage.java deleted file mode 100644 index 89ca63d44..000000000 --- a/src/com/android/gallery3d/app/CropImage.java +++ /dev/null @@ -1,1040 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.app; - -import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.ProgressDialog; -import android.app.WallpaperManager; -import android.content.ContentValues; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.Bitmap.Config; -import android.graphics.BitmapFactory; -import android.graphics.BitmapRegionDecoder; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Environment; -import android.os.Handler; -import android.os.Message; -import android.provider.MediaStore; -import android.provider.MediaStore.Images; -import android.util.FloatMath; -import android.view.Menu; -import android.view.MenuItem; -import android.view.Window; -import android.view.WindowManager; -import android.widget.Toast; - -import com.android.camera.Util; -import com.android.gallery3d.R; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; -import com.android.gallery3d.data.DataManager; -import com.android.gallery3d.data.LocalImage; -import com.android.gallery3d.data.MediaItem; -import com.android.gallery3d.data.MediaObject; -import com.android.gallery3d.data.Path; -import com.android.gallery3d.exif.ExifData; -import com.android.gallery3d.exif.ExifOutputStream; -import com.android.gallery3d.exif.ExifReader; -import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.picasasource.PicasaSource; -import com.android.gallery3d.ui.BitmapScreenNail; -import com.android.gallery3d.ui.BitmapTileProvider; -import com.android.gallery3d.ui.CropView; -import com.android.gallery3d.ui.GLRoot; -import com.android.gallery3d.ui.SynchronizedHandler; -import com.android.gallery3d.ui.TileImageViewAdapter; -import com.android.gallery3d.util.BucketNames; -import com.android.gallery3d.util.Future; -import com.android.gallery3d.util.FutureListener; -import com.android.gallery3d.util.GalleryUtils; -import com.android.gallery3d.util.InterruptableOutputStream; -import com.android.gallery3d.util.ThreadPool.CancelListener; -import com.android.gallery3d.util.ThreadPool.Job; -import com.android.gallery3d.util.ThreadPool.JobContext; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteOrder; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * The activity can crop specific region of interest from an image. - */ -public class CropImage extends AbstractGalleryActivity { - private static final String TAG = "CropImage"; - public static final String ACTION_CROP = "com.android.camera.action.CROP"; - - private static final int MAX_PIXEL_COUNT = 5 * 1000000; // 5M pixels - private static final int MAX_FILE_INDEX = 1000; - private static final int TILE_SIZE = 512; - private static final int BACKUP_PIXEL_COUNT = 480000; // around 800x600 - - private static final int MSG_LARGE_BITMAP = 1; - private static final int MSG_BITMAP = 2; - private static final int MSG_SAVE_COMPLETE = 3; - private static final int MSG_SHOW_SAVE_ERROR = 4; - private static final int MSG_CANCEL_DIALOG = 5; - - private static final int MAX_BACKUP_IMAGE_SIZE = 320; - private static final int DEFAULT_COMPRESS_QUALITY = 90; - private static final String TIME_STAMP_NAME = "'IMG'_yyyyMMdd_HHmmss"; - - public static final String KEY_RETURN_DATA = "return-data"; - public static final String KEY_CROPPED_RECT = "cropped-rect"; - public static final String KEY_ASPECT_X = "aspectX"; - public static final String KEY_ASPECT_Y = "aspectY"; - public static final String KEY_SPOTLIGHT_X = "spotlightX"; - public static final String KEY_SPOTLIGHT_Y = "spotlightY"; - public static final String KEY_OUTPUT_X = "outputX"; - public static final String KEY_OUTPUT_Y = "outputY"; - public static final String KEY_SCALE = "scale"; - public static final String KEY_DATA = "data"; - public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded"; - public static final String KEY_OUTPUT_FORMAT = "outputFormat"; - public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper"; - public static final String KEY_NO_FACE_DETECTION = "noFaceDetection"; - public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; - - private static final String KEY_STATE = "state"; - - private static final int STATE_INIT = 0; - private static final int STATE_LOADED = 1; - private static final int STATE_SAVING = 2; - - public static final File DOWNLOAD_BUCKET = new File( - Environment.getExternalStorageDirectory(), BucketNames.DOWNLOAD); - - public static final String CROP_ACTION = "com.android.camera.action.CROP"; - - private int mState = STATE_INIT; - - private CropView mCropView; - - private boolean mDoFaceDetection = true; - - private Handler mMainHandler; - - // We keep the following members so that we can free them - - // mBitmap is the unrotated bitmap we pass in to mCropView for detect faces. - // mCropView is responsible for rotating it to the way that it is viewed by users. - private Bitmap mBitmap; - private BitmapTileProvider mBitmapTileProvider; - private BitmapRegionDecoder mRegionDecoder; - private Bitmap mBitmapInIntent; - private boolean mUseRegionDecoder = false; - private BitmapScreenNail mBitmapScreenNail; - - private ProgressDialog mProgressDialog; - private Future<BitmapRegionDecoder> mLoadTask; - private Future<Bitmap> mLoadBitmapTask; - private Future<Intent> mSaveTask; - - private MediaItem mMediaItem; - - @Override - public void onCreate(Bundle bundle) { - super.onCreate(bundle); - requestWindowFeature(Window.FEATURE_ACTION_BAR); - requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - - // Initialize UI - setContentView(R.layout.cropimage); - mCropView = new CropView(this); - getGLRoot().setContentPane(mCropView); - - ActionBar actionBar = getActionBar(); - int displayOptions = ActionBar.DISPLAY_HOME_AS_UP - | ActionBar.DISPLAY_SHOW_TITLE; - actionBar.setDisplayOptions(displayOptions, displayOptions); - - Bundle extra = getIntent().getExtras(); - if (extra != null) { - if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) { - actionBar.setTitle(getString(R.string.set_wallpaper)); - } - if (extra.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) { - getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - } - } - - mMainHandler = new SynchronizedHandler(getGLRoot()) { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case MSG_LARGE_BITMAP: { - dismissProgressDialogIfShown(); - onBitmapRegionDecoderAvailable((BitmapRegionDecoder) message.obj); - break; - } - case MSG_BITMAP: { - dismissProgressDialogIfShown(); - onBitmapAvailable((Bitmap) message.obj); - break; - } - case MSG_SHOW_SAVE_ERROR: { - dismissProgressDialogIfShown(); - setResult(RESULT_CANCELED); - Toast.makeText(CropImage.this, - CropImage.this.getString(R.string.save_error), - Toast.LENGTH_LONG).show(); - finish(); - } - case MSG_SAVE_COMPLETE: { - dismissProgressDialogIfShown(); - setResult(RESULT_OK, (Intent) message.obj); - finish(); - break; - } - case MSG_CANCEL_DIALOG: { - setResult(RESULT_CANCELED); - finish(); - break; - } - } - } - }; - - setCropParameters(); - } - - @Override - protected void onSaveInstanceState(Bundle saveState) { - saveState.putInt(KEY_STATE, mState); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - getMenuInflater().inflate(R.menu.crop, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: { - finish(); - break; - } - case R.id.cancel: { - setResult(RESULT_CANCELED); - finish(); - break; - } - case R.id.save: { - onSaveClicked(); - break; - } - } - return true; - } - - @Override - public void onBackPressed() { - finish(); - } - - private class SaveOutput implements Job<Intent> { - private final RectF mCropRect; - - public SaveOutput(RectF cropRect) { - mCropRect = cropRect; - } - - @Override - public Intent run(JobContext jc) { - RectF cropRect = mCropRect; - Bundle extra = getIntent().getExtras(); - - Rect rect = new Rect( - Math.round(cropRect.left), Math.round(cropRect.top), - Math.round(cropRect.right), Math.round(cropRect.bottom)); - - Intent result = new Intent(); - result.putExtra(KEY_CROPPED_RECT, rect); - Bitmap cropped = null; - boolean outputted = false; - if (extra != null) { - Uri uri = (Uri) extra.getParcelable(MediaStore.EXTRA_OUTPUT); - if (uri != null) { - if (jc.isCancelled()) return null; - outputted = true; - cropped = getCroppedImage(rect); - if (!saveBitmapToUri(jc, cropped, uri)) return null; - } - if (extra.getBoolean(KEY_RETURN_DATA, false)) { - if (jc.isCancelled()) return null; - outputted = true; - if (cropped == null) cropped = getCroppedImage(rect); - result.putExtra(KEY_DATA, cropped); - } - if (extra.getBoolean(KEY_SET_AS_WALLPAPER, false)) { - if (jc.isCancelled()) return null; - outputted = true; - if (cropped == null) cropped = getCroppedImage(rect); - if (!setAsWallpaper(jc, cropped)) return null; - } - } - if (!outputted) { - if (jc.isCancelled()) return null; - if (cropped == null) cropped = getCroppedImage(rect); - Uri data = saveToMediaProvider(jc, cropped); - if (data != null) result.setData(data); - } - return result; - } - } - - public static String determineCompressFormat(MediaObject obj) { - String compressFormat = "JPEG"; - if (obj instanceof MediaItem) { - String mime = ((MediaItem) obj).getMimeType(); - if (mime.contains("png") || mime.contains("gif")) { - // Set the compress format to PNG for png and gif images - // because they may contain alpha values. - compressFormat = "PNG"; - } - } - return compressFormat; - } - - private boolean setAsWallpaper(JobContext jc, Bitmap wallpaper) { - try { - WallpaperManager.getInstance(this).setBitmap(wallpaper); - } catch (IOException e) { - Log.w(TAG, "fail to set wall paper", e); - } - return true; - } - - private File saveMedia( - JobContext jc, Bitmap cropped, File directory, String filename, ExifData exifData) { - // Try file-1.jpg, file-2.jpg, ... until we find a filename - // which does not exist yet. - File candidate = null; - String fileExtension = getFileExtension(); - for (int i = 1; i < MAX_FILE_INDEX; ++i) { - candidate = new File(directory, filename + "-" + i + "." - + fileExtension); - try { - if (candidate.createNewFile()) break; - } catch (IOException e) { - Log.e(TAG, "fail to create new file: " - + candidate.getAbsolutePath(), e); - return null; - } - } - if (!candidate.exists() || !candidate.isFile()) { - throw new RuntimeException("cannot create file: " + filename); - } - - candidate.setReadable(true, false); - candidate.setWritable(true, false); - - try { - FileOutputStream fos = new FileOutputStream(candidate); - try { - if (exifData != null) { - ExifOutputStream eos = new ExifOutputStream(fos); - eos.setExifData(exifData); - saveBitmapToOutputStream(jc, cropped, - convertExtensionToCompressFormat(fileExtension), eos); - } else { - saveBitmapToOutputStream(jc, cropped, - convertExtensionToCompressFormat(fileExtension), fos); - } - } finally { - fos.close(); - } - } catch (IOException e) { - Log.e(TAG, "fail to save image: " - + candidate.getAbsolutePath(), e); - candidate.delete(); - return null; - } - - if (jc.isCancelled()) { - candidate.delete(); - return null; - } - - return candidate; - } - - private ExifData getExifData(String path) { - FileInputStream is = null; - try { - is = new FileInputStream(path); - ExifReader reader = new ExifReader(); - ExifData data = reader.read(is); - return data; - } catch (Throwable t) { - Log.w(TAG, "Cannot read EXIF data", t); - return null; - } finally { - Util.closeSilently(is); - } - } - - private static final String EXIF_SOFTWARE_VALUE = "Android Gallery"; - - private void changeExifData(ExifData data, int width, int height) { - data.addTag(ExifTag.TAG_IMAGE_WIDTH).setValue(width); - data.addTag(ExifTag.TAG_IMAGE_LENGTH).setValue(height); - data.addTag(ExifTag.TAG_SOFTWARE).setValue(EXIF_SOFTWARE_VALUE); - data.addTag(ExifTag.TAG_DATE_TIME).setTimeValue(System.currentTimeMillis()); - // Remove the original thumbnail - // TODO: generate a new thumbnail for the cropped image. - data.removeThumbnailData(); - } - - private Uri saveToMediaProvider(JobContext jc, Bitmap cropped) { - if (PicasaSource.isPicasaImage(mMediaItem)) { - return savePicasaImage(jc, cropped); - } else if (mMediaItem instanceof LocalImage) { - return saveLocalImage(jc, cropped); - } else { - return saveGenericImage(jc, cropped); - } - } - - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - private static void setImageSize(ContentValues values, int width, int height) { - // The two fields are available since ICS but got published in JB - if (ApiHelper.HAS_MEDIA_COLUMNS_WIDTH_AND_HEIGHT) { - values.put(Images.Media.WIDTH, width); - values.put(Images.Media.HEIGHT, height); - } - } - - private Uri savePicasaImage(JobContext jc, Bitmap cropped) { - if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { - throw new RuntimeException("cannot create download folder"); - } - String filename = PicasaSource.getImageTitle(mMediaItem); - int pos = filename.lastIndexOf('.'); - if (pos >= 0) filename = filename.substring(0, pos); - ExifData exifData = new ExifData(ByteOrder.BIG_ENDIAN); - PicasaSource.extractExifValues(mMediaItem, exifData); - changeExifData(exifData, cropped.getWidth(), cropped.getHeight()); - File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, exifData); - if (output == null) return null; - - long now = System.currentTimeMillis() / 1000; - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, PicasaSource.getImageTitle(mMediaItem)); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, PicasaSource.getDateTaken(mMediaItem)); - values.put(Images.Media.DATE_MODIFIED, now); - values.put(Images.Media.DATE_ADDED, now); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - double latitude = PicasaSource.getLatitude(mMediaItem); - double longitude = PicasaSource.getLongitude(mMediaItem); - if (GalleryUtils.isValidLocation(latitude, longitude)) { - values.put(Images.Media.LATITUDE, latitude); - values.put(Images.Media.LONGITUDE, longitude); - } - return getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } - - private Uri saveLocalImage(JobContext jc, Bitmap cropped) { - LocalImage localImage = (LocalImage) mMediaItem; - - File oldPath = new File(localImage.filePath); - File directory = new File(oldPath.getParent()); - - String filename = oldPath.getName(); - int pos = filename.lastIndexOf('.'); - if (pos >= 0) filename = filename.substring(0, pos); - File output = null; - - ExifData exifData = null; - if (convertExtensionToCompressFormat(getFileExtension()) == CompressFormat.JPEG) { - exifData = getExifData(oldPath.getAbsolutePath()); - if (exifData != null) { - changeExifData(exifData, cropped.getWidth(), cropped.getHeight()); - } - } - output = saveMedia(jc, cropped, directory, filename, exifData); - if (output == null) return null; - - long now = System.currentTimeMillis() / 1000; - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, localImage.caption); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, localImage.dateTakenInMs); - values.put(Images.Media.DATE_MODIFIED, now); - values.put(Images.Media.DATE_ADDED, now); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - if (GalleryUtils.isValidLocation(localImage.latitude, localImage.longitude)) { - values.put(Images.Media.LATITUDE, localImage.latitude); - values.put(Images.Media.LONGITUDE, localImage.longitude); - } - return getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } - - private Uri saveGenericImage(JobContext jc, Bitmap cropped) { - if (!DOWNLOAD_BUCKET.isDirectory() && !DOWNLOAD_BUCKET.mkdirs()) { - throw new RuntimeException("cannot create download folder"); - } - - long now = System.currentTimeMillis(); - String filename = new SimpleDateFormat(TIME_STAMP_NAME). - format(new Date(now)); - - File output = saveMedia(jc, cropped, DOWNLOAD_BUCKET, filename, null); - if (output == null) return null; - - ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, filename); - values.put(Images.Media.DISPLAY_NAME, output.getName()); - values.put(Images.Media.DATE_TAKEN, now); - values.put(Images.Media.DATE_MODIFIED, now / 1000); - values.put(Images.Media.DATE_ADDED, now / 1000); - values.put(Images.Media.MIME_TYPE, getOutputMimeType()); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, output.getAbsolutePath()); - values.put(Images.Media.SIZE, output.length()); - - setImageSize(values, cropped.getWidth(), cropped.getHeight()); - - return getContentResolver().insert( - Images.Media.EXTERNAL_CONTENT_URI, values); - } - - private boolean saveBitmapToOutputStream( - JobContext jc, Bitmap bitmap, CompressFormat format, OutputStream os) { - // We wrap the OutputStream so that it can be interrupted. - final InterruptableOutputStream ios = new InterruptableOutputStream(os); - jc.setCancelListener(new CancelListener() { - @Override - public void onCancel() { - ios.interrupt(); - } - }); - try { - bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, ios); - return !jc.isCancelled(); - } finally { - jc.setCancelListener(null); - Utils.closeSilently(ios); - } - } - - private boolean saveBitmapToUri(JobContext jc, Bitmap bitmap, Uri uri) { - try { - OutputStream out = getContentResolver().openOutputStream(uri); - try { - return saveBitmapToOutputStream(jc, bitmap, - convertExtensionToCompressFormat(getFileExtension()), out); - } finally { - Utils.closeSilently(out); - } - } catch (FileNotFoundException e) { - Log.w(TAG, "cannot write output", e); - } - return true; - } - - private CompressFormat convertExtensionToCompressFormat(String extension) { - return extension.equals("png") - ? CompressFormat.PNG - : CompressFormat.JPEG; - } - - private String getOutputMimeType() { - return getFileExtension().equals("png") ? "image/png" : "image/jpeg"; - } - - private String getFileExtension() { - String requestFormat = getIntent().getStringExtra(KEY_OUTPUT_FORMAT); - String outputFormat = (requestFormat == null) - ? determineCompressFormat(mMediaItem) - : requestFormat; - - outputFormat = outputFormat.toLowerCase(); - return (outputFormat.equals("png") || outputFormat.equals("gif")) - ? "png" // We don't support gif compression. - : "jpg"; - } - - private void onSaveClicked() { - Bundle extra = getIntent().getExtras(); - RectF cropRect = mCropView.getCropRectangle(); - if (cropRect == null) return; - mState = STATE_SAVING; - int messageId = extra != null && extra.getBoolean(KEY_SET_AS_WALLPAPER) - ? R.string.wallpaper - : R.string.saving_image; - mProgressDialog = ProgressDialog.show( - this, null, getString(messageId), true, false); - mSaveTask = getThreadPool().submit(new SaveOutput(cropRect), - new FutureListener<Intent>() { - @Override - public void onFutureDone(Future<Intent> future) { - mSaveTask = null; - if (future.isCancelled()) return; - Intent intent = future.get(); - if (intent != null) { - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_SAVE_COMPLETE, intent)); - } else { - mMainHandler.sendEmptyMessage(MSG_SHOW_SAVE_ERROR); - } - } - }); - } - - private Bitmap getCroppedImage(Rect rect) { - Utils.assertTrue(rect.width() > 0 && rect.height() > 0); - - Bundle extras = getIntent().getExtras(); - // (outputX, outputY) = the width and height of the returning bitmap. - int outputX = rect.width(); - int outputY = rect.height(); - if (extras != null) { - outputX = extras.getInt(KEY_OUTPUT_X, outputX); - outputY = extras.getInt(KEY_OUTPUT_Y, outputY); - } - - if (outputX * outputY > MAX_PIXEL_COUNT) { - float scale = FloatMath.sqrt((float) MAX_PIXEL_COUNT / outputX / outputY); - Log.w(TAG, "scale down the cropped image: " + scale); - outputX = Math.round(scale * outputX); - outputY = Math.round(scale * outputY); - } - - // (rect.width() * scaleX, rect.height() * scaleY) = - // the size of drawing area in output bitmap - float scaleX = 1; - float scaleY = 1; - Rect dest = new Rect(0, 0, outputX, outputY); - if (extras == null || extras.getBoolean(KEY_SCALE, true)) { - scaleX = (float) outputX / rect.width(); - scaleY = (float) outputY / rect.height(); - if (extras == null || !extras.getBoolean( - KEY_SCALE_UP_IF_NEEDED, false)) { - if (scaleX > 1f) scaleX = 1; - if (scaleY > 1f) scaleY = 1; - } - } - - // Keep the content in the center (or crop the content) - int rectWidth = Math.round(rect.width() * scaleX); - int rectHeight = Math.round(rect.height() * scaleY); - dest.set(Math.round((outputX - rectWidth) / 2f), - Math.round((outputY - rectHeight) / 2f), - Math.round((outputX + rectWidth) / 2f), - Math.round((outputY + rectHeight) / 2f)); - - if (mBitmapInIntent != null) { - Bitmap source = mBitmapInIntent; - Bitmap result = Bitmap.createBitmap( - outputX, outputY, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - canvas.drawBitmap(source, rect, dest, null); - return result; - } - - if (mUseRegionDecoder) { - int rotation = mMediaItem.getFullImageRotation(); - rotateRectangle(rect, mCropView.getImageWidth(), - mCropView.getImageHeight(), 360 - rotation); - rotateRectangle(dest, outputX, outputY, 360 - rotation); - - BitmapFactory.Options options = new BitmapFactory.Options(); - int sample = BitmapUtils.computeSampleSizeLarger( - Math.max(scaleX, scaleY)); - options.inSampleSize = sample; - - // The decoding result is what we want if - // 1. The size of the decoded bitmap match the destination's size - // 2. The destination covers the whole output bitmap - // 3. No rotation - if ((rect.width() / sample) == dest.width() - && (rect.height() / sample) == dest.height() - && (outputX == dest.width()) && (outputY == dest.height()) - && rotation == 0) { - // To prevent concurrent access in GLThread - synchronized (mRegionDecoder) { - return mRegionDecoder.decodeRegion(rect, options); - } - } - Bitmap result = Bitmap.createBitmap( - outputX, outputY, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - rotateCanvas(canvas, outputX, outputY, rotation); - drawInTiles(canvas, mRegionDecoder, rect, dest, sample); - return result; - } else { - int rotation = mMediaItem.getRotation(); - rotateRectangle(rect, mCropView.getImageWidth(), - mCropView.getImageHeight(), 360 - rotation); - rotateRectangle(dest, outputX, outputY, 360 - rotation); - Bitmap result = Bitmap.createBitmap(outputX, outputY, Config.ARGB_8888); - Canvas canvas = new Canvas(result); - rotateCanvas(canvas, outputX, outputY, rotation); - canvas.drawBitmap(mBitmap, - rect, dest, new Paint(Paint.FILTER_BITMAP_FLAG)); - return result; - } - } - - private static void rotateCanvas( - Canvas canvas, int width, int height, int rotation) { - canvas.translate(width / 2, height / 2); - canvas.rotate(rotation); - if (((rotation / 90) & 0x01) == 0) { - canvas.translate(-width / 2, -height / 2); - } else { - canvas.translate(-height / 2, -width / 2); - } - } - - private static void rotateRectangle( - Rect rect, int width, int height, int rotation) { - if (rotation == 0 || rotation == 360) return; - - int w = rect.width(); - int h = rect.height(); - switch (rotation) { - case 90: { - rect.top = rect.left; - rect.left = height - rect.bottom; - rect.right = rect.left + h; - rect.bottom = rect.top + w; - return; - } - case 180: { - rect.left = width - rect.right; - rect.top = height - rect.bottom; - rect.right = rect.left + w; - rect.bottom = rect.top + h; - return; - } - case 270: { - rect.left = rect.top; - rect.top = width - rect.right; - rect.right = rect.left + h; - rect.bottom = rect.top + w; - return; - } - default: throw new AssertionError(); - } - } - - private void drawInTiles(Canvas canvas, - BitmapRegionDecoder decoder, Rect rect, Rect dest, int sample) { - int tileSize = TILE_SIZE * sample; - Rect tileRect = new Rect(); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inPreferredConfig = Config.ARGB_8888; - options.inSampleSize = sample; - canvas.translate(dest.left, dest.top); - canvas.scale((float) sample * dest.width() / rect.width(), - (float) sample * dest.height() / rect.height()); - Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); - for (int tx = rect.left, x = 0; - tx < rect.right; tx += tileSize, x += TILE_SIZE) { - for (int ty = rect.top, y = 0; - ty < rect.bottom; ty += tileSize, y += TILE_SIZE) { - tileRect.set(tx, ty, tx + tileSize, ty + tileSize); - if (tileRect.intersect(rect)) { - Bitmap bitmap; - - // To prevent concurrent access in GLThread - synchronized (decoder) { - bitmap = decoder.decodeRegion(tileRect, options); - } - canvas.drawBitmap(bitmap, x, y, paint); - bitmap.recycle(); - } - } - } - } - - private void onBitmapRegionDecoderAvailable( - BitmapRegionDecoder regionDecoder) { - - if (regionDecoder == null) { - Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show(); - finish(); - return; - } - mRegionDecoder = regionDecoder; - mUseRegionDecoder = true; - mState = STATE_LOADED; - - BitmapFactory.Options options = new BitmapFactory.Options(); - int width = regionDecoder.getWidth(); - int height = regionDecoder.getHeight(); - options.inSampleSize = BitmapUtils.computeSampleSize(width, height, - BitmapUtils.UNCONSTRAINED, BACKUP_PIXEL_COUNT); - mBitmap = regionDecoder.decodeRegion( - new Rect(0, 0, width, height), options); - - mBitmapScreenNail = new BitmapScreenNail(mBitmap); - - TileImageViewAdapter adapter = new TileImageViewAdapter(); - adapter.setScreenNail(mBitmapScreenNail, width, height); - adapter.setRegionDecoder(regionDecoder); - - mCropView.setDataModel(adapter, mMediaItem.getFullImageRotation()); - if (mDoFaceDetection) { - mCropView.detectFaces(mBitmap); - } else { - mCropView.initializeHighlightRectangle(); - } - } - - private void onBitmapAvailable(Bitmap bitmap) { - if (bitmap == null) { - Toast.makeText(this, R.string.fail_to_load_image, Toast.LENGTH_SHORT).show(); - finish(); - return; - } - mUseRegionDecoder = false; - mState = STATE_LOADED; - - mBitmap = bitmap; - BitmapFactory.Options options = new BitmapFactory.Options(); - mCropView.setDataModel(new BitmapTileProvider(bitmap, 512), - mMediaItem.getRotation()); - if (mDoFaceDetection) { - mCropView.detectFaces(bitmap); - } else { - mCropView.initializeHighlightRectangle(); - } - } - - private void setCropParameters() { - Bundle extras = getIntent().getExtras(); - if (extras == null) - return; - int aspectX = extras.getInt(KEY_ASPECT_X, 0); - int aspectY = extras.getInt(KEY_ASPECT_Y, 0); - if (aspectX != 0 && aspectY != 0) { - mCropView.setAspectRatio((float) aspectX / aspectY); - } - - float spotlightX = extras.getFloat(KEY_SPOTLIGHT_X, 0); - float spotlightY = extras.getFloat(KEY_SPOTLIGHT_Y, 0); - if (spotlightX != 0 && spotlightY != 0) { - mCropView.setSpotlightRatio(spotlightX, spotlightY); - } - } - - private void initializeData() { - Bundle extras = getIntent().getExtras(); - - if (extras != null) { - if (extras.containsKey(KEY_NO_FACE_DETECTION)) { - mDoFaceDetection = !extras.getBoolean(KEY_NO_FACE_DETECTION); - } - - mBitmapInIntent = extras.getParcelable(KEY_DATA); - - if (mBitmapInIntent != null) { - mBitmapTileProvider = - new BitmapTileProvider(mBitmapInIntent, MAX_BACKUP_IMAGE_SIZE); - mCropView.setDataModel(mBitmapTileProvider, 0); - if (mDoFaceDetection) { - mCropView.detectFaces(mBitmapInIntent); - } else { - mCropView.initializeHighlightRectangle(); - } - mState = STATE_LOADED; - return; - } - } - - mProgressDialog = ProgressDialog.show( - this, null, getString(R.string.loading_image), true, true); - mProgressDialog.setCanceledOnTouchOutside(false); - mProgressDialog.setCancelMessage(mMainHandler.obtainMessage(MSG_CANCEL_DIALOG)); - - mMediaItem = getMediaItemFromIntentData(); - if (mMediaItem == null) return; - - boolean supportedByBitmapRegionDecoder = - (mMediaItem.getSupportedOperations() & MediaItem.SUPPORT_FULL_IMAGE) != 0; - if (supportedByBitmapRegionDecoder) { - mLoadTask = getThreadPool().submit(new LoadDataTask(mMediaItem), - new FutureListener<BitmapRegionDecoder>() { - @Override - public void onFutureDone(Future<BitmapRegionDecoder> future) { - mLoadTask = null; - BitmapRegionDecoder decoder = future.get(); - if (future.isCancelled()) { - if (decoder != null) decoder.recycle(); - return; - } - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_LARGE_BITMAP, decoder)); - } - }); - } else { - mLoadBitmapTask = getThreadPool().submit(new LoadBitmapDataTask(mMediaItem), - new FutureListener<Bitmap>() { - @Override - public void onFutureDone(Future<Bitmap> future) { - mLoadBitmapTask = null; - Bitmap bitmap = future.get(); - if (future.isCancelled()) { - if (bitmap != null) bitmap.recycle(); - return; - } - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_BITMAP, bitmap)); - } - }); - } - } - - @Override - protected void onResume() { - super.onResume(); - if (mState == STATE_INIT) initializeData(); - if (mState == STATE_SAVING) onSaveClicked(); - - // TODO: consider to do it in GLView system - GLRoot root = getGLRoot(); - root.lockRenderThread(); - try { - mCropView.resume(); - } finally { - root.unlockRenderThread(); - } - } - - @Override - protected void onPause() { - super.onPause(); - dismissProgressDialogIfShown(); - - Future<BitmapRegionDecoder> loadTask = mLoadTask; - if (loadTask != null && !loadTask.isDone()) { - // load in progress, try to cancel it - loadTask.cancel(); - loadTask.waitDone(); - } - - Future<Bitmap> loadBitmapTask = mLoadBitmapTask; - if (loadBitmapTask != null && !loadBitmapTask.isDone()) { - // load in progress, try to cancel it - loadBitmapTask.cancel(); - loadBitmapTask.waitDone(); - } - - Future<Intent> saveTask = mSaveTask; - if (saveTask != null && !saveTask.isDone()) { - // save in progress, try to cancel it - saveTask.cancel(); - saveTask.waitDone(); - } - GLRoot root = getGLRoot(); - root.lockRenderThread(); - try { - mCropView.pause(); - } finally { - root.unlockRenderThread(); - } - } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (mBitmapScreenNail != null) { - mBitmapScreenNail.recycle(); - mBitmapScreenNail = null; - } - } - - private void dismissProgressDialogIfShown() { - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - } - - private MediaItem getMediaItemFromIntentData() { - Uri uri = getIntent().getData(); - DataManager manager = getDataManager(); - Path path = manager.findPathByUri(uri, getIntent().getType()); - if (path == null) { - Log.w(TAG, "cannot get path for: " + uri + ", or no data given"); - return null; - } - return (MediaItem) manager.getMediaObject(path); - } - - private class LoadDataTask implements Job<BitmapRegionDecoder> { - MediaItem mItem; - - public LoadDataTask(MediaItem item) { - mItem = item; - } - - @Override - public BitmapRegionDecoder run(JobContext jc) { - return mItem == null ? null : mItem.requestLargeImage().run(jc); - } - } - - private class LoadBitmapDataTask implements Job<Bitmap> { - MediaItem mItem; - - public LoadBitmapDataTask(MediaItem item) { - mItem = item; - } - @Override - public Bitmap run(JobContext jc) { - return mItem == null - ? null - : mItem.requestImage(MediaItem.TYPE_THUMBNAIL).run(jc); - } - } -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java b/src/com/android/gallery3d/app/FilmstripPage.java index 9653bed57..a9726cdc9 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWRed.java +++ b/src/com/android/gallery3d/app/FilmstripPage.java @@ -14,20 +14,8 @@ * limitations under the License. */ -package com.android.gallery3d.filtershow.presets; +package com.android.gallery3d.app; -import com.android.gallery3d.filtershow.filters.ImageFilterBWRed; - -public class ImagePresetBWRed extends ImagePreset { - - @Override - public String name() { - return "B&W - Red"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBWRed()); - } +public class FilmstripPage extends PhotoPage { } diff --git a/src/com/android/gallery3d/app/Gallery.java b/src/com/android/gallery3d/app/Gallery.java index e28404fac..7a494844a 100644 --- a/src/com/android/gallery3d/app/Gallery.java +++ b/src/com/android/gallery3d/app/Gallery.java @@ -27,7 +27,6 @@ import android.net.Uri; import android.os.Bundle; import android.provider.OpenableColumns; import android.view.Window; -import android.view.WindowManager; import android.widget.Toast; import com.android.gallery3d.R; @@ -49,7 +48,6 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi public static final String KEY_GET_ALBUM = "get-album"; public static final String KEY_TYPE_BITS = "type-bits"; public static final String KEY_MEDIA_TYPES = "mediaTypes"; - public static final String KEY_DISMISS_KEYGUARD = "dismiss-keyguard"; private static final String TAG = "Gallery"; private Dialog mVersionCheckDialog; @@ -60,11 +58,6 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi requestWindowFeature(Window.FEATURE_ACTION_BAR); requestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY); - if (getIntent().getBooleanExtra(KEY_DISMISS_KEYGUARD, false)) { - getWindow().addFlags( - WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); - } - setContentView(R.layout.main); if (savedInstanceState != null) { @@ -222,7 +215,7 @@ public final class Gallery extends AbstractGalleryActivity implements OnCancelLi } } - getStateManager().startState(PhotoPage.class, data); + getStateManager().startState(SinglePhotoPage.class, data); } } } diff --git a/src/com/android/gallery3d/app/ManageCachePage.java b/src/com/android/gallery3d/app/ManageCachePage.java index 37a97626e..4f5c35819 100644 --- a/src/com/android/gallery3d/app/ManageCachePage.java +++ b/src/com/android/gallery3d/app/ManageCachePage.java @@ -35,8 +35,8 @@ import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.ui.CacheStorageUsageInfo; -import com.android.gallery3d.ui.GLCanvas; import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.ManageCacheDrawer; diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java index 85dc4427e..00e4cd63b 100644 --- a/src/com/android/gallery3d/app/MoviePlayer.java +++ b/src/com/android/gallery3d/app/MoviePlayer.java @@ -74,8 +74,8 @@ public class MoviePlayer implements private static final long RESUMEABLE_TIMEOUT = 3 * 60 * 1000; // 3 mins private Context mContext; - private final View mRootView; private final VideoView mVideoView; + private final View mRootView; private final Bookmarker mBookmarker; private final Uri mUri; private final Handler mHandler = new Handler(); @@ -191,7 +191,6 @@ public class MoviePlayer implements if ((diff & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0 && (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) { mController.show(); - mRootView.setBackgroundColor(Color.BLACK); } } }); diff --git a/src/com/android/gallery3d/app/MuteVideo.java b/src/com/android/gallery3d/app/MuteVideo.java new file mode 100644 index 000000000..012b682ef --- /dev/null +++ b/src/com/android/gallery3d/app/MuteVideo.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.app; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.provider.MediaStore; +import android.widget.Toast; + +import com.android.gallery3d.R; +import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.util.SaveVideoFileInfo; +import com.android.gallery3d.util.SaveVideoFileUtils; + +import java.io.IOException; + +public class MuteVideo { + + private ProgressDialog mMuteProgress; + + private MediaItem mCurrentItem = null; + private Uri mUri = null; + private SaveVideoFileInfo mDstFileInfo = null; + private Activity mActivity = null; + private final Handler mHandler = new Handler(); + + final String TIME_STAMP_NAME = "'MUTE'_yyyyMMdd_HHmmss"; + + public MuteVideo(MediaItem current, Uri uri, Activity activity) { + mUri = uri; + mCurrentItem = current; + mActivity = activity; + } + + public void muteInBackground() { + mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, + mActivity.getContentResolver(), mUri, + mActivity.getString(R.string.folder_download)); + + showProgressDialog(); + new Thread(new Runnable() { + @Override + public void run() { + try { + VideoUtils.startMute(mCurrentItem.getFilePath(), mDstFileInfo); + SaveVideoFileUtils.insertContent( + mDstFileInfo, mActivity.getContentResolver(), mUri); + } catch (IOException e) { + Toast.makeText(mActivity, mActivity.getString(R.string.video_mute_err), + Toast.LENGTH_SHORT).show(); + } + // After muting is done, trigger the UI changed. + mHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(mActivity.getApplicationContext(), + mActivity.getString(R.string.save_into, + mDstFileInfo.mFolderName), + Toast.LENGTH_SHORT) + .show(); + + if (mMuteProgress != null) { + mMuteProgress.dismiss(); + mMuteProgress = null; + + // Show the result only when the activity not + // stopped. + Intent intent = new Intent(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); + intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); + mActivity.startActivity(intent); + } + } + }); + } + }).start(); + } + + private void showProgressDialog() { + mMuteProgress = new ProgressDialog(mActivity); + mMuteProgress.setTitle(mActivity.getString(R.string.muting)); + mMuteProgress.setMessage(mActivity.getString(R.string.please_wait)); + mMuteProgress.setCancelable(false); + mMuteProgress.setCanceledOnTouchOutside(false); + mMuteProgress.show(); + } +} diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java index 8485b25eb..2b586da94 100644 --- a/src/com/android/gallery3d/app/PhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java @@ -30,12 +30,12 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.TiledTexture; import com.android.gallery3d.ui.PhotoView; import com.android.gallery3d.ui.ScreenNail; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.ui.TileImageViewAdapter; import com.android.gallery3d.ui.TiledScreenNail; -import com.android.gallery3d.ui.TiledTexture; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; import com.android.gallery3d.util.MediaSetUtils; @@ -550,9 +550,8 @@ public class PhotoDataAdapter implements PhotoPage.Model { } @Override - public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize, BitmapPool pool) { - return mTileProvider.getTile(level, x, y, tileSize, borderSize, pool); + public Bitmap getTile(int level, int x, int y, int tileSize, BitmapPool pool) { + return mTileProvider.getTile(level, x, y, tileSize, pool); } @Override diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 506d1ca6f..c4d8d4090 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -71,7 +71,7 @@ import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GalleryUtils; -public class PhotoPage extends ActivityState implements +public abstract class PhotoPage extends ActivityState implements PhotoView.Listener, AppBridge.Server, PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener { private static final String TAG = "PhotoPage"; @@ -1019,11 +1019,16 @@ public class PhotoPage extends ActivityState implements refreshHidingMessage(); MediaItem current = mModel.getMediaItem(0); + // This is a shield for monkey when it clicks the action bar + // menu when transitioning from filmstrip to camera + if (current instanceof SnailItem) return true; + // TODO: We should check the current photo against the MediaItem + // that the menu was initially created for. We need to fix this + // after PhotoPage being refactored. if (current == null) { // item is not ready, ignore return true; } - int currentIndex = mModel.getCurrentIndex(); Path path = current.getPath(); @@ -1064,6 +1069,12 @@ public class PhotoPage extends ActivityState implements mActivity.startActivityForResult(intent, REQUEST_TRIM); return true; } + case R.id.action_mute: { + MuteVideo muteVideo = new MuteVideo(current, + manager.getContentUri(path), mActivity); + muteVideo.muteInBackground(); + return true; + } case R.id.action_edit: { launchPhotoEditor(); return true; @@ -1156,9 +1167,7 @@ public class PhotoPage extends ActivityState implements } else if (goBack) { onBackPressed(); } else if (unlock) { - Intent intent = new Intent(mActivity, Gallery.class); - intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true); - mActivity.startActivity(intent); + mActivity.getStateManager().finishState(this); } else if (launchCamera) { launchCamera(); } else { @@ -1242,7 +1251,7 @@ public class PhotoPage extends ActivityState implements Bundle data = new Bundle(getData()); data.putString(KEY_MEDIA_SET_PATH, albumPath.toString()); data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString()); - mActivity.getStateManager().startState(PhotoPage.class, data); + mActivity.getStateManager().startState(SinglePhotoPage.class, data); return; } mModel.setCurrentPhoto(path, mCurrentIndex); diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java b/src/com/android/gallery3d/app/SinglePhotoPage.java index 5d56aa1cd..beb87d358 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWBlue.java +++ b/src/com/android/gallery3d/app/SinglePhotoPage.java @@ -14,20 +14,8 @@ * limitations under the License. */ -package com.android.gallery3d.filtershow.presets; +package com.android.gallery3d.app; -import com.android.gallery3d.filtershow.filters.ImageFilterBWBlue; - -public class ImagePresetBWBlue extends ImagePreset { - - @Override - public String name() { - return "B&W - Blue"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBWBlue()); - } +public class SinglePhotoPage extends PhotoPage { } diff --git a/src/com/android/gallery3d/app/SlideshowPage.java b/src/com/android/gallery3d/app/SlideshowPage.java index 80edb367e..54aae67ab 100644 --- a/src/com/android/gallery3d/app/SlideshowPage.java +++ b/src/com/android/gallery3d/app/SlideshowPage.java @@ -31,7 +31,7 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; -import com.android.gallery3d.ui.GLCanvas; +import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.SlideshowView; import com.android.gallery3d.ui.SynchronizedHandler; diff --git a/src/com/android/gallery3d/app/TrimControllerOverlay.java b/src/com/android/gallery3d/app/TrimControllerOverlay.java index 9127ad159..cae016626 100644 --- a/src/com/android/gallery3d/app/TrimControllerOverlay.java +++ b/src/com/android/gallery3d/app/TrimControllerOverlay.java @@ -23,6 +23,8 @@ import android.content.Context; import android.view.MotionEvent; import android.view.View; +import com.android.gallery3d.common.ApiHelper; + /** * The controller for the Trimming Video. */ @@ -41,36 +43,41 @@ public class TrimControllerOverlay extends CommonControllerOverlay { if (mState == State.PLAYING) { mPlayPauseReplayView.setVisibility(View.INVISIBLE); } - mPlayPauseReplayView.setAlpha(1f); + if (ApiHelper.HAS_OBJECT_ANIMATION) { + mPlayPauseReplayView.setAlpha(1f); + } } @Override public void showPlaying() { super.showPlaying(); + if (ApiHelper.HAS_OBJECT_ANIMATION) { + // Add animation to hide the play button while playing. + ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); + anim.setDuration(200); + anim.start(); + anim.addListener(new AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + } - // Add animation to hide the play button while playing. - ObjectAnimator anim = ObjectAnimator.ofFloat(mPlayPauseReplayView, "alpha", 1f, 0f); - anim.setDuration(200); - anim.start(); - anim.addListener(new AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - } - - @Override - public void onAnimationEnd(Animator animation) { - hidePlayButtonIfPlaying(); - } + @Override + public void onAnimationEnd(Animator animation) { + hidePlayButtonIfPlaying(); + } - @Override - public void onAnimationCancel(Animator animation) { - hidePlayButtonIfPlaying(); - } + @Override + public void onAnimationCancel(Animator animation) { + hidePlayButtonIfPlaying(); + } - @Override - public void onAnimationRepeat(Animator animation) { - } - }); + @Override + public void onAnimationRepeat(Animator animation) { + } + }); + } else { + hidePlayButtonIfPlaying(); + } } @Override diff --git a/src/com/android/gallery3d/app/TrimVideo.java b/src/com/android/gallery3d/app/TrimVideo.java index 38b403b10..baab88990 100644 --- a/src/com/android/gallery3d/app/TrimVideo.java +++ b/src/com/android/gallery3d/app/TrimVideo.java @@ -19,19 +19,13 @@ package com.android.gallery3d.app; import android.app.ActionBar; import android.app.Activity; import android.app.ProgressDialog; -import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.database.Cursor; import android.media.MediaPlayer; import android.net.Uri; import android.os.Bundle; -import android.os.Environment; import android.os.Handler; import android.provider.MediaStore; -import android.provider.MediaStore.Video; -import android.provider.MediaStore.Video.VideoColumns; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -40,12 +34,11 @@ import android.widget.Toast; import android.widget.VideoView; import com.android.gallery3d.R; -import com.android.gallery3d.util.BucketNames; +import com.android.gallery3d.util.SaveVideoFileInfo; +import com.android.gallery3d.util.SaveVideoFileUtils; import java.io.File; import java.io.IOException; -import java.sql.Date; -import java.text.SimpleDateFormat; public class TrimVideo extends Activity implements MediaPlayer.OnErrorListener, @@ -53,6 +46,7 @@ public class TrimVideo extends Activity implements ControllerOverlay.Listener { private VideoView mVideoView; + private TextView mSaveVideoTextView; private TrimControllerOverlay mController; private Context mContext; private Uri mUri; @@ -70,13 +64,8 @@ public class TrimVideo extends Activity implements private boolean mHasPaused = false; private String mSrcVideoPath = null; - private String mSaveFileName = null; private static final String TIME_STAMP_NAME = "'TRIM'_yyyyMMdd_HHmmss"; - private File mSrcFile = null; - private File mDstFile = null; - private File mSaveDirectory = null; - // For showing the result. - private String saveFolderName = null; + private SaveVideoFileInfo mDstFileInfo = null; @Override public void onCreate(Bundle savedInstanceState) { @@ -93,13 +82,14 @@ public class TrimVideo extends Activity implements actionBar.setDisplayOptions(displayOptions, displayOptions); actionBar.setCustomView(R.layout.trim_menu); - TextView mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); + mSaveVideoTextView = (TextView) findViewById(R.id.start_trim); mSaveVideoTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { trimVideo(); } }); + mSaveVideoTextView.setEnabled(false); Intent intent = getIntent(); mUri = intent.getData(); @@ -221,72 +211,24 @@ public class TrimVideo extends Activity implements mController.showPaused(); } - // Copy from SaveCopyTask.java in terms of how to handle the destination - // path and filename : querySource() and getSaveDirectory(). - private interface ContentResolverQueryCallback { - void onCursorResult(Cursor cursor); - } - - private void querySource(String[] projection, ContentResolverQueryCallback callback) { - ContentResolver contentResolver = getContentResolver(); - Cursor cursor = null; - try { - cursor = contentResolver.query(mUri, projection, null, null, null); - if ((cursor != null) && cursor.moveToNext()) { - callback.onCursorResult(cursor); - } - } catch (Exception e) { - // Ignore error for lacking the data column from the source. - } finally { - if (cursor != null) { - cursor.close(); - } - } - } - - private File getSaveDirectory() { - final File[] dir = new File[1]; - querySource(new String[] { - VideoColumns.DATA }, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - dir[0] = new File(cursor.getString(0)).getParentFile(); - } - }); - return dir[0]; - } - - private void trimVideo() { + private boolean isModified() { int delta = mTrimEndTime - mTrimStartTime; + // Considering that we only trim at sync frame, we don't want to trim // when the time interval is too short or too close to the origin. - if (delta < 100 ) { - Toast.makeText(getApplicationContext(), - getString(R.string.trim_too_short), - Toast.LENGTH_SHORT).show(); - return; - } - if (Math.abs(mVideoView.getDuration() - delta) < 100) { - // If no change has been made, go back - onBackPressed(); - return; - } - // Use the default save directory if the source directory cannot be - // saved. - mSaveDirectory = getSaveDirectory(); - if ((mSaveDirectory == null) || !mSaveDirectory.canWrite()) { - mSaveDirectory = new File(Environment.getExternalStorageDirectory(), - BucketNames.DOWNLOAD); - saveFolderName = getString(R.string.folder_download); + if (delta < 100 || Math.abs(mVideoView.getDuration() - delta) < 100) { + return false; } else { - saveFolderName = mSaveDirectory.getName(); + return true; } - mSaveFileName = new SimpleDateFormat(TIME_STAMP_NAME).format( - new Date(System.currentTimeMillis())); + } - mDstFile = new File(mSaveDirectory, mSaveFileName + ".mp4"); - mSrcFile = new File(mSrcVideoPath); + private void trimVideo() { + + mDstFileInfo = SaveVideoFileUtils.getDstMp4FileInfo(TIME_STAMP_NAME, + getContentResolver(), mUri, getString(R.string.folder_download)); + final File mSrcFile = new File(mSrcVideoPath); showProgressDialog(); @@ -294,9 +236,11 @@ public class TrimVideo extends Activity implements @Override public void run() { try { - TrimVideoUtils.startTrim(mSrcFile, mDstFile, mTrimStartTime, mTrimEndTime); + VideoUtils.startTrim(mSrcFile, mDstFileInfo.mFile, + mTrimStartTime, mTrimEndTime, mVideoView.getDuration()); // Update the database for adding a new video file. - insertContent(mDstFile); + SaveVideoFileUtils.insertContent(mDstFileInfo, + getContentResolver(), mUri); } catch (IOException e) { e.printStackTrace(); } @@ -305,7 +249,7 @@ public class TrimVideo extends Activity implements @Override public void run() { Toast.makeText(getApplicationContext(), - getString(R.string.save_into) + " " + saveFolderName, + getString(R.string.save_into, mDstFileInfo.mFolderName), Toast.LENGTH_SHORT) .show(); // TODO: change trimming into a service to avoid @@ -315,7 +259,7 @@ public class TrimVideo extends Activity implements mProgress = null; // Show the result only when the activity not stopped. Intent intent = new Intent(android.content.Intent.ACTION_VIEW); - intent.setDataAndTypeAndNormalize(Uri.fromFile(mDstFile), "video/*"); + intent.setDataAndType(Uri.fromFile(mDstFileInfo.mFile), "video/*"); intent.putExtra(MediaStore.EXTRA_FINISH_ON_COMPLETION, false); startActivity(intent); finish(); @@ -338,53 +282,6 @@ public class TrimVideo extends Activity implements mProgress.show(); } - /** - * Insert the content (saved file) with proper video properties. - */ - private Uri insertContent(File file) { - long nowInMs = System.currentTimeMillis(); - long nowInSec = nowInMs / 1000; - final ContentValues values = new ContentValues(12); - values.put(Video.Media.TITLE, mSaveFileName); - values.put(Video.Media.DISPLAY_NAME, file.getName()); - values.put(Video.Media.MIME_TYPE, "video/mp4"); - values.put(Video.Media.DATE_TAKEN, nowInMs); - values.put(Video.Media.DATE_MODIFIED, nowInSec); - values.put(Video.Media.DATE_ADDED, nowInSec); - values.put(Video.Media.DATA, file.getAbsolutePath()); - values.put(Video.Media.SIZE, file.length()); - // Copy the data taken and location info from src. - String[] projection = new String[] { - VideoColumns.DATE_TAKEN, - VideoColumns.LATITUDE, - VideoColumns.LONGITUDE, - VideoColumns.RESOLUTION, - }; - - // Copy some info from the source file. - querySource(projection, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - long timeTaken = cursor.getLong(0); - if (timeTaken > 0) { - values.put(Video.Media.DATE_TAKEN, timeTaken); - } - double latitude = cursor.getDouble(1); - double longitude = cursor.getDouble(2); - // TODO: Change || to && after the default location issue is - // fixed. - if ((latitude != 0f) || (longitude != 0f)) { - values.put(Video.Media.LATITUDE, latitude); - values.put(Video.Media.LONGITUDE, longitude); - } - values.put(Video.Media.RESOLUTION, cursor.getString(3)); - - } - }); - - return getContentResolver().insert(Video.Media.EXTERNAL_CONTENT_URI, values); - } - @Override public void onPlayPause() { if (mVideoView.isPlaying()) { @@ -410,6 +307,8 @@ public class TrimVideo extends Activity implements mTrimStartTime = start; mTrimEndTime = end; setProgress(); + // Enable save if there's modifications + mSaveVideoTextView.setEnabled(isModified()); } @Override diff --git a/src/com/android/gallery3d/app/TrimVideoUtils.java b/src/com/android/gallery3d/app/VideoUtils.java index ae9b1e9ce..8ffc3d5eb 100644 --- a/src/com/android/gallery3d/app/TrimVideoUtils.java +++ b/src/com/android/gallery3d/app/VideoUtils.java @@ -19,6 +19,7 @@ package com.android.gallery3d.app; +import com.android.gallery3d.util.SaveVideoFileInfo; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.TimeToSampleBox; import com.googlecode.mp4parser.authoring.Movie; @@ -36,12 +37,46 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; -/** - * Shortens/Crops a track - */ -public class TrimVideoUtils { +public class VideoUtils { - public static void startTrim(File src, File dst, int startMs, int endMs) throws IOException { + public static void startMute(String filePath, SaveVideoFileInfo dstFileInfo) throws IOException { + File dst = dstFileInfo.mFile; + File src = new File(filePath); + RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); + Movie movie = MovieCreator.build(randomAccessFile.getChannel()); + + // remove all tracks we will create new tracks from the old + List<Track> tracks = movie.getTracks(); + movie.setTracks(new LinkedList<Track>()); + + for (Track track : tracks) { + if (track.getHandler().equals("vide")) { + movie.addTrack(track); + } + } + writeMovieIntoFile(dst, movie); + randomAccessFile.close(); + } + + private static void writeMovieIntoFile(File dst, Movie movie) + throws IOException { + if (!dst.exists()) { + dst.createNewFile(); + } + + IsoFile out = new DefaultMp4Builder().build(movie); + FileOutputStream fos = new FileOutputStream(dst); + FileChannel fc = fos.getChannel(); + out.getBox(fc); // This one build up the memory. + + fc.close(); + fos.close(); + } + + /** + * Shortens/Crops a track + */ + public static void startTrim(File src, File dst, int startMs, int endMs, int totalMs) throws IOException { RandomAccessFile randomAccessFile = new RandomAccessFile(src, "r"); Movie movie = MovieCreator.build(randomAccessFile.getChannel()); @@ -100,18 +135,7 @@ public class TrimVideoUtils { } movie.addTrack(new CroppedTrack(track, startSample, endSample)); } - IsoFile out = new DefaultMp4Builder().build(movie); - - if (!dst.exists()) { - dst.createNewFile(); - } - - FileOutputStream fos = new FileOutputStream(dst); - FileChannel fc = fos.getChannel(); - out.getBox(fc); // This one build up the memory. - - fc.close(); - fos.close(); + writeMovieIntoFile(dst, movie); randomAccessFile.close(); } diff --git a/src/com/android/gallery3d/app/Wallpaper.java b/src/com/android/gallery3d/app/Wallpaper.java index 996d3f080..1bbe8d2c6 100644 --- a/src/com/android/gallery3d/app/Wallpaper.java +++ b/src/com/android/gallery3d/app/Wallpaper.java @@ -26,6 +26,8 @@ import android.os.Bundle; import android.view.Display; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; /** * Wallpaper picker for the gallery application. This just redirects to the @@ -98,19 +100,18 @@ public class Wallpaper extends Activity { Point size = getDefaultDisplaySize(new Point()); float spotlightX = (float) size.x / width; float spotlightY = (float) size.y / height; - Intent request = new Intent(CropImage.ACTION_CROP) + Intent request = new Intent(FilterShowActivity.CROP_ACTION) .setDataAndType(mPickedItem, IMAGE_TYPE) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) - .putExtra(CropImage.KEY_OUTPUT_X, width) - .putExtra(CropImage.KEY_OUTPUT_Y, height) - .putExtra(CropImage.KEY_ASPECT_X, width) - .putExtra(CropImage.KEY_ASPECT_Y, height) - .putExtra(CropImage.KEY_SPOTLIGHT_X, spotlightX) - .putExtra(CropImage.KEY_SPOTLIGHT_Y, spotlightY) - .putExtra(CropImage.KEY_SCALE, true) - .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true) - .putExtra(CropImage.KEY_NO_FACE_DETECTION, true) - .putExtra(CropImage.KEY_SET_AS_WALLPAPER, true); + .putExtra(CropExtras.KEY_OUTPUT_X, width) + .putExtra(CropExtras.KEY_OUTPUT_Y, height) + .putExtra(CropExtras.KEY_ASPECT_X, width) + .putExtra(CropExtras.KEY_ASPECT_Y, height) + .putExtra(CropExtras.KEY_SPOTLIGHT_X, spotlightX) + .putExtra(CropExtras.KEY_SPOTLIGHT_Y, spotlightY) + .putExtra(CropExtras.KEY_SCALE, true) + .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropExtras.KEY_SET_AS_WALLPAPER, true); startActivity(request); finish(); } diff --git a/src/com/android/gallery3d/data/Exif.java b/src/com/android/gallery3d/data/Exif.java index ba5862a86..30aba7e97 100644 --- a/src/com/android/gallery3d/data/Exif.java +++ b/src/com/android/gallery3d/data/Exif.java @@ -18,144 +18,55 @@ package com.android.gallery3d.data; import android.util.Log; +import com.android.gallery3d.exif.ExifInvalidFormatException; +import com.android.gallery3d.exif.ExifParser; +import com.android.gallery3d.exif.ExifTag; + import java.io.IOException; import java.io.InputStream; public class Exif { - private static final String TAG = "CameraExif"; + private static final String TAG = "GalleryExif"; public static int getOrientation(InputStream is) { if (is == null) { return 0; } - byte[] buf = new byte[8]; - int length = 0; - - // ISO/IEC 10918-1:1993(E) - while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) { - int marker = buf[1] & 0xFF; - - // Check if the marker is a padding. - if (marker == 0xFF) { - continue; - } - - // Check if the marker is SOI or TEM. - if (marker == 0xD8 || marker == 0x01) { - continue; - } - // Check if the marker is EOI or SOS. - if (marker == 0xD9 || marker == 0xDA) { - return 0; - } - - // Get the length and check if it is reasonable. - if (!read(is, buf, 2)) { - return 0; - } - length = pack(buf, 0, 2, false); - if (length < 2) { - Log.e(TAG, "Invalid length"); - return 0; - } - length -= 2; - - // Break if the marker is EXIF in APP1. - if (marker == 0xE1 && length >= 6) { - if (!read(is, buf, 6)) return 0; - length -= 6; - if (pack(buf, 0, 4, false) == 0x45786966 && - pack(buf, 4, 2, false) == 0) { - break; - } - } - - // Skip other markers. - try { - is.skip(length); - } catch (IOException ex) { - return 0; - } - length = 0; - } - - // JEITA CP-3451 Exif Version 2.2 - if (length > 8) { - int offset = 0; - byte[] jpeg = new byte[length]; - if (!read(is, jpeg, length)) { - return 0; - } - - // Identify the byte order. - int tag = pack(jpeg, offset, 4, false); - if (tag != 0x49492A00 && tag != 0x4D4D002A) { - Log.e(TAG, "Invalid byte order"); - return 0; - } - boolean littleEndian = (tag == 0x49492A00); - - // Get the offset and check if it is reasonable. - int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; - if (count < 10 || count > length) { - Log.e(TAG, "Invalid offset"); - return 0; - } - offset += count; - length -= count; - - // Get the count and go through all the elements. - count = pack(jpeg, offset - 2, 2, littleEndian); - while (count-- > 0 && length >= 12) { - // Get the tag and check if it is orientation. - tag = pack(jpeg, offset, 2, littleEndian); - if (tag == 0x0112) { - // We do not really care about type and count, do we? - int orientation = pack(jpeg, offset + 8, 2, littleEndian); - switch (orientation) { - case 1: - return 0; - case 3: - return 180; - case 6: - return 90; - case 8: - return 270; + try { + ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + if (event == ExifParser.EVENT_NEW_TAG) { + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_ORIENTATION && + tag.hasValue()) { + int orient = (int) tag.getValueAt(0); + switch (orient) { + case ExifTag.Orientation.TOP_LEFT: + return 0; + case ExifTag.Orientation.BOTTOM_LEFT: + return 180; + case ExifTag.Orientation.RIGHT_TOP: + return 90; + case ExifTag.Orientation.RIGHT_BOTTOM: + return 270; + default: + Log.i(TAG, "Unsupported orientation"); + return 0; + } } - Log.i(TAG, "Unsupported orientation"); - return 0; } - offset += 12; - length -= 12; + event = parser.next(); } - } - - Log.i(TAG, "Orientation not found"); - return 0; - } - - private static int pack(byte[] bytes, int offset, int length, - boolean littleEndian) { - int step = 1; - if (littleEndian) { - offset += length - 1; - step = -1; - } - - int value = 0; - while (length-- > 0) { - value = (value << 8) | (bytes[offset] & 0xFF); - offset += step; - } - return value; - } - - private static boolean read(InputStream is, byte[] buf, int length) { - try { - return is.read(buf, 0, length) == length; - } catch (IOException ex) { - return false; + Log.i(TAG, "Orientation not found"); + return 0; + } catch (IOException e) { + Log.w(TAG, "Failed to read EXIF orientation", e); + return 0; + } catch (ExifInvalidFormatException e) { + Log.w(TAG, "Failed to read EXIF orientation", e); + return 0; } } } diff --git a/src/com/android/gallery3d/data/LocalAlbum.java b/src/com/android/gallery3d/data/LocalAlbum.java index e05aac01b..7b7015af6 100644 --- a/src/com/android/gallery3d/data/LocalAlbum.java +++ b/src/com/android/gallery3d/data/LocalAlbum.java @@ -20,6 +20,7 @@ import android.content.ContentResolver; import android.content.res.Resources; import android.database.Cursor; import android.net.Uri; +import android.os.Environment; import android.provider.MediaStore; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; @@ -29,9 +30,11 @@ import android.provider.MediaStore.Video.VideoColumns; import com.android.gallery3d.R; import com.android.gallery3d.app.GalleryApp; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.util.BucketNames; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.MediaSetUtils; +import java.io.File; import java.util.ArrayList; // LocalAlbumSet lists all media items in one bucket on local storage. @@ -61,7 +64,7 @@ public class LocalAlbum extends MediaSet { mApplication = application; mResolver = application.getContentResolver(); mBucketId = bucketId; - mName = getLocalizedName(application.getResources(), bucketId, name); + mName = name; mIsImage = isImage; if (isImage) { @@ -245,7 +248,7 @@ public class LocalAlbum extends MediaSet { @Override public String getName() { - return mName; + return getLocalizedName(mApplication.getResources(), mBucketId, mName); } @Override @@ -290,4 +293,33 @@ public class LocalAlbum extends MediaSet { return name; } } + + // Relative path is the absolute path minus external storage path + public static String getRelativePath(int bucketId) { + String relativePath = "/"; + if (bucketId == MediaSetUtils.CAMERA_BUCKET_ID) { + relativePath += BucketNames.CAMERA; + } else if (bucketId == MediaSetUtils.DOWNLOAD_BUCKET_ID) { + relativePath += BucketNames.DOWNLOAD; + } else if (bucketId == MediaSetUtils.IMPORTED_BUCKET_ID) { + relativePath += BucketNames.IMPORTED; + } else if (bucketId == MediaSetUtils.SNAPSHOT_BUCKET_ID) { + relativePath += BucketNames.SCREENSHOTS; + } else if (bucketId == MediaSetUtils.EDITED_ONLINE_PHOTOS_BUCKET_ID) { + relativePath += BucketNames.EDITED_ONLINE_PHOTOS; + } else { + // If the first few cases didn't hit the matching path, do a + // thorough search in the local directories. + File extStorage = Environment.getExternalStorageDirectory(); + String path = GalleryUtils.searchDirForPath(extStorage, bucketId); + if (path == null) { + Log.w(TAG, "Relative path for bucket id: " + bucketId + " is not found."); + relativePath = null; + } else { + relativePath = path.substring(extStorage.getAbsolutePath().length()); + } + } + return relativePath; + } + } diff --git a/src/com/android/gallery3d/data/LocalMergeAlbum.java b/src/com/android/gallery3d/data/LocalMergeAlbum.java index cbaf82fff..f0b5e5726 100644 --- a/src/com/android/gallery3d/data/LocalMergeAlbum.java +++ b/src/com/android/gallery3d/data/LocalMergeAlbum.java @@ -41,7 +41,6 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { private final Comparator<MediaItem> mComparator; private final MediaSet[] mSources; - private String mName; private FetchCache[] mFetcher; private int mSupportedOperation; private int mBucketId; @@ -54,7 +53,6 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { super(path, INVALID_DATA_VERSION); mComparator = comparator; mSources = sources; - mName = sources.length == 0 ? "" : sources[0].getName(); mBucketId = bucketId; for (MediaSet set : mSources) { set.addContentListener(this); @@ -82,7 +80,6 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { mSupportedOperation = supported; mIndex.clear(); mIndex.put(0, new int[mSources.length]); - mName = mSources.length == 0 ? "" : mSources[0].getName(); } private void invalidateCache() { @@ -111,7 +108,7 @@ public class LocalMergeAlbum extends MediaSet implements ContentListener { @Override public String getName() { - return mName; + return mSources.length == 0 ? "" : mSources[0].getName(); } @Override diff --git a/src/com/android/gallery3d/data/LocalVideo.java b/src/com/android/gallery3d/data/LocalVideo.java index 44b853901..b1e1cb3aa 100644 --- a/src/com/android/gallery3d/data/LocalVideo.java +++ b/src/com/android/gallery3d/data/LocalVideo.java @@ -180,7 +180,7 @@ public class LocalVideo extends LocalMediaItem { @Override public int getSupportedOperations() { - return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM; + return SUPPORT_DELETE | SUPPORT_SHARE | SUPPORT_PLAY | SUPPORT_INFO | SUPPORT_TRIM | SUPPORT_MUTE; } @Override diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java index 298224729..16716dae4 100644 --- a/src/com/android/gallery3d/data/MediaDetails.java +++ b/src/com/android/gallery3d/data/MediaDetails.java @@ -19,9 +19,15 @@ package com.android.gallery3d.data; import android.media.ExifInterface; import com.android.gallery3d.R; -import com.android.gallery3d.common.ExifTags; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.exif.ExifData; +import com.android.gallery3d.exif.ExifInvalidFormatException; +import com.android.gallery3d.exif.ExifReader; +import com.android.gallery3d.exif.ExifTag; +import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Iterator; import java.util.Map.Entry; @@ -105,10 +111,18 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { return mUnits.get(index); } - private static void setExifData(MediaDetails details, ExifInterface exif, String tag, + private static void setExifData(MediaDetails details, ExifTag tag, int key) { - String value = exif.getAttribute(tag); - if (value != null) { + if (tag != null) { + String value = null; + int type = tag.getDataType(); + if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) { + value = String.valueOf(tag.getRational(0).toDouble()); + } else if (type == ExifTag.TYPE_ASCII) { + value = tag.getString(); + } else { + value = String.valueOf(tag.getValueAt(0)); + } if (key == MediaDetails.INDEX_FLASH) { MediaDetails.FlashState state = new MediaDetails.FlashState( Integer.valueOf(value.toString())); @@ -120,29 +134,37 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { } public static void extractExifInfo(MediaDetails details, String filePath) { + InputStream is = null; try { - ExifInterface exif = new ExifInterface(filePath); - setExifData(details, exif, ExifInterface.TAG_FLASH, MediaDetails.INDEX_FLASH); - setExifData(details, exif, ExifInterface.TAG_IMAGE_WIDTH, MediaDetails.INDEX_WIDTH); - setExifData(details, exif, ExifInterface.TAG_IMAGE_LENGTH, - MediaDetails.INDEX_HEIGHT); - setExifData(details, exif, ExifInterface.TAG_MAKE, MediaDetails.INDEX_MAKE); - setExifData(details, exif, ExifInterface.TAG_MODEL, MediaDetails.INDEX_MODEL); - setExifData(details, exif, ExifTags.TAG_APERTURE, MediaDetails.INDEX_APERTURE); - setExifData(details, exif, ExifTags.TAG_ISO, MediaDetails.INDEX_ISO); - setExifData(details, exif, ExifInterface.TAG_WHITE_BALANCE, + is = new FileInputStream(filePath); + ExifData data = new ExifReader().read(is); + setExifData(details, data.getTag(ExifTag.TAG_FLASH), MediaDetails.INDEX_FLASH); + setExifData(details, data.getTag(ExifTag.TAG_IMAGE_WIDTH), MediaDetails.INDEX_WIDTH); + setExifData(details, data.getTag(ExifTag.TAG_IMAGE_LENGTH), MediaDetails.INDEX_HEIGHT); + setExifData(details, data.getTag(ExifTag.TAG_MAKE), MediaDetails.INDEX_MAKE); + setExifData(details, data.getTag(ExifTag.TAG_MODEL),MediaDetails.INDEX_MODEL); + setExifData(details, data.getTag(ExifTag.TAG_APERTURE_VALUE), + MediaDetails.INDEX_APERTURE); + setExifData(details, data.getTag(ExifTag.TAG_ISO_SPEED_RATINGS), + MediaDetails.INDEX_ISO); + setExifData(details, data.getTag(ExifTag.TAG_WHITE_BALANCE), MediaDetails.INDEX_WHITE_BALANCE); - setExifData(details, exif, ExifTags.TAG_EXPOSURE_TIME, + setExifData(details, data.getTag(ExifTag.TAG_EXPOSURE_TIME), MediaDetails.INDEX_EXPOSURE_TIME); - - double data = exif.getAttributeDouble(ExifInterface.TAG_FOCAL_LENGTH, 0); - if (data != 0f) { - details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, data); + ExifTag focalTag = data.getTag(ExifTag.TAG_FOCAL_LENGTH); + if (focalTag != null) { + details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, + focalTag.getRational(0).toDouble()); details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm); } } catch (IOException ex) { // ignore it. Log.w(TAG, "", ex); + } catch (ExifInvalidFormatException ex) { + // ignore it. + Log.w(TAG, "", ex); + } finally { + Utils.closeSilently(is); } } } diff --git a/src/com/android/gallery3d/data/MediaObject.java b/src/com/android/gallery3d/data/MediaObject.java index a41b275fb..9c82661f6 100644 --- a/src/com/android/gallery3d/data/MediaObject.java +++ b/src/com/android/gallery3d/data/MediaObject.java @@ -41,6 +41,7 @@ public abstract class MediaObject { public static final int SUPPORT_BACK = 1 << 14; public static final int SUPPORT_ACTION = 1 << 15; public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 16; + public static final int SUPPORT_MUTE = 1 << 17; public static final int SUPPORT_ALL = 0xffffffff; // These are the bits returned from getMediaType(): diff --git a/src/com/android/gallery3d/filtershow/CropExtras.java b/src/com/android/gallery3d/filtershow/CropExtras.java new file mode 100644 index 000000000..7ed8f1eb5 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/CropExtras.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow; + +import android.net.Uri; + +public class CropExtras { + + public static final String KEY_CROPPED_RECT = "cropped-rect"; + public static final String KEY_OUTPUT_X = "outputX"; + public static final String KEY_OUTPUT_Y = "outputY"; + public static final String KEY_SCALE = "scale"; + public static final String KEY_SCALE_UP_IF_NEEDED = "scaleUpIfNeeded"; + public static final String KEY_ASPECT_X = "aspectX"; + public static final String KEY_ASPECT_Y = "aspectY"; + public static final String KEY_SET_AS_WALLPAPER = "set-as-wallpaper"; + public static final String KEY_RETURN_DATA = "return-data"; + public static final String KEY_DATA = "data"; + public static final String KEY_SPOTLIGHT_X = "spotlightX"; + public static final String KEY_SPOTLIGHT_Y = "spotlightY"; + public static final String KEY_SHOW_WHEN_LOCKED = "showWhenLocked"; + public static final String KEY_OUTPUT_FORMAT = "outputFormat"; + + private int mOutputX = 0; + private int mOutputY = 0; + private boolean mScaleUp = true; + private int mAspectX = 0; + private int mAspectY = 0; + private boolean mSetAsWallpaper = false; + private boolean mReturnData = false; + private Uri mExtraOutput = null; + private String mOutputFormat = null; + private boolean mShowWhenLocked = false; + private float mSpotlightX = 0; + private float mSpotlightY = 0; + + public CropExtras(int outputX, int outputY, boolean scaleUp, int aspectX, int aspectY, + boolean setAsWallpaper, boolean returnData, Uri extraOutput, String outputFormat, + boolean showWhenLocked, float spotlightX, float spotlightY) { + mOutputX = outputX; + mOutputY = outputY; + mScaleUp = scaleUp; + mAspectX = aspectX; + mAspectY = aspectY; + mSetAsWallpaper = setAsWallpaper; + mReturnData = returnData; + mExtraOutput = extraOutput; + mOutputFormat = outputFormat; + mShowWhenLocked = showWhenLocked; + mSpotlightX = spotlightX; + mSpotlightY = spotlightY; + } + + public CropExtras(CropExtras c) { + this(c.mOutputX, c.mOutputY, c.mScaleUp, c.mAspectX, c.mAspectY, c.mSetAsWallpaper, + c.mReturnData, c.mExtraOutput, c.mOutputFormat, c.mShowWhenLocked, + c.mSpotlightX, c.mSpotlightY); + } + + public int getOutputX() { + return mOutputX; + } + + public int getOutputY() { + return mOutputY; + } + + public boolean getScaleUp() { + return mScaleUp; + } + + public int getAspectX() { + return mAspectX; + } + + public int getAspectY() { + return mAspectY; + } + + public boolean getSetAsWallpaper() { + return mSetAsWallpaper; + } + + public boolean getReturnData() { + return mReturnData; + } + + public Uri getExtraOutput() { + return mExtraOutput; + } + + public String getOutputFormat() { + return mOutputFormat; + } + + public boolean getShowWhenLocked() { + return mShowWhenLocked; + } + + public float getSpotlightX() { + return mSpotlightX; + } + + public float getSpotlightY() { + return mSpotlightY; + } +} diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index ff78bd1b9..4198da020 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -20,6 +20,7 @@ import android.annotation.TargetApi; import android.app.ActionBar; import android.app.Activity; import android.app.ProgressDialog; +import android.app.WallpaperManager; import android.content.ContentValues; import android.content.Intent; import android.content.res.Configuration; @@ -32,6 +33,7 @@ import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.provider.MediaStore; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; @@ -58,13 +60,19 @@ import com.android.gallery3d.filtershow.filters.ImageFilter; import com.android.gallery3d.filtershow.filters.ImageFilterBorder; import com.android.gallery3d.filtershow.filters.ImageFilterBwFilter; import com.android.gallery3d.filtershow.filters.ImageFilterContrast; +import com.android.gallery3d.filtershow.filters.ImageFilterCurves; +import com.android.gallery3d.filtershow.filters.ImageFilterDownsample; +import com.android.gallery3d.filtershow.filters.ImageFilterEdge; import com.android.gallery3d.filtershow.filters.ImageFilterExposure; import com.android.gallery3d.filtershow.filters.ImageFilterFx; import com.android.gallery3d.filtershow.filters.ImageFilterHue; +import com.android.gallery3d.filtershow.filters.ImageFilterKMeans; +import com.android.gallery3d.filtershow.filters.ImageFilterNegative; import com.android.gallery3d.filtershow.filters.ImageFilterParametricBorder; import com.android.gallery3d.filtershow.filters.ImageFilterRS; import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; import com.android.gallery3d.filtershow.filters.ImageFilterShadows; +import com.android.gallery3d.filtershow.filters.ImageFilterSharpen; import com.android.gallery3d.filtershow.filters.ImageFilterTinyPlanet; import com.android.gallery3d.filtershow.filters.ImageFilterVibrance; import com.android.gallery3d.filtershow.filters.ImageFilterVignette; @@ -72,13 +80,13 @@ import com.android.gallery3d.filtershow.filters.ImageFilterWBalance; import com.android.gallery3d.filtershow.imageshow.ImageBorder; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageFlip; +import com.android.gallery3d.filtershow.imageshow.ImageRedEyes; import com.android.gallery3d.filtershow.imageshow.ImageRotate; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.imageshow.ImageSmallBorder; import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.imageshow.ImageStraighten; import com.android.gallery3d.filtershow.imageshow.ImageTinyPlanet; -import com.android.gallery3d.filtershow.imageshow.ImageWithIcon; import com.android.gallery3d.filtershow.imageshow.ImageZoom; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.provider.SharedImageProvider; @@ -90,6 +98,7 @@ import com.android.gallery3d.filtershow.ui.Spline; import com.android.gallery3d.util.GalleryUtils; import java.io.File; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.Vector; @@ -97,14 +106,19 @@ import java.util.Vector; public class FilterShowActivity extends Activity implements OnItemClickListener, OnShareTargetSelectedListener { - public static final String CROP_ACTION = "com.android.camera.action.EDITOR_CROP"; + // fields for supporting crop action + public static final String CROP_ACTION = "com.android.camera.action.CROP"; + private CropExtras mCropExtras = null; + public static final String TINY_PLANET_ACTION = "com.android.camera.action.TINY_PLANET"; public static final String LAUNCH_FULLSCREEN = "launch-fullscreen"; + public static final int MAX_BMAP_IN_INTENT = 990000; private final PanelController mPanelController = new PanelController(); private ImageLoader mImageLoader = null; private ImageShow mImageShow = null; private ImageCurves mImageCurves = null; private ImageBorder mImageBorders = null; + private ImageRedEyes mImageRedEyes = null; private ImageStraighten mImageStraighten = null; private ImageZoom mImageZoom = null; private ImageCrop mImageCrop = null; @@ -117,6 +131,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private View mListGeometry = null; private View mListColors = null; private View mListFilterButtons = null; + private View mSaveButton = null; private ImageButton mFxButton = null; private ImageButton mBorderButton = null; @@ -155,7 +170,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, ImageFilterRS.setRenderScriptContext(this); ImageShow.setDefaultBackgroundColor(getResources().getColor(R.color.background_screen)); - ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor(R.color.background_main_toolbar)); + ImageSmallFilter.setDefaultBackgroundColor(getResources().getColor( + R.color.background_main_toolbar)); // TODO: get those values from XML. ImageZoom.setZoomedSize(getPixelsFromDip(256)); FramedTextButton.setTextSize((int) getPixelsFromDip(14)); @@ -180,7 +196,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM); actionBar.setCustomView(R.layout.filtershow_actionbar); - actionBar.getCustomView().setOnClickListener(new OnClickListener() { + mSaveButton = actionBar.getCustomView(); + mSaveButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { saveImage(); @@ -202,9 +219,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageRotate = (ImageRotate) findViewById(R.id.imageRotate); mImageFlip = (ImageFlip) findViewById(R.id.imageFlip); mImageTinyPlanet = (ImageTinyPlanet) findViewById(R.id.imageTinyPlanet); + mImageRedEyes = (ImageRedEyes) findViewById(R.id.imageRedEyes); mImageCrop.setAspectTextSize((int) getPixelsFromDip(18)); ImageCrop.setTouchTolerance((int) getPixelsFromDip(25)); + ImageCrop.setMinCropSize((int) getPixelsFromDip(55)); mImageViews.add(mImageShow); mImageViews.add(mImageCurves); mImageViews.add(mImageBorders); @@ -214,6 +233,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageViews.add(mImageRotate); mImageViews.add(mImageFlip); mImageViews.add(mImageTinyPlanet); + mImageViews.add(mImageRedEyes); + for (ImageShow imageShow : mImageViews) { + mImageLoader.addCacheListener(imageShow); + } mListFx = findViewById(R.id.fxList); mListBorders = findViewById(R.id.bordersList); @@ -253,6 +276,8 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mImageFlip.setMaster(mImageShow); mImageTinyPlanet.setImageLoader(mImageLoader); mImageTinyPlanet.setMaster(mImageShow); + mImageRedEyes.setImageLoader(mImageLoader); + mImageRedEyes.setMaster(mImageShow); mPanelController.setActivity(this); @@ -265,6 +290,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addImageView(findViewById(R.id.imageFlip)); mPanelController.addImageView(findViewById(R.id.imageZoom)); mPanelController.addImageView(findViewById(R.id.imageTinyPlanet)); + mPanelController.addImageView(findViewById(R.id.imageRedEyes)); mPanelController.addPanel(mFxButton, mListFx, 0); mPanelController.addPanel(mBorderButton, mListBorders, 1); @@ -274,86 +300,45 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mPanelController.addComponent(mGeometryButton, findViewById(R.id.cropButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.rotateButton)); mPanelController.addComponent(mGeometryButton, findViewById(R.id.flipButton)); + mPanelController.addComponent(mGeometryButton, findViewById(R.id.redEyeButton)); mPanelController.addPanel(mColorsButton, mListColors, 3); - int[] recastIDs = { - R.id.tinyplanetButton, - R.id.vignetteButton, - R.id.vibranceButton, - R.id.contrastButton, - R.id.saturationButton, - R.id.bwfilterButton, - R.id.wbalanceButton, - R.id.hueButton, - R.id.exposureButton, - R.id.shadowRecoveryButton - }; ImageFilter[] filters = { new ImageFilterTinyPlanet(), + new ImageFilterWBalance(), + new ImageFilterExposure(), new ImageFilterVignette(), - new ImageFilterVibrance(), new ImageFilterContrast(), + new ImageFilterShadows(), + new ImageFilterVibrance(), + new ImageFilterSharpen(), + new ImageFilterCurves(), + new ImageFilterHue(), new ImageFilterSaturated(), new ImageFilterBwFilter(), - new ImageFilterWBalance(), - new ImageFilterHue(), - new ImageFilterExposure(), - new ImageFilterShadows() + new ImageFilterNegative(), + new ImageFilterEdge(), + new ImageFilterKMeans(), + new ImageFilterDownsample() }; for (int i = 0; i < filters.length; i++) { ImageSmallFilter fView = new ImageSmallFilter(this); - View v = listColors.findViewById(recastIDs[i]); - int pos = listColors.indexOfChild(v); - listColors.removeView(v); - filters[i].setParameter(filters[i].getPreviewParameter()); - if (v instanceof ImageButtonTitle) - filters[i].setName(((ImageButtonTitle) v).getText()); + filters[i].setName(getString(filters[i].getTextId())); fView.setImageFilter(filters[i]); fView.setController(this); fView.setImageLoader(mImageLoader); - fView.setId(recastIDs[i]); - mPanelController.addComponent(mColorsButton, fView); - listColors.addView(fView, pos); - } - - int[] overlayIDs = { - R.id.sharpenButton, - R.id.curvesButtonRGB - }; - int[] overlayBitmaps = { - R.drawable.filtershow_button_colors_sharpen, - R.drawable.filtershow_button_colors_curve - }; - int[] overlayNames = { - R.string.sharpness, - R.string.curvesRGB - }; - - for (int i = 0; i < overlayIDs.length; i++) { - ImageWithIcon fView = new ImageWithIcon(this); - View v = listColors.findViewById(overlayIDs[i]); - int pos = listColors.indexOfChild(v); - listColors.removeView(v); - final int sid = overlayNames[i]; - ImageFilterExposure efilter = new ImageFilterExposure() { - { - mName = getString(sid); - } - }; - efilter.setParameter(-300); - Bitmap bitmap = BitmapFactory.decodeResource(getResources(), - overlayBitmaps[i]); - - fView.setIcon(bitmap); - fView.setImageFilter(efilter); - fView.setController(this); - fView.setImageLoader(mImageLoader); - fView.setId(overlayIDs[i]); + fView.setId(filters[i].getButtonId()); + if (filters[i].getOverlayBitmaps() != 0) { + Bitmap bitmap = BitmapFactory.decodeResource(getResources(), + filters[i].getOverlayBitmaps()); + fView.setOverlayBitmap(bitmap); + } mPanelController.addComponent(mColorsButton, fView); - listColors.addView(fView, pos); + mPanelController.addFilter(filters[i]); + listColors.addView(fView); } mPanelController.addView(findViewById(R.id.applyEffect)); @@ -395,8 +380,37 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, pickImage(); } + // Handle behavior for various actions String action = intent.getAction(); if (action.equalsIgnoreCase(CROP_ACTION)) { + Bundle extras = intent.getExtras(); + if (extras != null) { + mCropExtras = new CropExtras(extras.getInt(CropExtras.KEY_OUTPUT_X, 0), + extras.getInt(CropExtras.KEY_OUTPUT_Y, 0), + extras.getBoolean(CropExtras.KEY_SCALE, true) && + extras.getBoolean(CropExtras.KEY_SCALE_UP_IF_NEEDED, false), + extras.getInt(CropExtras.KEY_ASPECT_X, 0), + extras.getInt(CropExtras.KEY_ASPECT_Y, 0), + extras.getBoolean(CropExtras.KEY_SET_AS_WALLPAPER, false), + extras.getBoolean(CropExtras.KEY_RETURN_DATA, false), + (Uri) extras.getParcelable(MediaStore.EXTRA_OUTPUT), + extras.getString(CropExtras.KEY_OUTPUT_FORMAT), + extras.getBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, false), + extras.getFloat(CropExtras.KEY_SPOTLIGHT_X), + extras.getFloat(CropExtras.KEY_SPOTLIGHT_Y)); + + if (mCropExtras.getShowWhenLocked()) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + mImageShow.getImagePreset().mGeoData.setCropExtras(mCropExtras); + + mImageCrop.setExtras(mCropExtras); + String s = getString(R.string.Fixed); + mImageCrop.setAspectString(s); + mImageCrop.setCropActionFlag(true); + mPanelController.setFixedAspect(mCropExtras.getAspectX() > 0 + && mCropExtras.getAspectY() > 0); + } mPanelController.showComponent(findViewById(R.id.cropButton)); } else if (action.equalsIgnoreCase(TINY_PLANET_ACTION)) { mPanelController.showComponent(findViewById(R.id.tinyplanetButton)); @@ -416,7 +430,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, mLoadBitmapTask.execute(uri); } - private class LoadBitmapTask extends AsyncTask<Uri, Void, Boolean> { + private class LoadBitmapTask extends AsyncTask<Uri, Boolean, Boolean> { View mTinyPlanetButton; int mBitmapSize; @@ -427,19 +441,26 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, @Override protected Boolean doInBackground(Uri... params) { - mImageLoader.loadBitmap(params[0], mBitmapSize); - publishProgress(); - return mImageLoader.queryLightCycle360(); + if (!mImageLoader.loadBitmap(params[0], mBitmapSize)) { + return false; + } + publishProgress(mImageLoader.queryLightCycle360()); + return true; } @Override - protected void onProgressUpdate(Void... values) { + protected void onProgressUpdate(Boolean... values) { super.onProgressUpdate(values); - if (isCancelled()) return; + if (isCancelled()) { + return; + } final View filters = findViewById(R.id.filtersPanel); final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); filters.setVisibility(View.VISIBLE); + if (values[0]) { + mTinyPlanetButton.setVisibility(View.VISIBLE); + } } @Override @@ -447,9 +468,10 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, if (isCancelled()) { return; } - if (result) { - mTinyPlanetButton.setVisibility(View.VISIBLE); + if (!result) { + cannotLoadImage(); } + mLoadBitmapTask = null; super.onPostExecute(result); } @@ -645,6 +667,11 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, return false; } + public void enableSave(boolean enable) { + if (mSaveButton != null) + mSaveButton.setEnabled(enable); + } + private void fillListImages(LinearLayout listFilters) { // TODO: use listview // TODO: load the filters straight from the filesystem @@ -1011,17 +1038,88 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, } } + private boolean mSaveToExtraUri = false; + private boolean mSaveAsWallpaper = false; + private boolean mReturnAsExtra = false; + private boolean outputted = false; + public void saveImage() { - if (mImageShow.hasModifications()) { - // Get the name of the album, to which the image will be saved - File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); - int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); - String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); - showSavingProgress(albumName); - mImageShow.saveImage(this, null); - } else { - finish(); + // boolean outputted = false; + if (mCropExtras != null) { + if (mCropExtras.getExtraOutput() != null) { + mSaveToExtraUri = true; + outputted = true; + } + if (mCropExtras.getSetAsWallpaper()) { + mSaveAsWallpaper = true; + outputted = true; + } + if (mCropExtras.getReturnData()) { + + mReturnAsExtra = true; + outputted = true; + } + + if (outputted) { + mImageShow.getImagePreset().mGeoData.setUseCropExtrasFlag(true); + showSavingProgress(null); + mImageShow.returnFilteredResult(this); + } + } + if (!outputted) { + if (mImageShow.hasModifications()) { + // Get the name of the album, to which the image will be saved + File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); + int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); + String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); + showSavingProgress(albumName); + mImageShow.saveImage(this, null); + } else { + done(); + } + } + } + + public void onFilteredResult(Bitmap filtered) { + Intent intent = new Intent(); + intent.putExtra(CropExtras.KEY_CROPPED_RECT, mImageShow.getImageCropBounds()); + if (mSaveToExtraUri) { + mImageShow.saveToUri(filtered, mCropExtras.getExtraOutput(), + mCropExtras.getOutputFormat(), this); + } + if (mSaveAsWallpaper) { + try { + WallpaperManager.getInstance(this).setBitmap(filtered); + } catch (IOException e) { + Log.w(LOGTAG, "fail to set wall paper", e); + } } + if (mReturnAsExtra) { + if (filtered != null) { + int bmapSize = filtered.getRowBytes() * filtered.getHeight(); + /* + * Max size of Binder transaction buffer is 1Mb, so constrain + * Bitmap to be somewhat less than this, otherwise we get + * TransactionTooLargeExceptions. + */ + if (bmapSize > MAX_BMAP_IN_INTENT) { + Log.w(LOGTAG, "Bitmap too large to be returned via intent"); + } else { + intent.putExtra(CropExtras.KEY_DATA, filtered); + } + } + } + setResult(RESULT_OK, intent); + if (!mSaveToExtraUri) { + done(); + } + } + + public void done() { + if (outputted) { + hideSavingProgress(); + } + finish(); } static { diff --git a/src/com/android/gallery3d/filtershow/PanelController.java b/src/com/android/gallery3d/filtershow/PanelController.java index e92c54936..94259e91d 100644 --- a/src/com/android/gallery3d/filtershow/PanelController.java +++ b/src/com/android/gallery3d/filtershow/PanelController.java @@ -32,6 +32,7 @@ import com.android.gallery3d.filtershow.filters.ImageFilterContrast; import com.android.gallery3d.filtershow.filters.ImageFilterCurves; import com.android.gallery3d.filtershow.filters.ImageFilterExposure; import com.android.gallery3d.filtershow.filters.ImageFilterHue; +import com.android.gallery3d.filtershow.filters.ImageFilterNegative; import com.android.gallery3d.filtershow.filters.ImageFilterRedEye; import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; import com.android.gallery3d.filtershow.filters.ImageFilterShadows; @@ -42,6 +43,7 @@ import com.android.gallery3d.filtershow.filters.ImageFilterVignette; import com.android.gallery3d.filtershow.filters.ImageFilterWBalance; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageShow; +import com.android.gallery3d.filtershow.imageshow.ImageSmallFilter; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.filtershow.ui.FramedTextButton; import com.android.gallery3d.filtershow.ui.ImageCurves; @@ -57,6 +59,11 @@ public class PanelController implements OnClickListener { private static final int ANIM_DURATION = 200; private static final String LOGTAG = "PanelController"; private boolean mDisableFilterButtons = false; + private boolean mFixedAspect = false; + + public void setFixedAspect(boolean t) { + mFixedAspect = t; + } class Panel { private final View mView; @@ -301,6 +308,7 @@ public class PanelController implements OnClickListener { private final HashMap<View, Panel> mPanels = new HashMap<View, Panel>(); private final HashMap<View, ViewType> mViews = new HashMap<View, ViewType>(); + private final HashMap<String, ImageFilter> mFilters = new HashMap<String, ImageFilter>(); private final Vector<View> mImageViews = new Vector<View>(); private View mCurrentPanel = null; private View mRowPanel = null; @@ -334,6 +342,10 @@ public class PanelController implements OnClickListener { mViews.put(component, new ViewType(component, COMPONENT)); } + public void addFilter(ImageFilter filter) { + mFilters.put(filter.getName(), filter); + } + public void addImageView(View view) { mImageViews.add(view); ImageShow imageShow = (ImageShow) view; @@ -487,60 +499,21 @@ public class PanelController implements OnClickListener { filter = copy.getFilter(name); } - if (filter == null && name.equalsIgnoreCase( - mCurrentImage.getContext().getString(R.string.curvesRGB))) { - filter = setImagePreset(new ImageFilterCurves(), name); - } - if (filter == null && name.equalsIgnoreCase( - mCurrentImage.getContext().getString(R.string.tinyplanet))) { - filter = setImagePreset(new ImageFilterTinyPlanet(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.vignette))) { - filter = setImagePreset(new ImageFilterVignette(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.sharpness))) { - filter = setImagePreset(new ImageFilterSharpen(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.contrast))) { - filter = setImagePreset(new ImageFilterContrast(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.saturation))) { - filter = setImagePreset(new ImageFilterSaturated(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.bwfilter))) { - filter = setImagePreset(new ImageFilterBwFilter(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.hue))) { - filter = setImagePreset(new ImageFilterHue(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.exposure))) { - filter = setImagePreset(new ImageFilterExposure(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.vibrance))) { - filter = setImagePreset(new ImageFilterVibrance(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString( - R.string.shadow_recovery))) { - filter = setImagePreset(new ImageFilterShadows(), name); - } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.redeye))) { - filter = setImagePreset(new ImageFilterRedEye(), name); + if (filter == null) { + ImageFilter filterInstance = mFilters.get(name); + if (filterInstance != null) { + try { + ImageFilter newFilter = filterInstance.clone(); + newFilter.reset(); + filter = setImagePreset(newFilter, name); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + } } - if (filter == null - && name.equalsIgnoreCase(mCurrentImage.getContext().getString(R.string.wbalance))) { - filter = setImagePreset(new ImageFilterWBalance(), name); + if (filter != null) { + mMasterImage.setCurrentFilter(filter); } - mMasterImage.setCurrentFilter(filter); } private void showCurvesPopupMenu(final ImageCurves curves, final FramedTextButton anchor) { @@ -597,6 +570,27 @@ public class PanelController implements OnClickListener { } mUtilityPanel.hideAspectButtons(); mUtilityPanel.hideCurvesButtons(); + + if (view instanceof ImageSmallFilter) { + ImageSmallFilter component = (ImageSmallFilter) view; + ImageFilter filter = component.getImageFilter(); + if (filter.getEditingViewId() != 0) { + mCurrentImage = showImageView(filter.getEditingViewId()); + mCurrentImage.setShowControls(filter.showEditingControls()); + String ename = mCurrentImage.getContext().getString(filter.getTextId()); + mUtilityPanel.setEffectName(ename); + if (view.getId() == R.id.curvesButtonRGB) { + // TODO: delegate to the filter / editing view the management of the + // panel accessory view + mUtilityPanel.showCurvesButtons(); + } + mUtilityPanel.setShowParameter(filter.showParameterValue()); + ensureFilter(ename); + mCurrentImage.select(); + } + return; + } + switch (view.getId()) { case R.id.tinyplanetButton: { mCurrentImage = showImageView(R.id.imageTinyPlanet).setShowControls(true); @@ -620,11 +614,13 @@ public class PanelController implements OnClickListener { String ename = mCurrentImage.getContext().getString(R.string.crop); mUtilityPanel.setEffectName(ename); mUtilityPanel.setShowParameter(false); - if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed){ - ((ImageCrop) mCurrentImage).applyClear(); + if (mCurrentImage instanceof ImageCrop && mUtilityPanel.firstTimeCropDisplayed) { + ((ImageCrop) mCurrentImage).clear(); mUtilityPanel.firstTimeCropDisplayed = false; } - mUtilityPanel.showAspectButtons(); + if (!mFixedAspect) { + mUtilityPanel.showAspectButtons(); + } break; } case R.id.rotateButton: { @@ -640,89 +636,8 @@ public class PanelController implements OnClickListener { mUtilityPanel.setShowParameter(false); break; } - case R.id.vignetteButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.vignette); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.curvesButtonRGB: { - ImageCurves curves = (ImageCurves) showImageView(R.id.imageCurves); - String ename = curves.getContext().getString(R.string.curvesRGB); - mUtilityPanel.setEffectName(ename); - mUtilityPanel.setShowParameter(false); - mUtilityPanel.showCurvesButtons(); - mCurrentImage = curves; - ensureFilter(ename); - break; - } - case R.id.sharpenButton: { - mCurrentImage = showImageView(R.id.imageZoom).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.sharpness); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.contrastButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.contrast); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.saturationButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.saturation); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.bwfilterButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.bwfilter); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.wbalanceButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(false); - String ename = mCurrentImage.getContext().getString(R.string.wbalance); - mUtilityPanel.setEffectName(ename); - mUtilityPanel.setShowParameter(false); - ensureFilter(ename); - break; - } - case R.id.hueButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.hue); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.exposureButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.exposure); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.vibranceButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.vibrance); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } - case R.id.shadowRecoveryButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); - String ename = mCurrentImage.getContext().getString(R.string.shadow_recovery); - mUtilityPanel.setEffectName(ename); - ensureFilter(ename); - break; - } case R.id.redEyeButton: { - mCurrentImage = showImageView(R.id.imageShow).setShowControls(true); + mCurrentImage = showImageView(R.id.imageRedEyes).setShowControls(true); String ename = mCurrentImage.getContext().getString(R.string.redeye); mUtilityPanel.setEffectName(ename); ensureFilter(ename); diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index afef58aad..a1a1ba186 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.cache; import android.content.ContentResolver; import android.content.Context; +import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.database.sqlite.SQLiteException; @@ -26,6 +27,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; +import android.graphics.Bitmap.CompressFormat; import android.media.ExifInterface; import android.net.Uri; import android.provider.MediaStore; @@ -36,19 +38,27 @@ import com.adobe.xmp.XMPMeta; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.exif.ExifInvalidFormatException; +import com.android.gallery3d.exif.ExifParser; +import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.HistoryAdapter; import com.android.gallery3d.filtershow.imageshow.ImageCrop; import com.android.gallery3d.filtershow.imageshow.ImageShow; import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.filtershow.tools.BitmapTask; import com.android.gallery3d.filtershow.tools.SaveCopyTask; +import com.android.gallery3d.util.InterruptableOutputStream; import com.android.gallery3d.util.XmpUtilHelper; import java.io.Closeable; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.Vector; import java.util.concurrent.locks.ReentrantLock; @@ -69,13 +79,16 @@ public class ImageLoader { private FilterShowActivity mActivity = null; - public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; - public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; + public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; + public static final int DEFAULT_COMPRESS_QUALITY = 95; + + public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; + public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180; public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270; - public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; - public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; - public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; + public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; + public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; + public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE; private Context mContext = null; @@ -101,18 +114,24 @@ public class ImageLoader { return mActivity; } - public void loadBitmap(Uri uri,int size) { + public boolean loadBitmap(Uri uri, int size) { mLoadingLock.lock(); mUri = uri; mOrientation = getOrientation(mContext, uri); mOriginalBitmapSmall = loadScaledBitmap(uri, 160); if (mOriginalBitmapSmall == null) { // Couldn't read the bitmap, let's exit - mActivity.cannotLoadImage(); + mLoadingLock.unlock(); + return false; } mOriginalBitmapLarge = loadScaledBitmap(uri, size); + if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); + return false; + } updateBitmaps(); mLoadingLock.unlock(); + return true; } public Uri getUri() { @@ -135,21 +154,25 @@ public class ImageLoader { MediaStore.Images.ImageColumns.ORIENTATION }, null, null, null); - if (cursor.moveToNext()){ - int ori = cursor.getInt(0); - - switch (ori){ - case 0: return ORI_NORMAL; - case 90: return ORI_ROTATE_90; - case 270: return ORI_ROTATE_270; - case 180: return ORI_ROTATE_180; - default: - return -1; - } - } else{ + if (cursor.moveToNext()) { + int ori = cursor.getInt(0); + + switch (ori) { + case 0: + return ORI_NORMAL; + case 90: + return ORI_ROTATE_90; + case 270: + return ORI_ROTATE_270; + case 180: + return ORI_ROTATE_180; + default: + return -1; + } + } else { return -1; } - } catch (SQLiteException e){ + } catch (SQLiteException e) { return ExifInterface.ORIENTATION_UNDEFINED; } catch (IllegalArgumentException e) { return ExifInterface.ORIENTATION_UNDEFINED; @@ -160,12 +183,27 @@ public class ImageLoader { static int getOrientationFromPath(String path) { int orientation = -1; + InputStream is = null; try { - ExifInterface EXIF = new ExifInterface(path); - orientation = EXIF.getAttributeInt(ExifInterface.TAG_ORIENTATION, - 1); + is = new FileInputStream(path); + ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + if (event == ExifParser.EVENT_NEW_TAG) { + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_ORIENTATION) { + orientation = (int) tag.getValueAt(0); + break; + } + } + event = parser.next(); + } } catch (IOException e) { e.printStackTrace(); + } catch (ExifInvalidFormatException e) { + e.printStackTrace(); + } finally { + Utils.closeSilently(is); } return orientation; } @@ -181,46 +219,46 @@ public class ImageLoader { warnListeners(); } - public static Bitmap rotateToPortrait(Bitmap bitmap,int ori) { - Matrix matrix = new Matrix(); - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - if (ori == ORI_ROTATE_90 || - ori == ORI_ROTATE_270 || - ori == ORI_TRANSPOSE|| - ori == ORI_TRANSVERSE) { - int tmp = w; - w = h; - h = tmp; - } - switch(ori){ - case ORI_ROTATE_90: - matrix.setRotate(90,w/2f,h/2f); - break; - case ORI_ROTATE_180: - matrix.setRotate(180,w/2f,h/2f); - break; - case ORI_ROTATE_270: - matrix.setRotate(270,w/2f,h/2f); - break; - case ORI_FLIP_HOR: - matrix.preScale(-1, 1); - break; - case ORI_FLIP_VERT: - matrix.preScale(1, -1); - break; - case ORI_TRANSPOSE: - matrix.setRotate(90,w/2f,h/2f); - matrix.preScale(1, -1); - break; - case ORI_TRANSVERSE: - matrix.setRotate(270,w/2f,h/2f); - matrix.preScale(1, -1); - break; - case ORI_NORMAL: - default: - return bitmap; - } + public static Bitmap rotateToPortrait(Bitmap bitmap, int ori) { + Matrix matrix = new Matrix(); + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + if (ori == ORI_ROTATE_90 || + ori == ORI_ROTATE_270 || + ori == ORI_TRANSPOSE || + ori == ORI_TRANSVERSE) { + int tmp = w; + w = h; + h = tmp; + } + switch (ori) { + case ORI_ROTATE_90: + matrix.setRotate(90, w / 2f, h / 2f); + break; + case ORI_ROTATE_180: + matrix.setRotate(180, w / 2f, h / 2f); + break; + case ORI_ROTATE_270: + matrix.setRotate(270, w / 2f, h / 2f); + break; + case ORI_FLIP_HOR: + matrix.preScale(-1, 1); + break; + case ORI_FLIP_VERT: + matrix.preScale(1, -1); + break; + case ORI_TRANSPOSE: + matrix.setRotate(90, w / 2f, h / 2f); + matrix.preScale(1, -1); + break; + case ORI_TRANSVERSE: + matrix.setRotate(270, w / 2f, h / 2f); + matrix.preScale(1, -1); + break; + case ORI_NORMAL: + default: + return bitmap; + } return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); @@ -253,6 +291,7 @@ public class ImageLoader { } static final int MAX_BITMAP_DIM = 2048; + private Bitmap loadScaledBitmap(Uri uri, int size) { InputStream is = null; try { @@ -338,7 +377,8 @@ public class ImageLoader { } }; - // TODO: this currently does the loading + filtering on the UI thread -- need to + // TODO: this currently does the loading + filtering on the UI thread -- + // need to // move this to a background thread. public Bitmap getScaleOneImageForPreset(ImageShow caller, ImagePreset imagePreset, Rect bounds, boolean force) { @@ -354,6 +394,7 @@ public class ImageLoader { bmp2 = imagePreset.apply(bmp2); imagePreset.setScaleFactor(scaleFactor); mZoomCache.setImage(imagePreset, bounds, bmp2); + mLoadingLock.unlock(); return bmp2; } } @@ -366,9 +407,11 @@ public class ImageLoader { boolean hiRes) { mLoadingLock.lock(); if (mOriginalBitmapSmall == null) { + mLoadingLock.unlock(); return null; } if (mOriginalBitmapLarge == null) { + mLoadingLock.unlock(); return null; } @@ -415,6 +458,109 @@ public class ImageLoader { }).execute(preset); } + public static Bitmap loadMutableBitmap(Context context, Uri sourceUri) + throws FileNotFoundException { + BitmapFactory.Options options = new BitmapFactory.Options(); + // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't + // exist) + options.inMutable = true; + + InputStream is = context.getContentResolver().openInputStream(sourceUri); + Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); + int orientation = ImageLoader.getOrientation(context, sourceUri); + bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); + return bitmap; + } + + public void returnFilteredResult(ImagePreset preset, + final FilterShowActivity filterShowActivity) { + preset.setIsHighQuality(true); + preset.setScaleFactor(1.0f); + + BitmapTask.Callbacks<ImagePreset> cb = new BitmapTask.Callbacks<ImagePreset>() { + + @Override + public void onComplete(Bitmap result) { + filterShowActivity.onFilteredResult(result); + } + + @Override + public void onCancel() { + } + + @Override + public Bitmap onExecute(ImagePreset param) { + if (param == null) { + return null; + } + try { + Bitmap bitmap = param.apply(loadMutableBitmap(mContext, mUri)); + return bitmap; + } catch (FileNotFoundException ex) { + Log.w(LOGTAG, "Failed to save image!", ex); + return null; + } + } + }; + + (new BitmapTask<ImagePreset>(cb)).execute(preset); + } + + private String getFileExtension(String requestFormat) { + String outputFormat = (requestFormat == null) + ? "jpg" + : requestFormat; + outputFormat = outputFormat.toLowerCase(); + return (outputFormat.equals("png") || outputFormat.equals("gif")) + ? "png" // We don't support gif compression. + : "jpg"; + } + + private CompressFormat convertExtensionToCompressFormat(String extension) { + return extension.equals("png") ? CompressFormat.PNG : CompressFormat.JPEG; + } + + public void saveToUri(Bitmap bmap, Uri uri, final String outputFormat, + final FilterShowActivity filterShowActivity) { + + OutputStream out = null; + try { + out = filterShowActivity.getContentResolver().openOutputStream(uri); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "cannot write output", e); + out = null; + } finally { + if (bmap == null || out == null) { + return; + } + } + + final InterruptableOutputStream ios = new InterruptableOutputStream(out); + + BitmapTask.Callbacks<Bitmap> cb = new BitmapTask.Callbacks<Bitmap>() { + + @Override + public void onComplete(Bitmap result) { + filterShowActivity.done(); + } + + @Override + public void onCancel() { + ios.interrupt(); + } + + @Override + public Bitmap onExecute(Bitmap param) { + CompressFormat cf = convertExtensionToCompressFormat(getFileExtension(outputFormat)); + param.compress(cf, DEFAULT_COMPRESS_QUALITY, ios); + Utils.closeSilently(ios); + return null; + } + }; + + (new BitmapTask<Bitmap>(cb)).execute(bmap); + } + public void setAdapter(HistoryAdapter adapter) { mAdapter = adapter; } @@ -475,4 +621,9 @@ public class ImageLoader { } } + public void addCacheListener(ImageShow imageShow) { + mHiresCache.addObserver(imageShow); + mCache.addObserver(imageShow); + } + } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java index 7f4d5ed2a..b8f0cf84f 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java @@ -18,35 +18,64 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.presets.ImagePreset; public class ImageFilter implements Cloneable { - protected int mMaxParameter = 100; - protected int mMinParameter = -100; + public static int DEFAULT_MAX_PARAMETER = 100; + public static int DEFAULT_MIN_PARAMETER = -100; + public static int DEFAULT_INITIAL_PARAMETER = 0; + + protected int mMaxParameter = DEFAULT_MAX_PARAMETER; + protected int mMinParameter = DEFAULT_MIN_PARAMETER; protected int mPreviewParameter = mMaxParameter; - protected int mDefaultParameter = 0; - protected int mParameter = 0; + protected int mDefaultParameter = DEFAULT_INITIAL_PARAMETER; + protected int mParameter = DEFAULT_INITIAL_PARAMETER; private ImagePreset mImagePreset; protected String mName = "Original"; private final String LOGTAG = "ImageFilter"; - public static final byte TYPE_BORDER =1; - public static final byte TYPE_FX = 2; + public static final byte TYPE_BORDER = 1; + public static final byte TYPE_FX = 2; public static final byte TYPE_WBALANCE = 3; public static final byte TYPE_VIGNETTE = 4; public static final byte TYPE_NORMAL = 5; public static final byte TYPE_TINYPLANET = 6; private byte filterType = TYPE_NORMAL; - public byte getFilterType(){ + public byte getFilterType() { return filterType; } - protected void setFilterType(byte type){ + protected void setFilterType(byte type) { filterType = type; } + public int getButtonId() { + return 0; + } + + public int getTextId() { + return 0; + } + + public int getOverlayBitmaps() { + return 0; + } + + public int getEditingViewId() { + return R.id.imageShow; + } + + public boolean showEditingControls() { + return true; + } + + public boolean showParameterValue() { + return true; + } + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilter filter = (ImageFilter) super.clone(); @@ -61,6 +90,10 @@ public class ImageFilter implements Cloneable { return filter; } + public void reset() { + setParameter(mDefaultParameter); + } + public boolean isNil() { if (mParameter == mDefaultParameter) { return true; @@ -93,7 +126,7 @@ public class ImageFilter implements Cloneable { * The maximum allowed value (inclusive) * @return maximum value allowed as input to this filter */ - public int getMaxParameter(){ + public int getMaxParameter() { return mMaxParameter; } @@ -101,7 +134,7 @@ public class ImageFilter implements Cloneable { * The parameter value to be used in previews. * @return parameter value to be used to preview the filter */ - public int getPreviewParameter(){ + public int getPreviewParameter() { return mPreviewParameter; } @@ -109,7 +142,7 @@ public class ImageFilter implements Cloneable { * The minimum allowed value (inclusive) * @return minimum value allowed as input to this filter */ - public int getMinParameter(){ + public int getMinParameter() { return mMinParameter; } @@ -117,7 +150,7 @@ public class ImageFilter implements Cloneable { * Returns the default value returned by this filter. * @return default value */ - public int getDefaultParameter(){ + public int getDefaultParameter() { return mDefaultParameter; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java deleted file mode 100644 index 45f49164b..000000000 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWBlue.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.filters; - -import android.graphics.Bitmap; - -public class ImageFilterBWBlue extends ImageFilter { - - public ImageFilterBWBlue() { - mName = "B&W - Blue"; - } - - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); - - @Override - public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); - return bitmap; - } - -} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java deleted file mode 100644 index f0c65d71e..000000000 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWRed.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.filters; - -import android.graphics.Bitmap; - -public class ImageFilterBWRed extends ImageFilter { - - public ImageFilterBWRed() { - mName = "B&W - Red"; - } - - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); - - @Override - public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); - return bitmap; - } - -} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java index 558abe3c3..1bb5c76ac 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; import android.graphics.Color; @@ -29,6 +31,16 @@ public class ImageFilterBwFilter extends ImageFilter { } @Override + public int getButtonId() { + return R.id.bwfilterButton; + } + + @Override + public int getTextId() { + return R.string.bwfilter; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterBwFilter filter = (ImageFilterBwFilter) super.clone(); return filter; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java index 0c3bb37ca..70e3d8589 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterContrast extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterContrast extends ImageFilter { mName = "Contrast"; } + @Override + public int getButtonId() { + return R.id.contrastButton; + } + + @Override + public int getTextId() { + return R.string.contrast; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java index 89641d103..ba49a8fcb 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterCurves.java @@ -18,6 +18,7 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import com.android.gallery3d.R; import com.android.gallery3d.filtershow.ui.Spline; public class ImageFilterCurves extends ImageFilter { @@ -31,6 +32,31 @@ public class ImageFilterCurves extends ImageFilter { } @Override + public int getButtonId() { + return R.id.curvesButtonRGB; + } + + @Override + public int getTextId() { + return R.string.curvesRGB; + } + + @Override + public int getOverlayBitmaps() { + return R.drawable.filtershow_button_colors_curve; + } + + @Override + public int getEditingViewId() { + return R.id.imageCurves; + } + + @Override + public boolean showParameterValue() { + return false; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterCurves filter = (ImageFilterCurves) super.clone(); for (int i = 0; i < 4; i++) { diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java new file mode 100644 index 000000000..fa2293ca7 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +public class ImageFilterDownsample extends ImageFilter { + + public ImageFilterDownsample() { + mName = "Downsample"; + mMaxParameter = 100; + mMinParameter = 5; + mPreviewParameter = 10; + mDefaultParameter = 50; + mParameter = 50; + } + + @Override + public int getButtonId() { + return R.id.downsampleButton; + } + + @Override + public int getTextId() { + return R.string.downsample; + } + + @Override + public boolean isNil() { + return false; + } + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + int p = mParameter; + if (p > 0 && p < 100) { + int newWidth = w * p / 100; + int newHeight = h * p / 100; + if (newWidth <= 0 || newHeight <= 0) { + return bitmap; + } + Bitmap ret = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + if (ret != bitmap) { + bitmap.recycle(); + } + return ret; + } + return bitmap; + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java index 8f91c3c82..9eda64874 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBWGreen.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java @@ -18,20 +18,39 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; -public class ImageFilterBWGreen extends ImageFilter { +import com.android.gallery3d.R; - public ImageFilterBWGreen() { - mName = "B&W - Green"; +public class ImageFilterEdge extends ImageFilter { + + public ImageFilterEdge() { + mName = "Edge"; + mPreviewParameter = 0; } - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float p); + + @Override + public int getButtonId() { + return R.id.edgeButton; + } + + @Override + public int getTextId() { + return R.string.edge; + } + + @Override + public boolean isNil() { + return false; + } @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); + float p = mParameter + 101; + p = (float) p / 100; + nativeApplyFilter(bitmap, w, h, p); return bitmap; } - } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java index e38dc8eb5..63f860171 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterExposure extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterExposure extends ImageFilter { mName = "Exposure"; } + @Override + public int getButtonId() { + return R.id.exposureButton; + } + + @Override + public int getTextId() { + return R.string.exposure; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java index d74a6faab..33ecc8ab9 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterGeometry.java @@ -23,6 +23,7 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.imageshow.GeometryMath; import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; @@ -69,22 +70,56 @@ public class ImageFilterGeometry extends ImageFilter { // TODO: implement bilinear or bicubic here... for now, just use // canvas to do a simple implementation... // TODO: and be more memory efficient! (do it in native?) + + CropExtras extras = mGeometry.getCropExtras(); + boolean useExtras = mGeometry.getUseCropExtrasFlag(); + int outputX = 0; + int outputY = 0; + boolean s = false; + if (extras != null && useExtras){ + outputX = extras.getOutputX(); + outputY = extras.getOutputY(); + s = extras.getScaleUp(); + } + + Rect cropBounds = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); RectF crop = mGeometry.getCropBounds(bitmap); if (crop.width() > 0 && crop.height() > 0) cropBounds = GeometryMath.roundNearest(crop); - Bitmap temp = null; - if (mGeometry.hasSwitchedWidthHeight()) { - temp = Bitmap.createBitmap(cropBounds.height(), cropBounds.width(), mConfig); - } else { - temp = Bitmap.createBitmap(cropBounds.width(), cropBounds.height(), mConfig); + + int width = cropBounds.width(); + int height = cropBounds.height(); + + if (mGeometry.hasSwitchedWidthHeight()){ + int temp = width; + width = height; + height = temp; } + + if(outputX <= 0 || outputY <= 0){ + outputX = width; + outputY = height; + } + + float scaleX = 1; + float scaleY = 1; + if (s){ + scaleX = (float) outputX / width; + scaleY = (float) outputY / height; + } + + Bitmap temp = null; + temp = Bitmap.createBitmap(outputX, outputY, mConfig); + float[] displayCenter = { temp.getWidth() / 2f, temp.getHeight() / 2f }; Matrix m1 = mGeometry.buildTotalXform(bitmap.getWidth(), bitmap.getHeight(), displayCenter); + m1.postScale(scaleX, scaleY, displayCenter[0], displayCenter[1]); + Canvas canvas = new Canvas(temp); Paint paint = new Paint(); paint.setAntiAlias(true); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java index 279718edb..e2ea388dc 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterHue extends ImageFilter { @@ -29,6 +31,16 @@ public class ImageFilterHue extends ImageFilter { } @Override + public int getButtonId() { + return R.id.hueButton; + } + + @Override + public int getTextId() { + return R.string.hue; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterHue filter = (ImageFilterHue) super.clone(); filter.cmatrix = new ColorSpaceMatrix(cmatrix); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java new file mode 100644 index 000000000..f03baca39 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.filters; + +import android.graphics.Bitmap; +import android.text.format.Time; + +import com.android.gallery3d.R; + +public class ImageFilterKMeans extends ImageFilter { + private int mSeed = 0; + + public ImageFilterKMeans() { + mName = "KMeans"; + mMaxParameter = 20; + mMinParameter = 2; + mPreviewParameter = 4; + mDefaultParameter = 4; + mParameter = 4; + + // set random seed for session + Time t = new Time(); + t.setToNow(); + mSeed = (int) t.toMillis(false); + } + + native protected void nativeApplyFilter(Bitmap bitmap, int width, int height, + Bitmap large_ds_bm, int lwidth, int lheight, Bitmap small_ds_bm, + int swidth, int sheight, int p, int seed); + + @Override + public int getButtonId() { + return R.id.kmeansButton; + } + + @Override + public int getTextId() { + return R.string.kmeans; + } + + @Override + public boolean isNil() { + return false; + } + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + + Bitmap large_bm_ds = bitmap; + Bitmap small_bm_ds = bitmap; + + // find width/height for larger downsampled bitmap + int lw = w; + int lh = h; + while (lw > 256 && lh > 256) { + lw /= 2; + lh /= 2; + } + if (lw != w) { + large_bm_ds = Bitmap.createScaledBitmap(bitmap, lw, lh, true); + } + + // find width/height for smaller downsampled bitmap + int sw = lw; + int sh = lh; + while (sw > 64 && sh > 64) { + sw /= 2; + sh /= 2; + } + if (sw != lw) { + small_bm_ds = Bitmap.createScaledBitmap(large_bm_ds, sw, sh, true); + } + + int p = Math.max(mParameter, mMinParameter) % (mMaxParameter + 1); + nativeApplyFilter(bitmap, w, h, large_bm_ds, lw, lh, small_bm_ds, sw, sh, p, mSeed); + return bitmap; + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java new file mode 100644 index 000000000..04fd1e42e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java @@ -0,0 +1,47 @@ +package com.android.gallery3d.filtershow.filters; + +import android.graphics.Bitmap; + +import com.android.gallery3d.R; + +public class ImageFilterNegative extends ImageFilter { + + public ImageFilterNegative() { + mName = "Negative"; + } + + @Override + public int getButtonId() { + return R.id.negativeButton; + } + + @Override + public int getTextId() { + return R.string.negative; + } + + @Override + public boolean isNil() { + return false; + } + + @Override + public boolean showEditingControls() { + return false; + } + + @Override + public boolean showParameterValue() { + return false; + } + + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); + + @Override + public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { + int w = bitmap.getWidth(); + int h = bitmap.getHeight(); + nativeApplyFilter(bitmap, w, h); + return bitmap; + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java index c77de330f..9ae6f511e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRedEye.java @@ -17,40 +17,167 @@ package com.android.gallery3d.filtershow.filters; import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.RectF; -public class ImageFilterRedEye extends ImageFilter { - private static final String TAG = "ImageFilterRedEye"; +import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.imageshow.GeometryMetadata; + +import java.util.Vector; +public class ImageFilterRedEye extends ImageFilter { + private static final String LOGTAG = "ImageFilterRedEye"; + private Vector<RedEyeCandidate> mCandidates = null; public ImageFilterRedEye() { - mName = "Redeye"; + mName = "Red Eye"; + } + @Override + public int getButtonId() { + return R.id.redEyeButton; + } + + @Override + public int getTextId() { + return R.string.redeye; + } + + @Override + public int getEditingViewId() { + return R.id.imageRedEyes; } @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterRedEye filter = (ImageFilterRedEye) super.clone(); - + if (mCandidates != null) { + int size = mCandidates.size(); + filter.mCandidates = new Vector<RedEyeCandidate>(); + for (int i = 0; i < size; i++) { + filter.mCandidates.add(new RedEyeCandidate(mCandidates.elementAt(i))); + } + } return filter; } - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short []matrix); + @Override + public boolean isNil() { + if (mCandidates != null && mCandidates.size() > 0) { + return false; + } + return true; + } + + @Override + public boolean same(ImageFilter filter) { + boolean isRedEyeFilter = super.same(filter); + if (!isRedEyeFilter) { + return false; + } + ImageFilterRedEye redEyeFilter = (ImageFilterRedEye) filter; + if (redEyeFilter.mCandidates == null && mCandidates == null) { + return true; + } + if (redEyeFilter.mCandidates == null || mCandidates == null) { + return false; + } + if (redEyeFilter.mCandidates.size() != mCandidates.size()) { + return false; + } + int size = mCandidates.size(); + for (int i = 0; i < size; i++) { + RedEyeCandidate c1 = mCandidates.elementAt(i); + RedEyeCandidate c2 = redEyeFilter.mCandidates.elementAt(i); + if (!c1.equals(c2)) { + return false; + } + } + return true; + } + + public Vector<RedEyeCandidate> getCandidates() { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + return mCandidates; + } + + public void addRect(RectF rect, RectF bounds) { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + Vector<RedEyeCandidate> intersects = new Vector<RedEyeCandidate>(); + for (int i = 0; i < mCandidates.size(); i++) { + RedEyeCandidate r = mCandidates.elementAt(i); + if (r.intersect(rect)) { + intersects.add(r); + } + } + for (int i = 0; i < intersects.size(); i++) { + RedEyeCandidate r = intersects.elementAt(i); + rect.union(r.mRect); + bounds.union(r.mBounds); + mCandidates.remove(r); + } + mCandidates.add(new RedEyeCandidate(rect, bounds)); + } + + public void clear() { + if (mCandidates == null) { + mCandidates = new Vector<RedEyeCandidate>(); + } + mCandidates.clear(); + } + + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, short[] matrix); @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - float p = mParameter; - float value = p; - int box = Math.min(w, h); - int sizex = Math.min((int)((p+100)*box/400),w/2); - int sizey = Math.min((int)((p+100)*box/800),h/2); - - short [] rect = new short[]{ - (short) (w/2-sizex),(short) (w/2-sizey), - (short) (2*sizex),(short) (2*sizey)}; + short[] rect = new short[4]; - nativeApplyFilter(bitmap, w, h, rect); + if (mCandidates != null && mCandidates.size() > 0) { + for (int i = 0; i < mCandidates.size(); i++) { + RectF r = new RectF(mCandidates.elementAt(i).mRect); + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(true, + getImagePreset().getImageLoader().getOriginalBounds().width(), + getImagePreset().getImageLoader().getOriginalBounds().height(), + w, h); + originalToScreen.mapRect(r); + if (r.left < 0) { + r.left = 0; + } + if (r.left > w) { + r.left = w; + } + if (r.top < 0) { + r.top = 0; + } + if (r.top > h) { + r.top = h; + } + if (r.right < 0) { + r.right = 0; + } + if (r.right > w) { + r.right = w; + } + if (r.bottom < 0) { + r.bottom = 0; + } + if (r.bottom > h) { + r.bottom = h; + } + rect[0] = (short) r.left; + rect[1] = (short) r.top; + rect[2] = (short) r.width(); + rect[3] = (short) r.height(); + nativeApplyFilter(bitmap, w, h, rect); + } + } return bitmap; } } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java index 1d3459195..129165b3e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterSaturated extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterSaturated extends ImageFilter { mName = "Saturated"; } + @Override + public int getButtonId() { + return R.id.saturationButton; + } + + @Override + public int getTextId() { + return R.string.saturation; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float saturation); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java index 4e6b848ae..de8fcd5ea 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterShadows extends ImageFilter { @@ -26,6 +28,16 @@ public class ImageFilterShadows extends ImageFilter { } @Override + public int getButtonId() { + return R.id.shadowRecoveryButton; + } + + @Override + public int getTextId() { + return R.string.shadow_recovery; + } + + @Override public ImageFilter clone() throws CloneNotSupportedException { ImageFilterShadows filter = (ImageFilterShadows) super.clone(); return filter; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java index a355539c2..1951b9b9e 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java @@ -28,6 +28,26 @@ public class ImageFilterSharpen extends ImageFilterRS { } @Override + public int getButtonId() { + return R.id.sharpenButton; + } + + @Override + public int getTextId() { + return R.string.sharpness; + } + + @Override + public int getOverlayBitmaps() { + return R.drawable.filtershow_button_colors_sharpen; + } + + @Override + public int getEditingViewId() { + return R.id.imageZoom; + } + + @Override public void createFilter(android.content.res.Resources res, float scaleFactor, boolean highQuality) { int w = mInPixelsAllocation.getType().getX(); diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java index effd89ebe..36bd62630 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java @@ -22,6 +22,7 @@ import android.graphics.RectF; import com.adobe.xmp.XMPException; import com.adobe.xmp.XMPMeta; +import com.android.gallery3d.R; import com.android.gallery3d.app.Log; import com.android.gallery3d.filtershow.presets.ImagePreset; @@ -59,6 +60,16 @@ public class ImageFilterTinyPlanet extends ImageFilter { mAngle = 0; } + @Override + public int getButtonId() { + return R.id.tinyplanetButton; + } + + @Override + public int getTextId() { + return R.string.tinyplanet; + } + public void setAngle(float angle) { mAngle = angle; } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java index 34f8b245e..7720d0490 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterVibrance extends ImageFilter { @@ -24,6 +26,16 @@ public class ImageFilterVibrance extends ImageFilter { mName = "Vibrance"; } + @Override + public int getButtonId() { + return R.id.vibranceButton; + } + + @Override + public int getTextId() { + return R.string.vibrance; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float bright); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java index 7a471e5b9..3c904fa6c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterVignette extends ImageFilter { @@ -25,6 +27,16 @@ public class ImageFilterVignette extends ImageFilter { mName = "Vignette"; } + @Override + public int getButtonId() { + return R.id.vignetteButton; + } + + @Override + public int getTextId() { + return R.string.vignette; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, float strength); @Override diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java index b00b867b3..8665dc54c 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java @@ -16,6 +16,8 @@ package com.android.gallery3d.filtershow.filters; +import com.android.gallery3d.R; + import android.graphics.Bitmap; public class ImageFilterWBalance extends ImageFilter { @@ -26,13 +28,32 @@ public class ImageFilterWBalance extends ImageFilter { mName = "WBalance"; } + @Override + public int getButtonId() { + return R.id.wbalanceButton; + } + + @Override + public int getTextId() { + return R.string.wbalance; + } + + public boolean showEditingControls() { + return false; + } + native protected void nativeApplyFilter(Bitmap bitmap, int w, int h, int locX, int locY); @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { int w = bitmap.getWidth(); int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h, -1,-1); + nativeApplyFilter(bitmap, w, h, -1, -1); return bitmap; } + + @Override + public boolean isNil() { + return false; + } } diff --git a/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java new file mode 100644 index 000000000..58d3afa3b --- /dev/null +++ b/src/com/android/gallery3d/filtershow/filters/RedEyeCandidate.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.filters; + +import android.graphics.RectF; + +public class RedEyeCandidate { + RectF mRect = new RectF(); + RectF mBounds = new RectF(); + + public RedEyeCandidate(RedEyeCandidate candidate) { + mRect.set(candidate.mRect); + mBounds.set(candidate.mBounds); + } + + public RedEyeCandidate(RectF rect, RectF bounds) { + mRect.set(rect); + mBounds.set(bounds); + } + + public boolean equals(RedEyeCandidate candidate) { + if (candidate.mRect.equals(mRect) + && candidate.mBounds.equals(mBounds)) { + return true; + } + return false; + } + + public boolean intersect(RectF rect) { + return mRect.intersect(rect); + } + + public RectF getRect() { + return mRect; + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java new file mode 100644 index 000000000..e94d1ed9e --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/BoundedRect.java @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.gallery3d.filtershow.imageshow; + +import android.graphics.Matrix; +import android.graphics.RectF; + +import java.util.Arrays; + +/** + * Maintains invariant that inner rectangle is constrained to be within the + * outer, rotated rectangle. + */ +public class BoundedRect { + private float rot; + private RectF outer; + private RectF inner; + private float[] innerRotated; + + public BoundedRect() { + rot = 0; + outer = new RectF(); + inner = new RectF(); + innerRotated = new float[8]; + } + + public BoundedRect(float rotation, RectF outerRect, RectF innerRect) { + rot = rotation; + outer = new RectF(outerRect); + inner = new RectF(innerRect); + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + /** + * Sets inner, and re-constrains it to fit within the rotated bounding rect. + */ + public void setInner(RectF newInner) { + if (inner.equals(newInner)) + return; + inner = newInner; + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + /** + * Sets rotation, and re-constrains inner to fit within the rotated bounding rect. + */ + public void setRotation(float rotation) { + if (rotation == rot) + return; + rot = rotation; + innerRotated = CropMath.getCornersFromRect(inner); + rotateInner(); + if (!isConstrained()) + reconstrain(); + } + + public RectF getInner() { + return new RectF(inner); + } + + /** + * Tries to move the inner rectangle by (dx, dy). If this would cause it to leave + * the bounding rectangle, snaps the inner rectangle to the edge of the bounding + * rectangle. + */ + public void moveInner(float dx, float dy) { + Matrix m0 = getInverseRotMatrix(); + + RectF translatedInner = new RectF(inner); + translatedInner.offset(dx, dy); + + float[] translatedInnerCorners = CropMath.getCornersFromRect(translatedInner); + float[] outerCorners = CropMath.getCornersFromRect(outer); + + m0.mapPoints(translatedInnerCorners); + float[] correction = { + 0, 0 + }; + + // find correction vectors for corners that have moved out of bounds + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { + float[] badCorner = { + correctedInnerX, correctedInnerY + }; + float[] nearestSide = CropMath.closestSide(badCorner, outerCorners); + float[] correctionVec = + GeometryMath.shortestVectorFromPointToLine(badCorner, nearestSide); + correction[0] += correctionVec[0]; + correction[1] += correctionVec[1]; + } + } + + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + if (!CropMath.inclusiveContains(outer, correctedInnerX, correctedInnerY)) { + float[] correctionVec = { + correctedInnerX, correctedInnerY + }; + CropMath.getEdgePoints(outer, correctionVec); + correctionVec[0] -= correctedInnerX; + correctionVec[1] -= correctedInnerY; + correction[0] += correctionVec[0]; + correction[1] += correctionVec[1]; + } + } + + // Set correction + for (int i = 0; i < translatedInnerCorners.length; i += 2) { + float correctedInnerX = translatedInnerCorners[i] + correction[0]; + float correctedInnerY = translatedInnerCorners[i + 1] + correction[1]; + // update translated corners with correction vectors + translatedInnerCorners[i] = correctedInnerX; + translatedInnerCorners[i + 1] = correctedInnerY; + } + + innerRotated = translatedInnerCorners; + // reconstrain to update inner + reconstrain(); + } + + /** + * Attempts to resize the inner rectangle. If this would cause it to leave + * the bounding rect, clips the inner rectangle to fit. + */ + public void resizeInner(RectF newInner) { + Matrix m = getRotMatrix(); + Matrix m0 = getInverseRotMatrix(); + + float[] outerCorners = CropMath.getCornersFromRect(outer); + m.mapPoints(outerCorners); + float[] oldInnerCorners = CropMath.getCornersFromRect(inner); + float[] newInnerCorners = CropMath.getCornersFromRect(newInner); + RectF ret = new RectF(newInner); + + for (int i = 0; i < newInnerCorners.length; i += 2) { + float[] c = { + newInnerCorners[i], newInnerCorners[i + 1] + }; + float[] c0 = Arrays.copyOf(c, 2); + m0.mapPoints(c0); + if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { + float[] outerSide = CropMath.closestSide(c, outerCorners); + float[] pathOfCorner = { + newInnerCorners[i], newInnerCorners[i + 1], + oldInnerCorners[i], oldInnerCorners[i + 1] + }; + float[] p = GeometryMath.lineIntersect(pathOfCorner, outerSide); + if (p == null) { + // lines are parallel or not well defined, so don't resize + p = new float[2]; + p[0] = oldInnerCorners[i]; + p[1] = oldInnerCorners[i + 1]; + } + // relies on corners being in same order as method + // getCornersFromRect + switch (i) { + case 0: + case 1: + ret.left = (p[0] > ret.left) ? p[0] : ret.left; + ret.top = (p[1] > ret.top) ? p[1] : ret.top; + break; + case 2: + case 3: + ret.right = (p[0] < ret.right) ? p[0] : ret.right; + ret.top = (p[1] > ret.top) ? p[1] : ret.top; + break; + case 4: + case 5: + ret.right = (p[0] < ret.right) ? p[0] : ret.right; + ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; + break; + case 6: + case 7: + ret.left = (p[0] > ret.left) ? p[0] : ret.left; + ret.bottom = (p[1] < ret.bottom) ? p[1] : ret.bottom; + break; + default: + break; + } + } + } + float[] retCorners = CropMath.getCornersFromRect(ret); + m0.mapPoints(retCorners); + innerRotated = retCorners; + // reconstrain to update inner + reconstrain(); + } + + /** + * Attempts to resize the inner rectangle. If this would cause it to leave + * the bounding rect, clips the inner rectangle to fit while maintaining + * aspect ratio. + */ + public void fixedAspectResizeInner(RectF newInner) { + Matrix m = getRotMatrix(); + Matrix m0 = getInverseRotMatrix(); + + float aspectW = inner.width(); + float aspectH = inner.height(); + float aspRatio = aspectW / aspectH; + float[] corners = CropMath.getCornersFromRect(outer); + + m.mapPoints(corners); + float[] oldInnerCorners = CropMath.getCornersFromRect(inner); + float[] newInnerCorners = CropMath.getCornersFromRect(newInner); + + // find fixed corner + int fixed = -1; + if (inner.top == newInner.top) { + if (inner.left == newInner.left) + fixed = 0; // top left + else if (inner.right == newInner.right) + fixed = 2; // top right + } else if (inner.bottom == newInner.bottom) { + if (inner.right == newInner.right) + fixed = 4; // bottom right + else if (inner.left == newInner.left) + fixed = 6; // bottom left + } + // no fixed corner, return without update + if (fixed == -1) + return; + float widthSoFar = newInner.width(); + int moved = -1; + for (int i = 0; i < newInnerCorners.length; i += 2) { + float[] c = { + newInnerCorners[i], newInnerCorners[i + 1] + }; + float[] c0 = Arrays.copyOf(c, 2); + m0.mapPoints(c0); + if (!CropMath.inclusiveContains(outer, c0[0], c0[1])) { + moved = i; + if (moved == fixed) + continue; + float[] l2 = CropMath.closestSide(c, corners); + float[] l1 = { + newInnerCorners[i], newInnerCorners[i + 1], + oldInnerCorners[i], oldInnerCorners[i + 1] + }; + float[] p = GeometryMath.lineIntersect(l1, l2); + if (p == null) { + // lines are parallel or not well defined, so set to old + // corner + p = new float[2]; + p[0] = oldInnerCorners[i]; + p[1] = oldInnerCorners[i + 1]; + } + // relies on corners being in same order as method + // getCornersFromRect + float fixed_x = oldInnerCorners[fixed]; + float fixed_y = oldInnerCorners[fixed + 1]; + float newWidth = Math.abs(fixed_x - p[0]); + float newHeight = Math.abs(fixed_y - p[1]); + newWidth = Math.max(newWidth, aspRatio * newHeight); + if (newWidth < widthSoFar) + widthSoFar = newWidth; + } + } + + float heightSoFar = widthSoFar / aspRatio; + RectF ret = new RectF(inner); + if (fixed == 0) { + ret.right = ret.left + widthSoFar; + ret.bottom = ret.top + heightSoFar; + } else if (fixed == 2) { + ret.left = ret.right - widthSoFar; + ret.bottom = ret.top + heightSoFar; + } else if (fixed == 4) { + ret.left = ret.right - widthSoFar; + ret.top = ret.bottom - heightSoFar; + } else if (fixed == 6) { + ret.right = ret.left + widthSoFar; + ret.top = ret.bottom - heightSoFar; + } + float[] retCorners = CropMath.getCornersFromRect(ret); + m0.mapPoints(retCorners); + innerRotated = retCorners; + // reconstrain to update inner + reconstrain(); + } + + // internal methods + + private boolean isConstrained() { + for (int i = 0; i < 8; i += 2) { + if (!CropMath.inclusiveContains(outer, innerRotated[i], innerRotated[i + 1])) + return false; + } + return true; + } + + private void reconstrain() { + // innerRotated has been changed to have incorrect values + CropMath.getEdgePoints(outer, innerRotated); + Matrix m = getRotMatrix(); + float[] unrotated = Arrays.copyOf(innerRotated, 8); + m.mapPoints(unrotated); + inner = CropMath.trapToRect(unrotated); + } + + private void rotateInner() { + Matrix m = getInverseRotMatrix(); + m.mapPoints(innerRotated); + } + + private Matrix getRotMatrix() { + Matrix m = new Matrix(); + m.setRotate(rot, outer.centerX(), outer.centerY()); + return m; + } + + private Matrix getInverseRotMatrix() { + Matrix m = new Matrix(); + m.setRotate(-rot, outer.centerX(), outer.centerY()); + return m; + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/CropMath.java b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java new file mode 100644 index 000000000..9037ca043 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/CropMath.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.imageshow; + +import android.graphics.Matrix; +import android.graphics.RectF; + +import java.util.Arrays; + +public class CropMath { + + /** + * Gets a float array of the 2D coordinates representing a rectangles + * corners. + * The order of the corners in the float array is: + * 0------->1 + * ^ | + * | v + * 3<-------2 + * + * @param r the rectangle to get the corners of + * @return the float array of corners (8 floats) + */ + + public static float[] getCornersFromRect(RectF r) { + float[] corners = { + r.left, r.top, + r.right, r.top, + r.right, r.bottom, + r.left, r.bottom + }; + return corners; + } + + /** + * Returns true iff point (x, y) is within or on the rectangle's bounds. + * RectF's "contains" function treats points on the bottom and right bound + * as not being contained. + * + * @param r the rectangle + * @param x the x value of the point + * @param y the y value of the point + * @return + */ + public static boolean inclusiveContains(RectF r, float x, float y) { + return !(x > r.right || x < r.left || y > r.bottom || y < r.top); + } + + /** + * Takes an array of 2D coordinates representing corners and returns the + * smallest rectangle containing those coordinates. + * + * @param array array of 2D coordinates + * @return smallest rectangle containing coordinates + */ + public static RectF trapToRect(float[] array) { + RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); + for (int i = 1; i < array.length; i += 2) { + float x = array[i - 1]; + float y = array[i]; + r.left = (x < r.left) ? x : r.left; + r.top = (y < r.top) ? y : r.top; + r.right = (x > r.right) ? x : r.right; + r.bottom = (y > r.bottom) ? y : r.bottom; + } + r.sort(); + return r; + } + + /** + * If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the + * image bound rectangle, clamps it to the edge of the rectangle. + * + * @param imageBound the rectangle to clamp edge points to. + * @param array an array of points to clamp to the rectangle, gets set to + * the clamped values. + */ + public static void getEdgePoints(RectF imageBound, float[] array) { + if (array.length < 2) + return; + for (int x = 0; x < array.length; x += 2) { + array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); + array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); + } + } + + /** + * Takes a point and the corners of a rectangle and returns the two corners + * representing the side of the rectangle closest to the point. + * + * @param point the point which is being checked + * @param corners the corners of the rectangle + * @return two corners representing the side of the rectangle + */ + public static float[] closestSide(float[] point, float[] corners) { + int len = corners.length; + float oldMag = Float.POSITIVE_INFINITY; + float[] bestLine = null; + for (int i = 0; i < len; i += 2) { + float[] line = { + corners[i], corners[(i + 1) % len], + corners[(i + 2) % len], corners[(i + 3) % len] + }; + float mag = GeometryMath.vectorLength( + GeometryMath.shortestVectorFromPointToLine(point, line)); + if (mag < oldMag) { + oldMag = mag; + bestLine = line; + } + } + return bestLine; + } + + /** + * Checks if a given point is within a rotated rectangle. + * + * @param point 2D point to check + * @param bound rectangle to rotate + * @param rot angle of rotation about rectangle center + * @return true if point is within rotated rectangle + */ + public static boolean pointInRotatedRect(float[] point, RectF bound, float rot) { + Matrix m = new Matrix(); + float[] p = Arrays.copyOf(point, 2); + m.setRotate(rot, bound.centerX(), bound.centerY()); + Matrix m0 = new Matrix(); + if (!m.invert(m0)) + return false; + m0.mapPoints(p); + return inclusiveContains(bound, p[0], p[1]); + } + + /** + * Checks if a given point is within a rotated rectangle. + * + * @param point 2D point to check + * @param rotatedRect corners of a rotated rectangle + * @param center center of the rotated rectangle + * @return true if point is within rotated rectangle + */ + public static boolean pointInRotatedRect(float[] point, float[] rotatedRect, float[] center) { + RectF unrotated = new RectF(); + float angle = getUnrotated(rotatedRect, center, unrotated); + return pointInRotatedRect(point, unrotated, angle); + } + + /** + * Resizes rectangle to have a certain aspect ratio (center remains + * stationary). + * + * @param r rectangle to resize + * @param w new width aspect + * @param h new height aspect + */ + public static void fixAspectRatio(RectF r, float w, float h) { + float scale = Math.min(r.width() / w, r.height() / h); + float centX = r.centerX(); + float centY = r.centerY(); + float hw = scale * w / 2; + float hh = scale * h / 2; + r.set(centX - hw, centY - hh, centX + hw, centY + hh); + } + + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { + float dy = rotatedRect[1] - rotatedRect[3]; + float dx = rotatedRect[0] - rotatedRect[2]; + float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); + Matrix m = new Matrix(); + m.setRotate(-angle, center[0], center[1]); + float[] unrotatedRect = new float[rotatedRect.length]; + m.mapPoints(unrotatedRect, rotatedRect); + unrotated.set(trapToRect(unrotatedRect)); + return angle; + } + +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java index 55f791820..568dadfc3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMath.java @@ -26,11 +26,37 @@ public class GeometryMath { return Math.max(Math.min(i, high), low); } - protected static float[] shortestVectorFromPointToLine(float[] point, float[] l1, float[] l2) { - float x1 = l1[0]; - float x2 = l2[0]; - float y1 = l1[1]; - float y2 = l2[1]; + public static float[] lineIntersect(float[] line1, float[] line2) { + float a0 = line1[0]; + float a1 = line1[1]; + float b0 = line1[2]; + float b1 = line1[3]; + float c0 = line2[0]; + float c1 = line2[1]; + float d0 = line2[2]; + float d1 = line2[3]; + float t0 = a0 - b0; + float t1 = a1 - b1; + float t2 = b0 - d0; + float t3 = d1 - b1; + float t4 = c0 - d0; + float t5 = c1 - d1; + + float denom = t1 * t4 - t0 * t5; + if (denom == 0) + return null; + float u = (t3 * t4 + t5 * t2) / denom; + float[] intersect = { + b0 + u * t0, b1 + u * t1 + }; + return intersect; + } + + public static float[] shortestVectorFromPointToLine(float[] point, float[] line) { + float x1 = line[0]; + float x2 = line[2]; + float y1 = line[1]; + float y2 = line[3]; float xdelt = x2 - x1; float ydelt = y2 - y1; if (xdelt == 0 && ydelt == 0) @@ -40,67 +66,75 @@ public class GeometryMath { float[] ret = { (x1 + u * (x2 - x1)), (y1 + u * (y2 - y1)) }; - float [] vec = {ret[0] - point[0], ret[1] - point[1] }; + float[] vec = { + ret[0] - point[0], ret[1] - point[1] + }; return vec; } // A . B - public static float dotProduct(float[] a, float[] b){ + public static float dotProduct(float[] a, float[] b) { return a[0] * b[0] + a[1] * b[1]; } - public static float[] normalize(float[] a){ + public static float[] normalize(float[] a) { float length = (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]); - float[] b = { a[0] / length, a[1] / length }; + float[] b = { + a[0] / length, a[1] / length + }; return b; } // A onto B - public static float scalarProjection(float[] a, float[] b){ + public static float scalarProjection(float[] a, float[] b) { float length = (float) Math.sqrt(b[0] * b[0] + b[1] * b[1]); return dotProduct(a, b) / length; } - public static float[] getVectorFromPoints(float [] point1, float [] point2){ - float [] p = { point2[0] - point1[0], point2[1] - point1[1] }; + public static float[] getVectorFromPoints(float[] point1, float[] point2) { + float[] p = { + point2[0] - point1[0], point2[1] - point1[1] + }; return p; } - public static float[] getUnitVectorFromPoints(float [] point1, float [] point2){ - float [] p = { point2[0] - point1[0], point2[1] - point1[1] }; + public static float[] getUnitVectorFromPoints(float[] point1, float[] point2) { + float[] p = { + point2[0] - point1[0], point2[1] - point1[1] + }; float length = (float) Math.sqrt(p[0] * p[0] + p[1] * p[1]); p[0] = p[0] / length; p[1] = p[1] / length; return p; } - public static RectF scaleRect(RectF r, float scale){ + public static RectF scaleRect(RectF r, float scale) { return new RectF(r.left * scale, r.top * scale, r.right * scale, r.bottom * scale); } // A - B - public static float[] vectorSubtract(float [] a, float [] b){ + public static float[] vectorSubtract(float[] a, float[] b) { int len = a.length; if (len != b.length) return null; - float [] ret = new float[len]; - for (int i = 0; i < len; i++){ + float[] ret = new float[len]; + for (int i = 0; i < len; i++) { ret[i] = a[i] - b[i]; } return ret; } - public static float vectorLength(float [] a){ + public static float vectorLength(float[] a) { return (float) Math.sqrt(a[0] * a[0] + a[1] * a[1]); } public static float scale(float oldWidth, float oldHeight, float newWidth, float newHeight) { if (oldHeight == 0 || oldWidth == 0) return 1; - return Math.min(newWidth / oldWidth , newHeight / oldHeight); + return Math.min(newWidth / oldWidth, newHeight / oldHeight); } - public static Rect roundNearest(RectF r){ + public static Rect roundNearest(RectF r) { Rect q = new Rect(Math.round(r.left), Math.round(r.top), Math.round(r.right), Math.round(r.bottom)); return q; diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java index dffdc2449..b53284061 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java +++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java @@ -21,12 +21,11 @@ import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.ImageFilterGeometry; public class GeometryMetadata { - // Applied in order: rotate, crop, scale. - // Do not scale saved image (presumably?). private static final ImageFilterGeometry mImageFilter = new ImageFilterGeometry(); private static final String LOGTAG = "GeometryMetadata"; private float mScaleFactor = 1.0f; @@ -36,12 +35,29 @@ public class GeometryMetadata { private final RectF mPhotoBounds = new RectF(); private FLIP mFlip = FLIP.NONE; - private RectF mBounds = new RectF(); - public enum FLIP { NONE, VERTICAL, HORIZONTAL, BOTH } + // Output format data from intent extras + private boolean mUseCropExtras = false; + private CropExtras mCropExtras = null; + public void setUseCropExtrasFlag(boolean f){ + mUseCropExtras = f; + } + + public boolean getUseCropExtrasFlag(){ + return mUseCropExtras; + } + + public void setCropExtras(CropExtras e){ + mCropExtras = e; + } + + public CropExtras getCropExtras(){ + return mCropExtras; + } + public GeometryMetadata() { } @@ -86,7 +102,11 @@ public class GeometryMetadata { mCropBounds.set(g.mCropBounds); mPhotoBounds.set(g.mPhotoBounds); mFlip = g.mFlip; - mBounds = g.mBounds; + + mUseCropExtras = g.mUseCropExtras; + if (g.mCropExtras != null){ + mCropExtras = new CropExtras(g.mCropExtras); + } } public float getScaleFactor() { @@ -184,48 +204,16 @@ public class GeometryMetadata { + ",photoRect=" + mPhotoBounds.toShortString() + "]"; } - // TODO: refactor away - protected static Matrix getHorizontalMatrix(float width) { - Matrix flipHorizontalMatrix = new Matrix(); - flipHorizontalMatrix.setScale(-1, 1); - flipHorizontalMatrix.postTranslate(width, 0); - return flipHorizontalMatrix; - } - protected static void concatHorizontalMatrix(Matrix m, float width) { m.postScale(-1, 1); m.postTranslate(width, 0); } - // TODO: refactor away - protected static Matrix getVerticalMatrix(float height) { - Matrix flipVerticalMatrix = new Matrix(); - flipVerticalMatrix.setScale(1, -1); - flipVerticalMatrix.postTranslate(0, height); - return flipVerticalMatrix; - } - protected static void concatVerticalMatrix(Matrix m, float height) { m.postScale(1, -1); m.postTranslate(0, height); } - // TODO: refactor away - public static Matrix getFlipMatrix(float width, float height, FLIP type) { - if (type == FLIP.HORIZONTAL) { - return getHorizontalMatrix(width); - } else if (type == FLIP.VERTICAL) { - return getVerticalMatrix(height); - } else if (type == FLIP.BOTH) { - Matrix flipper = getVerticalMatrix(height); - flipper.postConcat(getHorizontalMatrix(width)); - return flipper; - } else { - Matrix m = new Matrix(); - m.reset(); // identity - return m; - } - } public static void concatMirrorMatrix(Matrix m, float width, float height, FLIP type) { if (type == FLIP.HORIZONTAL) { @@ -331,46 +319,10 @@ public class GeometryMetadata { return m1; } - // TODO: refactor away - public Matrix getFlipMatrix(float width, float height) { - FLIP type = getFlipType(); - return getFlipMatrix(width, height, type); - } - public boolean hasSwitchedWidthHeight() { return (((int) (mRotation / 90)) % 2) != 0; } - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - float rotation) { - float dx0 = width / 2; - float dy0 = height / 2; - Matrix m = getFlipMatrix(width, height); - m.postTranslate(-dx0, -dy0); - m.postRotate(rotation); - m.postScale(scaling, scaling); - m.postTranslate(dx, dy); - return m; - } - - // TODO: refactor away - public Matrix buildGeometryMatrix(float width, float height, float scaling, float dx, float dy, - boolean onlyRotate) { - float rot = mRotation; - if (!onlyRotate) { - rot += mStraightenRotation; - } - return buildGeometryMatrix(width, height, scaling, dx, dy, rot); - } - - // TODO: refactor away - public Matrix buildGeometryUIMatrix(float scaling, float dx, float dy) { - float w = mPhotoBounds.width(); - float h = mPhotoBounds.height(); - return buildGeometryMatrix(w, h, scaling, dx, dy, false); - } - public static Matrix buildPhotoMatrix(RectF photo, RectF crop, float rotation, float straighten, FLIP type) { Matrix m = new Matrix(); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java index a352a16e7..cd1ad5178 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageCrop.java @@ -29,22 +29,25 @@ import android.util.AttributeSet; import android.util.Log; import com.android.gallery3d.R; +import com.android.gallery3d.filtershow.CropExtras; public class ImageCrop extends ImageGeometry { private static final boolean LOGV = false; + + // Sides private static final int MOVE_LEFT = 1; private static final int MOVE_TOP = 2; private static final int MOVE_RIGHT = 4; private static final int MOVE_BOTTOM = 8; private static final int MOVE_BLOCK = 16; - //Corners + // Corners private static final int TOP_LEFT = MOVE_TOP | MOVE_LEFT; private static final int TOP_RIGHT = MOVE_TOP | MOVE_RIGHT; private static final int BOTTOM_RIGHT = MOVE_BOTTOM | MOVE_RIGHT; private static final int BOTTOM_LEFT = MOVE_BOTTOM | MOVE_LEFT; - private static final float MIN_CROP_WIDTH_HEIGHT = 0.1f; + private static int mMinSideSize = 100; private static int mTouchTolerance = 45; private boolean mFirstDraw = true; @@ -53,23 +56,30 @@ public class ImageCrop extends ImageGeometry { private boolean mFixAspectRatio = false; private float mLastRot = 0; - private final Paint borderPaint; + private BoundedRect mBounded = null; private int movingEdges; private final Drawable cropIndicator; private final int indicatorSize; private final int mBorderColor = Color.argb(128, 255, 255, 255); + // Offset between crop center and photo center + private float[] mOffset = { + 0, 0 + }; + private CropExtras mCropExtras = null; + private boolean mDoingCropIntentAction = false; + private static final String LOGTAG = "ImageCrop"; private String mAspect = ""; private int mAspectTextSize = 24; - public void setAspectTextSize(int textSize){ + public void setAspectTextSize(int textSize) { mAspectTextSize = textSize; } - public void setAspectString(String a){ + public void setAspectString(String a) { mAspect = a; } @@ -80,10 +90,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } public ImageCrop(Context context, AttributeSet attrs) { @@ -91,10 +97,6 @@ public class ImageCrop extends ImageGeometry { Resources resources = context.getResources(); cropIndicator = resources.getDrawable(R.drawable.camera_crop); indicatorSize = (int) resources.getDimension(R.dimen.crop_indicator_size); - borderPaint = new Paint(); - borderPaint.setStyle(Paint.Style.STROKE); - borderPaint.setColor(mBorderColor); - borderPaint.setStrokeWidth(2f); } @Override @@ -102,84 +104,46 @@ public class ImageCrop extends ImageGeometry { return getContext().getString(R.string.crop); } - private void swapAspect(){ + private void swapAspect() { + if (mDoingCropIntentAction) { + return; + } float temp = mAspectWidth; mAspectWidth = mAspectHeight; mAspectHeight = temp; } - public static void setTouchTolerance(int tolerance){ + /** + * Set tolerance for crop marker selection (in pixels) + */ + public static void setTouchTolerance(int tolerance) { mTouchTolerance = tolerance; } - private boolean switchCropBounds(int moving_corner, RectF dst) { - RectF crop = getCropBoundsDisplayed(); - float dx1 = 0; - float dy1 = 0; - float dx2 = 0; - float dy2 = 0; - if ((moving_corner & MOVE_RIGHT) != 0) { - dx1 = mCurrentX - crop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx1 = mCurrentX - crop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy1 = mCurrentY - crop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy1 = mCurrentY - crop.top; - } - RectF newCrop = null; - //Fix opposite corner in place and move sides - if (moving_corner == BOTTOM_RIGHT) { - newCrop = new RectF(crop.left, crop.top, crop.left + crop.height(), crop.top - + crop.width()); - } else if (moving_corner == BOTTOM_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.top, crop.right, crop.top - + crop.width()); - } else if (moving_corner == TOP_LEFT) { - newCrop = new RectF(crop.right - crop.height(), crop.bottom - crop.width(), - crop.right, crop.bottom); - } else if (moving_corner == TOP_RIGHT) { - newCrop = new RectF(crop.left, crop.bottom - crop.width(), crop.left - + crop.height(), crop.bottom); - } - if ((moving_corner & MOVE_RIGHT) != 0) { - dx2 = mCurrentX - newCrop.right; - } else if ((moving_corner & MOVE_LEFT) != 0) { - dx2 = mCurrentX - newCrop.left; - } - if ((moving_corner & MOVE_BOTTOM) != 0) { - dy2 = mCurrentY - newCrop.bottom; - } else if ((moving_corner & MOVE_TOP) != 0) { - dy2 = mCurrentY - newCrop.top; - } - if (Math.sqrt(dx1*dx1 + dy1*dy1) > Math.sqrt(dx2*dx2 + dy2*dy2)){ - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return false; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return false; - } - swapAspect(); - dst.set(newCrop); - return true; - } - return false; + /** + * Set minimum side length for crop box (in pixels) + */ + public static void setMinCropSize(int minHeightWidth) { + mMinSideSize = minHeightWidth; + } + + public void setExtras(CropExtras e) { + mCropExtras = e; + } + + public void setCropActionFlag(boolean f) { + mDoingCropIntentAction = f; } - public void apply(float w, float h){ + public void apply(float w, float h) { mFixAspectRatio = true; mAspectWidth = w; mAspectHeight = h; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } @@ -194,202 +158,159 @@ public class ImageCrop extends ImageGeometry { mAspectHeight = h / scale; setLocalCropBounds(getUntranslatedStraightenCropBounds(photobounds, getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } public void applyClear() { mFixAspectRatio = false; + mAspectWidth = 1; + mAspectHeight = 1; setLocalCropBounds(getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten())); - cropSetup(); + if (mVisibilityGained) { + cropSetup(); + } saveAndSetPreset(); invalidate(); } - private float getScaledMinWidthHeight() { - RectF disp = new RectF(0, 0, getWidth(), getHeight()); - float scaled = Math.min(disp.width(), disp.height()) * MIN_CROP_WIDTH_HEIGHT - / computeScale(getWidth(), getHeight()); - return scaled; - } - - protected Matrix getCropRotationMatrix(float rotation, RectF localImage) { - Matrix m = getLocalGeoFlipMatrix(localImage.width(), localImage.height()); - m.postRotate(rotation, localImage.centerX(), localImage.centerY()); - if (!m.rectStaysRect()) { - return null; - } - return m; - } - - protected Matrix getCropBoundDisplayMatrix(){ - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); - } - float zoom = computeScale(getWidth(), getHeight()); - m.postTranslate(mXOffset, mYOffset); - m.postScale(zoom, zoom, mCenterX, mCenterY); - return m; - } - - protected RectF getCropBoundsDisplayed() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - m = new Matrix(); + public void clear() { + if (mCropExtras != null) { + int x = mCropExtras.getAspectX(); + int y = mCropExtras.getAspectY(); + if (mDoingCropIntentAction && x > 0 && y > 0) { + apply(x, y); + } } else { - m.mapRect(crop); + applyClear(); } - m = new Matrix(); - float zoom = computeScale(getWidth(), getHeight()); - m.setScale(zoom, zoom, mCenterX, mCenterY); - m.preTranslate(mXOffset, mYOffset); - m.mapRect(crop); - return crop; } - private RectF getRotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF crop = new RectF(bounds); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP CROP BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(crop); - } - return crop; + private Matrix getPhotoBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m.preScale(scale, scale); + return m; } - private RectF getUnrotatedCropBounds(RectF cropBounds) { - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO GET ROTATION MATRIX"); - return null; - } - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - return null; - } - RectF crop = new RectF(cropBounds); - if (!m0.mapRect(crop)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - return null; - } - return crop; + private Matrix getCropBoundDisplayedMatrix() { + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); + Matrix m1 = GeometryMetadata.buildWanderingCropMatrix(scaledPhoto, scaledCrop, + getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); + m1.preScale(scale, scale); + return m1; } - private RectF getRotatedStraightenBounds() { - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - - if (m == null) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP STRAIGHTEN BOUNDS TO RECTANGLE"); - return null; - } else { - m.mapRect(straightenBounds); - } - return straightenBounds; + /** + * Takes the rotated corners of a rectangle and returns the angle; sets + * unrotated to be the unrotated version of the rectangle. + */ + private static float getUnrotated(float[] rotatedRect, float[] center, RectF unrotated) { + float dy = rotatedRect[1] - rotatedRect[3]; + float dx = rotatedRect[0] - rotatedRect[2]; + float angle = (float) (Math.atan(dy / dx) * 180 / Math.PI); + Matrix m = new Matrix(); + m.setRotate(-angle, center[0], center[1]); + float[] unrotatedRect = new float[rotatedRect.length]; + m.mapPoints(unrotatedRect, rotatedRect); + unrotated.set(CropMath.trapToRect(unrotatedRect)); + return angle; } /** * Sets cropped bounds; modifies the bounds if it's smaller than the allowed * dimensions. */ - public void setCropBounds(RectF bounds) { - // Avoid cropping smaller than minimum width or height. + public boolean setCropBounds(RectF bounds) { RectF cbounds = new RectF(bounds); - float minWidthHeight = getScaledMinWidthHeight(); - float aw = mAspectWidth; - float ah = mAspectHeight; - if (mFixAspectRatio) { - minWidthHeight /= aw * ah; - int r = (int) (getLocalRotation() / 90); - if (r % 2 != 0) { - float temp = aw; - aw = ah; - ah = temp; - } - } - + Matrix mc = getCropBoundDisplayedMatrix(); + Matrix mcInv = new Matrix(); + mc.invert(mcInv); + mcInv.mapRect(cbounds); + // Avoid cropping smaller than minimum float newWidth = cbounds.width(); float newHeight = cbounds.height(); - if (mFixAspectRatio) { - if (newWidth < (minWidthHeight * aw) || newHeight < (minWidthHeight * ah)) { - newWidth = minWidthHeight * aw; - newHeight = minWidthHeight * ah; - } - } else { - if (newWidth < minWidthHeight) { - newWidth = minWidthHeight; - } - if (newHeight < minWidthHeight) { - newHeight = minWidthHeight; - } - } + float scale = getTransformState(null, null, null); + float minWidthHeight = mMinSideSize / scale; RectF pbounds = getLocalPhotoBounds(); - if (pbounds.width() < minWidthHeight) { - newWidth = pbounds.width(); + + // if photo is smaller than minimum, refuse to set crop bounds + if (pbounds.width() < minWidthHeight || pbounds.height() < minWidthHeight) { + return false; } - if (pbounds.height() < minWidthHeight) { - newHeight = pbounds.height(); + + // if incoming crop is smaller than minimum, refuse to set crop bounds + if (newWidth < minWidthHeight || newHeight < minWidthHeight) { + return false; } - cbounds.set(cbounds.left, cbounds.top, cbounds.left + newWidth, cbounds.top + newHeight); - RectF straightenBounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), - getLocalStraighten()); - cbounds.intersect(straightenBounds); + float newX = bounds.centerX() - (getWidth() / 2f); + float newY = bounds.centerY() - (getHeight() / 2f); + mOffset[0] = newX; + mOffset[1] = newY; - if (mFixAspectRatio) { - fixAspectRatio(cbounds, aw, ah); - } setLocalCropBounds(cbounds); invalidate(); + return true; + } + + private BoundedRect getBoundedCrop(RectF crop) { + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + RectF scaledPhoto = new RectF(); + float angle = getUnrotated(photoCorners, photoCenter, scaledPhoto); + return new BoundedRect(angle, scaledPhoto, crop); } private void detectMovingEdges(float x, float y) { - RectF cropped = getCropBoundsDisplayed(); + Matrix m = getCropBoundDisplayedMatrix(); + RectF cropped = getLocalCropBounds(); + m.mapRect(cropped); + mBounded = getBoundedCrop(cropped); movingEdges = 0; - // Check left or right. float left = Math.abs(x - cropped.left); float right = Math.abs(x - cropped.right); - if ((left <= mTouchTolerance) && (left < right)) { + float top = Math.abs(y - cropped.top); + float bottom = Math.abs(y - cropped.bottom); + + // Check left or right. + if ((left <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom) && (left < right)) { movingEdges |= MOVE_LEFT; } - else if (right <= mTouchTolerance) { + else if ((right <= mTouchTolerance) && ((y + mTouchTolerance) >= cropped.top) + && ((y - mTouchTolerance) <= cropped.bottom)) { movingEdges |= MOVE_RIGHT; } // Check top or bottom. - float top = Math.abs(y - cropped.top); - float bottom = Math.abs(y - cropped.bottom); - if ((top <= mTouchTolerance) & (top < bottom)) { + if ((top <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right) && (top < bottom)) { movingEdges |= MOVE_TOP; } - else if (bottom <= mTouchTolerance) { + else if ((bottom <= mTouchTolerance) && ((x + mTouchTolerance) >= cropped.left) + && ((x - mTouchTolerance) <= cropped.right)) { movingEdges |= MOVE_BOTTOM; } - // Check inside block. - if (cropped.contains(x, y) && (movingEdges == 0)) { + if (movingEdges == 0) { movingEdges = MOVE_BLOCK; } if (mFixAspectRatio && (movingEdges != MOVE_BLOCK)) { @@ -398,7 +319,7 @@ public class ImageCrop extends ImageGeometry { invalidate(); } - private int fixEdgeToCorner(int moving_edges){ + private int fixEdgeToCorner(int moving_edges) { if (moving_edges == MOVE_LEFT) { moving_edges |= MOVE_TOP; } @@ -414,9 +335,9 @@ public class ImageCrop extends ImageGeometry { return moving_edges; } - private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy){ + private RectF fixedCornerResize(RectF r, int moving_corner, float dx, float dy) { RectF newCrop = null; - //Fix opposite corner in place and move sides + // Fix opposite corner in place and move sides if (moving_corner == BOTTOM_RIGHT) { newCrop = new RectF(r.left, r.top, r.left + r.width() + dx, r.top + r.height() + dy); @@ -434,120 +355,90 @@ public class ImageCrop extends ImageGeometry { } private void moveEdges(float dX, float dY) { - RectF cropped = getRotatedCropBounds(); - float minWidthHeight = getScaledMinWidthHeight(); - float scale = computeScale(getWidth(), getHeight()); - float deltaX = dX / scale; - float deltaY = dY / scale; - int select = movingEdges; - if (mFixAspectRatio && (select != MOVE_BLOCK)) { - - // TODO: add in orientation change for fixed aspect - /*if (select == TOP_LEFT || select == TOP_RIGHT || - select == BOTTOM_LEFT || select == BOTTOM_RIGHT){ - RectF blank = new RectF(); - if(switchCropBounds(select, blank)){ - setCropBounds(blank); - return; - } - }*/ - if (select == MOVE_LEFT) { - select |= MOVE_TOP; - } - if (select == MOVE_TOP) { - select |= MOVE_LEFT; - } - if (select == MOVE_RIGHT) { - select |= MOVE_BOTTOM; - } - if (select == MOVE_BOTTOM) { - select |= MOVE_RIGHT; - } - } - - if (select == MOVE_BLOCK) { - RectF straight = getRotatedStraightenBounds(); - // Move the whole cropped bounds within the photo display bounds. - deltaX = (deltaX > 0) ? Math.min(straight.right - cropped.right, deltaX) - : Math.max(straight.left - cropped.left, deltaX); - deltaY = (deltaY > 0) ? Math.min(straight.bottom - cropped.bottom, deltaY) - : Math.max(straight.top - cropped.top, deltaY); - cropped.offset(deltaX, deltaY); + RectF crop = mBounded.getInner(); + + Matrix mc = getCropBoundDisplayedMatrix(); + + RectF photo = getLocalPhotoBounds(); + Matrix mp = getPhotoBoundDisplayedMatrix(); + float[] photoCorners = CropMath.getCornersFromRect(photo); + float[] photoCenter = { + photo.centerX(), photo.centerY() + }; + mp.mapPoints(photoCorners); + mp.mapPoints(photoCenter); + + float minWidthHeight = mMinSideSize; + + if (movingEdges == MOVE_BLOCK) { + mBounded.moveInner(-dX, -dY); + RectF r = mBounded.getInner(); + setCropBounds(r); + return; } else { float dx = 0; float dy = 0; - if ((select & MOVE_LEFT) != 0) { - dx = Math.min(cropped.left + deltaX, cropped.right - minWidthHeight) - cropped.left; + if ((movingEdges & MOVE_LEFT) != 0) { + dx = Math.min(crop.left + dX, crop.right - minWidthHeight) - crop.left; } - if ((select & MOVE_TOP) != 0) { - dy = Math.min(cropped.top + deltaY, cropped.bottom - minWidthHeight) - cropped.top; + if ((movingEdges & MOVE_TOP) != 0) { + dy = Math.min(crop.top + dY, crop.bottom - minWidthHeight) - crop.top; } - if ((select & MOVE_RIGHT) != 0) { - dx = Math.max(cropped.right + deltaX, cropped.left + minWidthHeight) - - cropped.right; + if ((movingEdges & MOVE_RIGHT) != 0) { + dx = Math.max(crop.right + dX, crop.left + minWidthHeight) + - crop.right; } - if ((select & MOVE_BOTTOM) != 0) { - dy = Math.max(cropped.bottom + deltaY, cropped.top + minWidthHeight) - - cropped.bottom; + if ((movingEdges & MOVE_BOTTOM) != 0) { + dy = Math.max(crop.bottom + dY, crop.top + minWidthHeight) + - crop.bottom; } if (mFixAspectRatio) { - RectF crop = getCropBoundsDisplayed(); - float [] l1 = {crop.left, crop.bottom}; - float [] l2 = {crop.right, crop.top}; - if(movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT){ + float[] l1 = { + crop.left, crop.bottom + }; + float[] l2 = { + crop.right, crop.top + }; + if (movingEdges == TOP_LEFT || movingEdges == BOTTOM_RIGHT) { l1[1] = crop.top; l2[1] = crop.bottom; } - float[] b = { l1[0] - l2[0], l1[1] - l2[1] }; - float[] disp = {dx, dy}; + float[] b = { + l1[0] - l2[0], l1[1] - l2[1] + }; + float[] disp = { + dx, dy + }; float[] bUnit = GeometryMath.normalize(b); float sp = GeometryMath.scalarProjection(disp, bUnit); dx = sp * bUnit[0]; dy = sp * bUnit[1]; - RectF newCrop = fixedCornerResize(crop, select, dx * scale, dy * scale); - Matrix m = getCropBoundDisplayMatrix(); - Matrix m0 = new Matrix(); - if (!m.invert(m0)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT CROP MATRIX"); - return; - } - if (!m0.mapRect(newCrop)){ - if (LOGV) - Log.v(LOGTAG, "FAILED TO MAP RECTANGLE TO RECTANGLE"); - return; - } + RectF newCrop = fixedCornerResize(crop, movingEdges, dx, dy); + + mBounded.fixedAspectResizeInner(newCrop); + newCrop = mBounded.getInner(); setCropBounds(newCrop); return; } else { - if ((select & MOVE_LEFT) != 0) { - cropped.left += dx; + if ((movingEdges & MOVE_LEFT) != 0) { + crop.left += dx; } - if ((select & MOVE_TOP) != 0) { - cropped.top += dy; + if ((movingEdges & MOVE_TOP) != 0) { + crop.top += dy; } - if ((select & MOVE_RIGHT) != 0) { - cropped.right += dx; + if ((movingEdges & MOVE_RIGHT) != 0) { + crop.right += dx; } - if ((select & MOVE_BOTTOM) != 0) { - cropped.bottom += dy; + if ((movingEdges & MOVE_BOTTOM) != 0) { + crop.bottom += dy; } } } - movingEdges = select; - Matrix m = getCropRotationMatrix(getLocalRotation(), getLocalPhotoBounds()); - Matrix m0 = new Matrix(); - if (!m.invert(m0)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO INVERT ROTATION MATRIX"); - } - if (!m0.mapRect(cropped)) { - if (LOGV) - Log.v(LOGTAG, "FAILED TO UNROTATE CROPPING BOUNDS"); - } - setCropBounds(cropped); + mBounded.resizeInner(crop); + crop = mBounded.getInner(); + setCropBounds(crop); } private void drawIndicator(Canvas canvas, Drawable indicator, float centerX, float centerY) { @@ -560,7 +451,8 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionDown(float x, float y) { super.setActionDown(x, y); - detectMovingEdges(x, y); + detectMovingEdges(x + mOffset[0], y + mOffset[1]); + } @Override @@ -571,20 +463,54 @@ public class ImageCrop extends ImageGeometry { @Override protected void setActionMove(float x, float y) { - if (movingEdges != 0){ + + if (movingEdges != 0) { moveEdges(x - mCurrentX, y - mCurrentY); } super.setActionMove(x, y); + + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + setActionUp(); + cropSetup(); + invalidate(); } private void cropSetup() { + RectF crop = getLocalCropBounds(); + Matrix m = getCropBoundDisplayedMatrix(); + m.mapRect(crop); if (mFixAspectRatio) { - RectF cb = getRotatedCropBounds(); - fixAspectRatio(cb, mAspectWidth, mAspectHeight); - RectF cb0 = getUnrotatedCropBounds(cb); - setCropBounds(cb0); - } else { - setCropBounds(getLocalCropBounds()); + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + float dCentX = getWidth() / 2; + float dCentY = getHeight() / 2; + + BoundedRect r = getBoundedCrop(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + float h = mMinSideSize / 2; + float wScale = 1; + float hScale = mAspectHeight / mAspectWidth; + if (hScale < 1) { + wScale = mAspectWidth / mAspectHeight; + hScale = 1; + } + crop.set(dCentX - h * wScale, dCentY - h * hScale, dCentX + h * wScale, dCentY + h + * hScale); + if (mFixAspectRatio) { + CropMath.fixAspectRatio(crop, mAspectWidth, mAspectHeight); + } + r.setInner(crop); + crop = r.getInner(); + if (!setCropBounds(crop)) { + crop.set(dCentX - h, dCentY - h, dCentX + h, dCentY + h); + r.setInner(crop); + crop = r.getInner(); + setCropBounds(crop); + } } } @@ -592,7 +518,7 @@ public class ImageCrop extends ImageGeometry { public void imageLoaded() { super.imageLoaded(); syncLocalToMasterGeometry(); - applyClear(); + clear(); invalidate(); } @@ -600,7 +526,7 @@ public class ImageCrop extends ImageGeometry { protected void gainedVisibility() { float rot = getLocalRotation(); // if has changed orientation via rotate - if( ((int) ((rot - mLastRot) / 90)) % 2 != 0 ){ + if (((int) ((rot - mLastRot) / 90)) % 2 != 0) { swapAspect(); } cropSetup(); @@ -610,7 +536,6 @@ public class ImageCrop extends ImageGeometry { @Override public void resetParameter() { super.resetParameter(); - cropSetup(); } @Override @@ -635,101 +560,109 @@ public class ImageCrop extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - // TODO: move style to xml gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); if (mFirstDraw) { cropSetup(); mFirstDraw = false; } - float rotation = getLocalRotation(); - RectF crop = drawTransformed(canvas, image, gPaint); + RectF crop = drawTransformed(canvas, image, gPaint, mOffset); gPaint.setColor(mBorderColor); gPaint.setStrokeWidth(3); gPaint.setStyle(Paint.Style.STROKE); - drawRuleOfThird(canvas, crop, gPaint); - - if (mFixAspectRatio){ - float w = crop.width(); - float h = crop.height(); - float diag = (float) Math.sqrt(w*w + h*h); - - float dash_len = 20; - int num_intervals = (int) (diag / dash_len); - float [] tl = { crop.left, crop.top }; - float centX = tl[0] + w/2; - float centY = tl[1] + h/2 + 5; - float [] br = { crop.right, crop.bottom }; - float [] vec = GeometryMath.getUnitVectorFromPoints(tl, br); - - float [] counter = tl; - for (int x = 0; x < num_intervals; x++ ){ - float tempX = counter[0] + vec[0] * dash_len; - float tempY = counter[1] + vec[1] * dash_len; - if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2){ - canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); - } - counter[0] = tempX; - counter[1] = tempY; + + boolean doThirds = true; + + if (mFixAspectRatio) { + float spotlightX = 0; + float spotlightY = 0; + if (mCropExtras != null) { + spotlightX = mCropExtras.getSpotlightX(); + spotlightY = mCropExtras.getSpotlightY(); } + if (mDoingCropIntentAction && spotlightX > 0 && spotlightY > 0) { + float sx = crop.width() * spotlightX; + float sy = crop.height() * spotlightY; + float cx = crop.centerX(); + float cy = crop.centerY(); + RectF r1 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2); + float temp = sx; + sx = sy; + sy = temp; + RectF r2 = new RectF(cx - sx / 2, cy - sy / 2, cx + sx / 2, cy + sy / 2); + canvas.drawRect(r1, gPaint); + canvas.drawRect(r2, gPaint); + doThirds = false; + } else { + float w = crop.width(); + float h = crop.height(); + float diag = (float) Math.sqrt(w * w + h * h); + + float dash_len = 20; + int num_intervals = (int) (diag / dash_len); + float[] tl = { + crop.left, crop.top + }; + float centX = tl[0] + w / 2; + float centY = tl[1] + h / 2 + 5; + float[] br = { + crop.right, crop.bottom + }; + float[] vec = GeometryMath.getUnitVectorFromPoints(tl, br); + + float[] counter = tl; + for (int x = 0; x < num_intervals; x++) { + float tempX = counter[0] + vec[0] * dash_len; + float tempY = counter[1] + vec[1] * dash_len; + if ((x % 2) == 0 && Math.abs(x - num_intervals / 2) > 2) { + canvas.drawLine(counter[0], counter[1], tempX, tempY, gPaint); + } + counter[0] = tempX; + counter[1] = tempY; + } - gPaint.setTextAlign(Paint.Align.CENTER); - gPaint.setTextSize(mAspectTextSize); - canvas.drawText(mAspect, centX, centY, gPaint); + gPaint.setTextAlign(Paint.Align.CENTER); + gPaint.setTextSize(mAspectTextSize); + canvas.drawText(mAspect, centX, centY, gPaint); + } } - gPaint.setColor(mBorderColor); - gPaint.setStrokeWidth(3); - gPaint.setStyle(Paint.Style.STROKE); - drawStraighten(canvas, gPaint); - - int decoded_moving = decoder(movingEdges, rotation); - canvas.save(); - canvas.rotate(rotation, mCenterX, mCenterY); - RectF scaledCrop = unrotatedCropBounds(); - boolean notMoving = decoded_moving == 0; - if (((decoded_moving & MOVE_TOP) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); - } - if (((decoded_moving & MOVE_BOTTOM) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); - } - if (((decoded_moving & MOVE_LEFT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); - } - if (((decoded_moving & MOVE_RIGHT) != 0) || notMoving) { - drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + if (doThirds) { + drawRuleOfThird(canvas, crop, gPaint); + } - canvas.restore(); - } - - private int bitCycleLeft(int x, int times, int d) { - int mask = (1 << d) - 1; - int mout = x & mask; - times %= d; - int hi = mout >> (d - times); - int low = (mout << times) & mask; - int ret = x & ~mask; - ret |= low; - ret |= hi; - return ret; - } - - protected int decoder(int movingEdges, float rotation) { - int rot = constrainedRotation(rotation); - switch (rot) { - case 90: - return bitCycleLeft(movingEdges, 3, 4); - case 180: - return bitCycleLeft(movingEdges, 2, 4); - case 270: - return bitCycleLeft(movingEdges, 1, 4); - default: - return movingEdges; + + RectF scaledCrop = crop; + boolean notMoving = (movingEdges == 0); + if (mFixAspectRatio) { + if ((movingEdges == TOP_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.top); + } + if ((movingEdges == TOP_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.top); + } + if ((movingEdges == BOTTOM_LEFT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.bottom); + } + if ((movingEdges == BOTTOM_RIGHT) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.bottom); + } + } else { + if (((movingEdges & MOVE_TOP) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.top); + } + if (((movingEdges & MOVE_BOTTOM) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.centerX(), scaledCrop.bottom); + } + if (((movingEdges & MOVE_LEFT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.left, scaledCrop.centerY()); + } + if (((movingEdges & MOVE_RIGHT) != 0) || notMoving) { + drawIndicator(canvas, cropIndicator, scaledCrop.right, scaledCrop.centerY()); + } } } + } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java index 5d6fe502f..6bfba1b2c 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageFlip.java @@ -136,8 +136,6 @@ public class ImageFlip extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); drawTransformedCropped(canvas, image, gPaint); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java index 42dd139bc..c8ae444da 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageGeometry.java @@ -33,7 +33,7 @@ import com.android.gallery3d.filtershow.imageshow.GeometryMetadata.FLIP; import com.android.gallery3d.filtershow.presets.ImagePreset; public abstract class ImageGeometry extends ImageSlave { - private boolean mVisibilityGained = false; + protected boolean mVisibilityGained = false; private boolean mHasDrawn = false; protected static final float MAX_STRAIGHTEN_ANGLE = 45; @@ -191,8 +191,8 @@ public abstract class ImageGeometry extends ImageSlave { return r * 90; } - protected Matrix getLocalGeoFlipMatrix(float width, float height) { - return mLocalGeometry.getFlipMatrix(width, height); + protected boolean isHeightWidthSwapped() { + return ((int) (getLocalRotation() / 90)) % 2 != 0; } protected void setLocalStraighten(float r) { @@ -217,32 +217,6 @@ public abstract class ImageGeometry extends ImageSlave { return getLocalRotation() + getLocalStraighten(); } - protected static float[] getCornersFromRect(RectF r) { - // Order is: - // 0------->1 - // ^ | - // | v - // 3<-------2 - float[] corners = { - r.left, r.top, // 0 - r.right, r.top, // 1 - r.right, r.bottom,// 2 - r.left, r.bottom // 3 - }; - return corners; - } - - // If edge point [x, y] in array [x0, y0, x1, y1, ...] is outside of the - // image bound rectangle, clamps it to the edge of the rectangle. - protected static void getEdgePoints(RectF imageBound, float[] array) { - if (array.length < 2) - return; - for (int x = 0; x < array.length; x += 2) { - array[x] = GeometryMath.clamp(array[x], imageBound.left, imageBound.right); - array[x + 1] = GeometryMath.clamp(array[x + 1], imageBound.top, imageBound.bottom); - } - } - protected static Path drawClosedPath(Canvas canvas, Paint paint, float[] points) { Path crop = new Path(); crop.moveTo(points[0], points[1]); @@ -254,16 +228,6 @@ public abstract class ImageGeometry extends ImageSlave { return crop; } - protected static void fixAspectRatio(RectF r, float w, float h) { - float scale = Math.min(r.width() / w, r.height() / h); - float centX = r.centerX(); - float centY = r.centerY(); - float hw = scale * w / 2; - float hh = scale * h / 2; - r.set(centX - hw, centY - hh, centX + hw, centY + hh); - - } - protected static float getNewHeightForWidthAspect(float width, float w, float h) { return width * h / w; } @@ -290,11 +254,11 @@ public abstract class ImageGeometry extends ImageSlave { } protected void gainedVisibility() { - // TODO: Override this stub. + // Override this stub. } protected void lostVisibility() { - // TODO: Override this stub. + // Override this stub. } @Override @@ -327,7 +291,7 @@ public abstract class ImageGeometry extends ImageSlave { } protected int getLocalValue() { - return 0; // TODO: Override this + return 0; // Override this } protected void setActionDown(float x, float y) { @@ -402,110 +366,19 @@ public abstract class ImageGeometry extends ImageSlave { return new RectF(left, top, right, bottom); } - protected Matrix getGeoMatrix(RectF r, boolean onlyRotate) { - RectF pbounds = getLocalPhotoBounds(); - float scale = GeometryMath - .scale(pbounds.width(), pbounds.height(), getWidth(), getHeight()); - if (((int) (getLocalRotation() / 90)) % 2 != 0) { - scale = GeometryMath.scale(pbounds.width(), pbounds.height(), getHeight(), getWidth()); - } - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - float w = r.left * 2 + r.width(); - float h = r.top * 2 + r.height(); - return mLocalGeometry.buildGeometryMatrix(w, h, scale, xoff, yoff, onlyRotate); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint, Matrix m) { - canvas.save(); - canvas.drawBitmap(bitmap, m, paint); - canvas.restore(); - } - - protected void drawImageBitmap(Canvas canvas, Bitmap bitmap, Paint paint) { - float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryUIMatrix(scale, xoff, yoff); - drawImageBitmap(canvas, bitmap, paint, m); - } - protected RectF straightenBounds() { RectF bounds = getUntranslatedStraightenCropBounds(getLocalPhotoBounds(), getLocalStraighten()); - Matrix m = getGeoMatrix(bounds, true); - m.mapRect(bounds); - return bounds; - } - - protected void drawStraighten(Canvas canvas, Paint paint) { - RectF bounds = straightenBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected RectF unrotatedCropBounds() { - RectF bounds = getLocalCropBounds(); - RectF pbounds = getLocalPhotoBounds(); float scale = computeScale(getWidth(), getHeight()); - float yoff = getHeight() / 2; - float xoff = getWidth() / 2; - Matrix m = mLocalGeometry.buildGeometryMatrix(pbounds.width(), pbounds.height(), scale, - xoff, yoff, 0); - m.mapRect(bounds); - return bounds; - } - - protected RectF cropBounds() { - RectF bounds = getLocalCropBounds(); - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - m.mapRect(bounds); + bounds = GeometryMath.scaleRect(bounds, scale); + float dx = (getWidth() / 2) - bounds.centerX(); + float dy = (getHeight() / 2) - bounds.centerY(); + bounds.offset(dx, dy); return bounds; } - // Fails for non-90 degree - protected void drawCrop(Canvas canvas, Paint paint) { - RectF bounds = cropBounds(); - canvas.save(); - canvas.drawRect(bounds, paint); - canvas.restore(); - } - - protected void drawCropSafe(Canvas canvas, Paint paint) { - Matrix m = getGeoMatrix(getLocalPhotoBounds(), true); - RectF crop = getLocalCropBounds(); - if (!m.rectStaysRect()) { - float[] corners = getCornersFromRect(crop); - m.mapPoints(corners); - drawClosedPath(canvas, paint, corners); - } else { - m.mapRect(crop); - Path path = new Path(); - path.addRect(crop, Path.Direction.CCW); - canvas.drawPath(path, paint); - } - } - - protected void drawTransformedBitmap(Canvas canvas, Bitmap bitmap, Paint paint, boolean clip) { - paint.setARGB(255, 0, 0, 0); - drawImageBitmap(canvas, bitmap, paint); - paint.setColor(Color.WHITE); - paint.setStyle(Style.STROKE); - paint.setStrokeWidth(2); - drawCropSafe(canvas, paint); - paint.setColor(getDefaultBackgroundColor()); - paint.setStyle(Paint.Style.FILL); - drawShadows(canvas, paint, unrotatedCropBounds()); - } - - protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { - RectF display = new RectF(0, 0, getWidth(), getHeight()); - drawShadows(canvas, p, innerBounds, display, getLocalRotation(), getWidth() / 2, - getHeight() / 2); - } - - protected static void drawShadows(Canvas canvas, Paint p, RectF innerBounds, RectF outerBounds, + protected static void drawRotatedShadows(Canvas canvas, Paint p, RectF innerBounds, + RectF outerBounds, float rotation, float centerX, float centerY) { canvas.save(); canvas.rotate(rotation, centerX, centerY); @@ -527,6 +400,15 @@ public abstract class ImageGeometry extends ImageSlave { canvas.restore(); } + protected void drawShadows(Canvas canvas, Paint p, RectF innerBounds) { + float w = getWidth(); + float h = getHeight(); + canvas.drawRect(0f, 0f, w, innerBounds.top, p); + canvas.drawRect(0f, innerBounds.top, innerBounds.left, innerBounds.bottom, p); + canvas.drawRect(innerBounds.right, innerBounds.top, w, innerBounds.bottom, p); + canvas.drawRect(0f, innerBounds.bottom, w, h, p); + } + @Override public void onDraw(Canvas canvas) { if (getDirtyGeometryFlag()) { @@ -547,21 +429,38 @@ public abstract class ImageGeometry extends ImageSlave { // TODO: Override this stub. } - protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p) { - p.setARGB(255, 0, 0, 0); + /** + * Sets up inputs for buildCenteredPhotoMatrix and buildWanderingCropMatrix + * and returns the scale factor. + */ + protected float getTransformState(RectF photo, RectF crop, float[] displayCenter) { RectF photoBounds = getLocalPhotoBounds(); RectF cropBounds = getLocalCropBounds(); float scale = computeScale(getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = computeScale(getHeight(), getWidth()); } // put in screen coordinates - RectF scaledCrop = GeometryMath.scaleRect(cropBounds, scale); - RectF scaledPhoto = GeometryMath.scaleRect(photoBounds, scale); - float[] displayCenter = { - getWidth() / 2f, getHeight() / 2f - }; + if (crop != null) { + crop.set(GeometryMath.scaleRect(cropBounds, scale)); + } + if (photo != null) { + photo.set(GeometryMath.scaleRect(photoBounds, scale)); + } + if (displayCenter != null && displayCenter.length >= 2) { + displayCenter[0] = getWidth() / 2f; + displayCenter[1] = getHeight() / 2f; + } + return scale; + } + + protected RectF drawTransformed(Canvas canvas, Bitmap photo, Paint p, float[] offset) { + p.setARGB(255, 0, 0, 0); + float[] displayCenter = new float[2]; + RectF scaledCrop = new RectF(); + RectF scaledPhoto = new RectF(); + float scale = getTransformState(scaledPhoto, scaledCrop, displayCenter); Matrix m = GeometryMetadata.buildCenteredPhotoMatrix(scaledPhoto, scaledCrop, getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); @@ -569,9 +468,11 @@ public abstract class ImageGeometry extends ImageSlave { getLocalRotation(), getLocalStraighten(), getLocalFlip(), displayCenter); m1.mapRect(scaledCrop); Path path = new Path(); + scaledCrop.offset(-offset[0], -offset[1]); path.addRect(scaledCrop, Path.Direction.CCW); m.preScale(scale, scale); + m.postTranslate(-offset[0], -offset[1]); canvas.save(); canvas.drawBitmap(photo, m, p); canvas.restore(); @@ -580,6 +481,11 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Style.STROKE); p.setStrokeWidth(2); canvas.drawPath(path, p); + + p.setColor(getDefaultBackgroundColor()); + p.setAlpha(128); + p.setStyle(Paint.Style.FILL); + drawShadows(canvas, p, scaledCrop); return scaledCrop; } @@ -590,7 +496,7 @@ public abstract class ImageGeometry extends ImageSlave { float imageHeight = cropBounds.height(); float scale = GeometryMath.scale(imageWidth, imageHeight, getWidth(), getHeight()); // checks if local rotation is an odd multiple of 90. - if (((int) (getLocalRotation() / 90)) % 2 != 0) { + if (isHeightWidthSwapped()) { scale = GeometryMath.scale(imageWidth, imageHeight, getHeight(), getWidth()); } // put in screen coordinates @@ -618,6 +524,8 @@ public abstract class ImageGeometry extends ImageSlave { p.setStyle(Paint.Style.FILL); scaledCrop.offset(displayCenter[0] - scaledCrop.centerX(), displayCenter[1] - scaledCrop.centerY()); - drawShadows(canvas, p, scaledCrop); + RectF display = new RectF(0, 0, getWidth(), getHeight()); + drawRotatedShadows(canvas, p, scaledCrop, display, getLocalRotation(), getWidth() / 2, + getHeight() / 2); } } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java new file mode 100644 index 000000000..5119dff3c --- /dev/null +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRedEyes.java @@ -0,0 +1,148 @@ + +package com.android.gallery3d.filtershow.imageshow; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import com.android.gallery3d.filtershow.filters.ImageFilterRedEye; +import com.android.gallery3d.filtershow.filters.RedEyeCandidate; + +public class ImageRedEyes extends ImageSlave { + + private static final String LOGTAG = "ImageRedEyes"; + private RectF mCurrentRect = null; + private static float mTouchPadding = 80; + + public static void setTouchPadding(float padding) { + mTouchPadding = padding; + } + + public ImageRedEyes(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ImageRedEyes(Context context) { + super(context); + } + + @Override + public void resetParameter() { + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + if (filter != null) { + filter.clear(); + } + mCurrentRect = null; + invalidate(); + } + + @Override + public void updateImage() { + super.updateImage(); + invalidate(); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + float ex = event.getX(); + float ey = event.getY(); + + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + + // let's transform (ex, ey) to displayed image coordinates + if (event.getAction() == MotionEvent.ACTION_DOWN) { + mCurrentRect = new RectF(); + mCurrentRect.left = ex - mTouchPadding; + mCurrentRect.top = ey - mTouchPadding; + } + if (event.getAction() == MotionEvent.ACTION_MOVE) { + mCurrentRect.right = ex + mTouchPadding; + mCurrentRect.bottom = ey + mTouchPadding; + } + if (event.getAction() == MotionEvent.ACTION_UP) { + if (mCurrentRect != null) { + // transform to original coordinates + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(true, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), + getWidth(), getHeight()); + Matrix originalNoRotateToScreen = geo.getOriginalToScreen(false, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), + getWidth(), getHeight()); + + Matrix invert = new Matrix(); + originalToScreen.invert(invert); + RectF r = new RectF(mCurrentRect); + invert.mapRect(r); + RectF r2 = new RectF(mCurrentRect); + invert.reset(); + originalNoRotateToScreen.invert(invert); + invert.mapRect(r2); + filter.addRect(r, r2); + this.resetImageCaches(this); + } + mCurrentRect = null; + } + invalidate(); + return true; + } + + @Override + public void onDraw(Canvas canvas) { + super.onDraw(canvas); + Paint paint = new Paint(); + paint.setStyle(Style.STROKE); + paint.setColor(Color.RED); + paint.setStrokeWidth(2); + if (mCurrentRect != null) { + paint.setColor(Color.RED); + RectF drawRect = new RectF(mCurrentRect); + canvas.drawRect(drawRect, paint); + } + + GeometryMetadata geo = getImagePreset().mGeoData; + Matrix originalToScreen = geo.getOriginalToScreen(false, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); + Matrix originalRotateToScreen = geo.getOriginalToScreen(true, + mImageLoader.getOriginalBounds().width(), + mImageLoader.getOriginalBounds().height(), getWidth(), getHeight()); + + ImageFilterRedEye filter = (ImageFilterRedEye) getCurrentFilter(); + for (RedEyeCandidate candidate : filter.getCandidates()) { + RectF rect = candidate.getRect(); + RectF drawRect = new RectF(); + originalToScreen.mapRect(drawRect, rect); + RectF fullRect = new RectF(); + originalRotateToScreen.mapRect(fullRect, rect); + paint.setColor(Color.BLUE); + canvas.drawRect(fullRect, paint); + canvas.drawLine(fullRect.centerX(), fullRect.top, + fullRect.centerX(), fullRect.bottom, paint); + canvas.drawLine(fullRect.left, fullRect.centerY(), + fullRect.right, fullRect.centerY(), paint); + paint.setColor(Color.GREEN); + float dw = drawRect.width(); + float dh = drawRect.height(); + float dx = fullRect.centerX() - dw/2; + float dy = fullRect.centerY() - dh/2; + drawRect.set(dx, dy, dx + dw, dy + dh); + canvas.drawRect(drawRect, paint); + canvas.drawLine(drawRect.centerX(), drawRect.top, + drawRect.centerX(), drawRect.bottom, paint); + canvas.drawLine(drawRect.left, drawRect.centerY(), + drawRect.right, drawRect.centerY(), paint); + canvas.drawCircle(drawRect.centerX(), drawRect.centerY(), + mTouchPadding, paint); + } + } +} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java index a4131ff80..30cc9e2f3 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageRotate.java @@ -81,8 +81,6 @@ public class ImageRotate extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { gPaint.setAntiAlias(true); - gPaint.setFilterBitmap(true); - gPaint.setDither(true); gPaint.setARGB(255, 255, 255, 255); drawTransformedCropped(canvas, image, gPaint); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 0145c24dc..4c74b16ca 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -23,6 +23,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.net.Uri; import android.os.Handler; import android.util.AttributeSet; import android.view.GestureDetector; @@ -74,16 +75,12 @@ public class ImageShow extends View implements OnGestureListener, private Bitmap mFiltersOnlyImage = null; private Bitmap mFilteredImage = null; - private final boolean USE_SLIDER_GESTURE = false; // set to true to have - // slider gesture - protected SliderController mSliderController = new SliderController(); - private GestureDetector mGestureDetector = null; private HistoryAdapter mHistoryAdapter = null; private ImageStateAdapter mImageStateAdapter = null; - private Rect mImageBounds = new Rect(); + protected Rect mImageBounds = new Rect(); private boolean mTouchShowOriginal = false; private long mTouchShowOriginalDate = 0; @@ -152,15 +149,15 @@ public class ImageShow extends View implements OnGestureListener, private final Handler mHandler = new Handler(); public void select() { + if (mSeekBar != null) { + mSeekBar.setOnSeekBarChangeListener(this); + } if (getCurrentFilter() != null) { int parameter = getCurrentFilter().getParameter(); int maxp = getCurrentFilter().getMaxParameter(); int minp = getCurrentFilter().getMinParameter(); updateSeekBar(parameter, minp, maxp); } - if (mSeekBar != null) { - mSeekBar.setOnSeekBarChangeListener(this); - } } private int parameterToUI(int parameter, int minp, int maxp, int uimax) { @@ -178,9 +175,6 @@ public class ImageShow extends View implements OnGestureListener, int seekMax = mSeekBar.getMax(); int progress = parameterToUI(parameter, minp, maxp, seekMax); mSeekBar.setProgress(progress); - if (getPanelController() != null) { - getPanelController().onNewValue(parameter); - } } public void unselect() { @@ -197,10 +191,8 @@ public class ImageShow extends View implements OnGestureListener, public void resetParameter() { ImageFilter currentFilter = getCurrentFilter(); if (currentFilter != null) { - onNewValue(currentFilter.getDefaultParameter()); - } - if (USE_SLIDER_GESTURE) { - mSliderController.reset(); + updateSeekBar(currentFilter.getDefaultParameter(), + getCurrentFilter().getMinParameter(), getCurrentFilter().getMaxParameter()); } } @@ -214,8 +206,8 @@ public class ImageShow extends View implements OnGestureListener, @Override public void onNewValue(int parameter) { - int maxp = 100; - int minp = -100; + int maxp = ImageFilter.DEFAULT_MAX_PARAMETER; + int minp = ImageFilter.DEFAULT_MIN_PARAMETER; if (getCurrentFilter() != null) { getCurrentFilter().setParameter(parameter); maxp = getCurrentFilter().getMaxParameter(); @@ -228,8 +220,8 @@ public class ImageShow extends View implements OnGestureListener, if (getPanelController() != null) { getPanelController().onNewValue(parameter); } - updateSeekBar(parameter, minp, maxp); invalidate(); + mActivity.enableSave(hasModifications()); } @Override @@ -245,9 +237,6 @@ public class ImageShow extends View implements OnGestureListener, public ImageShow(Context context, AttributeSet attrs) { super(context, attrs); - if (USE_SLIDER_GESTURE) { - mSliderController.setListener(this); - } mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row, R.id.rowTextView); mImageStateAdapter = new ImageStateAdapter(context, @@ -258,9 +247,6 @@ public class ImageShow extends View implements OnGestureListener, public ImageShow(Context context) { super(context); - if (USE_SLIDER_GESTURE) { - mSliderController.setListener(this); - } mHistoryAdapter = new HistoryAdapter(context, R.layout.filtershow_history_operation_row, R.id.rowTextView); setupGestureDetector(context); @@ -276,10 +262,6 @@ public class ImageShow extends View implements OnGestureListener, int parentWidth = MeasureSpec.getSize(widthMeasureSpec); int parentHeight = MeasureSpec.getSize(heightMeasureSpec); setMeasuredDimension(parentWidth, parentHeight); - if (USE_SLIDER_GESTURE) { - mSliderController.setWidth(parentWidth); - mSliderController.setHeight(parentHeight); - } } public void setSeekBar(SeekBar seekBar) { @@ -323,6 +305,10 @@ public class ImageShow extends View implements OnGestureListener, return dst; } + public Rect getImageCropBounds() { + return GeometryMath.roundNearest(getImagePreset().mGeoData.getPreviewCropBounds()); + } + public Rect getDisplayedImageBounds() { return mImageBounds; } @@ -377,12 +363,6 @@ public class ImageShow extends View implements OnGestureListener, 1.5f * mTextPadding, mPaint); } - if (showControls()) { - if (USE_SLIDER_GESTURE) { - mSliderController.onDraw(canvas); - } - } - drawToast(canvas); } @@ -396,6 +376,7 @@ public class ImageShow extends View implements OnGestureListener, public void updateImagePresets(boolean force) { ImagePreset preset = getImagePreset(); if (preset == null) { + mActivity.enableSave(false); return; } if (force) { @@ -419,6 +400,7 @@ public class ImageShow extends View implements OnGestureListener, mFiltersOnlyImage = null; } } + mActivity.enableSave(hasModifications()); } public void requestFilteredImages() { @@ -643,13 +625,13 @@ public class ImageShow extends View implements OnGestureListener, } public void updateImage() { + invalidate(); if (!updateGeometryFlags()) { return; } Bitmap bitmap = mImageLoader.getOriginalBitmapLarge(); if (bitmap != null) { imageSizeChanged(bitmap); - invalidate(); } } @@ -666,12 +648,17 @@ public class ImageShow extends View implements OnGestureListener, mImageLoader.saveImage(getImagePreset(), filterShowActivity, file); } + public void saveToUri(Bitmap f, Uri u, String m, FilterShowActivity filterShowActivity) { + mImageLoader.saveToUri(f, u, m, filterShowActivity); + } + + public void returnFilteredResult(FilterShowActivity filterShowActivity) { + mImageLoader.returnFilteredResult(getImagePreset(), filterShowActivity); + } + @Override public boolean onTouchEvent(MotionEvent event) { super.onTouchEvent(event); - if (USE_SLIDER_GESTURE) { - mSliderController.onTouchEvent(event); - } if (mGestureDetector != null) { mGestureDetector.onTouchEvent(event); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java index 6a79e18a1..2a3ee2856 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageSmallFilter.java @@ -47,6 +47,13 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener protected final int mTextColor = Color.WHITE; private ImageSmallFilter mNullFilter; + private Bitmap mOverlayBitmap = null; + private final int mOverlayTint = Color.argb(100, 0, 0, 0); + + public void setOverlayBitmap(Bitmap bitmap){ + mOverlayBitmap = bitmap; + } + public static void setMargin(int value) { mMargin = value; } @@ -77,6 +84,10 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener mImagePreset.add(mImageFilter); } + public ImageFilter getImageFilter() { + return mImageFilter; + } + @Override public void setSelected(boolean value) { if (mIsSelected != value) { @@ -188,6 +199,13 @@ public class ImageSmallFilter extends ImageShow implements View.OnClickListener mPaint.setTextSize(mTextSize); mPaint.setColor(mTextColor); canvas.drawText(mImageFilter.getName(), x, y - mTextMargin, mPaint); + if (mOverlayBitmap != null) { + mPaint.setColor(mOverlayTint); + canvas.drawRect(0, mMargin, getWidth(), getWidth() + mMargin, mPaint); + Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth()); + mPaint.setColor(Color.BLACK); + drawImage(canvas, mOverlayBitmap, d); + } } public void drawImage(Canvas canvas, Bitmap image, Rect destination) { diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java index 57a22aab3..7a539da8f 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageStraighten.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; @@ -105,10 +106,10 @@ public class ImageStraighten extends ImageGeometry { @Override protected void drawShape(Canvas canvas, Bitmap image) { - drawTransformed(canvas, image, gPaint); + float [] o = {0, 0}; + RectF bounds = drawTransformed(canvas, image, gPaint, o); // Draw the grid - RectF bounds = straightenBounds(); Path path = new Path(); path.addRect(bounds, Path.Direction.CCW); gPaint.setARGB(255, 255, 255, 255); diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java b/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java deleted file mode 100644 index a332fa72a..000000000 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageWithIcon.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.imageshow; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; - -/** - * TODO: Insert description here. (generated by hoford) - */ -public class ImageWithIcon extends ImageSmallFilter { - /** - * @param context - */ - public ImageWithIcon(Context context) { - super(context); - // TODO(hoford): Auto-generated constructor stub - } - - private Bitmap bitmap; - - public void setIcon(Bitmap bitmap){ - this.bitmap = bitmap; - } - - @Override - public void onDraw(Canvas canvas) { - super.onDraw(canvas); - if (bitmap != null) { - Rect d = new Rect(0, mMargin, getWidth() - mMargin, getWidth()); - drawImage(canvas, bitmap, d); - } - } -} diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java index c7586fe9b..b66da0128 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageZoom.java @@ -128,10 +128,6 @@ public class ImageZoom extends ImageSlave { drawImage(canvas, filteredImage); canvas.restore(); - if (showControls()) { - mSliderController.onDraw(canvas); - } - drawToast(canvas); } diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java deleted file mode 100644 index d1b4e5d78..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBWGreen.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterBWGreen; - -public class ImagePresetBWGreen extends ImagePreset { - - @Override - public String name() { - return "B&W - Green"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBWGreen()); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java deleted file mode 100644 index 5e1db8336..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetOld.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.presets; - -import android.graphics.Color; - -import com.android.gallery3d.filtershow.filters.ImageFilterGradient; - -public class ImagePresetOld extends ImagePreset { - - @Override - public String name() { - return "Old"; - } - - @Override - public void setup() { - ImageFilterGradient filter = new ImageFilterGradient(); - filter.addColor(Color.BLACK, 0.0f); - filter.addColor(Color.argb(255, 228, 231, 193), 1.0f); - mFilters.add(filter); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java deleted file mode 100644 index ddfca7508..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetSaturated.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.presets; - -import com.android.gallery3d.filtershow.filters.ImageFilterSaturated; - -public class ImagePresetSaturated extends ImagePreset { - - @Override - public String name() { - return "Saturated"; - } - - @Override - public void setup() { - ImageFilterSaturated filter = new ImageFilterSaturated(); - filter.setParameter(50); - mFilters.add(filter); - } - -} diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java b/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java deleted file mode 100644 index 7957b5e7c..000000000 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetXProcessing.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.filtershow.presets; - -import android.graphics.Color; - -import com.android.gallery3d.filtershow.filters.ImageFilterGradient; - -public class ImagePresetXProcessing extends ImagePreset { - - @Override - public String name() { - return "X-Process"; - } - - @Override - public void setup() { - ImageFilterGradient filter = new ImageFilterGradient(); - filter.addColor(Color.BLACK, 0.0f); - filter.addColor(Color.argb(255, 29, 82, 83), 0.4f); - filter.addColor(Color.argb(255, 211, 217, 186), 1.0f); - mFilters.add(filter); - } - -} diff --git a/src/com/android/gallery3d/filtershow/tools/BitmapTask.java b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java new file mode 100644 index 000000000..62801c1f2 --- /dev/null +++ b/src/com/android/gallery3d/filtershow/tools/BitmapTask.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.filtershow.tools; + +import android.graphics.Bitmap; +import android.os.AsyncTask; + +/** + * Asynchronous task filtering or doign I/O with bitmaps. + */ +public class BitmapTask <T> extends AsyncTask<T, Void, Bitmap> { + + private Callbacks<T> mCallbacks; + private static final String LOGTAG = "BitmapTask"; + + public BitmapTask(Callbacks<T> callbacks) { + mCallbacks = callbacks; + } + + @Override + protected Bitmap doInBackground(T... params) { + if (params == null || mCallbacks == null) { + return null; + } + return mCallbacks.onExecute(params[0]); + } + + @Override + protected void onPostExecute(Bitmap result) { + if (mCallbacks == null) { + return; + } + mCallbacks.onComplete(result); + } + + @Override + protected void onCancelled() { + if (mCallbacks == null) { + return; + } + mCallbacks.onCancel(); + } + + /** + * Callbacks for the asynchronous task. + */ + public interface Callbacks<P> { + void onComplete(Bitmap result); + + void onCancel(); + + Bitmap onExecute(P param); + } +} diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index 9c55623d1..30659e677 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -52,9 +52,6 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { private static final String LOGTAG = "SaveCopyTask"; - private static final int DEFAULT_COMPRESS_QUALITY = 95; - private static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; - /** * Saves the bitmap in the final destination */ @@ -62,7 +59,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { OutputStream os = null; try { os = new FileOutputStream(destination); - bitmap.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, os); + bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, os); } catch (FileNotFoundException e) { Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath()); } finally { @@ -123,7 +120,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { File saveDirectory = getSaveDirectory(context, sourceUri); if ((saveDirectory == null) || !saveDirectory.canWrite()) { saveDirectory = new File(Environment.getExternalStorageDirectory(), - DEFAULT_SAVE_DIRECTORY); + ImageLoader.DEFAULT_SAVE_DIRECTORY); } // Create the directory if it doesn't exist if (!saveDirectory.exists()) saveDirectory.mkdirs(); @@ -137,19 +134,6 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return new File(saveDirectory, filename + ".JPG"); } - private Bitmap loadMutableBitmap() throws FileNotFoundException { - BitmapFactory.Options options = new BitmapFactory.Options(); - // TODO: on <3.x we need a copy of the bitmap (inMutable doesn't - // exist) - options.inMutable = true; - - InputStream is = context.getContentResolver().openInputStream(sourceUri); - Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); - int orientation = ImageLoader.getOrientation(context, sourceUri); - bitmap = ImageLoader.rotateToPortrait(bitmap, orientation); - return bitmap; - } - private static final String[] COPY_EXIF_ATTRIBUTES = new String[] { ExifInterface.TAG_APERTURE, ExifInterface.TAG_DATETIME, @@ -228,7 +212,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { ImagePreset preset = params[0]; try { - Bitmap bitmap = preset.apply(loadMutableBitmap()); + Bitmap bitmap = preset.apply(ImageLoader.loadMutableBitmap(context, sourceUri)); Object xmp = null; InputStream is = null; diff --git a/src/com/android/gallery3d/gadget/WidgetConfigure.java b/src/com/android/gallery3d/gadget/WidgetConfigure.java index 331e7d2c4..4818d261b 100644 --- a/src/com/android/gallery3d/gadget/WidgetConfigure.java +++ b/src/com/android/gallery3d/gadget/WidgetConfigure.java @@ -23,13 +23,20 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; +import android.util.Log; import android.widget.RemoteViews; import com.android.gallery3d.R; import com.android.gallery3d.app.AlbumPicker; -import com.android.gallery3d.app.CropImage; import com.android.gallery3d.app.DialogPicker; +import com.android.gallery3d.app.GalleryApp; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.data.DataManager; +import com.android.gallery3d.data.LocalAlbum; +import com.android.gallery3d.data.MediaSet; +import com.android.gallery3d.data.Path; +import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.filtershow.CropExtras; public class WidgetConfigure extends Activity { @SuppressWarnings("unused") @@ -142,14 +149,14 @@ public class WidgetConfigure extends Activity { int widgetHeight = Math.round(height * scale); mPickedItem = data.getData(); - Intent request = new Intent(CropImage.ACTION_CROP, mPickedItem) - .putExtra(CropImage.KEY_OUTPUT_X, widgetWidth) - .putExtra(CropImage.KEY_OUTPUT_Y, widgetHeight) - .putExtra(CropImage.KEY_ASPECT_X, widgetWidth) - .putExtra(CropImage.KEY_ASPECT_Y, widgetHeight) - .putExtra(CropImage.KEY_SCALE_UP_IF_NEEDED, true) - .putExtra(CropImage.KEY_SCALE, true) - .putExtra(CropImage.KEY_RETURN_DATA, true); + Intent request = new Intent(FilterShowActivity.CROP_ACTION, mPickedItem) + .putExtra(CropExtras.KEY_OUTPUT_X, widgetWidth) + .putExtra(CropExtras.KEY_OUTPUT_Y, widgetHeight) + .putExtra(CropExtras.KEY_ASPECT_X, widgetWidth) + .putExtra(CropExtras.KEY_ASPECT_Y, widgetHeight) + .putExtra(CropExtras.KEY_SCALE_UP_IF_NEEDED, true) + .putExtra(CropExtras.KEY_SCALE, true) + .putExtra(CropExtras.KEY_RETURN_DATA, true); startActivityForResult(request, REQUEST_CROP_IMAGE); } @@ -157,8 +164,21 @@ public class WidgetConfigure extends Activity { String albumPath = data.getStringExtra(AlbumPicker.KEY_ALBUM_PATH); WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this); try { + String relativePath = null; + GalleryApp galleryApp = (GalleryApp) getApplicationContext(); + DataManager manager = galleryApp.getDataManager(); + Path path = Path.fromString(albumPath); + MediaSet mediaSet = (MediaSet) manager.getMediaObject(path); + if (mediaSet instanceof LocalAlbum) { + int bucketId = Integer.parseInt(path.getSuffix()); + // If the chosen album is a local album, find relative path + // Otherwise, leave the relative path field empty + relativePath = LocalAlbum.getRelativePath(bucketId); + Log.i(TAG, "Setting widget, album path: " + albumPath + + ", relative path: " + relativePath); + } helper.setWidget(mAppWidgetId, - WidgetDatabaseHelper.TYPE_ALBUM, albumPath); + WidgetDatabaseHelper.TYPE_ALBUM, albumPath, relativePath); updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); } finally { helper.close(); @@ -173,7 +193,7 @@ public class WidgetConfigure extends Activity { } else if (widgetType == R.id.widget_type_shuffle) { WidgetDatabaseHelper helper = new WidgetDatabaseHelper(this); try { - helper.setWidget(mAppWidgetId, WidgetDatabaseHelper.TYPE_SHUFFLE, null); + helper.setWidget(mAppWidgetId, WidgetDatabaseHelper.TYPE_SHUFFLE, null, null); updateWidgetAndFinish(helper.getEntry(mAppWidgetId)); } finally { helper.close(); diff --git a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java index c411c365f..c0145843b 100644 --- a/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java +++ b/src/com/android/gallery3d/gadget/WidgetDatabaseHelper.java @@ -36,7 +36,9 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "PhotoDatabaseHelper"; private static final String DATABASE_NAME = "launcher.db"; - private static final int DATABASE_VERSION = 4; + // Increment the database version to 5. In version 5, we + // add a column in widgets table to record relative paths. + private static final int DATABASE_VERSION = 5; private static final String TABLE_WIDGETS = "widgets"; @@ -45,6 +47,7 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { private static final String FIELD_PHOTO_BLOB = "photoBlob"; private static final String FIELD_WIDGET_TYPE = "widgetType"; private static final String FIELD_ALBUM_PATH = "albumPath"; + private static final String FIELD_RELATIVE_PATH = "relativePath"; public static final int TYPE_SINGLE_PHOTO = 0; public static final int TYPE_SHUFFLE = 1; @@ -52,12 +55,13 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { private static final String[] PROJECTION = { FIELD_WIDGET_TYPE, FIELD_IMAGE_URI, FIELD_PHOTO_BLOB, FIELD_ALBUM_PATH, - FIELD_APPWIDGET_ID}; + FIELD_APPWIDGET_ID, FIELD_RELATIVE_PATH}; private static final int INDEX_WIDGET_TYPE = 0; private static final int INDEX_IMAGE_URI = 1; private static final int INDEX_PHOTO_BLOB = 2; private static final int INDEX_ALBUM_PATH = 3; private static final int INDEX_APPWIDGET_ID = 4; + private static final int INDEX_RELATIVE_PATH = 5; private static final String WHERE_APPWIDGET_ID = FIELD_APPWIDGET_ID + " = ?"; private static final String WHERE_WIDGET_TYPE = FIELD_WIDGET_TYPE + " = ?"; @@ -67,6 +71,7 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { public String imageUri; public byte imageData[]; public String albumPath; + public String relativePath; private Entry() {} @@ -78,6 +83,7 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { imageData = cursor.getBlob(INDEX_PHOTO_BLOB); } else if (type == TYPE_ALBUM) { albumPath = cursor.getString(INDEX_ALBUM_PATH); + relativePath = cursor.getString(INDEX_RELATIVE_PATH); } } @@ -97,7 +103,8 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { + FIELD_WIDGET_TYPE + " INTEGER DEFAULT 0, " + FIELD_IMAGE_URI + " TEXT, " + FIELD_ALBUM_PATH + " TEXT, " - + FIELD_PHOTO_BLOB + " BLOB)"); + + FIELD_PHOTO_BLOB + " BLOB, " + + FIELD_RELATIVE_PATH + " TEXT)"); } private void saveData(SQLiteDatabase db, int oldVersion, ArrayList<Entry> data) { @@ -157,20 +164,27 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - int version = oldVersion; - - if (version != DATABASE_VERSION) { + if (oldVersion < 4) { + // Table "photos" is renamed to "widget" in version 4 ArrayList<Entry> data = new ArrayList<Entry>(); saveData(db, oldVersion, data); Log.w(TAG, "destroying all old data."); - // Table "photos" is renamed to "widget" in version 4 db.execSQL("DROP TABLE IF EXISTS photos"); db.execSQL("DROP TABLE IF EXISTS " + TABLE_WIDGETS); onCreate(db); restoreData(db, data); } + // Add a column for relative path + if (oldVersion < DATABASE_VERSION) { + try { + db.execSQL("ALTER TABLE widgets ADD COLUMN relativePath TEXT"); + } catch (Throwable t) { + Log.e(TAG, "Failed to add the column for relative path."); + return; + } + } } /** @@ -201,12 +215,13 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { } } - public boolean setWidget(int id, int type, String albumPath) { + public boolean setWidget(int id, int type, String albumPath, String relativePath) { try { ContentValues values = new ContentValues(); values.put(FIELD_APPWIDGET_ID, id); values.put(FIELD_WIDGET_TYPE, type); values.put(FIELD_ALBUM_PATH, Utils.ensureNotNull(albumPath)); + values.put(FIELD_RELATIVE_PATH, relativePath); getWritableDatabase().replaceOrThrow(TABLE_WIDGETS, null, values); return true; } catch (Throwable e) { @@ -223,7 +238,8 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { WHERE_APPWIDGET_ID, new String[] {String.valueOf(appWidgetId)}, null, null, null); if (cursor == null || !cursor.moveToNext()) { - Log.e(TAG, "query fail: empty cursor: " + cursor); + Log.e(TAG, "query fail: empty cursor: " + cursor + " appWidgetId: " + + appWidgetId); return null; } return new Entry(appWidgetId, cursor); @@ -271,6 +287,7 @@ public class WidgetDatabaseHelper extends SQLiteOpenHelper { values.put(FIELD_ALBUM_PATH, entry.albumPath); values.put(FIELD_IMAGE_URI, entry.imageUri); values.put(FIELD_PHOTO_BLOB, entry.imageData); + values.put(FIELD_RELATIVE_PATH, entry.relativePath); getWritableDatabase().insert(TABLE_WIDGETS, null, values); } catch (Throwable e) { Log.e(TAG, "set widget fail", e); diff --git a/src/com/android/gallery3d/ui/BasicTexture.java b/src/com/android/gallery3d/glrenderer/BasicTexture.java index 99cf0571c..48227f6ef 100644 --- a/src/com/android/gallery3d/ui/BasicTexture.java +++ b/src/com/android/gallery3d/glrenderer/BasicTexture.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + +import android.util.Log; import com.android.gallery3d.common.Utils; @@ -23,7 +25,7 @@ import java.util.WeakHashMap; // BasicTexture is a Texture corresponds to a real GL texture. // The state of a BasicTexture indicates whether its data is loaded to GL memory. // If a BasicTexture is loaded into GL memory, it has a GL texture id. -abstract class BasicTexture implements Texture { +public abstract class BasicTexture implements Texture { @SuppressWarnings("unused") private static final String TAG = "BasicTexture"; @@ -36,7 +38,7 @@ abstract class BasicTexture implements Texture { // Log a warning if a texture is larger along a dimension private static final int MAX_TEXTURE_SIZE = 4096; - protected int mId; + protected int mId = -1; protected int mState; protected int mWidth = UNSPECIFIED; @@ -73,7 +75,7 @@ abstract class BasicTexture implements Texture { * Sets the content size of this texture. In OpenGL, the actual texture * size must be of power of 2, the size of the content may be smaller. */ - protected void setSize(int width, int height) { + public void setSize(int width, int height) { mWidth = width; mHeight = height; mTextureWidth = Utils.nextPowerOf2(width); @@ -165,8 +167,9 @@ abstract class BasicTexture implements Texture { private void freeResource() { GLCanvas canvas = mCanvasRef; - if (canvas != null && isLoaded()) { + if (canvas != null && mId != -1) { canvas.unloadTexture(this); + mId = -1; // Don't free it again. } mState = STATE_UNLOADED; setAssociatedCanvas(null); diff --git a/src/com/android/gallery3d/ui/BitmapTexture.java b/src/com/android/gallery3d/glrenderer/BitmapTexture.java index 607544907..100b0b3b9 100644 --- a/src/com/android/gallery3d/ui/BitmapTexture.java +++ b/src/com/android/gallery3d/glrenderer/BitmapTexture.java @@ -14,11 +14,11 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; -import com.android.gallery3d.common.Utils; +import junit.framework.Assert; // BitmapTexture is a texture whose content is specified by a fixed Bitmap. // @@ -34,7 +34,7 @@ public class BitmapTexture extends UploadedTexture { public BitmapTexture(Bitmap bitmap, boolean hasBorder) { super(hasBorder); - Utils.assertTrue(bitmap != null && !bitmap.isRecycled()); + Assert.assertTrue(bitmap != null && !bitmap.isRecycled()); mContentBitmap = bitmap; } diff --git a/src/com/android/gallery3d/ui/CanvasTexture.java b/src/com/android/gallery3d/glrenderer/CanvasTexture.java index a2e9e48ad..bff9d4baa 100644 --- a/src/com/android/gallery3d/ui/CanvasTexture.java +++ b/src/com/android/gallery3d/glrenderer/CanvasTexture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; diff --git a/src/com/android/gallery3d/ui/ColorTexture.java b/src/com/android/gallery3d/glrenderer/ColorTexture.java index 733c05653..904c78e1b 100644 --- a/src/com/android/gallery3d/ui/ColorTexture.java +++ b/src/com/android/gallery3d/glrenderer/ColorTexture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import com.android.gallery3d.common.Utils; diff --git a/src/com/android/gallery3d/ui/ExtTexture.java b/src/com/android/gallery3d/glrenderer/ExtTexture.java index eac504fe5..b1fbd195c 100644 --- a/src/com/android/gallery3d/ui/ExtTexture.java +++ b/src/com/android/gallery3d/glrenderer/ExtTexture.java @@ -14,51 +14,22 @@ * limitations under the License. */ -package com.android.gallery3d.ui; - -import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; +package com.android.gallery3d.glrenderer; // ExtTexture is a texture whose content comes from a external texture. // Before drawing, setSize() should be called. public class ExtTexture extends BasicTexture { - private static int[] sTextureId = new int[1]; - private static float[] sCropRect = new float[4]; private int mTarget; public ExtTexture(int target) { - GLId.glGenTextures(1, sTextureId, 0); - mId = sTextureId[0]; + GLId glId = GLCanvas.getGLId(); + mId = glId.generateTexture(); mTarget = target; } private void uploadToCanvas(GLCanvas canvas) { - GL11 gl = canvas.getGLInstance(); - - int width = getWidth(); - int height = getHeight(); - // Define a vertically flipped crop rectangle for OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = 0; - sCropRect[1] = height; - sCropRect[2] = width; - sCropRect[3] = -height; - - // Set texture parameters. - gl.glBindTexture(mTarget, mId); - gl.glTexParameterfv(mTarget, - GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(mTarget, - GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(mTarget, - GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(mTarget, - GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(mTarget, - GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - + canvas.setTextureParameters(this); setAssociatedCanvas(canvas); mState = STATE_LOADED; } diff --git a/src/com/android/gallery3d/ui/FadeInTexture.java b/src/com/android/gallery3d/glrenderer/FadeInTexture.java index c6a9811f6..838d465f5 100644 --- a/src/com/android/gallery3d/ui/FadeInTexture.java +++ b/src/com/android/gallery3d/glrenderer/FadeInTexture.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + // FadeInTexture is a texture which begins with a color, then gradually animates // into a given texture. diff --git a/src/com/android/gallery3d/ui/FadeOutTexture.java b/src/com/android/gallery3d/glrenderer/FadeOutTexture.java index 7050e535e..b05f3b631 100644 --- a/src/com/android/gallery3d/ui/FadeOutTexture.java +++ b/src/com/android/gallery3d/glrenderer/FadeOutTexture.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + // FadeOutTexture is a texture which begins with a given texture, then gradually animates // into fading out totally. diff --git a/src/com/android/gallery3d/ui/FadeTexture.java b/src/com/android/gallery3d/glrenderer/FadeTexture.java index 5236d3639..002c90f5c 100644 --- a/src/com/android/gallery3d/ui/FadeTexture.java +++ b/src/com/android/gallery3d/glrenderer/FadeTexture.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.ui.AnimationTime; // FadeTexture is a texture which fades the given texture along the time. public abstract class FadeTexture implements Texture { diff --git a/src/com/android/gallery3d/glrenderer/GLCanvas.java b/src/com/android/gallery3d/glrenderer/GLCanvas.java new file mode 100644 index 000000000..5e319f894 --- /dev/null +++ b/src/com/android/gallery3d/glrenderer/GLCanvas.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.glrenderer; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.RectF; + +import com.android.gallery3d.common.ApiHelper; + +import javax.microedition.khronos.opengles.GL11; + +// +// GLCanvas gives a convenient interface to draw using OpenGL. +// +// When a rectangle is specified in this interface, it means the region +// [x, x+width) * [y, y+height) +// +public abstract class GLCanvas { + public enum Blending { + Additive, Mix, + } + + private static GLCanvas sInstance = instantiateCanvas(); + private static GLId sGLId = instantiateGLId(); + + public static GLId getGLId() { + return sGLId; + } + + public static GLCanvas getInstance() { + return sInstance; + } + + private static GLId instantiateGLId() { + return ApiHelper.HAS_GLES20_REQUIRED ? (GLES20Canvas) sInstance : new GLIdImpl(); + } + + private static GLCanvas instantiateCanvas() { + return ApiHelper.HAS_GLES20_REQUIRED ? new GLES20Canvas() : new GLES11Canvas(); + } + + public static int getEGLContextClientVersion() { + return ApiHelper.HAS_GLES20_REQUIRED ? 2 : 1; + } + + public abstract void initialize(GL11 gl); + + // Tells GLCanvas the size of the underlying GL surface. This should be + // called before first drawing and when the size of GL surface is changed. + // This is called by GLRoot and should not be called by the clients + // who only want to draw on the GLCanvas. Both width and height must be + // nonnegative. + public abstract void setSize(int width, int height); + + // Clear the drawing buffers. This should only be used by GLRoot. + public abstract void clearBuffer(); + + public abstract void clearBuffer(float[] argb); + + // Sets and gets the current alpha, alpha must be in [0, 1]. + public abstract void setAlpha(float alpha); + + public abstract float getAlpha(); + + // (current alpha) = (current alpha) * alpha + public abstract void multiplyAlpha(float alpha); + + // Change the current transform matrix. + public abstract void translate(float x, float y, float z); + + public abstract void translate(float x, float y); + + public abstract void scale(float sx, float sy, float sz); + + public abstract void rotate(float angle, float x, float y, float z); + + public abstract void multiplyMatrix(float[] mMatrix, int offset); + + // Pushes the configuration state (matrix, and alpha) onto + // a private stack. + public abstract void save(); + + // Same as save(), but only save those specified in saveFlags. + public abstract void save(int saveFlags); + + public static final int SAVE_FLAG_ALL = 0xFFFFFFFF; + public static final int SAVE_FLAG_ALPHA = 0x01; + public static final int SAVE_FLAG_MATRIX = 0x02; + public static final int SAVE_FLAG_BLEND = 0x04; + + // Pops from the top of the stack as current configuration state (matrix, + // alpha, and clip). This call balances a previous call to save(), and is + // used to remove all modifications to the configuration state since the + // last save call. + public abstract void restore(); + + // Draws a line using the specified paint from (x1, y1) to (x2, y2). + // (Both end points are included). + public abstract void drawLine(float x1, float y1, float x2, float y2, GLPaint paint); + + // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2). + // (Both end points are included). + public abstract void drawRect(float x1, float y1, float x2, float y2, GLPaint paint); + + // Fills the specified rectangle with the specified color. + public abstract void fillRect(float x, float y, float width, float height, int color); + + // Draws a texture to the specified rectangle. + public abstract void drawTexture( + BasicTexture texture, int x, int y, int width, int height); + + public abstract void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, + int uvBuffer, int indexBuffer, int indexCount); + + // Draws the source rectangle part of the texture to the target rectangle. + public abstract void drawTexture(BasicTexture texture, RectF source, RectF target); + + // Draw a texture with a specified texture transform. + public abstract void drawTexture(BasicTexture texture, float[] mTextureTransform, + int x, int y, int w, int h); + + // Draw two textures to the specified rectangle. The actual texture used is + // from * (1 - ratio) + to * ratio + // The two textures must have the same size. + public abstract void drawMixed(BasicTexture from, int toColor, + float ratio, int x, int y, int w, int h); + + // Draw a region of a texture and a specified color to the specified + // rectangle. The actual color used is from * (1 - ratio) + to * ratio. + // The region of the texture is defined by parameter "src". The target + // rectangle is specified by parameter "target". + public abstract void drawMixed(BasicTexture from, int toColor, + float ratio, RectF src, RectF target); + + // Unloads the specified texture from the canvas. The resource allocated + // to draw the texture will be released. The specified texture will return + // to the unloaded state. This function should be called only from + // BasicTexture or its descendant + public abstract boolean unloadTexture(BasicTexture texture); + + // Delete the specified buffer object, similar to unloadTexture. + public abstract void deleteBuffer(int bufferId); + + // Delete the textures and buffers in GL side. This function should only be + // called in the GL thread. + public abstract void deleteRecycledResources(); + + // Dump statistics information and clear the counters. For debug only. + public abstract void dumpStatisticsAndClear(); + + public abstract void beginRenderTarget(RawTexture texture); + + public abstract void endRenderTarget(); + + /** + * Sets texture parameters to use GL_CLAMP_TO_EDGE for both + * GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T. Sets texture parameters to be + * GL_LINEAR for GL_TEXTURE_MIN_FILTER and GL_TEXTURE_MAG_FILTER. + * bindTexture() must be called prior to this. + * + * @param texture The texture to set parameters on. + */ + public abstract void setTextureParameters(BasicTexture texture); + + /** + * Initializes the texture to a size by calling texImage2D on it. + * + * @param texture The texture to initialize the size. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + */ + public abstract void initializeTextureSize(BasicTexture texture, int format, int type); + + /** + * Initializes the texture to a size by calling texImage2D on it. + * + * @param texture The texture to initialize the size. + * @param bitmap The bitmap to initialize the bitmap with. + */ + public abstract void initializeTexture(BasicTexture texture, Bitmap bitmap); + + /** + * Calls glTexSubImage2D to upload a bitmap to the texture. + * + * @param texture The target texture to write to. + * @param xOffset Specifies a texel offset in the x direction within the + * texture array. + * @param yOffset Specifies a texel offset in the y direction within the + * texture array. + * @param format The texture format (e.g. GL_RGBA) + * @param type The texture type (e.g. GL_UNSIGNED_BYTE) + */ + public abstract void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, + Bitmap bitmap, + int format, int type); + + /** + * Generates buffers and uploads the buffer data. + * + * @param buffer The buffer to upload + * @return The buffer ID that was generated. + */ + public abstract int uploadBuffer(java.nio.FloatBuffer buffer); + + /** + * Generates buffers and uploads the element array buffer data. + * + * @param buffer The buffer to upload + * @return The buffer ID that was generated. + */ + public abstract int uploadBuffer(java.nio.ByteBuffer buffer); + + /** + * Sets the blending algorithm if a texture is not opaque. + * + * @param blending Either mixing (overlay) or adding a texture. + */ + public abstract void setBlending(Blending blending); + + /** + * Enable stencil test + */ + public abstract void enableStencil(); + + /** + * Disable stencil. + */ + public abstract void disableStencil(); + + /** + * Clears the stencil so that a new stencil can be generated. + */ + public abstract void clearStencilBuffer(); + + /** + * Start/stop updating the stencil buffer. + * + * @param update True if the stencil should be updated, false otherwise. + */ + public abstract void updateStencil(boolean update); + + /** + * Changes how the stencil buffer is used. + * + * @param onlyOutside If true, only the area outside the stencil can be + * changed. If false, the area inside the stencil can be drawn to + * as well. + */ + public abstract void drawOnlyOutsideStencil(boolean onlyOutside); + + /** + * After LightCycle makes GL calls, this method is called to restore the GL + * configuration to the one expected by GLCanvas. + */ + public abstract void recoverFromLightCycle(); + + /** + * Gets the bounds given by x, y, width, and height as well as the internal + * matrix state. There is no special handling for non-90-degree rotations. + * It only considers the lower-left and upper-right corners as the bounds. + * + * @param bounds The output bounds to write to. + * @param x The left side of the input rectangle. + * @param y The bottom of the input rectangle. + * @param width The width of the input rectangle. + * @param height The height of the input rectangle. + */ + public abstract void getBounds(Rect bounds, int x, int y, int width, int height); +} diff --git a/src/com/android/gallery3d/ui/GLCanvasImpl.java b/src/com/android/gallery3d/glrenderer/GLES11Canvas.java index 45903b3cd..e4f7d1ac4 100644 --- a/src/com/android/gallery3d/ui/GLCanvasImpl.java +++ b/src/com/android/gallery3d/glrenderer/GLES11Canvas.java @@ -14,15 +14,22 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; +import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.RectF; import android.opengl.GLU; +import android.opengl.GLUtils; import android.opengl.Matrix; +import android.util.Log; import com.android.gallery3d.common.Utils; import com.android.gallery3d.util.IntArray; +import junit.framework.Assert; + +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -33,7 +40,7 @@ import javax.microedition.khronos.opengles.GL11; import javax.microedition.khronos.opengles.GL11Ext; import javax.microedition.khronos.opengles.GL11ExtensionPack; -public class GLCanvasImpl implements GLCanvas { +public class GLES11Canvas extends GLCanvas { @SuppressWarnings("unused") private static final String TAG = "GLCanvasImp"; @@ -47,7 +54,7 @@ public class GLCanvasImpl implements GLCanvas { 0, 0, 1, 1, // used for drawing a line 0, 0, 0, 1, 1, 1, 1, 0}; // used for drawing the outline of a rectangle - private final GL11 mGL; + private GL11 mGL; private final float mMatrixValues[] = new float[16]; private final float mTextureMatrixValues[] = new float[16]; @@ -60,7 +67,7 @@ public class GLCanvasImpl implements GLCanvas { private int mBoxCoords; - private final GLState mGLState; + private GLState mGLState; private final ArrayList<RawTexture> mTargetStack = new ArrayList<RawTexture>(); private float mAlpha; @@ -76,8 +83,10 @@ public class GLCanvasImpl implements GLCanvas { private int mScreenHeight; private boolean mBlendEnabled = true; private int mFrameBuffer[] = new int[1]; + private static float[] sCropRect = new float[4]; private RawTexture mTargetTexture; + private Blending mBlending = Blending.Mix; // Drawing statistics int mCountDrawLine; @@ -86,15 +95,12 @@ public class GLCanvasImpl implements GLCanvas { int mCountTextureRect; int mCountTextureOES; - GLCanvasImpl(GL11 gl) { - mGL = gl; - mGLState = new GLState(gl); - initialize(); + GLES11Canvas() { } @Override public void setSize(int width, int height) { - Utils.assertTrue(width >= 0 && height >= 0); + Assert.assertTrue(width >= 0 && height >= 0); if (mTargetTexture == null) { mScreenWidth = width; @@ -122,7 +128,7 @@ public class GLCanvasImpl implements GLCanvas { @Override public void setAlpha(float alpha) { - Utils.assertTrue(alpha >= 0 && alpha <= 1); + Assert.assertTrue(alpha >= 0 && alpha <= 1); mAlpha = alpha; } @@ -133,7 +139,7 @@ public class GLCanvasImpl implements GLCanvas { @Override public void multiplyAlpha(float alpha) { - Utils.assertTrue(alpha >= 0 && alpha <= 1); + Assert.assertTrue(alpha >= 0 && alpha <= 1); mAlpha *= alpha; } @@ -141,16 +147,18 @@ public class GLCanvasImpl implements GLCanvas { return ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()); } - private void initialize() { - GL11 gl = mGL; - + @Override + public void initialize(GL11 gl) { + mGL = gl; + mGLState = new GLState(gl); // First create an nio buffer, then create a VBO from it. int size = BOX_COORDINATES.length * Float.SIZE / Byte.SIZE; FloatBuffer xyBuffer = allocateDirectNativeOrderBuffer(size).asFloatBuffer(); xyBuffer.put(BOX_COORDINATES, 0, BOX_COORDINATES.length).position(0); int[] name = new int[1]; - GLId.glGenBuffers(1, name, 0); + GLId glId = getGLId(); + glId.glGenBuffers(1, name, 0); mBoxCoords = name[0]; gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBoxCoords); @@ -684,11 +692,6 @@ public class GLCanvasImpl implements GLCanvas { } @Override - public GL11 getGLInstance() { - return mGL; - } - - @Override public void clearBuffer(float[] argb) { if(argb != null && argb.length == 4) { mGL.glClearColor(argb[1], argb[2], argb[3], argb[0]); @@ -748,14 +751,15 @@ public class GLCanvasImpl implements GLCanvas { public void deleteRecycledResources() { synchronized (mUnboundTextures) { IntArray ids = mUnboundTextures; + GLId glId = getGLId(); if (ids.size() > 0) { - GLId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); + glId.glDeleteTextures(mGL, ids.size(), ids.getInternalArray(), 0); ids.clear(); } ids = mDeleteBuffers; if (ids.size() > 0) { - GLId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); + glId.glDeleteBuffers(mGL, ids.size(), ids.getInternalArray(), 0); ids.clear(); } } @@ -776,6 +780,11 @@ public class GLCanvasImpl implements GLCanvas { config.mAlpha = -1; } + if ((saveFlags & SAVE_FLAG_BLEND) != 0) { + config.mBlending = mBlending; + } else { + config.mBlending = null; + } if ((saveFlags & SAVE_FLAG_MATRIX) != 0) { System.arraycopy(mMatrixValues, 0, config.mMatrix, 0, 16); @@ -811,13 +820,17 @@ public class GLCanvasImpl implements GLCanvas { private static class ConfigState { float mAlpha; float mMatrix[] = new float[16]; + Blending mBlending; ConfigState mNextFree; - public void restore(GLCanvasImpl canvas) { + public void restore(GLES11Canvas canvas) { if (mAlpha >= 0) canvas.setAlpha(mAlpha); if (mMatrix[0] != Float.NEGATIVE_INFINITY) { System.arraycopy(mMatrix, 0, canvas.mMatrixValues, 0, 16); } + if (mBlending != null) { + canvas.setBlending(mBlending); + } } } @@ -847,7 +860,8 @@ public class GLCanvasImpl implements GLCanvas { GL11ExtensionPack gl11ep = (GL11ExtensionPack) mGL; if (mTargetTexture == null && texture != null) { - GLId.glGenBuffers(1, mFrameBuffer, 0); + GLId glId = getGLId(); + glId.glGenBuffers(1, mFrameBuffer, 0); gl11ep.glBindFramebufferOES( GL11ExtensionPack.GL_FRAMEBUFFER_OES, mFrameBuffer[0]); } @@ -917,4 +931,120 @@ public class GLCanvasImpl implements GLCanvas { throw new RuntimeException(msg + ":" + Integer.toHexString(status)); } } + + @Override + public void setTextureParameters(BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + // Define a vertically flipped crop rectangle for OES_draw_texture. + // The four values in sCropRect are: left, bottom, width, and + // height. Negative value of width or height means flip. + sCropRect[0] = 0; + sCropRect[1] = height; + sCropRect[2] = width; + sCropRect[3] = -height; + + // Set texture parameters. + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + mGL.glTexParameterfv(target, GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); + mGL.glTexParameterf(target, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + mGL.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + mGL.glBindTexture(target, texture.getId()); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, Float.SIZE / Byte.SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buf, int elementSize) { + int[] bufferIds = new int[1]; + GLId glId = getGLId(); + glId.glGenBuffers(bufferIds.length, bufferIds, 0); + int bufferId = bufferIds[0]; + mGL.glBindBuffer(GL11.GL_ARRAY_BUFFER, bufferId); + mGL.glBufferData(GL11.GL_ARRAY_BUFFER, buf.capacity() * elementSize, buf, + GL11.GL_STATIC_DRAW); + return bufferId; + } + + @Override + public void setBlending(Blending blending) { + if (mBlending == blending) { + return; + } + Assert.assertTrue(blending == Blending.Additive || blending == Blending.Mix); + mBlending = blending; + int srcFunc = GL11.GL_ONE; + int dstFunc = (blending == Blending.Additive) ? GL11.GL_ONE : GL11.GL_ONE_MINUS_SRC_ALPHA; + mGL.glBlendFunc(srcFunc, dstFunc); + } + + @Override + public void enableStencil() { + mGL.glEnable(GL11.GL_STENCIL_TEST); + } + + @Override + public void disableStencil() { + mGL.glDisable(GL11.GL_STENCIL_TEST); + } + + @Override + public void clearStencilBuffer() { + mGL.glClear(GL11.GL_STENCIL_BUFFER_BIT); + } + + @Override + public void updateStencil(boolean update) { + int passOp = update ? GL11.GL_REPLACE : GL11.GL_KEEP; + mGL.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, passOp); + } + + @Override + public void drawOnlyOutsideStencil(boolean onlyOutside) { + int func = onlyOutside ? GL11.GL_NOTEQUAL : GL11.GL_ALWAYS; + mGL.glStencilFunc(func, 1, 1); + } + + @Override + public void recoverFromLightCycle() { + // This is only required for GLES20 + } + + @Override + public void getBounds(Rect bounds, int x, int y, int width, int height) { + // This is only required for GLES20 + } } diff --git a/src/com/android/gallery3d/glrenderer/GLES20Canvas.java b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java new file mode 100644 index 000000000..c1f816463 --- /dev/null +++ b/src/com/android/gallery3d/glrenderer/GLES20Canvas.java @@ -0,0 +1,1097 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.gallery3d.glrenderer; + +import android.graphics.Bitmap; +import android.graphics.Rect; +import android.graphics.RectF; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.opengl.Matrix; +import android.util.Log; + +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.util.IntArray; + +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Arrays; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +public class GLES20Canvas extends GLCanvas implements GLId { + // ************** Constants ********************** + private static final String TAG = GLES20Canvas.class.getSimpleName(); + private static final int FLOAT_SIZE = Float.SIZE / Byte.SIZE; + private static final float OPAQUE_ALPHA = 0.95f; + + private static final int COORDS_PER_VERTEX = 2; + private static final int VERTEX_STRIDE = COORDS_PER_VERTEX * FLOAT_SIZE; + + private static final int COUNT_FILL_VERTEX = 4; + private static final int COUNT_LINE_VERTEX = 2; + private static final int COUNT_RECT_VERTEX = 4; + private static final int OFFSET_FILL_RECT = 0; + private static final int OFFSET_DRAW_LINE = OFFSET_FILL_RECT + COUNT_FILL_VERTEX; + private static final int OFFSET_DRAW_RECT = OFFSET_DRAW_LINE + COUNT_LINE_VERTEX; + + private static final float[] BOX_COORDINATES = { + 0, 0, // Fill rectangle + 1, 0, + 0, 1, + 1, 1, + 0, 0, // Draw line + 1, 1, + 0, 0, // Draw rectangle outline + 0, 1, + 1, 1, + 1, 0, + }; + + private static final float[] BOUNDS_COORDINATES = { + 0, 0, 0, 1, + 1, 1, 0, 1, + }; + + private static final String POSITION_ATTRIBUTE = "aPosition"; + private static final String COLOR_UNIFORM = "uColor"; + private static final String MATRIX_UNIFORM = "uMatrix"; + private static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix"; + private static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler"; + private static final String ALPHA_UNIFORM = "uAlpha"; + private static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate"; + + private static final String DRAW_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + "}\n"; + + private static final String DRAW_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "uniform vec4 " + COLOR_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = " + COLOR_UNIFORM + ";\n" + + "}\n"; + + private static final String TEXTURE_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "uniform mat4 " + TEXTURE_MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = (" + TEXTURE_MATRIX_UNIFORM + " * pos).xy;\n" + + "}\n"; + + private static final String MESH_VERTEX_SHADER = "" + + "uniform mat4 " + MATRIX_UNIFORM + ";\n" + + "attribute vec2 " + POSITION_ATTRIBUTE + ";\n" + + "attribute vec2 " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " vec4 pos = vec4(" + POSITION_ATTRIBUTE + ", 0.0, 1.0);\n" + + " gl_Position = " + MATRIX_UNIFORM + " * pos;\n" + + " vTextureCoord = " + TEXTURE_COORD_ATTRIBUTE + ";\n" + + "}\n"; + + private static final String TEXTURE_FRAGMENT_SHADER = "" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform sampler2D " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor.a *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + + private static final String OES_TEXTURE_FRAGMENT_SHADER = "" + + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform float " + ALPHA_UNIFORM + ";\n" + + "uniform samplerExternalOES " + TEXTURE_SAMPLER_UNIFORM + ";\n" + + "void main() {\n" + + " gl_FragColor = texture2D(" + TEXTURE_SAMPLER_UNIFORM + ", vTextureCoord);\n" + + " gl_FragColor.a *= " + ALPHA_UNIFORM + ";\n" + + "}\n"; + + private static final int INITIAL_RESTORE_STATE_SIZE = 8; + private static final int MATRIX_SIZE = 16; + + // Keep track of restore state + private float[] mMatrices = new float[INITIAL_RESTORE_STATE_SIZE * MATRIX_SIZE]; + private float[] mAlphas = new float[INITIAL_RESTORE_STATE_SIZE]; + private IntArray mSaveFlags = new IntArray(); + private ArrayList<Blending> mBlendings = new ArrayList<Blending>(); + + private int mCurrentAlphaIndex = 0; + private int mCurrentMatrixIndex = 0; + + // Viewport size + private int mWidth; + private int mHeight; + + // Projection matrix + private float[] mProjectionMatrix = new float[MATRIX_SIZE]; + + // Screen size for when we aren't bound to a texture + private int mScreenWidth; + private int mScreenHeight; + + // GL programs + private int mDrawProgram; + private int mTextureProgram; + private int mOesTextureProgram; + private int mMeshProgram; + + // GL buffer containing BOX_COORDINATES + private int mBoxCoordinates; + + // Handle indices -- common + private static final int INDEX_POSITION = 0; + private static final int INDEX_MATRIX = 1; + + // Handle indices -- draw + private static final int INDEX_COLOR = 2; + + // Handle indices -- texture + private static final int INDEX_TEXTURE_MATRIX = 2; + private static final int INDEX_TEXTURE_SAMPLER = 3; + private static final int INDEX_ALPHA = 4; + + // Handle indices -- mesh + private static final int INDEX_TEXTURE_COORD = 2; + + private abstract static class ShaderParameter { + public int handle; + protected final String mName; + + public ShaderParameter(String name) { + mName = name; + } + + public abstract void loadHandle(int program); + } + + private static class UniformShaderParameter extends ShaderParameter { + public UniformShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetUniformLocation(program, mName); + checkError(); + } + } + + private static class AttributeShaderParameter extends ShaderParameter { + public AttributeShaderParameter(String name) { + super(name); + } + + @Override + public void loadHandle(int program) { + handle = GLES20.glGetAttribLocation(program, mName); + checkError(); + } + } + + ShaderParameter[] mDrawParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(COLOR_UNIFORM), // INDEX_COLOR + }; + ShaderParameter[] mTextureParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + ShaderParameter[] mOesTextureParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new UniformShaderParameter(TEXTURE_MATRIX_UNIFORM), // INDEX_TEXTURE_MATRIX + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + ShaderParameter[] mMeshParameters = { + new AttributeShaderParameter(POSITION_ATTRIBUTE), // INDEX_POSITION + new UniformShaderParameter(MATRIX_UNIFORM), // INDEX_MATRIX + new AttributeShaderParameter(TEXTURE_COORD_ATTRIBUTE), // INDEX_TEXTURE_COORD + new UniformShaderParameter(TEXTURE_SAMPLER_UNIFORM), // INDEX_TEXTURE_SAMPLER + new UniformShaderParameter(ALPHA_UNIFORM), // INDEX_ALPHA + }; + + private final IntArray mUnboundTextures = new IntArray(); + private final IntArray mDeleteBuffers = new IntArray(); + + // Keep track of statistics for debugging + private int mCountDrawMesh = 0; + private int mCountTextureRect = 0; + private int mCountFillRect = 0; + private int mCountDrawLine = 0; + + // Buffer for framebuffer IDs -- we keep track so we can switch the attached + // texture. + private int[] mFrameBuffer = new int[1]; + + // Bound textures. + private ArrayList<RawTexture> mTargetTextures = new ArrayList<RawTexture>(); + + // Temporary variables used within calculations + private final float[] mTempMatrix = new float[32]; + private final float[] mTempColor = new float[4]; + private final RectF mTempSourceRect = new RectF(); + private final RectF mTempTargetRect = new RectF(); + private final float[] mTempTextureMatrix = new float[MATRIX_SIZE]; + private final int[] mTempIntArray = new int[1]; + + public GLES20Canvas() { + Matrix.setIdentityM(mTempTextureMatrix, 0); + Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); + mAlphas[mCurrentAlphaIndex] = 1f; + mTargetTextures.add(null); + } + + @Override + public void initialize(GL11 gl) { + FloatBuffer boxBuffer = createBuffer(BOX_COORDINATES); + mBoxCoordinates = uploadBuffer(boxBuffer); + + int drawVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DRAW_VERTEX_SHADER); + int textureVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, TEXTURE_VERTEX_SHADER); + int meshVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, MESH_VERTEX_SHADER); + int drawFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DRAW_FRAGMENT_SHADER); + int textureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, TEXTURE_FRAGMENT_SHADER); + int oesTextureFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, + OES_TEXTURE_FRAGMENT_SHADER); + + mDrawProgram = assembleProgram(drawVertexShader, drawFragmentShader, mDrawParameters); + mTextureProgram = assembleProgram(textureVertexShader, textureFragmentShader, + mTextureParameters); + mOesTextureProgram = assembleProgram(textureVertexShader, oesTextureFragmentShader, + mOesTextureParameters); + mMeshProgram = assembleProgram(meshVertexShader, textureFragmentShader, mMeshParameters); + + mBlendings.clear(); + mBlendings.add(null); + setBlending(Blending.Mix); + } + + private static FloatBuffer createBuffer(float[] values) { + // First create an nio buffer, then create a VBO from it. + int size = values.length * FLOAT_SIZE; + FloatBuffer buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + buffer.put(values, 0, values.length).position(0); + return buffer; + } + + private int assembleProgram(int vertexShader, int fragmentShader, ShaderParameter[] params) { + int program = GLES20.glCreateProgram(); + checkError(); + if (program == 0) { + throw new RuntimeException("Cannot create GL program: " + GLES20.glGetError()); + } + GLES20.glAttachShader(program, vertexShader); + checkError(); + GLES20.glAttachShader(program, fragmentShader); + checkError(); + GLES20.glLinkProgram(program); + checkError(); + int[] mLinkStatus = mTempIntArray; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mLinkStatus, 0); + if (mLinkStatus[0] != GLES20.GL_TRUE) { + Log.e(TAG, "Could not link program: "); + Log.e(TAG, GLES20.glGetProgramInfoLog(program)); + GLES20.glDeleteProgram(program); + program = 0; + } + for (int i = 0; i < params.length; i++) { + params[i].loadHandle(program); + } + return program; + } + + private static int loadShader(int type, String shaderCode) { + // create a vertex shader type (GLES20.GL_VERTEX_SHADER) + // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER) + int shader = GLES20.glCreateShader(type); + + // add the source code to the shader and compile it + GLES20.glShaderSource(shader, shaderCode); + checkError(); + GLES20.glCompileShader(shader); + checkError(); + + return shader; + } + + @Override + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + GLES20.glViewport(0, 0, mWidth, mHeight); + checkError(); + Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); + Matrix.orthoM(mProjectionMatrix, 0, 0, width, 0, height, -1, 1); + if (getTargetTexture() == null) { + mScreenWidth = width; + mScreenHeight = height; + Matrix.translateM(mMatrices, mCurrentMatrixIndex, 0, height, 0); + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, 1, -1, 1); + } + } + + @Override + public void clearBuffer() { + GLES20.glClearColor(0f, 0f, 0f, 1f); + checkError(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + checkError(); + } + + @Override + public void clearBuffer(float[] argb) { + GLES20.glClearColor(argb[1], argb[2], argb[3], argb[0]); + checkError(); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + checkError(); + } + + @Override + public float getAlpha() { + return mAlphas[mCurrentAlphaIndex]; + } + + @Override + public void setAlpha(float alpha) { + mAlphas[mCurrentAlphaIndex] = alpha; + } + + @Override + public void multiplyAlpha(float alpha) { + setAlpha(getAlpha() * alpha); + } + + @Override + public void translate(float x, float y, float z) { + Matrix.translateM(mMatrices, mCurrentMatrixIndex, x, y, z); + } + + // This is a faster version of translate(x, y, z) because + // (1) we knows z = 0, (2) we inline the Matrix.translateM call, + // (3) we unroll the loop + @Override + public void translate(float x, float y) { + int index = mCurrentMatrixIndex; + float[] m = mMatrices; + m[index + 12] += m[index + 0] * x + m[index + 4] * y; + m[index + 13] += m[index + 1] * x + m[index + 5] * y; + m[index + 14] += m[index + 2] * x + m[index + 6] * y; + m[index + 15] += m[index + 3] * x + m[index + 7] * y; + } + + @Override + public void scale(float sx, float sy, float sz) { + Matrix.scaleM(mMatrices, mCurrentMatrixIndex, sx, sy, sz); + } + + @Override + public void rotate(float angle, float x, float y, float z) { + if (angle == 0f) { + return; + } + float[] temp = mTempMatrix; + Matrix.setRotateM(temp, 0, angle, x, y, z); + float[] matrix = mMatrices; + int index = mCurrentMatrixIndex; + Matrix.multiplyMM(temp, MATRIX_SIZE, matrix, index, temp, 0); + System.arraycopy(temp, MATRIX_SIZE, matrix, index, MATRIX_SIZE); + } + + @Override + public void multiplyMatrix(float[] matrix, int offset) { + float[] temp = mTempMatrix; + float[] currentMatrix = mMatrices; + int index = mCurrentMatrixIndex; + Matrix.multiplyMM(temp, 0, currentMatrix, index, matrix, offset); + System.arraycopy(temp, 0, currentMatrix, index, 16); + } + + @Override + public void save() { + save(SAVE_FLAG_ALL); + } + + @Override + public void save(int saveFlags) { + boolean saveAlpha = (saveFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; + if (saveAlpha) { + float currentAlpha = getAlpha(); + mCurrentAlphaIndex++; + if (mAlphas.length <= mCurrentAlphaIndex) { + mAlphas = Arrays.copyOf(mAlphas, mAlphas.length * 2); + } + mAlphas[mCurrentAlphaIndex] = currentAlpha; + } + boolean saveMatrix = (saveFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; + if (saveMatrix) { + int currentIndex = mCurrentMatrixIndex; + mCurrentMatrixIndex += MATRIX_SIZE; + if (mMatrices.length <= mCurrentMatrixIndex) { + mMatrices = Arrays.copyOf(mMatrices, mMatrices.length * 2); + } + System.arraycopy(mMatrices, currentIndex, mMatrices, mCurrentMatrixIndex, MATRIX_SIZE); + } + boolean saveBlending = (saveFlags & SAVE_FLAG_BLEND) == SAVE_FLAG_BLEND; + if (saveBlending) { + mBlendings.add(mBlendings.get(mBlendings.size() - 1)); + } + mSaveFlags.add(saveFlags); + } + + @Override + public void restore() { + int restoreFlags = mSaveFlags.removeLast(); + boolean restoreAlpha = (restoreFlags & SAVE_FLAG_ALPHA) == SAVE_FLAG_ALPHA; + if (restoreAlpha) { + mCurrentAlphaIndex--; + } + boolean restoreMatrix = (restoreFlags & SAVE_FLAG_MATRIX) == SAVE_FLAG_MATRIX; + if (restoreMatrix) { + mCurrentMatrixIndex -= MATRIX_SIZE; + } + boolean restoreBlending = (restoreFlags & SAVE_FLAG_BLEND) == SAVE_FLAG_BLEND; + if (restoreBlending) { + setBlending(mBlendings.get(mBlendings.size() - 2)); + mBlendings.remove(mBlendings.size() - 1); + } + } + + @Override + public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) { + draw(GLES20.GL_LINE_STRIP, OFFSET_DRAW_LINE, COUNT_LINE_VERTEX, x1, y1, x2 - x1, y2 - y1, + paint); + mCountDrawLine++; + } + + @Override + public void drawRect(float x, float y, float width, float height, GLPaint paint) { + draw(GLES20.GL_LINE_LOOP, OFFSET_DRAW_RECT, COUNT_RECT_VERTEX, x, y, width, height, paint); + mCountDrawLine++; + } + + private void draw(int type, int offset, int count, float x, float y, float width, float height, + GLPaint paint) { + draw(type, offset, count, x, y, width, height, paint.getColor(), paint.getLineWidth()); + } + + private void draw(int type, int offset, int count, float x, float y, float width, float height, + int color, float lineWidth) { + prepareDraw(offset, color, lineWidth); + draw(mDrawParameters, type, count, x, y, width, height); + } + + private void prepareDraw(int offset, int color, float lineWidth) { + GLES20.glUseProgram(mDrawProgram); + checkError(); + if (lineWidth > 0) { + GLES20.glLineWidth(lineWidth); + checkError(); + } + float[] colorArray = getColor(color); + boolean blendingEnabled = (colorArray[3] < 1f); + enableBlending(blendingEnabled); + if (blendingEnabled) { + GLES20.glBlendColor(colorArray[0], colorArray[1], colorArray[2], colorArray[3]); + checkError(); + } + + GLES20.glUniform4fv(mDrawParameters[INDEX_COLOR].handle, 1, colorArray, 0); + setPosition(mDrawParameters, offset); + checkError(); + } + + private float[] getColor(int color) { + float alpha = ((color >>> 24) & 0xFF) / 255f * getAlpha(); + float red = ((color >>> 16) & 0xFF) / 255f * alpha; + float green = ((color >>> 8) & 0xFF) / 255f * alpha; + float blue = (color & 0xFF) / 255f * alpha; + mTempColor[0] = red; + mTempColor[1] = green; + mTempColor[2] = blue; + mTempColor[3] = alpha; + return mTempColor; + } + + private void enableBlending(boolean enableBlending) { + if (enableBlending) { + GLES20.glEnable(GLES20.GL_BLEND); + checkError(); + } else { + GLES20.glDisable(GLES20.GL_BLEND); + checkError(); + } + } + + private void setPosition(ShaderParameter[] params, int offset) { + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mBoxCoordinates); + checkError(); + GLES20.glVertexAttribPointer(params[INDEX_POSITION].handle, COORDS_PER_VERTEX, + GLES20.GL_FLOAT, false, VERTEX_STRIDE, offset * VERTEX_STRIDE); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + checkError(); + } + + private void draw(ShaderParameter[] params, int type, int count, float x, float y, float width, + float height) { + setMatrix(params, x, y, width, height); + int positionHandle = params[INDEX_POSITION].handle; + GLES20.glEnableVertexAttribArray(positionHandle); + checkError(); + GLES20.glDrawArrays(type, 0, count); + checkError(); + GLES20.glDisableVertexAttribArray(positionHandle); + checkError(); + } + + private void setMatrix(ShaderParameter[] params, float x, float y, float width, float height) { + Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); + Matrix.scaleM(mTempMatrix, 0, width, height, 1f); + Matrix.multiplyMM(mTempMatrix, MATRIX_SIZE, mProjectionMatrix, 0, mTempMatrix, 0); + GLES20.glUniformMatrix4fv(params[INDEX_MATRIX].handle, 1, false, mTempMatrix, MATRIX_SIZE); + checkError(); + } + + @Override + public void fillRect(float x, float y, float width, float height, int color) { + draw(GLES20.GL_TRIANGLE_STRIP, OFFSET_FILL_RECT, COUNT_FILL_VERTEX, x, y, width, height, + color, 0f); + mCountFillRect++; + } + + @Override + public void drawTexture(BasicTexture texture, int x, int y, int width, int height) { + if (width <= 0 || height <= 0) { + return; + } + copyTextureCoordinates(texture, mTempSourceRect); + mTempTargetRect.set(x, y, x + width, y + height); + convertCoordinate(mTempSourceRect, mTempTargetRect, texture); + drawTextureRect(texture, mTempSourceRect, mTempTargetRect); + } + + private static void copyTextureCoordinates(BasicTexture texture, RectF outRect) { + int left = 0; + int top = 0; + int right = texture.getWidth(); + int bottom = texture.getHeight(); + if (texture.hasBorder()) { + left = 1; + top = 1; + right -= 1; + bottom -= 1; + } + outRect.set(left, top, right, bottom); + } + + @Override + public void drawTexture(BasicTexture texture, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) { + return; + } + mTempSourceRect.set(source); + mTempTargetRect.set(target); + + convertCoordinate(mTempSourceRect, mTempTargetRect, texture); + drawTextureRect(texture, mTempSourceRect, mTempTargetRect); + } + + @Override + public void drawTexture(BasicTexture texture, float[] textureTransform, int x, int y, int w, + int h) { + if (w <= 0 || h <= 0) { + return; + } + mTempTargetRect.set(x, y, x + w, y + h); + drawTextureRect(texture, textureTransform, mTempTargetRect); + } + + private void drawTextureRect(BasicTexture texture, RectF source, RectF target) { + setTextureMatrix(source); + drawTextureRect(texture, mTempTextureMatrix, target); + } + + private void setTextureMatrix(RectF source) { + mTempTextureMatrix[0] = source.width(); + mTempTextureMatrix[5] = source.height(); + mTempTextureMatrix[12] = source.left; + mTempTextureMatrix[13] = source.top; + } + + // This function changes the source coordinate to the texture coordinates. + // It also clips the source and target coordinates if it is beyond the + // bound of the texture. + private static void convertCoordinate(RectF source, RectF target, BasicTexture texture) { + int width = texture.getWidth(); + int height = texture.getHeight(); + int texWidth = texture.getTextureWidth(); + int texHeight = texture.getTextureHeight(); + // Convert to texture coordinates + source.left /= texWidth; + source.right /= texWidth; + source.top /= texHeight; + source.bottom /= texHeight; + + // Clip if the rendering range is beyond the bound of the texture. + float xBound = (float) width / texWidth; + if (source.right > xBound) { + target.right = target.left + target.width() * (xBound - source.left) / source.width(); + source.right = xBound; + } + float yBound = (float) height / texHeight; + if (source.bottom > yBound) { + target.bottom = target.top + target.height() * (yBound - source.top) / source.height(); + source.bottom = yBound; + } + } + + private void drawTextureRect(BasicTexture texture, float[] textureMatrix, RectF target) { + ShaderParameter[] params = prepareTexture(texture); + setPosition(params, OFFSET_FILL_RECT); + GLES20.glUniformMatrix4fv(params[INDEX_TEXTURE_MATRIX].handle, 1, false, textureMatrix, 0); + checkError(); + draw(params, GLES20.GL_TRIANGLE_STRIP, COUNT_FILL_VERTEX, target.left, target.top, + target.width(), target.height()); + mCountTextureRect++; + } + + private ShaderParameter[] prepareTexture(BasicTexture texture) { + ShaderParameter[] params; + int program; + if (texture.getTarget() == GLES20.GL_TEXTURE_2D) { + params = mTextureParameters; + program = mTextureProgram; + } else { + params = mOesTextureParameters; + program = mOesTextureProgram; + } + prepareTexture(texture, program, params); + return params; + } + + private void prepareTexture(BasicTexture texture, int program, ShaderParameter[] params) { + GLES20.glUseProgram(program); + checkError(); + enableBlending(!texture.isOpaque() || getAlpha() < OPAQUE_ALPHA); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + checkError(); + texture.onBind(this); + GLES20.glBindTexture(texture.getTarget(), texture.getId()); + checkError(); + GLES20.glUniform1i(params[INDEX_TEXTURE_SAMPLER].handle, 0); + checkError(); + GLES20.glUniform1f(params[INDEX_ALPHA].handle, getAlpha()); + checkError(); + } + + @Override + public void drawMesh(BasicTexture texture, int x, int y, int xyBuffer, int uvBuffer, + int indexBuffer, int indexCount) { + prepareTexture(texture, mMeshProgram, mMeshParameters); + + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, indexBuffer); + checkError(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, xyBuffer); + checkError(); + int positionHandle = mMeshParameters[INDEX_POSITION].handle; + GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, + VERTEX_STRIDE, 0); + checkError(); + + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, uvBuffer); + checkError(); + int texCoordHandle = mMeshParameters[INDEX_TEXTURE_COORD].handle; + GLES20.glVertexAttribPointer(texCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, + false, VERTEX_STRIDE, 0); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0); + checkError(); + + GLES20.glEnableVertexAttribArray(positionHandle); + checkError(); + GLES20.glEnableVertexAttribArray(texCoordHandle); + checkError(); + + setMatrix(mMeshParameters, x, y, 1, 1); + GLES20.glDrawElements(GLES20.GL_TRIANGLE_STRIP, indexCount, GLES20.GL_UNSIGNED_BYTE, 0); + checkError(); + + GLES20.glDisableVertexAttribArray(positionHandle); + checkError(); + GLES20.glDisableVertexAttribArray(texCoordHandle); + checkError(); + GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0); + checkError(); + mCountDrawMesh++; + } + + @Override + public void drawMixed(BasicTexture texture, int toColor, float ratio, int x, int y, int w, int h) { + copyTextureCoordinates(texture, mTempSourceRect); + mTempTargetRect.set(x, y, x + w, y + h); + drawMixed(texture, toColor, ratio, mTempSourceRect, mTempTargetRect); + } + + @Override + public void drawMixed(BasicTexture texture, int toColor, float ratio, RectF source, RectF target) { + if (target.width() <= 0 || target.height() <= 0) { + return; + } + save(SAVE_FLAG_ALPHA); + + float currentAlpha = getAlpha(); + float cappedRatio = Math.min(1f, Math.max(0f, ratio)); + + float textureAlpha = (1f - cappedRatio) * currentAlpha; + setAlpha(textureAlpha); + drawTexture(texture, source, target); + + float colorAlpha = cappedRatio * currentAlpha; + setAlpha(colorAlpha); + fillRect(target.left, target.top, target.width(), target.height(), toColor); + + restore(); + } + + @Override + public boolean unloadTexture(BasicTexture texture) { + boolean unload = texture.isLoaded(); + if (unload) { + synchronized (mUnboundTextures) { + mUnboundTextures.add(texture.getId()); + } + } + return unload; + } + + @Override + public void deleteBuffer(int bufferId) { + synchronized (mUnboundTextures) { + mDeleteBuffers.add(bufferId); + } + } + + @Override + public void deleteRecycledResources() { + synchronized (mUnboundTextures) { + IntArray ids = mUnboundTextures; + if (mUnboundTextures.size() > 0) { + glDeleteTextures(null, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + + ids = mDeleteBuffers; + if (ids.size() > 0) { + glDeleteBuffers(null, ids.size(), ids.getInternalArray(), 0); + ids.clear(); + } + } + } + + @Override + public void dumpStatisticsAndClear() { + String line = String.format("MESH:%d, TEX_RECT:%d, FILL_RECT:%d, LINE:%d", mCountDrawMesh, + mCountTextureRect, mCountFillRect, mCountDrawLine); + mCountDrawMesh = 0; + mCountTextureRect = 0; + mCountFillRect = 0; + mCountDrawLine = 0; + Log.d(TAG, line); + } + + @Override + public void endRenderTarget() { + RawTexture oldTexture = mTargetTextures.remove(mTargetTextures.size() - 1); + RawTexture texture = getTargetTexture(); + setRenderTarget(oldTexture, texture); + restore(); // restore matrix and alpha + } + + @Override + public void beginRenderTarget(RawTexture texture) { + save(); // save matrix and alpha and blending + RawTexture oldTexture = getTargetTexture(); + mTargetTextures.add(texture); + setRenderTarget(oldTexture, texture); + } + + private RawTexture getTargetTexture() { + return mTargetTextures.get(mTargetTextures.size() - 1); + } + + private void setRenderTarget(BasicTexture oldTexture, RawTexture texture) { + if (oldTexture == null && texture != null) { + GLES20.glGenFramebuffers(1, mFrameBuffer, 0); + checkError(); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFrameBuffer[0]); + checkError(); + } else if (oldTexture != null && texture == null) { + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + checkError(); + GLES20.glDeleteFramebuffers(1, mFrameBuffer, 0); + checkError(); + } + + if (texture == null) { + setSize(mScreenWidth, mScreenHeight); + } else { + setSize(texture.getWidth(), texture.getHeight()); + + if (!texture.isLoaded()) { + texture.prepare(this); + } + + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, + texture.getTarget(), texture.getId(), 0); + checkError(); + + checkFramebufferStatus(); + } + } + + private static void checkFramebufferStatus() { + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + String msg = ""; + switch (status) { + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: + msg = "GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS"; + break; + case GLES20.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + msg = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + break; + case GLES20.GL_FRAMEBUFFER_UNSUPPORTED: + msg = "GL_FRAMEBUFFER_UNSUPPORTED"; + break; + } + throw new RuntimeException(msg + ":" + Integer.toHexString(status)); + } + } + + @Override + public void setTextureParameters(BasicTexture texture) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(target, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameterf(target, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + } + + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + int width = texture.getTextureWidth(); + int height = texture.getTextureHeight(); + GLES20.glTexImage2D(target, 0, format, width, height, 0, format, type, null); + } + + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLUtils.texImage2D(target, 0, bitmap, 0); + } + + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + int target = texture.getTarget(); + GLES20.glBindTexture(target, texture.getId()); + checkError(); + GLUtils.texSubImage2D(target, 0, xOffset, yOffset, bitmap, format, type); + } + + @Override + public int uploadBuffer(FloatBuffer buf) { + return uploadBuffer(buf, FLOAT_SIZE); + } + + @Override + public int uploadBuffer(ByteBuffer buf) { + return uploadBuffer(buf, 1); + } + + private int uploadBuffer(Buffer buffer, int elementSize) { + glGenBuffers(1, mTempIntArray, 0); + checkError(); + int bufferId = mTempIntArray[0]; + GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, bufferId); + checkError(); + GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, buffer.capacity() * elementSize, buffer, + GLES20.GL_STATIC_DRAW); + checkError(); + return bufferId; + } + + @Override + public void setBlending(Blending blending) { + Blending currentBlending = mBlendings.get(mBlendings.size() - 1); + if (currentBlending == blending) { + return; // nothing to change + } + mBlendings.set(mBlendings.size() - 1, blending); + int srcFunc = GLES20.GL_ONE; + int dstFunc; + switch (blending) { + case Additive: + dstFunc = GLES20.GL_ONE; + break; + case Mix: + dstFunc = GLES20.GL_ONE_MINUS_SRC_ALPHA; + break; + default: + Utils.fail("Unknown blend: " + blending); + dstFunc = GLES20.GL_ONE_MINUS_SRC_ALPHA; + break; + } + GLES20.glBlendFunc(srcFunc, dstFunc); + checkError(); + } + + @Override + public int generateTexture() { + GLES20.glGenTextures(1, mTempIntArray, 0); + checkError(); + return mTempIntArray[0]; + } + + @Override + public void glGenBuffers(int n, int[] buffers, int offset) { + GLES20.glGenBuffers(n, buffers, offset); + checkError(); + } + + @Override + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { + GLES20.glDeleteTextures(n, textures, offset); + checkError(); + } + + @Override + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { + GLES20.glDeleteBuffers(n, buffers, offset); + checkError(); + } + + @Override + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { + GLES20.glDeleteFramebuffers(n, buffers, offset); + checkError(); + } + + @Override + public void enableStencil() { + GLES20.glEnable(GLES20.GL_STENCIL_TEST); + } + + @Override + public void disableStencil() { + GLES20.glDisable(GLES20.GL_STENCIL_TEST); + } + + @Override + public void clearStencilBuffer() { + GLES20.glClear(GLES20.GL_STENCIL_BUFFER_BIT); + } + + @Override + public void updateStencil(boolean update) { + int passOp = update ? GLES20.GL_REPLACE : GLES20.GL_KEEP; + GLES20.glStencilOp(GLES20.GL_KEEP, GLES20.GL_KEEP, passOp); + } + + @Override + public void drawOnlyOutsideStencil(boolean onlyOutside) { + int func = onlyOutside ? GLES20.GL_NOTEQUAL : GLES20.GL_ALWAYS; + GLES20.glStencilFunc(func, 1, 1); + } + + public static void checkError() { + int error = GLES20.glGetError(); + if (error != 0) { + Throwable t = new Throwable(); + Log.e(TAG, "GL error: " + error, t); + } + } + + @SuppressWarnings("unused") + private static void printMatrix(String message, float[] m, int offset) { + StringBuilder b = new StringBuilder(message); + for (int i = 0; i < MATRIX_SIZE; i++) { + b.append(' '); + if (i % 4 == 0) { + b.append('\n'); + } + b.append(m[offset + i]); + } + Log.v(TAG, b.toString()); + } + + @Override + public void recoverFromLightCycle() { + GLES20.glViewport(0, 0, mWidth, mHeight); + int blendingIndex = mBlendings.size() - 1; + Blending currentBlending = mBlendings.get(blendingIndex); + mBlendings.set(blendingIndex, null); + setBlending(currentBlending); + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + } + + @Override + public void getBounds(Rect bounds, int x, int y, int width, int height) { + Matrix.translateM(mTempMatrix, 0, mMatrices, mCurrentMatrixIndex, x, y, 0f); + Matrix.scaleM(mTempMatrix, 0, width, height, 1f); + Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE, mTempMatrix, 0, BOUNDS_COORDINATES, 0); + Matrix.multiplyMV(mTempMatrix, MATRIX_SIZE + 4, mTempMatrix, 0, BOUNDS_COORDINATES, 4); + bounds.left = Math.round(mTempMatrix[MATRIX_SIZE]); + bounds.right = Math.round(mTempMatrix[MATRIX_SIZE + 4]); + bounds.top = Math.round(mTempMatrix[MATRIX_SIZE + 1]); + bounds.bottom = Math.round(mTempMatrix[MATRIX_SIZE + 5]); + bounds.sort(); + } +} diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java b/src/com/android/gallery3d/glrenderer/GLId.java index f4a7717f9..3cec558f6 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBW.java +++ b/src/com/android/gallery3d/glrenderer/GLId.java @@ -14,24 +14,20 @@ * limitations under the License. */ -package com.android.gallery3d.filtershow.filters; +package com.android.gallery3d.glrenderer; -import android.graphics.Bitmap; +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; -public class ImageFilterBW extends ImageFilter { +// This mimics corresponding GL functions. +public interface GLId { + public int generateTexture(); - public ImageFilterBW() { - mName = "Black & White"; - } + public void glGenBuffers(int n, int[] buffers, int offset); - native protected void nativeApplyFilter(Bitmap bitmap, int w, int h); + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset); - @Override - public Bitmap apply(Bitmap bitmap, float scaleFactor, boolean highQuality) { - int w = bitmap.getWidth(); - int h = bitmap.getHeight(); - nativeApplyFilter(bitmap, w, h); - return bitmap; - } + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset); + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset); } diff --git a/src/com/android/gallery3d/glrenderer/GLIdImpl.java b/src/com/android/gallery3d/glrenderer/GLIdImpl.java new file mode 100644 index 000000000..92aa58ee4 --- /dev/null +++ b/src/com/android/gallery3d/glrenderer/GLIdImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.gallery3d.glrenderer; + +import javax.microedition.khronos.opengles.GL11; +import javax.microedition.khronos.opengles.GL11ExtensionPack; + +/** + * Open GL ES 1.1 implementation for generating and destroying texture IDs and + * buffer IDs + */ +public class GLIdImpl implements GLId { + private static int sNextId = 1; + // Mutex for sNextId + private static Object sLock = new Object(); + + @Override + public int generateTexture() { + synchronized (sLock) { + return sNextId++; + } + } + + @Override + public void glGenBuffers(int n, int[] buffers, int offset) { + synchronized (sLock) { + while (n-- > 0) { + buffers[offset + n] = sNextId++; + } + } + } + + @Override + public void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { + synchronized (sLock) { + gl.glDeleteTextures(n, textures, offset); + } + } + + @Override + public void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { + synchronized (sLock) { + gl.glDeleteBuffers(n, buffers, offset); + } + } + + @Override + public void glDeleteFramebuffers(GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { + synchronized (sLock) { + gl11ep.glDeleteFramebuffersOES(n, buffers, offset); + } + } + + +} diff --git a/src/com/android/gallery3d/ui/GLPaint.java b/src/com/android/gallery3d/glrenderer/GLPaint.java index eb75cc51e..16b220690 100644 --- a/src/com/android/gallery3d/ui/GLPaint.java +++ b/src/com/android/gallery3d/glrenderer/GLPaint.java @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.gallery3d.ui; - -import com.android.gallery3d.common.Utils; +package com.android.gallery3d.glrenderer; +import junit.framework.Assert; public class GLPaint { private float mLineWidth = 1f; @@ -32,7 +31,7 @@ public class GLPaint { } public void setLineWidth(float width) { - Utils.assertTrue(width >= 0); + Assert.assertTrue(width >= 0); mLineWidth = width; } diff --git a/src/com/android/gallery3d/ui/MultiLineTexture.java b/src/com/android/gallery3d/glrenderer/MultiLineTexture.java index b0c3c2bbf..82839f107 100644 --- a/src/com/android/gallery3d/ui/MultiLineTexture.java +++ b/src/com/android/gallery3d/glrenderer/MultiLineTexture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -22,6 +22,7 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; + // MultiLineTexture is a texture shows the content of a specified String. // // To create a MultiLineTexture, use the newInstance() method and specify diff --git a/src/com/android/gallery3d/ui/NinePatchChunk.java b/src/com/android/gallery3d/glrenderer/NinePatchChunk.java index 61bf22c33..9dc326622 100644 --- a/src/com/android/gallery3d/ui/NinePatchChunk.java +++ b/src/com/android/gallery3d/glrenderer/NinePatchChunk.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Rect; diff --git a/src/com/android/gallery3d/ui/NinePatchTexture.java b/src/com/android/gallery3d/glrenderer/NinePatchTexture.java index fa0e9cdc3..d0ddc46c3 100644 --- a/src/com/android/gallery3d/ui/NinePatchTexture.java +++ b/src/com/android/gallery3d/glrenderer/NinePatchTexture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.content.Context; import android.graphics.Bitmap; @@ -27,8 +27,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; -import javax.microedition.khronos.opengles.GL11; - // NinePatchTexture is a texture backed by a NinePatch resource. // // getPaddings() returns paddings specified in the NinePatch. @@ -199,7 +197,9 @@ class NinePatchInstance { private ByteBuffer mIndexBuffer; // Names for buffer names: xy, uv, index. - private int[] mBufferNames; + private int mXyBufferName = -1; + private int mUvBufferName; + private int mIndexBufferName; private int mIdxCount; @@ -396,24 +396,9 @@ class NinePatchInstance { } private void prepareBuffers(GLCanvas canvas) { - mBufferNames = new int[3]; - GL11 gl = canvas.getGLInstance(); - GLId.glGenBuffers(3, mBufferNames, 0); - - gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[0]); - gl.glBufferData(GL11.GL_ARRAY_BUFFER, - mXyBuffer.capacity() * (Float.SIZE / Byte.SIZE), - mXyBuffer, GL11.GL_STATIC_DRAW); - - gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, mBufferNames[1]); - gl.glBufferData(GL11.GL_ARRAY_BUFFER, - mUvBuffer.capacity() * (Float.SIZE / Byte.SIZE), - mUvBuffer, GL11.GL_STATIC_DRAW); - - gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, mBufferNames[2]); - gl.glBufferData(GL11.GL_ELEMENT_ARRAY_BUFFER, - mIndexBuffer.capacity(), - mIndexBuffer, GL11.GL_STATIC_DRAW); + mXyBufferName = canvas.uploadBuffer(mXyBuffer); + mUvBufferName = canvas.uploadBuffer(mUvBuffer); + mIndexBufferName = canvas.uploadBuffer(mIndexBuffer); // These buffers are never used again. mXyBuffer = null; @@ -422,19 +407,18 @@ class NinePatchInstance { } public void draw(GLCanvas canvas, NinePatchTexture tex, int x, int y) { - if (mBufferNames == null) { + if (mXyBufferName == -1) { prepareBuffers(canvas); } - canvas.drawMesh(tex, x, y, mBufferNames[0], mBufferNames[1], - mBufferNames[2], mIdxCount); + canvas.drawMesh(tex, x, y, mXyBufferName, mUvBufferName, mIndexBufferName, mIdxCount); } public void recycle(GLCanvas canvas) { - if (mBufferNames != null) { - canvas.deleteBuffer(mBufferNames[0]); - canvas.deleteBuffer(mBufferNames[1]); - canvas.deleteBuffer(mBufferNames[2]); - mBufferNames = null; + if (mXyBuffer == null) { + canvas.deleteBuffer(mXyBufferName); + canvas.deleteBuffer(mUvBufferName); + canvas.deleteBuffer(mIndexBufferName); + mXyBufferName = -1; } } } diff --git a/src/com/android/gallery3d/ui/RawTexture.java b/src/com/android/gallery3d/glrenderer/RawTexture.java index 4c0d9d365..73f2c499a 100644 --- a/src/com/android/gallery3d/ui/RawTexture.java +++ b/src/com/android/gallery3d/glrenderer/RawTexture.java @@ -14,17 +14,15 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + +import android.util.Log; import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; public class RawTexture extends BasicTexture { private static final String TAG = "RawTexture"; - private final static int[] sTextureId = new int[1]; - private final static float[] sCropRect = new float[4]; - private final boolean mOpaque; public RawTexture(int width, int height, boolean opaque) { @@ -38,36 +36,10 @@ public class RawTexture extends BasicTexture { } protected void prepare(GLCanvas canvas) { - GL11 gl = canvas.getGLInstance(); - - // Define a vertically flipped crop rectangle for - // OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = 0; - sCropRect[1] = mHeight; - sCropRect[2] = mWidth; - sCropRect[3] = -mHeight; - - // Upload the bitmap to a new texture. - GLId.glGenTextures(1, sTextureId, 0); - gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]); - gl.glTexParameterfv(GL11.GL_TEXTURE_2D, - GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); - - gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, - getTextureWidth(), getTextureHeight(), - 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, null); - - mId = sTextureId[0]; + GLId glId = GLCanvas.getGLId(); + mId = glId.generateTexture(); + canvas.initializeTextureSize(this, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE); + canvas.setTextureParameters(this); mState = STATE_LOADED; setAssociatedCanvas(canvas); } diff --git a/src/com/android/gallery3d/ui/ResourceTexture.java b/src/com/android/gallery3d/glrenderer/ResourceTexture.java index 1fa9d7014..eb8e8a517 100644 --- a/src/com/android/gallery3d/ui/ResourceTexture.java +++ b/src/com/android/gallery3d/glrenderer/ResourceTexture.java @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import com.android.gallery3d.common.Utils; +import junit.framework.Assert; // ResourceTexture is a texture whose Bitmap is decoded from a resource. // By default ResourceTexture is not opaque. @@ -30,7 +30,8 @@ public class ResourceTexture extends UploadedTexture { protected final int mResId; public ResourceTexture(Context context, int resId) { - mContext = Utils.checkNotNull(context); + Assert.assertNotNull(context); + mContext = context; mResId = resId; setOpaque(false); } diff --git a/src/com/android/gallery3d/ui/StringTexture.java b/src/com/android/gallery3d/glrenderer/StringTexture.java index 97995c8a5..56ca29753 100644 --- a/src/com/android/gallery3d/ui/StringTexture.java +++ b/src/com/android/gallery3d/glrenderer/StringTexture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -29,7 +29,7 @@ import android.util.FloatMath; // // To create a StringTexture, use the newInstance() method and specify // the String, the font size, and the color. -class StringTexture extends CanvasTexture { +public class StringTexture extends CanvasTexture { private final String mText; private final TextPaint mPaint; private final FontMetricsInt mMetrics; diff --git a/src/com/android/gallery3d/ui/Texture.java b/src/com/android/gallery3d/glrenderer/Texture.java index 2c426f994..3dcae4aec 100644 --- a/src/com/android/gallery3d/ui/Texture.java +++ b/src/com/android/gallery3d/glrenderer/Texture.java @@ -14,7 +14,8 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + // Texture is a rectangular image which can be drawn on GLCanvas. // The isOpaque() function gives a hint about whether the texture is opaque, diff --git a/src/com/android/gallery3d/ui/TextureUploader.java b/src/com/android/gallery3d/glrenderer/TextureUploader.java index 9a8c47fc6..f17ab845c 100644 --- a/src/com/android/gallery3d/ui/TextureUploader.java +++ b/src/com/android/gallery3d/glrenderer/TextureUploader.java @@ -14,8 +14,9 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; +import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; import java.util.ArrayDeque; diff --git a/src/com/android/gallery3d/ui/TiledTexture.java b/src/com/android/gallery3d/glrenderer/TiledTexture.java index 02bde9f4f..6ca1de088 100644 --- a/src/com/android/gallery3d/ui/TiledTexture.java +++ b/src/com/android/gallery3d/glrenderer/TiledTexture.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; @@ -26,6 +26,7 @@ import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.os.SystemClock; +import com.android.gallery3d.ui.GLRoot; import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; import java.util.ArrayDeque; diff --git a/src/com/android/gallery3d/ui/UploadedTexture.java b/src/com/android/gallery3d/glrenderer/UploadedTexture.java index bb86d05ef..ee55736bb 100644 --- a/src/com/android/gallery3d/ui/UploadedTexture.java +++ b/src/com/android/gallery3d/glrenderer/UploadedTexture.java @@ -14,18 +14,17 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.opengl.GLUtils; -import com.android.gallery3d.common.Utils; +import junit.framework.Assert; import java.util.HashMap; import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11Ext; // UploadedTextures use a Bitmap for the content of the texture. // @@ -41,7 +40,7 @@ import javax.microedition.khronos.opengles.GL11Ext; // // By default an UploadedTexture is opaque (so it can be drawn faster without // blending). The user or subclass can override it using setOpaque(). -abstract class UploadedTexture extends BasicTexture { +public abstract class UploadedTexture extends BasicTexture { // To prevent keeping allocation the borders, we store those used borders here. // Since the length will be power of two, it won't use too much memory. @@ -145,7 +144,7 @@ abstract class UploadedTexture extends BasicTexture { } private void freeBitmap() { - Utils.assertTrue(mBitmap != null); + Assert.assertTrue(mBitmap != null); onFreeBitmap(mBitmap); mBitmap = null; } @@ -194,9 +193,7 @@ abstract class UploadedTexture extends BasicTexture { Bitmap bitmap = getBitmap(); int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); - canvas.getGLInstance().glBindTexture(GL11.GL_TEXTURE_2D, mId); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, mBorder, mBorder, - bitmap, format, type); + canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); freeBitmap(); mContentValid = true; } @@ -210,11 +207,7 @@ abstract class UploadedTexture extends BasicTexture { return sUploadedCount > UPLOAD_LIMIT; } - static int[] sTextureId = new int[1]; - static float[] sCropRect = new float[4]; - private void uploadToCanvas(GLCanvas canvas) { - GL11 gl = canvas.getGLInstance(); Bitmap bitmap = getBitmap(); if (bitmap != null) { @@ -226,67 +219,42 @@ abstract class UploadedTexture extends BasicTexture { int texWidth = getTextureWidth(); int texHeight = getTextureHeight(); - Utils.assertTrue(bWidth <= texWidth && bHeight <= texHeight); - - // Define a vertically flipped crop rectangle for - // OES_draw_texture. - // The four values in sCropRect are: left, bottom, width, and - // height. Negative value of width or height means flip. - sCropRect[0] = mBorder; - sCropRect[1] = mBorder + bHeight; - sCropRect[2] = bWidth; - sCropRect[3] = -bHeight; + Assert.assertTrue(bWidth <= texWidth && bHeight <= texHeight); // Upload the bitmap to a new texture. - GLId.glGenTextures(1, sTextureId, 0); - gl.glBindTexture(GL11.GL_TEXTURE_2D, sTextureId[0]); - gl.glTexParameterfv(GL11.GL_TEXTURE_2D, - GL11Ext.GL_TEXTURE_CROP_RECT_OES, sCropRect, 0); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameteri(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); - gl.glTexParameterf(GL11.GL_TEXTURE_2D, - GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); + mId = GLCanvas.getGLId().generateTexture(); + canvas.setTextureParameters(this); if (bWidth == texWidth && bHeight == texHeight) { - GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0); + canvas.initializeTexture(this, bitmap); } else { int format = GLUtils.getInternalFormat(bitmap); int type = GLUtils.getType(bitmap); Config config = bitmap.getConfig(); - gl.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, - texWidth, texHeight, 0, format, type, null); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - mBorder, mBorder, bitmap, format, type); + canvas.initializeTextureSize(this, format, type); + canvas.texSubImage2D(this, mBorder, mBorder, bitmap, format, type); if (mBorder > 0) { // Left border Bitmap line = getBorderLine(true, config, texHeight); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - 0, 0, line, format, type); + canvas.texSubImage2D(this, 0, 0, line, format, type); // Top border line = getBorderLine(false, config, texWidth); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - 0, 0, line, format, type); + canvas.texSubImage2D(this, 0, 0, line, format, type); } // Right border if (mBorder + bWidth < texWidth) { Bitmap line = getBorderLine(true, config, texHeight); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - mBorder + bWidth, 0, line, format, type); + canvas.texSubImage2D(this, mBorder + bWidth, 0, line, format, type); } // Bottom border if (mBorder + bHeight < texHeight) { Bitmap line = getBorderLine(false, config, texWidth); - GLUtils.texSubImage2D(GL11.GL_TEXTURE_2D, 0, - 0, mBorder + bHeight, line, format, type); + canvas.texSubImage2D(this, 0, mBorder + bHeight, line, format, type); } } } finally { @@ -294,7 +262,6 @@ abstract class UploadedTexture extends BasicTexture { } // Update texture state. setAssociatedCanvas(canvas); - mId = sTextureId[0]; mState = STATE_LOADED; mContentValid = true; } else { diff --git a/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java b/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java index 0187cba4b..ef26b1b97 100644 --- a/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java +++ b/src/com/android/gallery3d/onetimeinitializer/GalleryWidgetMigrator.java @@ -18,11 +18,13 @@ package com.android.gallery3d.onetimeinitializer; import android.content.Context; import android.content.SharedPreferences; +import android.os.Build; import android.os.Environment; import android.preference.PreferenceManager; import android.util.Log; import com.android.gallery3d.app.GalleryApp; +import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.LocalAlbum; import com.android.gallery3d.data.MediaSet; @@ -36,34 +38,35 @@ import java.util.HashMap; import java.util.List; /** - * This one-timer migrates local-album gallery app widgets from pre-JB releases to JB (or later) - * due to bucket ID (i.e., directory hash) change in JB (as the external storage path is changed - * from /mnt/sdcard to /storage/sdcard0). + * This one-timer migrates local-album gallery app widgets from old paths from prior releases + * to updated paths in the current build version. This migration is needed because of + * bucket ID (i.e., directory hash) change in JB and JB MR1 (The external storage path has changed + * from /mnt/sdcard in pre-JB releases, to /storage/sdcard0 in JB, then again + * to /external/storage/sdcard/0 in JB MR1). */ public class GalleryWidgetMigrator { private static final String TAG = "GalleryWidgetMigrator"; - private static final String OLD_EXT_PATH = "/mnt/sdcard"; + private static final String PRE_JB_EXT_PATH = "/mnt/sdcard"; + private static final String JB_EXT_PATH = "/storage/sdcard0"; private static final String NEW_EXT_PATH = Environment.getExternalStorageDirectory().getAbsolutePath(); private static final int RELATIVE_PATH_START = NEW_EXT_PATH.length(); - private static final String KEY_MIGRATION_DONE = "gallery_widget_migration_done"; + private static final String KEY_EXT_PATH = "external_storage_path"; /** - * Migrates local-album gallery widgets from pre-JB releases to JB (or later) due to bucket ID - * (i.e., directory hash) change in JB. + * Migrates local-album gallery widgets from prior releases to current release + * due to bucket ID (i.e., directory hash) change. */ public static void migrateGalleryWidgets(Context context) { - // no migration needed if path of external storage is not changed - if (OLD_EXT_PATH.equals(NEW_EXT_PATH)) return; - - // only need to migrate once; the "done" bit is saved to SharedPreferences SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - boolean isDone = prefs.getBoolean(KEY_MIGRATION_DONE, false); + // Migration is only needed when external storage path has changed + String extPath = prefs.getString(KEY_EXT_PATH, null); + boolean isDone = NEW_EXT_PATH.equals(extPath); if (isDone) return; try { migrateGalleryWidgetsInternal(context); - prefs.edit().putBoolean(KEY_MIGRATION_DONE, true).commit(); + prefs.edit().putString(KEY_EXT_PATH, NEW_EXT_PATH).commit(); } catch (Throwable t) { // exception may be thrown if external storage is not available(?) Log.w(TAG, "migrateGalleryWidgets", t); @@ -77,39 +80,62 @@ public class GalleryWidgetMigrator { // only need to migrate local-album entries of type TYPE_ALBUM List<Entry> entries = dbHelper.getEntries(WidgetDatabaseHelper.TYPE_ALBUM); - if (entries != null) { - HashMap<Integer, Entry> localEntries = new HashMap<Integer, Entry>(entries.size()); - for (Entry entry : entries) { - Path path = Path.fromString(entry.albumPath); - MediaSet mediaSet = (MediaSet) manager.getMediaObject(path); - if (mediaSet instanceof LocalAlbum) { + if (entries == null) return; + + // Check each entry's relativePath. If exists, update bucket id using relative + // path combined with external storage path. Otherwise, iterate through old external + // storage paths to find the relative path that matches the old bucket id, and then update + // bucket id and relative path + HashMap<Integer, Entry> localEntries = new HashMap<Integer, Entry>(entries.size()); + for (Entry entry : entries) { + Path path = Path.fromString(entry.albumPath); + MediaSet mediaSet = (MediaSet) manager.getMediaObject(path); + if (mediaSet instanceof LocalAlbum) { + if (entry.relativePath != null && entry.relativePath.length() > 0) { + // update entry using relative path + external storage path + updateEntryUsingRelativePath(entry, dbHelper); + } else { int bucketId = Integer.parseInt(path.getSuffix()); localEntries.put(bucketId, entry); } } - if (!localEntries.isEmpty()) migrateLocalEntries(localEntries, dbHelper); } + if (!localEntries.isEmpty()) migrateLocalEntries(context, localEntries, dbHelper); } - private static void migrateLocalEntries( + private static void migrateLocalEntries(Context context, HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) { - File root = Environment.getExternalStorageDirectory(); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + String oldExtPath = prefs.getString(KEY_EXT_PATH, null); + if (oldExtPath != null) { + migrateLocalEntries(entries, dbHelper, oldExtPath); + return; + } + // If old external storage path is unknown, it could be either Pre-JB or JB version + // we need to try both. + migrateLocalEntries(entries, dbHelper, PRE_JB_EXT_PATH); + if (!entries.isEmpty() && + Build.VERSION.SDK_INT > ApiHelper.VERSION_CODES.JELLY_BEAN) { + migrateLocalEntries(entries, dbHelper, JB_EXT_PATH); + } + } + private static void migrateLocalEntries(HashMap<Integer, Entry> entries, + WidgetDatabaseHelper dbHelper, String oldExtPath) { + File root = Environment.getExternalStorageDirectory(); // check the DCIM directory first; this should take care of 99% use cases - updatePath(new File(root, "DCIM"), entries, dbHelper); - + updatePath(new File(root, "DCIM"), entries, dbHelper, oldExtPath); // check other directories if DCIM doesn't cut it - if (!entries.isEmpty()) updatePath(root, entries, dbHelper); + if (!entries.isEmpty()) updatePath(root, entries, dbHelper, oldExtPath); } - - private static void updatePath( - File root, HashMap<Integer, Entry> entries, WidgetDatabaseHelper dbHelper) { + private static void updatePath(File root, HashMap<Integer, Entry> entries, + WidgetDatabaseHelper dbHelper, String oldExtStorage) { File[] files = root.listFiles(); if (files != null) { for (File file : files) { if (file.isDirectory() && !entries.isEmpty()) { String path = file.getAbsolutePath(); - String oldPath = OLD_EXT_PATH + path.substring(RELATIVE_PATH_START); + String oldPath = oldExtStorage + path.substring(RELATIVE_PATH_START); int oldBucketId = GalleryUtils.getBucketId(oldPath); Entry entry = entries.remove(oldBucketId); if (entry != null) { @@ -120,11 +146,24 @@ public class GalleryWidgetMigrator { .toString(); Log.d(TAG, "migrate from " + entry.albumPath + " to " + newAlbumPath); entry.albumPath = newAlbumPath; + // update entry's relative path + entry.relativePath = path.substring(RELATIVE_PATH_START); dbHelper.updateEntry(entry); } - updatePath(file, entries, dbHelper); // recursion + updatePath(file, entries, dbHelper, oldExtStorage); // recursion } } } } + + private static void updateEntryUsingRelativePath(Entry entry, WidgetDatabaseHelper dbHelper) { + String newPath = NEW_EXT_PATH + entry.relativePath; + int newBucketId = GalleryUtils.getBucketId(newPath); + String newAlbumPath = Path.fromString(entry.albumPath) + .getParent() + .getChild(newBucketId) + .toString(); + entry.albumPath = newAlbumPath; + dbHelper.updateEntry(entry); + } } diff --git a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java index 10b710d2d..729439dc3 100644 --- a/src/com/android/gallery3d/ui/AbstractSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AbstractSlotRenderer.java @@ -20,6 +20,11 @@ import android.content.Context; import android.graphics.Rect; import com.android.gallery3d.R; +import com.android.gallery3d.glrenderer.FadeOutTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.NinePatchTexture; +import com.android.gallery3d.glrenderer.ResourceTexture; +import com.android.gallery3d.glrenderer.Texture; public abstract class AbstractSlotRenderer implements SlotView.SlotRenderer { diff --git a/src/com/android/gallery3d/ui/ActionModeHandler.java b/src/com/android/gallery3d/ui/ActionModeHandler.java index 7191599ad..9b84bf75c 100644 --- a/src/com/android/gallery3d/ui/ActionModeHandler.java +++ b/src/com/android/gallery3d/ui/ActionModeHandler.java @@ -408,6 +408,15 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi // Pass1: Deal with unexpanded media object list for menu operation. ArrayList<MediaObject> selected = getSelectedMediaObjects(jc); if (selected == null) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + mMenuTask = null; + if (jc.isCancelled()) return; + // Disable all the operations when no item is selected + MenuExecutor.updateMenuOperation(mMenu, 0); + } + }); return null; } final int operation = computeMenuOptions(selected); @@ -466,7 +475,12 @@ public class ActionModeHandler implements Callback, PopupList.OnPopupItemClickLi mMenuExecutor.pause(); } + public void destroy() { + mMenuExecutor.destroy(); + } + public void resume() { if (mSelectionManager.inSelectionMode()) updateSupportedOperation(); + mMenuExecutor.resume(); } } diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java index 80dfc919f..d5a15b4ac 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java @@ -29,6 +29,10 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.BitmapTexture; +import com.android.gallery3d.glrenderer.Texture; +import com.android.gallery3d.glrenderer.TextureUploader; +import com.android.gallery3d.glrenderer.TiledTexture; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; import com.android.gallery3d.util.ThreadPool; diff --git a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java index 70d7c273a..5332ef89a 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlotRenderer.java @@ -21,6 +21,13 @@ import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.app.AlbumSetDataLoader; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.ColorTexture; +import com.android.gallery3d.glrenderer.FadeInTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.ResourceTexture; +import com.android.gallery3d.glrenderer.Texture; +import com.android.gallery3d.glrenderer.TiledTexture; +import com.android.gallery3d.glrenderer.UploadedTexture; import com.android.gallery3d.ui.AlbumSetSlidingWindow.AlbumSetEntry; public class AlbumSetSlotRenderer extends AbstractSlotRenderer { diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java index 678c43251..8cd2cf500 100644 --- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java @@ -27,6 +27,8 @@ import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.Path; import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback; +import com.android.gallery3d.glrenderer.Texture; +import com.android.gallery3d.glrenderer.TiledTexture; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; import com.android.gallery3d.util.JobLimiter; diff --git a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java index ce5b7ac24..dc6c89b0e 100644 --- a/src/com/android/gallery3d/ui/AlbumSlotRenderer.java +++ b/src/com/android/gallery3d/ui/AlbumSlotRenderer.java @@ -20,6 +20,11 @@ import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.app.AlbumDataLoader; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.ColorTexture; +import com.android.gallery3d.glrenderer.FadeInTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.Texture; +import com.android.gallery3d.glrenderer.TiledTexture; public class AlbumSlotRenderer extends AbstractSlotRenderer { @SuppressWarnings("unused") diff --git a/src/com/android/gallery3d/ui/BitmapScreenNail.java b/src/com/android/gallery3d/ui/BitmapScreenNail.java index 741eefbe3..a3d403946 100644 --- a/src/com/android/gallery3d/ui/BitmapScreenNail.java +++ b/src/com/android/gallery3d/ui/BitmapScreenNail.java @@ -19,6 +19,9 @@ package com.android.gallery3d.ui; import android.graphics.Bitmap; import android.graphics.RectF; +import com.android.gallery3d.glrenderer.BitmapTexture; +import com.android.gallery3d.glrenderer.GLCanvas; + public class BitmapScreenNail implements ScreenNail { private final BitmapTexture mBitmapTexture; diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java index d4c9b1d30..c3466e7fe 100644 --- a/src/com/android/gallery3d/ui/BitmapTileProvider.java +++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java @@ -25,7 +25,7 @@ import com.android.gallery3d.data.BitmapPool; import java.util.ArrayList; -public class BitmapTileProvider implements TileImageView.Model { +public class BitmapTileProvider implements TileImageView.TileSource { private final ScreenNail mScreenNail; private final Bitmap[] mMipmaps; private final Config mConfig; @@ -72,22 +72,21 @@ public class BitmapTileProvider implements TileImageView.Model { @Override public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize, BitmapPool pool) { + BitmapPool pool) { x >>= level; y >>= level; - int size = tileSize + 2 * borderSize; Bitmap result = pool == null ? null : pool.getBitmap(); if (result == null) { - result = Bitmap.createBitmap(size, size, mConfig); + result = Bitmap.createBitmap(tileSize, tileSize, mConfig); } else { result.eraseColor(0); } Bitmap mipmap = mMipmaps[level]; Canvas canvas = new Canvas(result); - int offsetX = -x + borderSize; - int offsetY = -y + borderSize; + int offsetX = -x; + int offsetY = -y; canvas.drawBitmap(mipmap, offsetX, offsetY, null); return result; } diff --git a/src/com/android/gallery3d/ui/CropView.java b/src/com/android/gallery3d/ui/CropView.java deleted file mode 100644 index 1890c7630..000000000 --- a/src/com/android/gallery3d/ui/CropView.java +++ /dev/null @@ -1,801 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.ui; - -import android.graphics.Bitmap; -import android.graphics.Bitmap.Config; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.RectF; -import android.media.FaceDetector; -import android.os.Handler; -import android.os.Message; -import android.util.FloatMath; -import android.view.MotionEvent; -import android.view.animation.DecelerateInterpolator; -import android.widget.Toast; - -import com.android.gallery3d.R; -import com.android.gallery3d.anim.Animation; -import com.android.gallery3d.app.AbstractGalleryActivity; -import com.android.gallery3d.common.Utils; - -import java.util.ArrayList; - -import javax.microedition.khronos.opengles.GL11; - -/** - * The activity can crop specific region of interest from an image. - */ -public class CropView extends GLView { - @SuppressWarnings("unused") - private static final String TAG = "CropView"; - - private static final int FACE_PIXEL_COUNT = 120000; // around 400x300 - - private static final int COLOR_OUTLINE = 0xFF008AFF; - private static final int COLOR_FACE_OUTLINE = 0xFF000000; - - private static final float OUTLINE_WIDTH = 3f; - - private static final int SIZE_UNKNOWN = -1; - private static final int TOUCH_TOLERANCE = 30; - - private static final float MIN_SELECTION_LENGTH = 16f; - public static final float UNSPECIFIED = -1f; - - private static final int MAX_FACE_COUNT = 3; - private static final float FACE_EYE_RATIO = 2f; - - private static final int ANIMATION_DURATION = 1250; - - private static final int MOVE_LEFT = 1; - private static final int MOVE_TOP = 2; - private static final int MOVE_RIGHT = 4; - private static final int MOVE_BOTTOM = 8; - private static final int MOVE_BLOCK = 16; - - private static final float MAX_SELECTION_RATIO = 0.8f; - private static final float MIN_SELECTION_RATIO = 0.4f; - private static final float SELECTION_RATIO = 0.60f; - private static final int ANIMATION_TRIGGER = 64; - - private static final int MSG_UPDATE_FACES = 1; - - private float mAspectRatio = UNSPECIFIED; - private float mSpotlightRatioX = 0; - private float mSpotlightRatioY = 0; - - private Handler mMainHandler; - - private FaceHighlightView mFaceDetectionView; - private HighlightRectangle mHighlightRectangle; - private TileImageView mImageView; - private AnimationController mAnimation = new AnimationController(); - - private int mImageWidth = SIZE_UNKNOWN; - private int mImageHeight = SIZE_UNKNOWN; - - private AbstractGalleryActivity mActivity; - - private GLPaint mPaint = new GLPaint(); - private GLPaint mFacePaint = new GLPaint(); - - private int mImageRotation; - - public CropView(AbstractGalleryActivity activity) { - mActivity = activity; - mImageView = new TileImageView(activity); - mFaceDetectionView = new FaceHighlightView(); - mHighlightRectangle = new HighlightRectangle(); - - addComponent(mImageView); - addComponent(mFaceDetectionView); - addComponent(mHighlightRectangle); - - mHighlightRectangle.setVisibility(GLView.INVISIBLE); - - mPaint.setColor(COLOR_OUTLINE); - mPaint.setLineWidth(OUTLINE_WIDTH); - - mFacePaint.setColor(COLOR_FACE_OUTLINE); - mFacePaint.setLineWidth(OUTLINE_WIDTH); - - mMainHandler = new SynchronizedHandler(activity.getGLRoot()) { - @Override - public void handleMessage(Message message) { - Utils.assertTrue(message.what == MSG_UPDATE_FACES); - ((DetectFaceTask) message.obj).updateFaces(); - } - }; - } - - public void setAspectRatio(float ratio) { - mAspectRatio = ratio; - } - - public void setSpotlightRatio(float ratioX, float ratioY) { - mSpotlightRatioX = ratioX; - mSpotlightRatioY = ratioY; - } - - @Override - public void onLayout(boolean changed, int l, int t, int r, int b) { - int width = r - l; - int height = b - t; - - mFaceDetectionView.layout(0, 0, width, height); - mHighlightRectangle.layout(0, 0, width, height); - mImageView.layout(0, 0, width, height); - if (mImageHeight != SIZE_UNKNOWN) { - mAnimation.initialize(); - if (mHighlightRectangle.getVisibility() == GLView.VISIBLE) { - mAnimation.parkNow( - mHighlightRectangle.mHighlightRect); - } - } - } - - private boolean setImageViewPosition(int centerX, int centerY, float scale) { - int inverseX = mImageWidth - centerX; - int inverseY = mImageHeight - centerY; - TileImageView t = mImageView; - int rotation = mImageRotation; - switch (rotation) { - case 0: return t.setPosition(centerX, centerY, scale, 0); - case 90: return t.setPosition(centerY, inverseX, scale, 90); - case 180: return t.setPosition(inverseX, inverseY, scale, 180); - case 270: return t.setPosition(inverseY, centerX, scale, 270); - default: throw new IllegalArgumentException(String.valueOf(rotation)); - } - } - - @Override - public void render(GLCanvas canvas) { - AnimationController a = mAnimation; - if (a.calculate(AnimationTime.get())) invalidate(); - setImageViewPosition(a.getCenterX(), a.getCenterY(), a.getScale()); - super.render(canvas); - } - - @Override - public void renderBackground(GLCanvas canvas) { - canvas.clearBuffer(); - } - - public RectF getCropRectangle() { - if (mHighlightRectangle.getVisibility() == GLView.INVISIBLE) return null; - RectF rect = mHighlightRectangle.mHighlightRect; - RectF result = new RectF(rect.left * mImageWidth, rect.top * mImageHeight, - rect.right * mImageWidth, rect.bottom * mImageHeight); - return result; - } - - public int getImageWidth() { - return mImageWidth; - } - - public int getImageHeight() { - return mImageHeight; - } - - private class FaceHighlightView extends GLView { - private static final int INDEX_NONE = -1; - private ArrayList<RectF> mFaces = new ArrayList<RectF>(); - private RectF mRect = new RectF(); - private int mPressedFaceIndex = INDEX_NONE; - - public void addFace(RectF faceRect) { - mFaces.add(faceRect); - invalidate(); - } - - private void renderFace(GLCanvas canvas, RectF face, boolean pressed) { - GL11 gl = canvas.getGLInstance(); - if (pressed) { - gl.glEnable(GL11.GL_STENCIL_TEST); - gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); - gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); - } - - RectF r = mAnimation.mapRect(face, mRect); - canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); - canvas.drawRect(r.left, r.top, r.width(), r.height(), mFacePaint); - - if (pressed) { - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); - } - } - - @Override - protected void renderBackground(GLCanvas canvas) { - ArrayList<RectF> faces = mFaces; - for (int i = 0, n = faces.size(); i < n; ++i) { - renderFace(canvas, faces.get(i), i == mPressedFaceIndex); - } - - GL11 gl = canvas.getGLInstance(); - if (mPressedFaceIndex != INDEX_NONE) { - gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); - canvas.fillRect(0, 0, getWidth(), getHeight(), 0x66000000); - gl.glDisable(GL11.GL_STENCIL_TEST); - } - } - - private void setPressedFace(int index) { - if (mPressedFaceIndex == index) return; - mPressedFaceIndex = index; - invalidate(); - } - - private int getFaceIndexByPosition(float x, float y) { - ArrayList<RectF> faces = mFaces; - for (int i = 0, n = faces.size(); i < n; ++i) { - RectF r = mAnimation.mapRect(faces.get(i), mRect); - if (r.contains(x, y)) return i; - } - return INDEX_NONE; - } - - @Override - protected boolean onTouch(MotionEvent event) { - float x = event.getX(); - float y = event.getY(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_MOVE: { - setPressedFace(getFaceIndexByPosition(x, y)); - break; - } - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - int index = mPressedFaceIndex; - setPressedFace(INDEX_NONE); - if (index != INDEX_NONE) { - mHighlightRectangle.setRectangle(mFaces.get(index)); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - setVisibility(GLView.INVISIBLE); - } - } - } - return true; - } - } - - private class AnimationController extends Animation { - private int mCurrentX; - private int mCurrentY; - private float mCurrentScale; - private int mStartX; - private int mStartY; - private float mStartScale; - private int mTargetX; - private int mTargetY; - private float mTargetScale; - - public AnimationController() { - setDuration(ANIMATION_DURATION); - setInterpolator(new DecelerateInterpolator(4)); - } - - public void initialize() { - mCurrentX = mImageWidth / 2; - mCurrentY = mImageHeight / 2; - mCurrentScale = Math.min(2, Math.min( - (float) getWidth() / mImageWidth, - (float) getHeight() / mImageHeight)); - } - - public void startParkingAnimation(RectF highlight) { - RectF r = mAnimation.mapRect(highlight, new RectF()); - int width = getWidth(); - int height = getHeight(); - - float wr = r.width() / width; - float hr = r.height() / height; - final int d = ANIMATION_TRIGGER; - if (wr >= MIN_SELECTION_RATIO && wr < MAX_SELECTION_RATIO - && hr >= MIN_SELECTION_RATIO && hr < MAX_SELECTION_RATIO - && r.left >= d && r.right < width - d - && r.top >= d && r.bottom < height - d) return; - - mStartX = mCurrentX; - mStartY = mCurrentY; - mStartScale = mCurrentScale; - calculateTarget(highlight); - start(); - } - - public void parkNow(RectF highlight) { - calculateTarget(highlight); - forceStop(); - mStartX = mCurrentX = mTargetX; - mStartY = mCurrentY = mTargetY; - mStartScale = mCurrentScale = mTargetScale; - } - - public void inverseMapPoint(PointF point) { - float s = mCurrentScale; - point.x = Utils.clamp(((point.x - getWidth() * 0.5f) / s - + mCurrentX) / mImageWidth, 0, 1); - point.y = Utils.clamp(((point.y - getHeight() * 0.5f) / s - + mCurrentY) / mImageHeight, 0, 1); - } - - public RectF mapRect(RectF input, RectF output) { - float offsetX = getWidth() * 0.5f; - float offsetY = getHeight() * 0.5f; - int x = mCurrentX; - int y = mCurrentY; - float s = mCurrentScale; - output.set( - offsetX + (input.left * mImageWidth - x) * s, - offsetY + (input.top * mImageHeight - y) * s, - offsetX + (input.right * mImageWidth - x) * s, - offsetY + (input.bottom * mImageHeight - y) * s); - return output; - } - - @Override - protected void onCalculate(float progress) { - mCurrentX = Math.round(mStartX + (mTargetX - mStartX) * progress); - mCurrentY = Math.round(mStartY + (mTargetY - mStartY) * progress); - mCurrentScale = mStartScale + (mTargetScale - mStartScale) * progress; - - if (mCurrentX == mTargetX && mCurrentY == mTargetY - && mCurrentScale == mTargetScale) forceStop(); - } - - public int getCenterX() { - return mCurrentX; - } - - public int getCenterY() { - return mCurrentY; - } - - public float getScale() { - return mCurrentScale; - } - - private void calculateTarget(RectF highlight) { - float width = getWidth(); - float height = getHeight(); - - if (mImageWidth != SIZE_UNKNOWN) { - float minScale = Math.min(width / mImageWidth, height / mImageHeight); - float scale = Utils.clamp(SELECTION_RATIO * Math.min( - width / (highlight.width() * mImageWidth), - height / (highlight.height() * mImageHeight)), minScale, 2f); - int centerX = Math.round( - mImageWidth * (highlight.left + highlight.right) * 0.5f); - int centerY = Math.round( - mImageHeight * (highlight.top + highlight.bottom) * 0.5f); - - if (Math.round(mImageWidth * scale) > width) { - int limitX = Math.round(width * 0.5f / scale); - centerX = Math.round( - (highlight.left + highlight.right) * mImageWidth / 2); - centerX = Utils.clamp(centerX, limitX, mImageWidth - limitX); - } else { - centerX = mImageWidth / 2; - } - if (Math.round(mImageHeight * scale) > height) { - int limitY = Math.round(height * 0.5f / scale); - centerY = Math.round( - (highlight.top + highlight.bottom) * mImageHeight / 2); - centerY = Utils.clamp(centerY, limitY, mImageHeight - limitY); - } else { - centerY = mImageHeight / 2; - } - mTargetX = centerX; - mTargetY = centerY; - mTargetScale = scale; - } - } - - } - - private class HighlightRectangle extends GLView { - private RectF mHighlightRect = new RectF(0.25f, 0.25f, 0.75f, 0.75f); - private RectF mTempRect = new RectF(); - private PointF mTempPoint = new PointF(); - - private ResourceTexture mArrow; - - private int mMovingEdges = 0; - private float mReferenceX; - private float mReferenceY; - - public HighlightRectangle() { - mArrow = new ResourceTexture(mActivity.getAndroidContext(), - R.drawable.camera_crop_holo); - } - - public void setInitRectangle() { - float targetRatio = mAspectRatio == UNSPECIFIED - ? 1f - : mAspectRatio * mImageHeight / mImageWidth; - float w = SELECTION_RATIO / 2f; - float h = SELECTION_RATIO / 2f; - if (targetRatio > 1) { - h = w / targetRatio; - } else { - w = h * targetRatio; - } - mHighlightRect.set(0.5f - w, 0.5f - h, 0.5f + w, 0.5f + h); - } - - public void setRectangle(RectF faceRect) { - mHighlightRect.set(faceRect); - mAnimation.startParkingAnimation(faceRect); - invalidate(); - } - - private void moveEdges(MotionEvent event) { - float scale = mAnimation.getScale(); - float dx = (event.getX() - mReferenceX) / scale / mImageWidth; - float dy = (event.getY() - mReferenceY) / scale / mImageHeight; - mReferenceX = event.getX(); - mReferenceY = event.getY(); - RectF r = mHighlightRect; - - if ((mMovingEdges & MOVE_BLOCK) != 0) { - dx = Utils.clamp(dx, -r.left, 1 - r.right); - dy = Utils.clamp(dy, -r.top , 1 - r.bottom); - r.top += dy; - r.bottom += dy; - r.left += dx; - r.right += dx; - } else { - PointF point = mTempPoint; - point.set(mReferenceX, mReferenceY); - mAnimation.inverseMapPoint(point); - float left = r.left + MIN_SELECTION_LENGTH / mImageWidth; - float right = r.right - MIN_SELECTION_LENGTH / mImageWidth; - float top = r.top + MIN_SELECTION_LENGTH / mImageHeight; - float bottom = r.bottom - MIN_SELECTION_LENGTH / mImageHeight; - if ((mMovingEdges & MOVE_RIGHT) != 0) { - r.right = Utils.clamp(point.x, left, 1f); - } - if ((mMovingEdges & MOVE_LEFT) != 0) { - r.left = Utils.clamp(point.x, 0, right); - } - if ((mMovingEdges & MOVE_TOP) != 0) { - r.top = Utils.clamp(point.y, 0, bottom); - } - if ((mMovingEdges & MOVE_BOTTOM) != 0) { - r.bottom = Utils.clamp(point.y, top, 1f); - } - if (mAspectRatio != UNSPECIFIED) { - float targetRatio = mAspectRatio * mImageHeight / mImageWidth; - if (r.width() / r.height() > targetRatio) { - float height = r.width() / targetRatio; - if ((mMovingEdges & MOVE_BOTTOM) != 0) { - r.bottom = Utils.clamp(r.top + height, top, 1f); - } else { - r.top = Utils.clamp(r.bottom - height, 0, bottom); - } - } else { - float width = r.height() * targetRatio; - if ((mMovingEdges & MOVE_LEFT) != 0) { - r.left = Utils.clamp(r.right - width, 0, right); - } else { - r.right = Utils.clamp(r.left + width, left, 1f); - } - } - if (r.width() / r.height() > targetRatio) { - float width = r.height() * targetRatio; - if ((mMovingEdges & MOVE_LEFT) != 0) { - r.left = Utils.clamp(r.right - width, 0, right); - } else { - r.right = Utils.clamp(r.left + width, left, 1f); - } - } else { - float height = r.width() / targetRatio; - if ((mMovingEdges & MOVE_BOTTOM) != 0) { - r.bottom = Utils.clamp(r.top + height, top, 1f); - } else { - r.top = Utils.clamp(r.bottom - height, 0, bottom); - } - } - } - } - invalidate(); - } - - private void setMovingEdges(MotionEvent event) { - RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); - float x = event.getX(); - float y = event.getY(); - - if (x > r.left + TOUCH_TOLERANCE && x < r.right - TOUCH_TOLERANCE - && y > r.top + TOUCH_TOLERANCE && y < r.bottom - TOUCH_TOLERANCE) { - mMovingEdges = MOVE_BLOCK; - return; - } - - boolean inVerticalRange = (r.top - TOUCH_TOLERANCE) <= y - && y <= (r.bottom + TOUCH_TOLERANCE); - boolean inHorizontalRange = (r.left - TOUCH_TOLERANCE) <= x - && x <= (r.right + TOUCH_TOLERANCE); - - if (inVerticalRange) { - boolean left = Math.abs(x - r.left) <= TOUCH_TOLERANCE; - boolean right = Math.abs(x - r.right) <= TOUCH_TOLERANCE; - if (left && right) { - left = Math.abs(x - r.left) < Math.abs(x - r.right); - right = !left; - } - if (left) mMovingEdges |= MOVE_LEFT; - if (right) mMovingEdges |= MOVE_RIGHT; - if (mAspectRatio != UNSPECIFIED && inHorizontalRange) { - mMovingEdges |= (y > - (r.top + r.bottom) / 2) ? MOVE_BOTTOM : MOVE_TOP; - } - } - if (inHorizontalRange) { - boolean top = Math.abs(y - r.top) <= TOUCH_TOLERANCE; - boolean bottom = Math.abs(y - r.bottom) <= TOUCH_TOLERANCE; - if (top && bottom) { - top = Math.abs(y - r.top) < Math.abs(y - r.bottom); - bottom = !top; - } - if (top) mMovingEdges |= MOVE_TOP; - if (bottom) mMovingEdges |= MOVE_BOTTOM; - if (mAspectRatio != UNSPECIFIED && inVerticalRange) { - mMovingEdges |= (x > - (r.left + r.right) / 2) ? MOVE_RIGHT : MOVE_LEFT; - } - } - } - - @Override - protected boolean onTouch(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - mReferenceX = event.getX(); - mReferenceY = event.getY(); - setMovingEdges(event); - invalidate(); - return true; - } - case MotionEvent.ACTION_MOVE: - moveEdges(event); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - mMovingEdges = 0; - mAnimation.startParkingAnimation(mHighlightRect); - invalidate(); - return true; - } - } - return true; - } - - @Override - protected void renderBackground(GLCanvas canvas) { - RectF r = mAnimation.mapRect(mHighlightRect, mTempRect); - drawHighlightRectangle(canvas, r); - - float centerY = (r.top + r.bottom) / 2; - float centerX = (r.left + r.right) / 2; - boolean notMoving = mMovingEdges == 0; - if ((mMovingEdges & MOVE_RIGHT) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(r.right - mArrow.getWidth() / 2), - Math.round(centerY - mArrow.getHeight() / 2)); - } - if ((mMovingEdges & MOVE_LEFT) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(r.left - mArrow.getWidth() / 2), - Math.round(centerY - mArrow.getHeight() / 2)); - } - if ((mMovingEdges & MOVE_TOP) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(centerX - mArrow.getWidth() / 2), - Math.round(r.top - mArrow.getHeight() / 2)); - } - if ((mMovingEdges & MOVE_BOTTOM) != 0 || notMoving) { - mArrow.draw(canvas, - Math.round(centerX - mArrow.getWidth() / 2), - Math.round(r.bottom - mArrow.getHeight() / 2)); - } - } - - private void drawHighlightRectangle(GLCanvas canvas, RectF r) { - GL11 gl = canvas.getGLInstance(); - gl.glLineWidth(3.0f); - gl.glEnable(GL11.GL_LINE_SMOOTH); - - gl.glEnable(GL11.GL_STENCIL_TEST); - gl.glClear(GL11.GL_STENCIL_BUFFER_BIT); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); - gl.glStencilFunc(GL11.GL_ALWAYS, 1, 1); - - if (mSpotlightRatioX == 0 || mSpotlightRatioY == 0) { - canvas.fillRect(r.left, r.top, r.width(), r.height(), Color.TRANSPARENT); - canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); - } else { - float sx = r.width() * mSpotlightRatioX; - float sy = r.height() * mSpotlightRatioY; - float cx = r.centerX(); - float cy = r.centerY(); - - canvas.fillRect(cx - sx / 2, cy - sy / 2, sx, sy, Color.TRANSPARENT); - canvas.drawRect(cx - sx / 2, cy - sy / 2, sx, sy, mPaint); - canvas.drawRect(r.left, r.top, r.width(), r.height(), mPaint); - - gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_REPLACE); - - canvas.drawRect(cx - sy / 2, cy - sx / 2, sy, sx, mPaint); - canvas.fillRect(cx - sy / 2, cy - sx / 2, sy, sx, Color.TRANSPARENT); - canvas.fillRect(r.left, r.top, r.width(), r.height(), 0x80000000); - } - - gl.glStencilFunc(GL11.GL_NOTEQUAL, 1, 1); - gl.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP); - - canvas.fillRect(0, 0, getWidth(), getHeight(), 0xA0000000); - - gl.glDisable(GL11.GL_STENCIL_TEST); - } - } - - private class DetectFaceTask extends Thread { - private final FaceDetector.Face[] mFaces = new FaceDetector.Face[MAX_FACE_COUNT]; - private final Bitmap mFaceBitmap; - private int mFaceCount; - - public DetectFaceTask(Bitmap bitmap) { - mFaceBitmap = bitmap; - setName("face-detect"); - } - - @Override - public void run() { - Bitmap bitmap = mFaceBitmap; - FaceDetector detector = new FaceDetector( - bitmap.getWidth(), bitmap.getHeight(), MAX_FACE_COUNT); - mFaceCount = detector.findFaces(bitmap, mFaces); - mMainHandler.sendMessage( - mMainHandler.obtainMessage(MSG_UPDATE_FACES, this)); - } - - private RectF getFaceRect(FaceDetector.Face face) { - PointF point = new PointF(); - face.getMidPoint(point); - - int width = mFaceBitmap.getWidth(); - int height = mFaceBitmap.getHeight(); - float rx = face.eyesDistance() * FACE_EYE_RATIO; - float ry = rx; - float aspect = mAspectRatio; - if (aspect != UNSPECIFIED) { - if (aspect > 1) { - rx = ry * aspect; - } else { - ry = rx / aspect; - } - } - - RectF r = new RectF( - point.x - rx, point.y - ry, point.x + rx, point.y + ry); - r.intersect(0, 0, width, height); - - if (aspect != UNSPECIFIED) { - if (r.width() / r.height() > aspect) { - float w = r.height() * aspect; - r.left = (r.left + r.right - w) * 0.5f; - r.right = r.left + w; - } else { - float h = r.width() / aspect; - r.top = (r.top + r.bottom - h) * 0.5f; - r.bottom = r.top + h; - } - } - - r.left /= width; - r.right /= width; - r.top /= height; - r.bottom /= height; - return r; - } - - public void updateFaces() { - if (mFaceCount > 1) { - for (int i = 0, n = mFaceCount; i < n; ++i) { - mFaceDetectionView.addFace(getFaceRect(mFaces[i])); - } - mFaceDetectionView.setVisibility(GLView.VISIBLE); - Toast.makeText(mActivity.getAndroidContext(), - R.string.multiface_crop_help, Toast.LENGTH_SHORT).show(); - } else if (mFaceCount == 1) { - mFaceDetectionView.setVisibility(GLView.INVISIBLE); - mHighlightRectangle.setRectangle(getFaceRect(mFaces[0])); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - } else /*mFaceCount == 0*/ { - mHighlightRectangle.setInitRectangle(); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - } - } - } - - public void setDataModel(TileImageView.Model dataModel, int rotation) { - if (((rotation / 90) & 0x01) != 0) { - mImageWidth = dataModel.getImageHeight(); - mImageHeight = dataModel.getImageWidth(); - } else { - mImageWidth = dataModel.getImageWidth(); - mImageHeight = dataModel.getImageHeight(); - } - - mImageRotation = rotation; - - mImageView.setModel(dataModel); - mAnimation.initialize(); - } - - public void detectFaces(Bitmap bitmap) { - int rotation = mImageRotation; - int width = bitmap.getWidth(); - int height = bitmap.getHeight(); - float scale = FloatMath.sqrt((float) FACE_PIXEL_COUNT / (width * height)); - - // faceBitmap is a correctly rotated bitmap, as viewed by a user. - Bitmap faceBitmap; - if (((rotation / 90) & 1) == 0) { - int w = (Math.round(width * scale) & ~1); // must be even - int h = Math.round(height * scale); - faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); - Canvas canvas = new Canvas(faceBitmap); - canvas.rotate(rotation, w / 2, h / 2); - canvas.scale((float) w / width, (float) h / height); - canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); - } else { - int w = (Math.round(height * scale) & ~1); // must be even - int h = Math.round(width * scale); - faceBitmap = Bitmap.createBitmap(w, h, Config.RGB_565); - Canvas canvas = new Canvas(faceBitmap); - canvas.translate(w / 2, h / 2); - canvas.rotate(rotation); - canvas.translate(-h / 2, -w / 2); - canvas.scale((float) w / height, (float) h / width); - canvas.drawBitmap(bitmap, 0, 0, new Paint(Paint.FILTER_BITMAP_FLAG)); - } - new DetectFaceTask(faceBitmap).start(); - } - - public void initializeHighlightRectangle() { - mHighlightRectangle.setInitRectangle(); - mHighlightRectangle.setVisibility(GLView.VISIBLE); - } - - public void resume() { - mImageView.prepareTextures(); - } - - public void pause() { - mImageView.freeTextures(); - } -} - diff --git a/src/com/android/gallery3d/ui/DetailsHelper.java b/src/com/android/gallery3d/ui/DetailsHelper.java index 301601156..47296f655 100644 --- a/src/com/android/gallery3d/ui/DetailsHelper.java +++ b/src/com/android/gallery3d/ui/DetailsHelper.java @@ -16,6 +16,8 @@ package com.android.gallery3d.ui; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.view.View.MeasureSpec; import com.android.gallery3d.R; @@ -44,6 +46,10 @@ public class DetailsHelper { public void hide(); } + public interface ResolutionResolvingListener { + public void onResolutionAvailable(int width, int height); + } + public DetailsHelper(AbstractGalleryActivity activity, GLView rootPane, DetailsSource source) { mContainer = new DialogDetailsView(activity, source); } @@ -75,6 +81,12 @@ public class DetailsHelper { return sAddressResolver.resolveAddress(latlng, listener); } + public static void resolveResolution(String path, ResolutionResolvingListener listener) { + Bitmap bitmap = BitmapFactory.decodeFile(path); + if (bitmap == null) return; + listener.onResolutionAvailable(bitmap.getWidth(), bitmap.getHeight()); + } + public static void pause() { if (sAddressResolver != null) sAddressResolver.cancel(); } diff --git a/src/com/android/gallery3d/ui/DialogDetailsView.java b/src/com/android/gallery3d/ui/DialogDetailsView.java index 8d96b821a..058c03654 100644 --- a/src/com/android/gallery3d/ui/DialogDetailsView.java +++ b/src/com/android/gallery3d/ui/DialogDetailsView.java @@ -37,6 +37,7 @@ import com.android.gallery3d.ui.DetailsAddressResolver.AddressResolvingListener; import com.android.gallery3d.ui.DetailsHelper.CloseListener; import com.android.gallery3d.ui.DetailsHelper.DetailsSource; import com.android.gallery3d.ui.DetailsHelper.DetailsViewContainer; +import com.android.gallery3d.ui.DetailsHelper.ResolutionResolvingListener; import java.util.ArrayList; import java.util.Map.Entry; @@ -111,9 +112,13 @@ public class DialogDetailsView implements DetailsViewContainer { }); } - private class DetailsAdapter extends BaseAdapter implements AddressResolvingListener { + + private class DetailsAdapter extends BaseAdapter + implements AddressResolvingListener, ResolutionResolvingListener { private final ArrayList<String> mItems; private int mLocationIndex; + private int mWidthIndex = -1; + private int mHeightIndex = -1; public DetailsAdapter(MediaDetails details) { Context context = mActivity.getAndroidContext(); @@ -123,6 +128,8 @@ public class DialogDetailsView implements DetailsViewContainer { } private void setDetails(Context context, MediaDetails details) { + boolean resolutionIsValid = true; + String path = null; for (Entry<Integer, Object> detail : details) { String value; switch (detail.getKey()) { @@ -170,6 +177,26 @@ public class DialogDetailsView implements DetailsViewContainer { } break; } + case MediaDetails.INDEX_WIDTH: + mWidthIndex = mItems.size(); + value = detail.getValue().toString(); + if (value.equalsIgnoreCase("0")) { + value = context.getString(R.string.unknown); + resolutionIsValid = false; + } + break; + case MediaDetails.INDEX_HEIGHT: { + mHeightIndex = mItems.size(); + value = detail.getValue().toString(); + if (value.equalsIgnoreCase("0")) { + value = context.getString(R.string.unknown); + resolutionIsValid = false; + } + break; + } + case MediaDetails.INDEX_PATH: + // Get the path and then fall through to the default case + path = detail.getValue().toString(); default: { Object valueObj = detail.getValue(); // This shouldn't happen, log its key to help us diagnose the problem. @@ -189,6 +216,9 @@ public class DialogDetailsView implements DetailsViewContainer { context, key), value); } mItems.add(value); + if (!resolutionIsValid) { + DetailsHelper.resolveResolution(path, this); + } } } @@ -235,6 +265,20 @@ public class DialogDetailsView implements DetailsViewContainer { mItems.set(mLocationIndex, address); notifyDataSetChanged(); } + + @Override + public void onResolutionAvailable(int width, int height) { + if (width == 0 || height == 0) return; + // Update the resolution with the new width and height + Context context = mActivity.getAndroidContext(); + String widthString = String.format("%s: %d", DetailsHelper.getDetailsName( + context, MediaDetails.INDEX_WIDTH), width); + String heightString = String.format("%s: %d", DetailsHelper.getDetailsName( + context, MediaDetails.INDEX_HEIGHT), height); + mItems.set(mWidthIndex, String.valueOf(widthString)); + mItems.set(mHeightIndex, String.valueOf(heightString)); + notifyDataSetChanged(); + } } @Override diff --git a/src/com/android/gallery3d/ui/EdgeEffect.java b/src/com/android/gallery3d/ui/EdgeEffect.java index ed369737b..87ff0c5d3 100644 --- a/src/com/android/gallery3d/ui/EdgeEffect.java +++ b/src/com/android/gallery3d/ui/EdgeEffect.java @@ -23,6 +23,8 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.Interpolator; import com.android.gallery3d.R; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.ResourceTexture; // This is copied from android.widget.EdgeEffect with some small modifications: // (1) Copy the images (overscroll_{edge|glow}.png) to local resources. diff --git a/src/com/android/gallery3d/ui/EdgeView.java b/src/com/android/gallery3d/ui/EdgeView.java index 4aff0494d..051de18fa 100644 --- a/src/com/android/gallery3d/ui/EdgeView.java +++ b/src/com/android/gallery3d/ui/EdgeView.java @@ -19,6 +19,8 @@ package com.android.gallery3d.ui; import android.content.Context; import android.opengl.Matrix; +import com.android.gallery3d.glrenderer.GLCanvas; + // EdgeView draws EdgeEffect (blue glow) at four sides of the view. public class EdgeView extends GLView { @SuppressWarnings("unused") diff --git a/src/com/android/gallery3d/ui/GLCanvas.java b/src/com/android/gallery3d/ui/GLCanvas.java deleted file mode 100644 index 6f8baef7e..000000000 --- a/src/com/android/gallery3d/ui/GLCanvas.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.ui; - -import android.graphics.RectF; - -import javax.microedition.khronos.opengles.GL11; - -// -// GLCanvas gives a convenient interface to draw using OpenGL. -// -// When a rectangle is specified in this interface, it means the region -// [x, x+width) * [y, y+height) -// -public interface GLCanvas { - // Tells GLCanvas the size of the underlying GL surface. This should be - // called before first drawing and when the size of GL surface is changed. - // This is called by GLRoot and should not be called by the clients - // who only want to draw on the GLCanvas. Both width and height must be - // nonnegative. - public void setSize(int width, int height); - - // Clear the drawing buffers. This should only be used by GLRoot. - public void clearBuffer(); - public void clearBuffer(float[] argb); - - // Sets and gets the current alpha, alpha must be in [0, 1]. - public void setAlpha(float alpha); - public float getAlpha(); - - // (current alpha) = (current alpha) * alpha - public void multiplyAlpha(float alpha); - - // Change the current transform matrix. - public void translate(float x, float y, float z); - public void translate(float x, float y); - public void scale(float sx, float sy, float sz); - public void rotate(float angle, float x, float y, float z); - public void multiplyMatrix(float[] mMatrix, int offset); - - // Pushes the configuration state (matrix, and alpha) onto - // a private stack. - public void save(); - - // Same as save(), but only save those specified in saveFlags. - public void save(int saveFlags); - - public static final int SAVE_FLAG_ALL = 0xFFFFFFFF; - public static final int SAVE_FLAG_ALPHA = 0x01; - public static final int SAVE_FLAG_MATRIX = 0x02; - - // Pops from the top of the stack as current configuration state (matrix, - // alpha, and clip). This call balances a previous call to save(), and is - // used to remove all modifications to the configuration state since the - // last save call. - public void restore(); - - // Draws a line using the specified paint from (x1, y1) to (x2, y2). - // (Both end points are included). - public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint); - - // Draws a rectangle using the specified paint from (x1, y1) to (x2, y2). - // (Both end points are included). - public void drawRect(float x1, float y1, float x2, float y2, GLPaint paint); - - // Fills the specified rectangle with the specified color. - public void fillRect(float x, float y, float width, float height, int color); - - // Draws a texture to the specified rectangle. - public void drawTexture( - BasicTexture texture, int x, int y, int width, int height); - public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, - int uvBuffer, int indexBuffer, int indexCount); - - // Draws the source rectangle part of the texture to the target rectangle. - public void drawTexture(BasicTexture texture, RectF source, RectF target); - - // Draw a texture with a specified texture transform. - public void drawTexture(BasicTexture texture, float[] mTextureTransform, - int x, int y, int w, int h); - - // Draw two textures to the specified rectangle. The actual texture used is - // from * (1 - ratio) + to * ratio - // The two textures must have the same size. - public void drawMixed(BasicTexture from, int toColor, - float ratio, int x, int y, int w, int h); - - // Draw a region of a texture and a specified color to the specified - // rectangle. The actual color used is from * (1 - ratio) + to * ratio. - // The region of the texture is defined by parameter "src". The target - // rectangle is specified by parameter "target". - public void drawMixed(BasicTexture from, int toColor, - float ratio, RectF src, RectF target); - - // Gets the underlying GL instance. This is used only when direct access to - // GL is needed. - public GL11 getGLInstance(); - - // Unloads the specified texture from the canvas. The resource allocated - // to draw the texture will be released. The specified texture will return - // to the unloaded state. This function should be called only from - // BasicTexture or its descendant - public boolean unloadTexture(BasicTexture texture); - - // Delete the specified buffer object, similar to unloadTexture. - public void deleteBuffer(int bufferId); - - // Delete the textures and buffers in GL side. This function should only be - // called in the GL thread. - public void deleteRecycledResources(); - - // Dump statistics information and clear the counters. For debug only. - public void dumpStatisticsAndClear(); - - public void beginRenderTarget(RawTexture texture); - - public void endRenderTarget(); -} diff --git a/src/com/android/gallery3d/ui/GLId.java b/src/com/android/gallery3d/ui/GLId.java deleted file mode 100644 index 689cf192e..000000000 --- a/src/com/android/gallery3d/ui/GLId.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2012 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.gallery3d.ui; - -import javax.microedition.khronos.opengles.GL11; -import javax.microedition.khronos.opengles.GL11ExtensionPack; - -// This mimics corresponding GL functions. -public class GLId { - static int sNextId = 1; - - public synchronized static void glGenTextures(int n, int[] textures, int offset) { - while (n-- > 0) { - textures[offset + n] = sNextId++; - } - } - - public synchronized static void glGenBuffers(int n, int[] buffers, int offset) { - while (n-- > 0) { - buffers[offset + n] = sNextId++; - } - } - - public synchronized static void glDeleteTextures(GL11 gl, int n, int[] textures, int offset) { - gl.glDeleteTextures(n, textures, offset); - } - - public synchronized static void glDeleteBuffers(GL11 gl, int n, int[] buffers, int offset) { - gl.glDeleteBuffers(n, buffers, offset); - } - - public synchronized static void glDeleteFramebuffers( - GL11ExtensionPack gl11ep, int n, int[] buffers, int offset) { - gl11ep.glDeleteFramebuffersOES(n, buffers, offset); - } -} diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java index e406b6703..33a82eaf7 100644 --- a/src/com/android/gallery3d/ui/GLRoot.java +++ b/src/com/android/gallery3d/ui/GLRoot.java @@ -20,6 +20,7 @@ import android.content.Context; import android.graphics.Matrix; import com.android.gallery3d.anim.CanvasAnimation; +import com.android.gallery3d.glrenderer.GLCanvas; public interface GLRoot { diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java index b7c48bf2e..755e10733 100644 --- a/src/com/android/gallery3d/ui/GLRootView.java +++ b/src/com/android/gallery3d/ui/GLRootView.java @@ -33,6 +33,9 @@ import com.android.gallery3d.R; import com.android.gallery3d.anim.CanvasAnimation; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.glrenderer.BasicTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.UploadedTexture; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.MotionEventHelper; import com.android.gallery3d.util.Profile; @@ -117,6 +120,7 @@ public class GLRootView extends GLSurfaceView super(context, attrs); mFlags |= FLAG_INITIALIZED; setBackgroundDrawable(null); + setEGLContextClientVersion(GLCanvas.getEGLContextClientVersion()); setEGLConfigChooser(mEglConfigChooser); setRenderer(this); if (ApiHelper.USE_888_PIXEL_FORMAT) { @@ -283,7 +287,8 @@ public class GLRootView extends GLSurfaceView mRenderLock.lock(); try { mGL = gl; - mCanvas = new GLCanvasImpl(gl); + mCanvas = GLCanvas.getInstance(); + mCanvas.initialize(gl); BasicTexture.invalidateAllTextures(); } finally { mRenderLock.unlock(); diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java index 664012c5a..83de19fe4 100644 --- a/src/com/android/gallery3d/ui/GLView.java +++ b/src/com/android/gallery3d/ui/GLView.java @@ -23,6 +23,7 @@ import android.view.MotionEvent; import com.android.gallery3d.anim.CanvasAnimation; import com.android.gallery3d.anim.StateTransitionAnimation; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.glrenderer.GLCanvas; import java.util.ArrayList; diff --git a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java index deeb3b76d..4cf3edb34 100644 --- a/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java +++ b/src/com/android/gallery3d/ui/GalleryEGLConfigChooser.java @@ -18,6 +18,7 @@ package com.android.gallery3d.ui; import android.opengl.GLSurfaceView.EGLConfigChooser; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.glrenderer.GLCanvas; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; @@ -49,12 +50,35 @@ class GalleryEGLConfigChooser implements EGLConfigChooser { EGL10.EGL_NONE }; + private final int mConfig2Spec565[] = new int[] { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_RENDERABLE_TYPE, 4, /* EGL_OPENGL_ES2_BIT */ + EGL10.EGL_NONE + }; + + private final int mConfig2Spec888[] = new int[] { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_RENDERABLE_TYPE, 4, /* EGL_OPENGL_ES2_BIT */ + EGL10.EGL_NONE + }; + @Override public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { int[] numConfig = new int[1]; - int mConfigSpec[] = ApiHelper.USE_888_PIXEL_FORMAT - ? mConfigSpec888 : mConfigSpec565; - if (!egl.eglChooseConfig(display, mConfigSpec, null, 0, numConfig)) { + + int configSpec[]; + if (GLCanvas.getEGLContextClientVersion() == 2) { + configSpec = ApiHelper.USE_888_PIXEL_FORMAT ? mConfig2Spec888 : mConfig2Spec565; + } else { + configSpec = ApiHelper.USE_888_PIXEL_FORMAT ? mConfigSpec888 : mConfigSpec565; + } + if (!egl.eglChooseConfig(display, configSpec, null, 0, numConfig)) { throw new RuntimeException("eglChooseConfig failed"); } @@ -64,7 +88,7 @@ class GalleryEGLConfigChooser implements EGLConfigChooser { EGLConfig[] configs = new EGLConfig[numConfig[0]]; if (!egl.eglChooseConfig(display, - mConfigSpec, configs, configs.length, numConfig)) { + configSpec, configs, configs.length, numConfig)) { throw new RuntimeException(); } diff --git a/src/com/android/gallery3d/ui/GestureRecognizer.java b/src/com/android/gallery3d/ui/GestureRecognizer.java index e4e0c49f5..1e5250b9b 100644 --- a/src/com/android/gallery3d/ui/GestureRecognizer.java +++ b/src/com/android/gallery3d/ui/GestureRecognizer.java @@ -32,7 +32,7 @@ public class GestureRecognizer { boolean onSingleTapUp(float x, float y); boolean onDoubleTap(float x, float y); boolean onScroll(float dx, float dy, float totalX, float totalY); - boolean onFling(float velocityX, float velocityY); + boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY); boolean onScaleBegin(float focusX, float focusY); boolean onScale(float focusX, float focusY, float scale); void onScaleEnd(); @@ -94,7 +94,7 @@ public class GestureRecognizer { @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - return mListener.onFling(velocityX, velocityY); + return mListener.onFling(e1, e2, velocityX, velocityY); } } diff --git a/src/com/android/gallery3d/ui/Log.java b/src/com/android/gallery3d/ui/Log.java index 32adc98eb..5570763bb 100644 --- a/src/com/android/gallery3d/ui/Log.java +++ b/src/com/android/gallery3d/ui/Log.java @@ -16,6 +16,7 @@ package com.android.gallery3d.ui; +// TODO: Delete this public class Log { public static int v(String tag, String msg) { return android.util.Log.v(tag, msg); diff --git a/src/com/android/gallery3d/ui/ManageCacheDrawer.java b/src/com/android/gallery3d/ui/ManageCacheDrawer.java index e989af27d..d210bd1f1 100644 --- a/src/com/android/gallery3d/ui/ManageCacheDrawer.java +++ b/src/com/android/gallery3d/ui/ManageCacheDrawer.java @@ -23,6 +23,9 @@ import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.data.DataSourceType; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.ResourceTexture; +import com.android.gallery3d.glrenderer.StringTexture; import com.android.gallery3d.ui.AlbumSetSlidingWindow.AlbumSetEntry; public class ManageCacheDrawer extends AlbumSetSlotRenderer { diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java index f432333ce..46e400483 100644 --- a/src/com/android/gallery3d/ui/MenuExecutor.java +++ b/src/com/android/gallery3d/ui/MenuExecutor.java @@ -31,7 +31,6 @@ import android.view.MenuItem; import com.android.gallery3d.R; import com.android.gallery3d.app.AbstractGalleryActivity; -import com.android.gallery3d.app.CropImage; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; @@ -62,6 +61,7 @@ public class MenuExecutor { private Future<?> mTask; // wait the operation to finish when we want to stop it. private boolean mWaitOnStop; + private boolean mPaused; private final AbstractGalleryActivity mActivity; private final SelectionManager mSelectionManager; @@ -113,7 +113,7 @@ public class MenuExecutor { break; } case MSG_TASK_UPDATE: { - if (mDialog != null) mDialog.setProgress(message.arg1); + if (mDialog != null && !mPaused) mDialog.setProgress(message.arg1); if (message.obj != null) { ProgressListener listener = (ProgressListener) message.obj; listener.onProgressUpdate(message.arg1); @@ -133,13 +133,23 @@ public class MenuExecutor { if (mTask != null) { if (!mWaitOnStop) mTask.cancel(); mTask.waitDone(); - mDialog.dismiss(); + if (mDialog != null && mDialog.isShowing()) mDialog.dismiss(); mDialog = null; mTask = null; } } + public void resume() { + mPaused = false; + if (mDialog != null) mDialog.show(); + } + public void pause() { + mPaused = true; + if (mDialog != null && mDialog.isShowing()) mDialog.hide(); + } + + public void destroy() { stopTaskAndDismissDialog(); } @@ -161,6 +171,7 @@ public class MenuExecutor { boolean supportRotate = (supported & MediaObject.SUPPORT_ROTATE) != 0; boolean supportCrop = (supported & MediaObject.SUPPORT_CROP) != 0; boolean supportTrim = (supported & MediaObject.SUPPORT_TRIM) != 0; + boolean supportMute = (supported & MediaObject.SUPPORT_MUTE) != 0; boolean supportShare = (supported & MediaObject.SUPPORT_SHARE) != 0; boolean supportSetAs = (supported & MediaObject.SUPPORT_SETAS) != 0; boolean supportShowOnMap = (supported & MediaObject.SUPPORT_SHOW_ON_MAP) != 0; @@ -174,6 +185,7 @@ public class MenuExecutor { setMenuItemVisible(menu, R.id.action_rotate_cw, supportRotate); setMenuItemVisible(menu, R.id.action_crop, supportCrop); setMenuItemVisible(menu, R.id.action_trim, supportTrim); + setMenuItemVisible(menu, R.id.action_mute, supportMute); // Hide panorama until call to updateMenuForPanorama corrects it setMenuItemVisible(menu, R.id.action_share_panorama, false); setMenuItemVisible(menu, R.id.action_share, supportShare); @@ -332,7 +344,7 @@ public class MenuExecutor { mDialog.show(); } MediaOperation operation = new MediaOperation(action, ids, listener); - mTask = mActivity.getThreadPool().submit(operation, null); + mTask = mActivity.getBatchServiceThreadPoolIfAvailable().submit(operation, null); mWaitOnStop = waitOnStop; } diff --git a/src/com/android/gallery3d/ui/PhotoFallbackEffect.java b/src/com/android/gallery3d/ui/PhotoFallbackEffect.java index 3ca09ab96..4603285a4 100644 --- a/src/com/android/gallery3d/ui/PhotoFallbackEffect.java +++ b/src/com/android/gallery3d/ui/PhotoFallbackEffect.java @@ -23,6 +23,8 @@ import android.view.animation.Interpolator; import com.android.gallery3d.anim.Animation; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.RawTexture; import com.android.gallery3d.ui.AlbumSlotRenderer.SlotFilter; import java.util.ArrayList; diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index 6dcae4ca4..40e01ab07 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -35,6 +35,11 @@ import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.Path; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.RawTexture; +import com.android.gallery3d.glrenderer.ResourceTexture; +import com.android.gallery3d.glrenderer.StringTexture; +import com.android.gallery3d.glrenderer.Texture; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.RangeArray; @@ -52,7 +57,7 @@ public class PhotoView extends GLView { public int height; } - public interface Model extends TileImageView.Model { + public interface Model extends TileImageView.TileSource { public int getCurrentIndex(); public void moveTo(int index); @@ -174,8 +179,9 @@ public class PhotoView extends GLView { public static final int SCREEN_NAIL_MAX = 3; // These are constants for the delete gesture. - private static final int SWIPE_ESCAPE_VELOCITY = 2500; // dp/sec - private static final int MAX_DISMISS_VELOCITY = 4000; // dp/sec + private static final int SWIPE_ESCAPE_VELOCITY = 500; // dp/sec + private static final int MAX_DISMISS_VELOCITY = 2500; // dp/sec + private static final int SWIPE_ESCAPE_DISTANCE = 150; // dp // The picture entries, the valid index is from -SCREEN_NAIL_MAX to // SCREEN_NAIL_MAX. @@ -1070,19 +1076,19 @@ public class PhotoView extends GLView { } @Override - public boolean onFling(float velocityX, float velocityY) { + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (mIgnoreSwipingGesture) return true; if (mModeChanged) return true; if (swipeImages(velocityX, velocityY)) { mIgnoreUpEvent = true; } else { - flingImages(velocityX, velocityY); + flingImages(velocityX, velocityY, Math.abs(e2.getY() - e1.getY())); } mHadFling = true; return true; } - private boolean flingImages(float velocityX, float velocityY) { + private boolean flingImages(float velocityX, float velocityY, float dY) { int vx = (int) (velocityX + 0.5f); int vy = (int) (velocityY + 0.5f); if (!mFilmMode) { @@ -1099,11 +1105,13 @@ public class PhotoView extends GLView { } int maxVelocity = GalleryUtils.dpToPixel(MAX_DISMISS_VELOCITY); int escapeVelocity = GalleryUtils.dpToPixel(SWIPE_ESCAPE_VELOCITY); + int escapeDistance = GalleryUtils.dpToPixel(SWIPE_ESCAPE_DISTANCE); int centerY = mPositionController.getPosition(mTouchBoxIndex) .centerY(); boolean fastEnough = (Math.abs(vy) > escapeVelocity) && (Math.abs(vy) > Math.abs(vx)) - && ((vy > 0) == (centerY > getHeight() / 2)); + && ((vy > 0) == (centerY > getHeight() / 2)) + && dY >= escapeDistance; if (fastEnough) { vy = Math.min(vy, maxVelocity); int duration = mPositionController.flingFilmY(mTouchBoxIndex, vy); @@ -1237,7 +1245,10 @@ public class PhotoView extends GLView { if (mFilmMode) { int xi = (int) (x + 0.5f); int yi = (int) (y + 0.5f); - mTouchBoxIndex = mPositionController.hitTest(xi, yi); + // We only care about being within the x bounds, necessary for + // handling very wide images which are otherwise very hard to fling + mTouchBoxIndex = mPositionController.hitTest(xi, getHeight() / 2); + if (mTouchBoxIndex < mPrevBound || mTouchBoxIndex > mNextBound) { mTouchBoxIndex = Integer.MAX_VALUE; } else { diff --git a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java index 36e7f4b82..f52aa5ff2 100644 --- a/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java +++ b/src/com/android/gallery3d/ui/PreparePageFadeoutTexture.java @@ -3,6 +3,8 @@ package com.android.gallery3d.ui; import android.os.ConditionVariable; import com.android.gallery3d.app.AbstractGalleryActivity; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.RawTexture; import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; public class PreparePageFadeoutTexture implements OnGLIdleListener { diff --git a/src/com/android/gallery3d/ui/ProgressSpinner.java b/src/com/android/gallery3d/ui/ProgressSpinner.java index 4f9381c76..1b31af278 100644 --- a/src/com/android/gallery3d/ui/ProgressSpinner.java +++ b/src/com/android/gallery3d/ui/ProgressSpinner.java @@ -19,6 +19,8 @@ package com.android.gallery3d.ui; import android.content.Context; import com.android.gallery3d.R; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.ResourceTexture; public class ProgressSpinner { private static float ROTATE_SPEED_OUTER = 1080f / 3500f; diff --git a/src/com/android/gallery3d/ui/ScreenNail.java b/src/com/android/gallery3d/ui/ScreenNail.java index 0a16ab850..965bf0b54 100644 --- a/src/com/android/gallery3d/ui/ScreenNail.java +++ b/src/com/android/gallery3d/ui/ScreenNail.java @@ -17,6 +17,8 @@ package com.android.gallery3d.ui; import android.graphics.RectF; +import com.android.gallery3d.glrenderer.GLCanvas; + public interface ScreenNail { public int getWidth(); public int getHeight(); diff --git a/src/com/android/gallery3d/ui/ScrollBarView.java b/src/com/android/gallery3d/ui/ScrollBarView.java index 82d4800cc..34fbcef7a 100644 --- a/src/com/android/gallery3d/ui/ScrollBarView.java +++ b/src/com/android/gallery3d/ui/ScrollBarView.java @@ -20,6 +20,9 @@ import android.content.Context; import android.graphics.Rect; import android.util.TypedValue; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.NinePatchTexture; + public class ScrollBarView extends GLView { @SuppressWarnings("unused") private static final String TAG = "ScrollBarView"; diff --git a/src/com/android/gallery3d/ui/SlideshowView.java b/src/com/android/gallery3d/ui/SlideshowView.java index bb36c47e9..b215c6661 100644 --- a/src/com/android/gallery3d/ui/SlideshowView.java +++ b/src/com/android/gallery3d/ui/SlideshowView.java @@ -21,11 +21,12 @@ import android.graphics.PointF; import com.android.gallery3d.anim.CanvasAnimation; import com.android.gallery3d.anim.FloatAnimation; +import com.android.gallery3d.glrenderer.BitmapTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.GLCanvas.Blending; import java.util.Random; -import javax.microedition.khronos.opengles.GL11; - public class SlideshowView extends GLView { @SuppressWarnings("unused") private static final String TAG = "SlideshowView"; @@ -93,8 +94,8 @@ public class SlideshowView extends GLView { protected void render(GLCanvas canvas) { long animTime = AnimationTime.get(); boolean requestRender = mTransitionAnimation.calculate(animTime); - GL11 gl = canvas.getGLInstance(); - gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE); + canvas.save(GLCanvas.SAVE_FLAG_BLEND); + canvas.setBlending(Blending.Additive); float alpha = mPrevTexture == null ? 1f : mTransitionAnimation.get(); if (mPrevTexture != null && alpha != 1f) { @@ -118,7 +119,7 @@ public class SlideshowView extends GLView { canvas.restore(); } if (requestRender) invalidate(); - gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA); + canvas.restore(); } private class SlideshowAnimation extends CanvasAnimation { diff --git a/src/com/android/gallery3d/ui/SlotView.java b/src/com/android/gallery3d/ui/SlotView.java index 15a583e5c..bd0ffdc15 100644 --- a/src/com/android/gallery3d/ui/SlotView.java +++ b/src/com/android/gallery3d/ui/SlotView.java @@ -25,6 +25,7 @@ import android.view.animation.DecelerateInterpolator; import com.android.gallery3d.anim.Animation; import com.android.gallery3d.app.AbstractGalleryActivity; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.glrenderer.GLCanvas; public class SlotView extends GLView { @SuppressWarnings("unused") diff --git a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java index ceed71abe..ef8959c9d 100644 --- a/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java +++ b/src/com/android/gallery3d/ui/SurfaceTextureScreenNail.java @@ -21,6 +21,8 @@ import android.graphics.RectF; import android.graphics.SurfaceTexture; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.glrenderer.ExtTexture; +import com.android.gallery3d.glrenderer.GLCanvas; @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) public abstract class SurfaceTextureScreenNail implements ScreenNail, diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index 8f26981fe..f1c31e49d 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -16,20 +16,24 @@ package com.android.gallery3d.ui; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; +import android.support.v4.util.LongSparseArray; +import android.util.DisplayMetrics; import android.util.FloatMath; +import android.view.WindowManager; import com.android.gallery3d.app.GalleryContext; import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.common.LongSparseArray; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.BitmapPool; import com.android.gallery3d.data.DecodeUtils; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.UploadedTexture; import com.android.gallery3d.util.Future; -import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.ThreadPool; import com.android.gallery3d.util.ThreadPool.CancelListener; import com.android.gallery3d.util.ThreadPool.JobContext; @@ -41,14 +45,11 @@ public class TileImageView extends GLView { @SuppressWarnings("unused") private static final String TAG = "TileImageView"; - - // TILE_SIZE must be 2^N - 2. We put one pixel border in each side of the - // texture to avoid seams between tiles. - private static int TILE_SIZE; - private static final int TILE_BORDER = 1; - private static int BITMAP_SIZE; private static final int UPLOAD_LIMIT = 1; + // TILE_SIZE must be 2^N + private static int sTileSize; + private static BitmapPool sTilePool; /* @@ -76,7 +77,7 @@ public class TileImageView extends GLView { private static final int STATE_RECYCLING = 0x20; private static final int STATE_RECYCLED = 0x40; - private Model mModel; + private TileSource mModel; private ScreenNail mScreenNail; protected int mLevelCount; // cache the value of mScaledBitmaps.length @@ -125,7 +126,7 @@ public class TileImageView extends GLView { private final ThreadPool mThreadPool; private boolean mBackgroundTileUploaded; - public static interface Model { + public static interface TileSource { public int getLevelCount(); public ScreenNail getScreenNail(); public int getImageWidth(); @@ -133,8 +134,7 @@ public class TileImageView extends GLView { // The tile returned by this method can be specified this way: Assuming // the image size is (width, height), first take the intersection of (0, - // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). Then - // extend this intersection region by borderSize pixels on each side. If + // 0) - (width, height) and (x, y) - (x + tileSize, y + tileSize). If // in extending the region, we found some part of the region are outside // the image, those pixels are filled with black. // @@ -144,27 +144,34 @@ public class TileImageView extends GLView { // // The method would be called in another thread. public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize, BitmapPool pool); + BitmapPool pool); + } + + public static boolean isHighResolution(Context context) { + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager wm = (WindowManager) + context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metrics); + return metrics.heightPixels > 2048 || metrics.widthPixels > 2048; } public TileImageView(GalleryContext context) { mThreadPool = context.getThreadPool(); mTileDecoder = mThreadPool.submit(new TileDecoder()); - if (TILE_SIZE == 0) { - if (GalleryUtils.isHighResolution(context.getAndroidContext())) { - TILE_SIZE = 510 ; + if (sTileSize == 0) { + if (isHighResolution(context.getAndroidContext())) { + sTileSize = 512 ; } else { - TILE_SIZE = 254; + sTileSize = 256; } - BITMAP_SIZE = TILE_SIZE + TILE_BORDER * 2; sTilePool = ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER - ? new BitmapPool(BITMAP_SIZE, BITMAP_SIZE, 128) + ? new BitmapPool(sTileSize, sTileSize, 128) : null; } } - public void setModel(Model model) { + public void setModel(TileSource model) { mModel = model; if (model != null) notifyModelInvalidated(); } @@ -266,7 +273,7 @@ public class TileImageView extends GLView { } for (int i = fromLevel; i < endLevel; ++i) { - int size = TILE_SIZE << i; + int size = sTileSize << i; Rect r = range[i - fromLevel]; for (int y = r.top, bottom = r.bottom; y < bottom; y += size) { for (int x = r.left, right = r.right; x < right; x += size) { @@ -320,7 +327,7 @@ public class TileImageView extends GLView { int bottom = (int) FloatMath.ceil(top + height / scale); // align the rectangle to tile boundary - int size = TILE_SIZE << level; + int size = sTileSize << level; left = Math.max(0, size * (left / size)); top = Math.max(0, size * (top / size)); right = Math.min(mImageWidth, right); @@ -431,7 +438,7 @@ public class TileImageView extends GLView { mScreenNail.noDraw(); } - int size = (TILE_SIZE << level); + int size = (sTileSize << level); float length = size * mScale; Rect r = mTileRange; @@ -594,7 +601,7 @@ public class TileImageView extends GLView { RectF source = mSourceRect; RectF target = mTargetRect; target.set(x, y, x + length, y + length); - source.set(0, 0, TILE_SIZE, TILE_SIZE); + source.set(0, 0, sTileSize, sTileSize); Tile tile = getTile(tx, ty, level); if (tile != null) { @@ -614,7 +621,7 @@ public class TileImageView extends GLView { if (drawTile(tile, canvas, source, target)) return; } if (mScreenNail != null) { - int size = TILE_SIZE << level; + int size = sTileSize << level; float scaleX = (float) mScreenNail.getWidth() / mImageWidth; float scaleY = (float) mScreenNail.getHeight() / mImageHeight; source.set(tx * scaleX, ty * scaleY, (tx + size) * scaleX, @@ -627,8 +634,6 @@ public class TileImageView extends GLView { Tile tile, GLCanvas canvas, RectF source, RectF target) { while (true) { if (tile.isContentValid()) { - // offset source rectangle for the texture border. - source.offset(TILE_BORDER, TILE_BORDER); canvas.drawTexture(tile, source, target); return true; } @@ -640,15 +645,15 @@ public class TileImageView extends GLView { source.left /= 2f; source.right /= 2f; } else { - source.left = (TILE_SIZE + source.left) / 2f; - source.right = (TILE_SIZE + source.right) / 2f; + source.left = (sTileSize + source.left) / 2f; + source.right = (sTileSize + source.right) / 2f; } if (tile.mY == parent.mY) { source.top /= 2f; source.bottom /= 2f; } else { - source.top = (TILE_SIZE + source.top) / 2f; - source.bottom = (TILE_SIZE + source.bottom) / 2f; + source.top = (sTileSize + source.top) / 2f; + source.bottom = (sTileSize + source.bottom) / 2f; } tile = parent; } @@ -678,7 +683,7 @@ public class TileImageView extends GLView { // by (1 << mTilelevel) from a region in the original image. try { mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile( - mTileLevel, mX, mY, TILE_SIZE, TILE_BORDER, sTilePool)); + mTileLevel, mX, mY, sTileSize, sTilePool)); } catch (Throwable t) { Log.w(TAG, "fail to decode tile", t); } @@ -691,9 +696,9 @@ public class TileImageView extends GLView { // We need to override the width and height, so that we won't // draw beyond the boundaries. - int rightEdge = ((mImageWidth - mX) >> mTileLevel) + TILE_BORDER; - int bottomEdge = ((mImageHeight - mY) >> mTileLevel) + TILE_BORDER; - setSize(Math.min(BITMAP_SIZE, rightEdge), Math.min(BITMAP_SIZE, bottomEdge)); + int rightEdge = ((mImageWidth - mX) >> mTileLevel); + int bottomEdge = ((mImageHeight - mY) >> mTileLevel); + setSize(Math.min(sTileSize, rightEdge), Math.min(sTileSize, bottomEdge)); Bitmap bitmap = mDecodedTile; mDecodedTile = null; @@ -707,12 +712,12 @@ public class TileImageView extends GLView { // boundary). @Override public int getTextureWidth() { - return TILE_SIZE + TILE_BORDER * 2; + return sTileSize; } @Override public int getTextureHeight() { - return TILE_SIZE + TILE_BORDER * 2; + return sTileSize; } public void update(int x, int y, int level) { @@ -724,7 +729,7 @@ public class TileImageView extends GLView { public Tile getParentTile() { if (mTileLevel + 1 == mLevelCount) return null; - int size = TILE_SIZE << (mTileLevel + 1); + int size = sTileSize << (mTileLevel + 1); int x = size * (mX / size); int y = size * (mY / size); return getTile(x, y, mTileLevel + 1); @@ -733,7 +738,7 @@ public class TileImageView extends GLView { @Override public String toString() { return String.format("tile(%s, %s, %s / %s)", - mX / TILE_SIZE, mY / TILE_SIZE, mLevel, mLevelCount); + mX / sTileSize, mY / sTileSize, mLevel, mLevelCount); } } diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java index 45e2ce218..0d20b0757 100644 --- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java +++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java @@ -28,7 +28,7 @@ import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.BitmapPool; -public class TileImageViewAdapter implements TileImageView.Model { +public class TileImageViewAdapter implements TileImageView.TileSource { private static final String TAG = "TileImageViewAdapter"; protected ScreenNail mScreenNail; protected boolean mOwnScreenNail; @@ -84,16 +84,14 @@ public class TileImageViewAdapter implements TileImageView.Model { // (44, 44, 256, 256) from the original photo and down sample it to 106. @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) @Override - public Bitmap getTile(int level, int x, int y, int tileSize, - int borderSize, BitmapPool pool) { + public Bitmap getTile(int level, int x, int y, int tileSize, BitmapPool pool) { if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) { - return getTileWithoutReusingBitmap(level, x, y, tileSize, borderSize); + return getTileWithoutReusingBitmap(level, x, y, tileSize); } - int b = borderSize << level; int t = tileSize << level; - Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b); + Rect wantRegion = new Rect(x, y, x + t, y + t); boolean needClear; BitmapRegionDecoder regionDecoder = null; @@ -112,8 +110,7 @@ public class TileImageViewAdapter implements TileImageView.Model { if (bitmap != null) { if (needClear) bitmap.eraseColor(0); } else { - int s = tileSize + 2 * borderSize; - bitmap = Bitmap.createBitmap(s, s, Config.ARGB_8888); + bitmap = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888); } BitmapFactory.Options options = new BitmapFactory.Options(); @@ -141,10 +138,9 @@ public class TileImageViewAdapter implements TileImageView.Model { } private Bitmap getTileWithoutReusingBitmap( - int level, int x, int y, int tileSize, int borderSize) { - int b = borderSize << level; + int level, int x, int y, int tileSize) { int t = tileSize << level; - Rect wantRegion = new Rect(x - b, y - b, x + t + b, y + t + b); + Rect wantRegion = new Rect(x, y, x + t, y + t); BitmapRegionDecoder regionDecoder; Rect overlapRegion; @@ -173,8 +169,7 @@ public class TileImageViewAdapter implements TileImageView.Model { if (wantRegion.equals(overlapRegion)) return bitmap; - int s = tileSize + 2 * borderSize; - Bitmap result = Bitmap.createBitmap(s, s, Config.ARGB_8888); + Bitmap result = Bitmap.createBitmap(tileSize, tileSize, Config.ARGB_8888); Canvas canvas = new Canvas(result); canvas.drawBitmap(bitmap, (overlapRegion.left - wantRegion.left) >> level, diff --git a/src/com/android/gallery3d/ui/TiledScreenNail.java b/src/com/android/gallery3d/ui/TiledScreenNail.java index 74665f584..ab24f5b6c 100644 --- a/src/com/android/gallery3d/ui/TiledScreenNail.java +++ b/src/com/android/gallery3d/ui/TiledScreenNail.java @@ -22,6 +22,8 @@ import android.graphics.RectF; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.BitmapPool; import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.TiledTexture; // This is a ScreenNail wraps a Bitmap. There are some extra functions: // diff --git a/src/com/android/gallery3d/ui/UndoBarView.java b/src/com/android/gallery3d/ui/UndoBarView.java index 8c9836deb..42f12ae72 100644 --- a/src/com/android/gallery3d/ui/UndoBarView.java +++ b/src/com/android/gallery3d/ui/UndoBarView.java @@ -21,6 +21,10 @@ import android.view.MotionEvent; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.NinePatchTexture; +import com.android.gallery3d.glrenderer.ResourceTexture; +import com.android.gallery3d.glrenderer.StringTexture; import com.android.gallery3d.util.GalleryUtils; public class UndoBarView extends GLView { diff --git a/src/com/android/gallery3d/util/AccessibilityUtils.java b/src/com/android/gallery3d/util/AccessibilityUtils.java new file mode 100644 index 000000000..9df8e4ece --- /dev/null +++ b/src/com/android/gallery3d/util/AccessibilityUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import android.content.Context; +import android.support.v4.view.accessibility.AccessibilityRecordCompat; +import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; + +import com.android.gallery3d.common.ApiHelper; + +/** + * AccessibilityUtils provides functions needed in accessibility mode. All the functions + * in this class are made compatible with gingerbread and later API's +*/ +public class AccessibilityUtils { + public static void makeAnnouncement(View view, CharSequence announcement) { + if (view == null) + return; + if (ApiHelper.HAS_ANNOUNCE_FOR_ACCESSIBILITY) { + view.announceForAccessibility(announcement); + } else { + // For API 15 and earlier, we need to construct an accessibility event + Context ctx = view.getContext(); + AccessibilityManager am = (AccessibilityManager) ctx.getSystemService( + Context.ACCESSIBILITY_SERVICE); + if (!am.isEnabled()) return; + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); + AccessibilityRecordCompat arc = new AccessibilityRecordCompat(event); + arc.setSource(view); + event.setClassName(view.getClass().getName()); + event.setPackageName(view.getContext().getPackageName()); + event.setEnabled(view.isEnabled()); + event.getText().add(announcement); + am.sendAccessibilityEvent(event); + } + } +}
\ No newline at end of file diff --git a/src/com/android/gallery3d/util/BucketNames.java b/src/com/android/gallery3d/util/BucketNames.java index df7684a04..990dc8224 100644 --- a/src/com/android/gallery3d/util/BucketNames.java +++ b/src/com/android/gallery3d/util/BucketNames.java @@ -21,7 +21,9 @@ package com.android.gallery3d.util; */ public class BucketNames { + public static final String CAMERA = "DCIM/Camera"; public static final String IMPORTED = "Imported"; public static final String DOWNLOAD = "download"; public static final String EDITED_ONLINE_PHOTOS = "EditedOnlinePhotos"; + public static final String SCREENSHOTS = "Pictures/Screenshots"; } diff --git a/src/com/android/gallery3d/util/GalleryUtils.java b/src/com/android/gallery3d/util/GalleryUtils.java index 547e2dda0..9245e2c5f 100644 --- a/src/com/android/gallery3d/util/GalleryUtils.java +++ b/src/com/android/gallery3d/util/GalleryUtils.java @@ -46,6 +46,7 @@ import com.android.gallery3d.ui.TiledScreenNail; import com.android.gallery3d.util.ThreadPool.CancelListener; import com.android.gallery3d.util.ThreadPool.JobContext; +import java.io.File; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -94,14 +95,6 @@ public class GalleryUtils { TiledScreenNail.setMaxSide(maxPixels / 2); } - public static boolean isHighResolution(Context context) { - DisplayMetrics metrics = new DisplayMetrics(); - WindowManager wm = (WindowManager) - context.getSystemService(Context.WINDOW_SERVICE); - wm.getDefaultDisplay().getMetrics(metrics); - return metrics.heightPixels > 2048 || metrics.widthPixels > 2048; - } - public static float[] intColorToFloatARGBArray(int from) { return new float[] { Color.alpha(from) / 255f, @@ -259,7 +252,9 @@ public class GalleryUtils { } public static void startGalleryActivity(Context context) { - Intent intent = new Intent(context, Gallery.class); + Intent intent = new Intent(context, Gallery.class) + .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP + | Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } @@ -315,6 +310,26 @@ public class GalleryUtils { return path.toLowerCase().hashCode(); } + // Return the local path that matches the given bucketId. If no match is + // found, return null + public static String searchDirForPath(File dir, int bucketId) { + File[] files = dir.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + String path = file.getAbsolutePath(); + if (GalleryUtils.getBucketId(path) == bucketId) { + return path; + } else { + path = searchDirForPath(file, bucketId); + if (path != null) return path; + } + } + } + } + return null; + } + // Returns a (localized) string for the given duration (in seconds). public static String formatDuration(final Context context, int duration) { int h = duration / 3600; diff --git a/src/com/android/gallery3d/util/IntArray.java b/src/com/android/gallery3d/util/IntArray.java index 082089a65..2c4dc2c83 100644 --- a/src/com/android/gallery3d/util/IntArray.java +++ b/src/com/android/gallery3d/util/IntArray.java @@ -31,6 +31,11 @@ public class IntArray { mData[mSize++] = value; } + public int removeLast() { + mSize--; + return mData[mSize]; + } + public int size() { return mSize; } diff --git a/src/com/android/gallery3d/util/MediaSetUtils.java b/src/com/android/gallery3d/util/MediaSetUtils.java index 83b6b320b..043800561 100644 --- a/src/com/android/gallery3d/util/MediaSetUtils.java +++ b/src/com/android/gallery3d/util/MediaSetUtils.java @@ -29,7 +29,8 @@ public class MediaSetUtils { public static final Comparator<MediaSet> NAME_COMPARATOR = new NameComparator(); public static final int CAMERA_BUCKET_ID = GalleryUtils.getBucketId( - Environment.getExternalStorageDirectory().toString() + "/DCIM/Camera"); + Environment.getExternalStorageDirectory().toString() + "/" + + BucketNames.CAMERA); public static final int DOWNLOAD_BUCKET_ID = GalleryUtils.getBucketId( Environment.getExternalStorageDirectory().toString() + "/" + BucketNames.DOWNLOAD); @@ -41,7 +42,7 @@ public class MediaSetUtils { + BucketNames.IMPORTED); public static final int SNAPSHOT_BUCKET_ID = GalleryUtils.getBucketId( Environment.getExternalStorageDirectory().toString() + - "/Pictures/Screenshots"); + "/" + BucketNames.SCREENSHOTS); private static final Path[] CAMERA_PATHS = { Path.fromString("/local/all/" + CAMERA_BUCKET_ID), diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java b/src/com/android/gallery3d/util/SaveVideoFileInfo.java index bfa6dacc7..c7e5e8568 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePresetBW.java +++ b/src/com/android/gallery3d/util/SaveVideoFileInfo.java @@ -14,20 +14,16 @@ * limitations under the License. */ -package com.android.gallery3d.filtershow.presets; +package com.android.gallery3d.util; -import com.android.gallery3d.filtershow.filters.ImageFilterBW; +import java.io.File; -public class ImagePresetBW extends ImagePreset { - - @Override - public String name() { - return "B&W"; - } - - @Override - public void setup() { - mFilters.add(new ImageFilterBW()); - } +public class SaveVideoFileInfo { + public File mFile = null; + public String mFileName = null; + // This the full directory path. + public File mDirectory = null; + // This is just the folder's name. + public String mFolderName = null; } diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java new file mode 100644 index 000000000..c281dd3e7 --- /dev/null +++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.util; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore.Video; +import android.provider.MediaStore.Video.VideoColumns; + +import java.io.File; +import java.sql.Date; +import java.text.SimpleDateFormat; + +public class SaveVideoFileUtils { + // Copy from SaveCopyTask.java in terms of how to handle the destination + // path and filename : querySource() and getSaveDirectory(). + public interface ContentResolverQueryCallback { + void onCursorResult(Cursor cursor); + } + + // This function can decide which folder to save the video file, and generate + // the needed information for the video file including filename. + public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat, + ContentResolver contentResolver, Uri uri, String defaultFolderName) { + SaveVideoFileInfo dstFileInfo = new SaveVideoFileInfo(); + // Use the default save directory if the source directory cannot be + // saved. + dstFileInfo.mDirectory = getSaveDirectory(contentResolver, uri); + if ((dstFileInfo.mDirectory == null) || !dstFileInfo.mDirectory.canWrite()) { + dstFileInfo.mDirectory = new File(Environment.getExternalStorageDirectory(), + BucketNames.DOWNLOAD); + dstFileInfo.mFolderName = defaultFolderName; + } else { + dstFileInfo.mFolderName = dstFileInfo.mDirectory.getName(); + } + dstFileInfo.mFileName = new SimpleDateFormat(fileNameFormat).format( + new Date(System.currentTimeMillis())); + + dstFileInfo.mFile = new File(dstFileInfo.mDirectory, dstFileInfo.mFileName + ".mp4"); + return dstFileInfo; + } + + private static void querySource(ContentResolver contentResolver, Uri uri, + String[] projection, ContentResolverQueryCallback callback) { + Cursor cursor = null; + try { + cursor = contentResolver.query(uri, projection, null, null, null); + if ((cursor != null) && cursor.moveToNext()) { + callback.onCursorResult(cursor); + } + } catch (Exception e) { + // Ignore error for lacking the data column from the source. + } finally { + if (cursor != null) { + cursor.close(); + } + } + } + + private static File getSaveDirectory(ContentResolver contentResolver, Uri uri) { + final File[] dir = new File[1]; + querySource(contentResolver, uri, + new String[] { VideoColumns.DATA }, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + dir[0] = new File(cursor.getString(0)).getParentFile(); + } + }); + return dir[0]; + } + + + /** + * Insert the content (saved file) with proper video properties. + */ + public static Uri insertContent(SaveVideoFileInfo mDstFileInfo, + ContentResolver contentResolver, Uri uri ) { + long nowInMs = System.currentTimeMillis(); + long nowInSec = nowInMs / 1000; + final ContentValues values = new ContentValues(12); + values.put(Video.Media.TITLE, mDstFileInfo.mFileName); + values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.getName()); + values.put(Video.Media.MIME_TYPE, "video/mp4"); + values.put(Video.Media.DATE_TAKEN, nowInMs); + values.put(Video.Media.DATE_MODIFIED, nowInSec); + values.put(Video.Media.DATE_ADDED, nowInSec); + values.put(Video.Media.DATA, mDstFileInfo.mFile.getAbsolutePath()); + values.put(Video.Media.SIZE, mDstFileInfo.mFile.length()); + // Copy the data taken and location info from src. + String[] projection = new String[] { + VideoColumns.DATE_TAKEN, + VideoColumns.LATITUDE, + VideoColumns.LONGITUDE, + VideoColumns.RESOLUTION, + }; + + // Copy some info from the source file. + querySource(contentResolver, uri, projection, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + long timeTaken = cursor.getLong(0); + if (timeTaken > 0) { + values.put(Video.Media.DATE_TAKEN, timeTaken); + } + double latitude = cursor.getDouble(1); + double longitude = cursor.getDouble(2); + // TODO: Change || to && after the default location + // issue is + // fixed. + if ((latitude != 0f) || (longitude != 0f)) { + values.put(Video.Media.LATITUDE, latitude); + values.put(Video.Media.LONGITUDE, longitude); + } + values.put(Video.Media.RESOLUTION, cursor.getString(3)); + + } + }); + + return contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values); + } + +} diff --git a/tests/src/com/android/gallery3d/exif/ExifDataTest.java b/tests/src/com/android/gallery3d/exif/ExifDataTest.java index ba656bfa8..fed8e1eaf 100644 --- a/tests/src/com/android/gallery3d/exif/ExifDataTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifDataTest.java @@ -42,13 +42,13 @@ public class ExifDataTest extends TestCase { // check data assertEquals("test", exifData.getTag(ExifTag.TAG_MAKE).getString()); - assertEquals(1000, exifData.getTag(ExifTag.TAG_IMAGE_WIDTH).getUnsignedLong(0)); - assertEquals(1, exifData.getTag(ExifTag.TAG_ISO_SPEED_RATINGS).getUnsignedShort(0)); + assertEquals(1000, (int) exifData.getTag(ExifTag.TAG_IMAGE_WIDTH).getValueAt(0)); + assertEquals(1, (int) exifData.getTag(ExifTag.TAG_ISO_SPEED_RATINGS).getValueAt(0)); assertEquals(new Rational(10, 100), exifData.getTag(ExifTag.TAG_GPS_ALTITUDE).getRational(0)); assertEquals("inter_test", exifData.getInteroperabilityTag(ExifTag.TAG_INTEROPERABILITY_INDEX).getString()); assertEquals("test_thumb", exifData.getThumbnailTag(ExifTag.TAG_MAKE).getString()); - assertEquals(100, exifData.getThumbnailTag(ExifTag.TAG_IMAGE_WIDTH).getUnsignedLong(0)); + assertEquals(100, (int) exifData.getThumbnailTag(ExifTag.TAG_IMAGE_WIDTH).getValueAt(0)); } } diff --git a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java index ad603df39..8c98fa037 100644 --- a/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifOutputStreamTest.java @@ -19,6 +19,8 @@ package com.android.gallery3d.exif; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -26,48 +28,82 @@ import java.io.IOException; import java.io.InputStream; public class ExifOutputStreamTest extends ExifXmlDataTestCase { - public ExifOutputStreamTest(int imageResourceId, int xmlResourceId) { - super(imageResourceId, xmlResourceId); + + private File mTmpFile; + + @Override + public void setUp() throws Exception { + super.setUp(); + mTmpFile = File.createTempFile("exif_test", ".jpg"); + } + + public ExifOutputStreamTest(int imgRes, int xmlRes) { + super(imgRes, xmlRes); } - public void testExifOutputStream() throws IOException, ExifInvalidFormatException { - File file = File.createTempFile("exif_test", ".jpg"); + public ExifOutputStreamTest(String imgPath, String xmlPath) { + super(imgPath, xmlPath); + } + + public void testExifOutputStream() throws Exception { InputStream imageInputStream = null; InputStream exifInputStream = null; FileInputStream reDecodeInputStream = null; FileInputStream reParseInputStream = null; try { - // Read the image - imageInputStream = getInstrumentation() - .getContext().getResources().openRawResource(mImageResourceId); - Bitmap bmp = BitmapFactory.decodeStream(imageInputStream); - - // Read exif data - exifInputStream = getInstrumentation() - .getContext().getResources().openRawResource(mImageResourceId); - ExifData exifData = new ExifReader().read(exifInputStream); - - // Encode the image with the exif data - FileOutputStream outputStream = new FileOutputStream(file); - ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream); - exifOutputStream.setExifData(exifData); - bmp.compress(Bitmap.CompressFormat.JPEG, 100, exifOutputStream); - exifOutputStream.close(); - - // Re-decode the temp file and check the data. - reDecodeInputStream = new FileInputStream(file); - Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream); - assertNotNull(decodedBmp); - - // Re-parse the temp file the check EXIF tag - reParseInputStream = new FileInputStream(file); - ExifData reExifData = new ExifReader().read(reParseInputStream); - assertEquals(exifData, reExifData); - } finally { - Util.closeSilently(imageInputStream); - Util.closeSilently(exifInputStream); - Util.closeSilently(reDecodeInputStream); - Util.closeSilently(reParseInputStream); + try { + byte[] imgData = readToByteArray(getImageInputStream()); + imageInputStream = new ByteArrayInputStream(imgData); + exifInputStream = new ByteArrayInputStream(imgData); + + // Read the image data + Bitmap bmp = BitmapFactory.decodeStream(imageInputStream); + // The image is invalid + if (bmp == null) return; + + // Read exif data + ExifData exifData = new ExifReader().read(exifInputStream); + + // Encode the image with the exif data + FileOutputStream outputStream = new FileOutputStream(mTmpFile); + ExifOutputStream exifOutputStream = new ExifOutputStream(outputStream); + exifOutputStream.setExifData(exifData); + bmp.compress(Bitmap.CompressFormat.JPEG, 100, exifOutputStream); + exifOutputStream.close(); + + // Re-decode the temp file and check the data. + reDecodeInputStream = new FileInputStream(mTmpFile); + Bitmap decodedBmp = BitmapFactory.decodeStream(reDecodeInputStream); + assertNotNull(getImageTitle(), decodedBmp); + + // Re-parse the temp file the check EXIF tag + reParseInputStream = new FileInputStream(mTmpFile); + ExifData reExifData = new ExifReader().read(reParseInputStream); + assertEquals(getImageTitle(), exifData, reExifData); + } finally { + Util.closeSilently(imageInputStream); + Util.closeSilently(exifInputStream); + Util.closeSilently(reDecodeInputStream); + Util.closeSilently(reParseInputStream); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + private byte[] readToByteArray(InputStream is) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + int len; + byte[] buf = new byte[1024]; + while ((len = is.read(buf)) > -1) { + bos.write(buf, 0, len); } + bos.flush(); + return bos.toByteArray(); + } + + @Override + public void tearDown() { + mTmpFile.delete(); } -}
\ No newline at end of file +} diff --git a/tests/src/com/android/gallery3d/exif/ExifParserTest.java b/tests/src/com/android/gallery3d/exif/ExifParserTest.java index c1ad8341b..7a9d6e631 100644 --- a/tests/src/com/android/gallery3d/exif/ExifParserTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifParserTest.java @@ -16,219 +16,210 @@ package com.android.gallery3d.exif; -import android.content.res.XmlResourceParser; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; +import java.util.List; +import java.util.Map; public class ExifParserTest extends ExifXmlDataTestCase { private static final String TAG = "ExifParserTest"; - private HashMap<Short, String> mIfd0Value = new HashMap<Short, String>(); - private HashMap<Short, String> mIfd1Value = new HashMap<Short, String>(); - private HashMap<Short, String> mExifIfdValue = new HashMap<Short, String>(); - private HashMap<Short, String> mInteroperabilityIfdValue = new HashMap<Short, String>(); - - private InputStream mImageInputStream; - - public ExifParserTest(int imageResourceId, int xmlResourceId) { - super(imageResourceId, xmlResourceId); + public ExifParserTest(int imgRes, int xmlRes) { + super(imgRes, xmlRes); } - @Override - protected void setUp() throws Exception { - mImageInputStream = getInstrumentation() - .getContext().getResources().openRawResource(mImageResourceId); + public ExifParserTest(String imgPath, String xmlPath) { + super(imgPath, xmlPath); + } - XmlResourceParser parser = - getInstrumentation().getContext().getResources().getXml(mXmlResourceId); + private List<Map<Short, List<String>>> mGroundTruth; - ExifXmlReader.readXml(parser, mIfd0Value, mIfd1Value, mExifIfdValue - , mInteroperabilityIfdValue); - parser.close(); + @Override + public void setUp() throws Exception { + super.setUp(); + mGroundTruth = ExifXmlReader.readXml(getXmlParser()); } - public void testParse() throws IOException, ExifInvalidFormatException { - ExifParser parser = ExifParser.parse(mImageInputStream); - int event = parser.next(); - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - break; - case ExifParser.EVENT_NEW_TAG: - ExifTag tag = parser.getTag(); - if (!tag.hasValue()) { - parser.registerForTagValue(tag); - } else { + public void testParse() throws Exception { + try { + ExifParser parser = ExifParser.parse(getImageInputStream()); + int event = parser.next(); + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (!tag.hasValue()) { + parser.registerForTagValue(tag); + } else { + checkTag(tag); + } + break; + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + tag.setValue(buf); + } checkTag(tag); - } - break; - case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: - tag = parser.getTag(); - if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { - byte[] buf = new byte[tag.getComponentCount()]; - parser.read(buf); - tag.setValue(buf); - } - checkTag(tag); - break; + break; + } + event = parser.next(); } - event = parser.next(); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); } } private void checkTag(ExifTag tag) { - HashMap<Short, String> truth = null; - switch (tag.getIfd()) { - case IfdId.TYPE_IFD_0: - truth = mIfd0Value; - break; - case IfdId.TYPE_IFD_1: - truth = mIfd1Value; - break; - case IfdId.TYPE_IFD_EXIF: - truth = mExifIfdValue; - break; - case IfdId.TYPE_IFD_INTEROPERABILITY: - truth = mInteroperabilityIfdValue; - break; - } + List<String> truth = mGroundTruth.get(tag.getIfd()).get(tag.getTagId()); - String truthString = truth.get(tag.getTagId()); - String dataString = tag.valueToString().trim(); - if (truthString == null) { - fail(String.format("Unknown Tag %02x", tag.getTagId())); + if (truth == null) { + fail(String.format("Unknown Tag %02x", tag.getTagId()) + ", " + getImageTitle()); } - assertEquals(String.format("Tag %02x", tag.getTagId()), truthString, dataString); + + // No value from exiftool. + if (truth.contains(null)) return; + + String dataString = Util.tagValueToString(tag).trim(); + assertTrue(String.format("Tag %02x", tag.getTagId()) + ", " + getImageTitle() + + ": " + dataString, + truth.contains(dataString)); } - private void parseOneIfd(int ifd, int options, HashMap<Short, String> expectedResult) - throws IOException, ExifInvalidFormatException { - int numOfTag = 0; - ExifParser parser = ExifParser.parse(mImageInputStream, options); - int event = parser.next(); - while(event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - assertEquals(ifd, parser.getCurrentIfd()); - break; - case ExifParser.EVENT_NEW_TAG: - numOfTag++; - ExifTag tag = parser.getTag(); - if (tag.hasValue()) { + private void parseOneIfd(int ifd, int options) throws Exception { + try { + Map<Short, List<String>> expectedResult = mGroundTruth.get(ifd); + int numOfTag = 0; + ExifParser parser = ExifParser.parse(getImageInputStream(), options); + int event = parser.next(); + while(event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + assertEquals(getImageTitle(), ifd, parser.getCurrentIfd()); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + numOfTag++; + if (tag.hasValue()) { + checkTag(tag); + } else { + parser.registerForTagValue(tag); + } + break; + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { + byte[] buf = new byte[tag.getComponentCount()]; + parser.read(buf); + tag.setValue(buf); + } checkTag(tag); - } else { - parser.registerForTagValue(tag); - } - break; - case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: - tag = parser.getTag(); - if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) { - byte[] buf = new byte[tag.getComponentCount()]; - parser.read(buf); - tag.setValue(buf); - } - checkTag(tag); - break; - case ExifParser.EVENT_COMPRESSED_IMAGE: - case ExifParser.EVENT_UNCOMPRESSED_STRIP: - fail("Invalid Event type: " + event); - break; + break; + case ExifParser.EVENT_COMPRESSED_IMAGE: + case ExifParser.EVENT_UNCOMPRESSED_STRIP: + fail("Invalid Event type: " + event + ", " + getImageTitle()); + break; + } + event = parser.next(); } - event = parser.next(); + assertEquals(getImageTitle(), ExifXmlReader.getTrueTagNumber(expectedResult), numOfTag); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); } - assertEquals(expectedResult.size(), numOfTag); } - public void testOnlyExifIfd() throws IOException, ExifInvalidFormatException { - parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF, mExifIfdValue); + public void testOnlyExifIfd() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_EXIF, ExifParser.OPTION_IFD_EXIF); } - public void testOnlyIfd0() throws IOException, ExifInvalidFormatException { - parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0, mIfd0Value); + public void testOnlyIfd0() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_0, ExifParser.OPTION_IFD_0); } - public void testOnlyIfd1() throws IOException, ExifInvalidFormatException { - parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1, mIfd1Value); + public void testOnlyIfd1() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_1, ExifParser.OPTION_IFD_1); } - public void testOnlyInteroperabilityIfd() throws IOException, ExifInvalidFormatException { - parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY - , mInteroperabilityIfdValue); + public void testOnlyInteroperabilityIfd() throws Exception { + parseOneIfd(IfdId.TYPE_IFD_INTEROPERABILITY, ExifParser.OPTION_IFD_INTEROPERABILITY); } - public void testOnlyReadSomeTag() throws IOException, ExifInvalidFormatException { - ExifParser parser = ExifParser.parse(mImageInputStream, ExifParser.OPTION_IFD_0); - int event = parser.next(); - boolean isTagFound = false; - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_START_OF_IFD: - assertEquals(IfdId.TYPE_IFD_0, parser.getCurrentIfd()); - break; - case ExifParser.EVENT_NEW_TAG: - ExifTag tag = parser.getTag(); - if (tag.getTagId() == ExifTag.TAG_MODEL) { - if (tag.hasValue()) { - isTagFound = true; - checkTag(tag); - } else { - parser.registerForTagValue(tag); + public void testOnlyReadSomeTag() throws Exception { + // Do not do this test if there is no model tag. + if (mGroundTruth.get(IfdId.TYPE_IFD_0).get(ExifTag.TAG_MODEL) == null) return; + + try { + ExifParser parser = ExifParser.parse(getImageInputStream(), ExifParser.OPTION_IFD_0); + int event = parser.next(); + boolean isTagFound = false; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + assertEquals(getImageTitle(), IfdId.TYPE_IFD_0, parser.getCurrentIfd()); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_MODEL) { + if (tag.hasValue()) { + isTagFound = true; + checkTag(tag); + } else { + parser.registerForTagValue(tag); + } + parser.skipRemainingTagsInCurrentIfd(); } - parser.skipRemainingTagsInCurrentIfd(); - } - break; - case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: - tag = parser.getTag(); - assertEquals(ExifTag.TAG_MODEL, tag.getTagId()); - checkTag(tag); - isTagFound = true; - break; + break; + case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG: + tag = parser.getTag(); + assertEquals(getImageTitle(), ExifTag.TAG_MODEL, tag.getTagId()); + checkTag(tag); + isTagFound = true; + break; + } + event = parser.next(); } - event = parser.next(); + assertTrue(getImageTitle(), isTagFound); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); } - assertTrue(isTagFound); } - public void testReadThumbnail() throws ExifInvalidFormatException, IOException { - ExifParser parser = ExifParser.parse(mImageInputStream, - ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL); - - int event = parser.next(); - Bitmap bmp = null; - boolean mIsContainCompressedImage = false; - while (event != ExifParser.EVENT_END) { - switch (event) { - case ExifParser.EVENT_NEW_TAG: - ExifTag tag = parser.getTag(); - if (tag.getTagId() == ExifTag.TAG_COMPRESSION) { - if (tag.getUnsignedShort(0) == ExifTag.Compression.JPEG) { - mIsContainCompressedImage = true; + public void testReadThumbnail() throws Exception { + try { + ExifParser parser = ExifParser.parse(getImageInputStream(), + ExifParser.OPTION_IFD_1 | ExifParser.OPTION_THUMBNAIL); + + int event = parser.next(); + Bitmap bmp = null; + boolean mIsContainCompressedImage = false; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_NEW_TAG: + ExifTag tag = parser.getTag(); + if (tag.getTagId() == ExifTag.TAG_COMPRESSION) { + if (tag.getValueAt(0) == ExifTag.Compression.JPEG) { + mIsContainCompressedImage = true; + } } - } - break; - case ExifParser.EVENT_COMPRESSED_IMAGE: - int imageSize = parser.getCompressedImageSize(); - byte buf[] = new byte[imageSize]; - parser.read(buf); - bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize); - break; + break; + case ExifParser.EVENT_COMPRESSED_IMAGE: + int imageSize = parser.getCompressedImageSize(); + byte buf[] = new byte[imageSize]; + parser.read(buf); + bmp = BitmapFactory.decodeByteArray(buf, 0, imageSize); + break; + } + event = parser.next(); } - event = parser.next(); - } - if (mIsContainCompressedImage) { - assertNotNull(bmp); + if (mIsContainCompressedImage) { + assertNotNull(getImageTitle(), bmp); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); } } - - @Override - protected void tearDown() throws IOException { - mImageInputStream.close(); - mIfd0Value.clear(); - mIfd1Value.clear(); - mExifIfdValue.clear(); - } -}
\ No newline at end of file +} diff --git a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java index 269120870..30f34c09a 100644 --- a/tests/src/com/android/gallery3d/exif/ExifReaderTest.java +++ b/tests/src/com/android/gallery3d/exif/ExifReaderTest.java @@ -16,119 +16,125 @@ package com.android.gallery3d.exif; -import android.content.res.XmlResourceParser; import android.graphics.BitmapFactory; -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; +import java.util.List; +import java.util.Map; public class ExifReaderTest extends ExifXmlDataTestCase { private static final String TAG = "ExifReaderTest"; - private final HashMap<Short, String> mIfd0Value = new HashMap<Short, String>(); - private final HashMap<Short, String> mIfd1Value = new HashMap<Short, String>(); - private final HashMap<Short, String> mExifIfdValue = new HashMap<Short, String>(); - private final HashMap<Short, String> mInteroperabilityIfdValue = new HashMap<Short, String>(); - - private InputStream mImageInputStream; - - public ExifReaderTest(int imageResourceId, int xmlResourceId) { - super(imageResourceId, xmlResourceId); - } + private List<Map<Short, List<String>>> mGroundTruth; @Override public void setUp() throws Exception { - mImageInputStream = getInstrumentation() - .getContext().getResources().openRawResource(mImageResourceId); + super.setUp(); + mGroundTruth = ExifXmlReader.readXml(getXmlParser()); + } - XmlResourceParser parser = - getInstrumentation().getContext().getResources().getXml(mXmlResourceId); + public ExifReaderTest(int imgRes, int xmlRes) { + super(imgRes, xmlRes); + } - ExifXmlReader.readXml(parser, mIfd0Value, mIfd1Value, mExifIfdValue - , mInteroperabilityIfdValue); - parser.close(); + public ExifReaderTest(String imgPath, String xmlPath) { + super(imgPath, xmlPath); } - public void testRead() throws ExifInvalidFormatException, IOException { - ExifReader reader = new ExifReader(); - ExifData exifData = reader.read(mImageInputStream); - checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_0), mIfd0Value); - checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_1), mIfd1Value); - checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_EXIF), mExifIfdValue); - checkIfd(exifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY), - mInteroperabilityIfdValue); - checkThumbnail(exifData); + public void testRead() throws Exception { + try { + ExifReader reader = new ExifReader(); + ExifData exifData = reader.read(getImageInputStream()); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + checkIfd(exifData.getIfdData(i), mGroundTruth.get(i)); + } + checkThumbnail(exifData); + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } } private void checkThumbnail(ExifData exifData) { + Map<Short, List<String>> ifd1Truth = mGroundTruth.get(IfdId.TYPE_IFD_1); + + List<String> typeTagValue = ifd1Truth.get(ExifTag.TAG_COMPRESSION); + if (typeTagValue == null) return; + IfdData ifd1 = exifData.getIfdData(IfdId.TYPE_IFD_1); - if (ifd1 != null) { - if (ifd1.getTag(ExifTag.TAG_COMPRESSION).getUnsignedShort(0) == - ExifTag.Compression.JPEG) { - assertTrue(exifData.hasCompressedThumbnail()); - byte[] thumbnail = exifData.getCompressedThumbnail(); - assertTrue(BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length) != null); - } else { - // Try to check the strip count with the formula provided by EXIF spec. - int planarType = ExifTag.PlanarConfiguration.CHUNKY; - ExifTag planarTag = ifd1.getTag(ExifTag.TAG_PLANAR_CONFIGURATION); - if (planarTag != null) { - planarType = planarTag.getUnsignedShort(0); - } + if (ifd1 == null) fail(getImageTitle() + ": failed to find IFD1"); + + String typeTagTruth = typeTagValue.get(0); + + int type = (int) ifd1.getTag(ExifTag.TAG_COMPRESSION).getValueAt(0); + + if (String.valueOf(ExifTag.Compression.JPEG).equals(typeTagTruth)) { + assertTrue(getImageTitle(), type == ExifTag.Compression.JPEG); + assertTrue(getImageTitle(), exifData.hasCompressedThumbnail()); + byte[] thumbnail = exifData.getCompressedThumbnail(); + assertTrue(getImageTitle(), + BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length) != null); + } else if (String.valueOf(ExifTag.Compression.UNCOMPRESSION).equals(typeTagTruth)) { + assertTrue(getImageTitle(), type == ExifTag.Compression.UNCOMPRESSION); + // Try to check the strip count with the formula provided by EXIF spec. + int planarType = ExifTag.PlanarConfiguration.CHUNKY; + ExifTag planarTag = ifd1.getTag(ExifTag.TAG_PLANAR_CONFIGURATION); + if (planarTag != null) { + planarType = (int) planarTag.getValueAt(0); + } - ExifTag heightTag = ifd1.getTag(ExifTag.TAG_IMAGE_LENGTH); - ExifTag rowPerStripTag = ifd1.getTag(ExifTag.TAG_ROWS_PER_STRIP); + if (!ifd1Truth.containsKey(ExifTag.TAG_IMAGE_LENGTH) || + !ifd1Truth.containsKey(ExifTag.TAG_ROWS_PER_STRIP)) return; - int imageLength = getUnsignedIntOrShort(heightTag); - int rowsPerStrip = getUnsignedIntOrShort(rowPerStripTag); - int stripCount = ifd1.getTag( - ExifTag.TAG_STRIP_OFFSETS).getComponentCount(); + ExifTag heightTag = ifd1.getTag(ExifTag.TAG_IMAGE_LENGTH); + ExifTag rowPerStripTag = ifd1.getTag(ExifTag.TAG_ROWS_PER_STRIP); - if (planarType == ExifTag.PlanarConfiguration.CHUNKY) { - assertTrue(stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip); - } else { - ExifTag samplePerPixelTag = ifd1.getTag(ExifTag.TAG_SAMPLES_PER_PIXEL); - int samplePerPixel = samplePerPixelTag.getUnsignedShort(0); - assertTrue(stripCount == - (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel); - } + // Fail the test if required tags are missing + if (heightTag == null || rowPerStripTag == null) fail(getImageTitle()); - for (int i = 0; i < stripCount; i++) { - ExifTag byteCountTag = ifd1.getTag(ExifTag.TAG_STRIP_BYTE_COUNTS); - if (byteCountTag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - assertEquals(byteCountTag.getUnsignedShort(i), exifData.getStrip(i).length); - } else { - assertEquals( - byteCountTag.getUnsignedLong(i), exifData.getStrip(i).length); - } - } + int imageLength = (int) heightTag.getValueAt(0); + int rowsPerStrip = (int) rowPerStripTag.getValueAt(0); + int stripCount = ifd1.getTag( + ExifTag.TAG_STRIP_OFFSETS).getComponentCount(); + + if (planarType == ExifTag.PlanarConfiguration.CHUNKY) { + assertTrue(getImageTitle(), + stripCount == (imageLength + rowsPerStrip - 1) / rowsPerStrip); + } else { + if (!ifd1Truth.containsKey(ExifTag.TAG_SAMPLES_PER_PIXEL)) return; + ExifTag samplePerPixelTag = ifd1.getTag(ExifTag.TAG_SAMPLES_PER_PIXEL); + int samplePerPixel = (int) samplePerPixelTag.getValueAt(0); + assertTrue(getImageTitle(), + stripCount == + (imageLength + rowsPerStrip - 1) / rowsPerStrip * samplePerPixel); } - } - } - private int getUnsignedIntOrShort(ExifTag tag) { - if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - return tag.getUnsignedShort(0); - } else { - return (int) tag.getUnsignedLong(0); + if (!ifd1Truth.containsKey(ExifTag.TAG_STRIP_BYTE_COUNTS)) return; + ExifTag byteCountTag = ifd1.getTag(ExifTag.TAG_STRIP_BYTE_COUNTS); + short byteCountDataType = byteCountTag.getDataType(); + for (int i = 0; i < stripCount; i++) { + if (byteCountDataType == ExifTag.TYPE_UNSIGNED_SHORT) { + assertEquals(getImageTitle(), + byteCountTag.getValueAt(i), exifData.getStrip(i).length); + } else { + assertEquals(getImageTitle(), + byteCountTag.getValueAt(i), exifData.getStrip(i).length); + } + } } } - private void checkIfd(IfdData ifd, HashMap<Short, String> ifdValue) { + private void checkIfd(IfdData ifd, Map<Short, List<String>> ifdValue) { if (ifd == null) { - assertEquals(0 ,ifdValue.size()); + assertEquals(getImageTitle(), 0 ,ifdValue.size()); return; } ExifTag[] tags = ifd.getAllTags(); for (ExifTag tag : tags) { - assertEquals(ifdValue.get(tag.getTagId()), tag.valueToString().trim()); + List<String> truth = ifdValue.get(tag.getTagId()); + assertNotNull(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), truth); + if (truth.contains(null)) continue; + assertTrue(String.format("Tag %x, ", tag.getTagId()) + getImageTitle(), + truth.contains(Util.tagValueToString(tag).trim())); } - assertEquals(ifdValue.size(), tags.length); - } - - @Override - public void tearDown() throws Exception { - mImageInputStream.close(); + assertEquals(getImageTitle(), ifdValue.size(), tags.length); } } diff --git a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java index 57a7111ad..519e87107 100644 --- a/tests/src/com/android/gallery3d/exif/ExifTestRunner.java +++ b/tests/src/com/android/gallery3d/exif/ExifTestRunner.java @@ -16,6 +16,8 @@ package com.android.gallery3d.exif; +import android.content.Context; +import android.os.Environment; import android.test.InstrumentationTestRunner; import android.test.InstrumentationTestSuite; import android.util.Log; @@ -25,8 +27,11 @@ import com.android.gallery3d.tests.R; import junit.framework.TestCase; import junit.framework.TestSuite; +import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; public class ExifTestRunner extends InstrumentationTestRunner { private static final String TAG = "ExifTestRunner"; @@ -34,12 +39,17 @@ public class ExifTestRunner extends InstrumentationTestRunner { private static final int[] IMG_RESOURCE = { R.raw.galaxy_nexus }; + private static final int[] EXIF_DATA_RESOURCE = { R.xml.galaxy_nexus }; + private static List<String> mTestImgPath = new ArrayList<String>(); + private static List<String> mTestXmlPath = new ArrayList<String>(); + @Override public TestSuite getAllTests() { + getTestImagePath(); TestSuite suite = new InstrumentationTestSuite(this); suite.addTestSuite(ExifDataTest.class); suite.addTestSuite(ExifTagTest.class); @@ -49,6 +59,25 @@ public class ExifTestRunner extends InstrumentationTestRunner { return suite; } + private void getTestImagePath() { + Context context = getContext(); + File imgDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); + File xmlDir = new File(context.getExternalFilesDir(null).getPath(), "Xml"); + + if (imgDir != null && xmlDir != null) { + String[] imgs = imgDir.list(); + if (imgs == null) return; + for (String imgName: imgs) { + String xmlName = imgName.substring(0, imgName.lastIndexOf('.')) + ".xml"; + File xmlFile = new File(xmlDir, xmlName); + if (xmlFile.exists()) { + mTestImgPath.add(new File(imgDir, imgName).getAbsolutePath()); + mTestXmlPath.add(xmlFile.getAbsolutePath()); + } + } + } + } + private void addAllTestsFromExifTestCase(Class<? extends ExifXmlDataTestCase> testClass, TestSuite suite) { for (Method method : testClass.getDeclaredMethods()) { @@ -72,6 +101,25 @@ public class ExifTestRunner extends InstrumentationTestRunner { Log.e(TAG, "Failed to create test case", e); } } + for (int i = 0, n = mTestImgPath.size(); i < n; i++) { + TestCase test; + try { + test = testClass.getDeclaredConstructor(String.class, String.class). + newInstance(mTestImgPath.get(i), mTestXmlPath.get(i)); + test.setName(method.getName()); + suite.addTest(test); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (InstantiationException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (IllegalAccessException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (InvocationTargetException e) { + Log.e(TAG, "Failed to create test case", e); + } catch (NoSuchMethodException e) { + Log.e(TAG, "Failed to create test case", e); + } + } } } } diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java index 41b315181..811b0a6c3 100644 --- a/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java +++ b/tests/src/com/android/gallery3d/exif/ExifXmlDataTestCase.java @@ -17,14 +17,80 @@ package com.android.gallery3d.exif; +import android.content.res.Resources; import android.test.InstrumentationTestCase; +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; public class ExifXmlDataTestCase extends InstrumentationTestCase { - protected final int mImageResourceId; - protected final int mXmlResourceId; + + private static final String RES_ID_TITLE = "Resource ID: %x"; + + private InputStream mImageInputStream; + private InputStream mXmlInputStream; + private XmlPullParser mXmlParser; + private final String mImagePath; + private final String mXmlPath; + private final int mImageResourceId; + private final int mXmlResourceId; public ExifXmlDataTestCase(int imageRes, int xmlRes) { + mImagePath = null; + mXmlPath = null; mImageResourceId = imageRes; mXmlResourceId = xmlRes; } + + public ExifXmlDataTestCase(String imagePath, String xmlPath) { + mImagePath = imagePath; + mXmlPath = xmlPath; + mImageResourceId = 0; + mXmlResourceId = 0; + } + + protected InputStream getImageInputStream() { + return mImageInputStream; + } + + protected XmlPullParser getXmlParser() { + return mXmlParser; + } + + @Override + public void setUp() throws Exception { + try { + if (mImagePath != null) { + mImageInputStream = new FileInputStream(mImagePath); + mXmlInputStream = new FileInputStream(mXmlPath); + mXmlParser = Xml.newPullParser(); + mXmlParser.setInput(new InputStreamReader(mXmlInputStream)); + } else { + Resources res = getInstrumentation().getContext().getResources(); + mImageInputStream = res.openRawResource(mImageResourceId); + mXmlParser = res.getXml(mXmlResourceId); + } + } catch (Exception e) { + throw new Exception(getImageTitle(), e); + } + } + + @Override + public void tearDown() throws Exception { + Util.closeSilently(mImageInputStream); + Util.closeSilently(mXmlInputStream); + mXmlParser = null; + } + + protected String getImageTitle() { + if (mImagePath != null) { + return mImagePath; + } else { + return String.format(RES_ID_TITLE, mImageResourceId); + } + } } diff --git a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java index 72dd31373..bb08ccd1c 100644 --- a/tests/src/com/android/gallery3d/exif/ExifXmlReader.java +++ b/tests/src/com/android/gallery3d/exif/ExifXmlReader.java @@ -20,78 +20,106 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.Map; public class ExifXmlReader { + private static final String TAG_EXIF = "exif"; + private static final String TAG_TAG = "tag"; - private static final String XML_EXIF_TAG = "exif"; - private static final String XML_IFD_TAG = "ifd"; - private static final String XML_IFD_NAME = "name"; - private static final String XML_TAG = "tag"; - private static final String XML_IFD0 = "ifd0"; - private static final String XML_IFD1 = "ifd1"; - private static final String XML_EXIF_IFD = "exif-ifd"; - private static final String XML_INTEROPERABILITY_IFD = "interoperability-ifd"; - private static final String XML_TAG_ID = "id"; - - public static void readXml(XmlPullParser parser, HashMap<Short, String> ifd0, - HashMap<Short, String> ifd1, HashMap<Short, String> exifIfd, - HashMap<Short, String> interoperabilityIfd) throws XmlPullParserException, - IOException { + private static final String IFD0 = "IFD0"; + private static final String EXIF_IFD = "ExifIFD"; + private static final String GPS_IFD = "GPS"; + private static final String IFD1 = "IFD1"; + private static final String INTEROP_IFD = "InteropIFD"; + + private static final String ATTR_ID = "id"; + private static final String ATTR_IFD = "ifd"; + + private static final String NO_VALUE = "NO_VALUE"; + + /** + * This function read the ground truth XML. + * + * @throws XmlPullParserException + * @throws IOException + */ + static public List<Map<Short, List<String>>> readXml(XmlPullParser parser) + throws XmlPullParserException, IOException { + + List<Map<Short, List<String>>> exifData = + new ArrayList<Map<Short, List<String>>>(IfdId.TYPE_IFD_COUNT); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + exifData.add(new HashMap<Short, List<String>>()); + } while (parser.next() != XmlPullParser.END_DOCUMENT) { if (parser.getEventType() == XmlPullParser.START_TAG) { break; } } + parser.require(XmlPullParser.START_TAG, null, TAG_EXIF); - assert(parser.getName().equals(XML_EXIF_TAG)); - - parser.require(XmlPullParser.START_TAG, null, XML_EXIF_TAG); while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() == XmlPullParser.START_TAG) { - readXmlIfd(parser, ifd0, ifd1, exifIfd, interoperabilityIfd); + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + + parser.require(XmlPullParser.START_TAG, null, TAG_TAG); + + int ifdId = getIfdIdFromString(parser.getAttributeValue(null, ATTR_IFD)); + short id = Integer.decode(parser.getAttributeValue(null, ATTR_ID)).shortValue(); + + String value = ""; + if (parser.next() == XmlPullParser.TEXT) { + value = parser.getText(); + parser.next(); } + + if (ifdId < 0) { + // TODO: the MarkerNote segment. + } else { + List<String> tagData = exifData.get(ifdId).get(id); + if (tagData == null) { + tagData = new ArrayList<String>(); + exifData.get(ifdId).put(id, tagData); + } + if (NO_VALUE.equals(value)) { + tagData.add(null); + } else { + tagData.add(value.trim()); + } + } + + parser.require(XmlPullParser.END_TAG, null, null); } - parser.require(XmlPullParser.END_TAG, null, XML_EXIF_TAG); + return exifData; } - private static void readXmlIfd(XmlPullParser parser, HashMap<Short, String> ifd0, - HashMap<Short, String> ifd1, HashMap<Short, String> exifIfd, - HashMap<Short, String> interoperabilityIfd) throws XmlPullParserException, - IOException { - parser.require(XmlPullParser.START_TAG, null, XML_IFD_TAG); - String name = parser.getAttributeValue(null, XML_IFD_NAME); - HashMap<Short, String> ifdData = null; - if (XML_IFD0.equals(name)) { - ifdData = ifd0; - } else if (XML_IFD1.equals(name)) { - ifdData = ifd1; - } else if (XML_EXIF_IFD.equals(name)) { - ifdData = exifIfd; - } else if (XML_INTEROPERABILITY_IFD.equals(name)) { - ifdData = interoperabilityIfd; + static private int getIfdIdFromString(String prefix) { + if (IFD0.equals(prefix)) { + return IfdId.TYPE_IFD_0; + } else if (EXIF_IFD.equals(prefix)) { + return IfdId.TYPE_IFD_EXIF; + } else if (GPS_IFD.equals(prefix)) { + return IfdId.TYPE_IFD_GPS; + } else if (IFD1.equals(prefix)) { + return IfdId.TYPE_IFD_1; + } else if (INTEROP_IFD.equals(prefix)) { + return IfdId.TYPE_IFD_INTEROPERABILITY; } else { - throw new RuntimeException("Unknown IFD name in xml file: " + name); - } - while (parser.next() != XmlPullParser.END_TAG) { - if (parser.getEventType() == XmlPullParser.START_TAG) { - readXmlTag(parser, ifdData); - } + assert(false); + return -1; } - parser.require(XmlPullParser.END_TAG, null, XML_IFD_TAG); } - private static void readXmlTag(XmlPullParser parser, HashMap<Short, String> data) - throws XmlPullParserException, IOException { - parser.require(XmlPullParser.START_TAG, null, XML_TAG); - short id = Integer.decode(parser.getAttributeValue(null, XML_TAG_ID)).shortValue(); - String value = ""; - if (parser.next() == XmlPullParser.TEXT) { - value = parser.getText(); - parser.next(); + static public int getTrueTagNumber(Map<Short, List<String>> ifdData) { + int size = 0; + for (List<String> tag: ifdData.values()) { + size += tag.size(); } - data.put(id, value); - parser.require(XmlPullParser.END_TAG, null, XML_TAG); + return size; } -}
\ No newline at end of file +} diff --git a/tests/src/com/android/gallery3d/exif/Util.java b/tests/src/com/android/gallery3d/exif/Util.java new file mode 100644 index 000000000..0e51fd7bf --- /dev/null +++ b/tests/src/com/android/gallery3d/exif/Util.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.exif; + +import java.io.Closeable; + +class Util { + public static boolean equals(Object a, Object b) { + return (a == b) || (a == null ? false : a.equals(b)); + } + + public static void closeSilently(Closeable c) { + if (c == null) return; + try { + c.close(); + } catch (Throwable t) { + // do nothing + } + } + + /** + * Tags that are not defined in the spec. + */ + static final short TAG_XP_TITLE = (short) 0x9c9b; + static final short TAG_XP_COMMENT = (short) 0x9c9c; + static final short TAG_XP_AUTHOR = (short) 0x9c9d; + static final short TAG_XP_KEYWORDS = (short) 0x9c9e; + static final short TAG_XP_SUBJECT = (short) 0x9c9f; + + private static String tagUndefinedTypeValueToString(ExifTag tag) { + StringBuilder sbuilder = new StringBuilder(); + byte[] buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + switch (tag.getTagId()) { + case ExifTag.TAG_COMPONENTS_CONFIGURATION: + for(int i = 0, n = tag.getComponentCount(); i < n; i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(buf[i]); + } + break; + default: + if (buf.length == 1) { + sbuilder.append(buf[0]); + } else { + for (int i = 0, n = buf.length; i < n; i++) { + byte code = buf[i]; + if (code == 0) continue; + if (code > 31 && code < 127) { + sbuilder.append((char) code); + } else { + sbuilder.append('.'); + } + } + } + } + return sbuilder.toString(); + } + + /** + * Returns a string representation of the value of this tag. + */ + public static String tagValueToString(ExifTag tag) { + StringBuilder sbuilder = new StringBuilder(); + short id = tag.getTagId(); + switch (tag.getDataType()) { + case ExifTag.TYPE_UNDEFINED: + sbuilder.append(tagUndefinedTypeValueToString(tag)); + break; + case ExifTag.TYPE_UNSIGNED_BYTE: + if (id == ExifTag.TAG_MAKER_NOTE || id == TAG_XP_TITLE || + id == TAG_XP_COMMENT || id == TAG_XP_AUTHOR || + id == TAG_XP_KEYWORDS || id == TAG_XP_SUBJECT) { + sbuilder.append(tagUndefinedTypeValueToString(tag)); + } else { + byte[] buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + for(int i = 0, n = tag.getComponentCount(); i < n; i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(buf[i]); + } + } + break; + case ExifTag.TYPE_ASCII: + byte[] buf = tag.getStringByte(); + for (int i = 0, n = buf.length; i < n; i++) { + byte code = buf[i]; + if (code == 0) { + // Treat some tag as undefined type data. + if (id == ExifTag.TAG_COPYRIGHT || id == ExifTag.TAG_GPS_DATE_STAMP) { + continue; + } else { + break; + } + } + if (code > 31 && code < 127) { + sbuilder.append((char) code); + } else { + sbuilder.append('.'); + } + } + break; + case ExifTag.TYPE_UNSIGNED_LONG: + for(int i = 0, n = tag.getComponentCount(); i < n; i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append(tag.getValueAt(i)); + } + break; + case ExifTag.TYPE_RATIONAL: + case ExifTag.TYPE_UNSIGNED_RATIONAL: + for(int i = 0, n = tag.getComponentCount(); i < n; i++) { + Rational r = tag.getRational(i); + if(i != 0) sbuilder.append(" "); + sbuilder.append(r.getNominator()).append("/").append(r.getDenominator()); + } + break; + case ExifTag.TYPE_UNSIGNED_SHORT: + for(int i = 0, n = tag.getComponentCount(); i < n; i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append((int) tag.getValueAt(i)); + } + break; + case ExifTag.TYPE_LONG: + for(int i = 0, n = tag.getComponentCount(); i < n; i++) { + if(i != 0) sbuilder.append(" "); + sbuilder.append((int) tag.getValueAt(i)); + } + break; + } + return sbuilder.toString(); + } +} diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasMock.java b/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java index f8100ddf6..a57c18840 100644 --- a/tests/src/com/android/gallery3d/ui/GLCanvasMock.java +++ b/tests/src/com/android/gallery3d/glrenderer/GLCanvasMock.java @@ -14,7 +14,10 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + +import com.android.gallery3d.glrenderer.BasicTexture; +import com.android.gallery3d.ui.GLCanvasStub; import javax.microedition.khronos.opengles.GL11; diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasTest.java b/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java index 72ccbfb96..c42f97dce 100644 --- a/tests/src/com/android/gallery3d/ui/GLCanvasTest.java +++ b/tests/src/com/android/gallery3d/glrenderer/GLCanvasTest.java @@ -14,11 +14,15 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.GLES11Canvas; +import com.android.gallery3d.glrenderer.GLPaint; + import junit.framework.TestCase; import java.util.Arrays; @@ -39,7 +43,8 @@ public class GLCanvasTest extends TestCase { @SmallTest public void testSetSize() { GL11 glStub = new GLStub(); - GLCanvas canvas = new GLCanvasImpl(glStub); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(glStub); canvas.setSize(100, 200); canvas.setSize(1000, 100); try { @@ -57,7 +62,8 @@ public class GLCanvasTest extends TestCase { private static class ClearBufferTest extends GLMock { void run() { - GLCanvas canvas = new GLCanvasImpl(this); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(this); assertEquals(0, mGLClearCalled); canvas.clearBuffer(); assertEquals(GL10.GL_COLOR_BUFFER_BIT, mGLClearMask); @@ -79,7 +85,8 @@ public class GLCanvasTest extends TestCase { 0x7F010101, 0xFEFEFDFC, 0x017F8081, 0x027F8081, 0x2ADE4C4D }; - GLCanvas canvas = new GLCanvasImpl(this); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(this); canvas.setSize(400, 300); // Test one color to make sure blend function is set. assertEquals(0, mGLColorCalled); @@ -107,7 +114,8 @@ public class GLCanvasTest extends TestCase { @SmallTest public void testSetGetMultiplyAlpha() { GL11 glStub = new GLStub(); - GLCanvas canvas = new GLCanvasImpl(glStub); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(glStub); canvas.setAlpha(1f); assertEquals(1f, canvas.getAlpha()); @@ -146,7 +154,8 @@ public class GLCanvasTest extends TestCase { private static class AlphaTest extends GLMock { void run() { - GLCanvas canvas = new GLCanvasImpl(this); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(this); canvas.setSize(400, 300); assertEquals(0, mGLColorCalled); @@ -188,7 +197,8 @@ public class GLCanvasTest extends TestCase { } void run() { - GLCanvas canvas = new GLCanvasImpl(this); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(this); canvas.setSize(400, 300); canvas.drawLine(2, 7, 1, 8, newColorPaint(0) /* color */); assertTrue(mGLVertexArrayEnabled); @@ -232,7 +242,8 @@ public class GLCanvasTest extends TestCase { } void run() { - GLCanvas canvas = new GLCanvasImpl(this); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(this); canvas.setSize(400, 300); canvas.fillRect(2, 7, 1, 8, 0 /* color */); assertTrue(mGLVertexArrayEnabled); @@ -294,7 +305,8 @@ public class GLCanvasTest extends TestCase { } void run() { - GLCanvas canvas = new GLCanvasImpl(this); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(this); canvas.setSize(40, 50); int color = 0; @@ -359,13 +371,6 @@ public class GLCanvasTest extends TestCase { } } - @SmallTest - public void testGetGLInstance() { - GL11 glStub = new GLStub(); - GLCanvas canvas = new GLCanvasImpl(glStub); - assertSame(glStub, canvas.getGLInstance()); - } - private static void assertPremultipliedBlending(GLMock mock) { assertTrue(mock.mGLBlendFuncCalled > 0); assertTrue(mock.mGLBlendEnabled); diff --git a/tests/src/com/android/gallery3d/ui/GLMock.java b/tests/src/com/android/gallery3d/glrenderer/GLMock.java index c1fe53c62..b242217a2 100644 --- a/tests/src/com/android/gallery3d/ui/GLMock.java +++ b/tests/src/com/android/gallery3d/glrenderer/GLMock.java @@ -14,7 +14,9 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; + +import com.android.gallery3d.ui.PointerInfo; import java.nio.Buffer; import java.util.HashMap; diff --git a/tests/src/com/android/gallery3d/ui/GLStub.java b/tests/src/com/android/gallery3d/glrenderer/GLStub.java index 2af73f905..4b66040dd 100644 --- a/tests/src/com/android/gallery3d/ui/GLStub.java +++ b/tests/src/com/android/gallery3d/glrenderer/GLStub.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; diff --git a/tests/src/com/android/gallery3d/ui/TextureTest.java b/tests/src/com/android/gallery3d/glrenderer/TextureTest.java index 36446b389..b13a333e0 100644 --- a/tests/src/com/android/gallery3d/ui/TextureTest.java +++ b/tests/src/com/android/gallery3d/glrenderer/TextureTest.java @@ -14,12 +14,19 @@ * limitations under the License. */ -package com.android.gallery3d.ui; +package com.android.gallery3d.glrenderer; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.test.suitebuilder.annotation.SmallTest; +import com.android.gallery3d.glrenderer.BasicTexture; +import com.android.gallery3d.glrenderer.BitmapTexture; +import com.android.gallery3d.glrenderer.ColorTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.GLES11Canvas; +import com.android.gallery3d.glrenderer.UploadedTexture; + import junit.framework.TestCase; import javax.microedition.khronos.opengles.GL11; @@ -48,6 +55,7 @@ public class TextureTest extends TestCase { return GL11.GL_TEXTURE_2D; } + @Override public boolean isOpaque() { mOpaqueCalled++; return true; @@ -61,7 +69,8 @@ public class TextureTest extends TestCase { @SmallTest public void testBasicTexture() { GL11 glStub = new GLStub(); - GLCanvas canvas = new GLCanvasImpl(glStub); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(glStub); MyBasicTexture texture = new MyBasicTexture(canvas, 47); assertEquals(47, texture.getId()); @@ -81,7 +90,8 @@ public class TextureTest extends TestCase { assertTrue(texture.isLoaded()); // For a different GL, it's not loaded. - GLCanvas canvas2 = new GLCanvasImpl(new GLStub()); + GLCanvas canvas2 = new GLES11Canvas(); + canvas2.initialize(glStub); assertFalse(texture.isLoaded()); assertEquals(0, texture.mOnBindCalled); @@ -135,7 +145,8 @@ public class TextureTest extends TestCase { @SmallTest public void testUploadedTexture() { GL11 glStub = new GLStub(); - GLCanvas canvas = new GLCanvasImpl(glStub); + GLCanvas canvas = new GLES11Canvas(); + canvas.initialize(glStub); MyUploadedTexture texture = new MyUploadedTexture(); // draw it and the bitmap should be fetched. @@ -181,6 +192,7 @@ public class TextureTest extends TestCase { return GL11.GL_TEXTURE_2D; } + @Override public boolean isOpaque() { return true; } diff --git a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java index 5a08b8599..d6bbc3d23 100644 --- a/tests/src/com/android/gallery3d/ui/GLCanvasStub.java +++ b/tests/src/com/android/gallery3d/ui/GLCanvasStub.java @@ -16,52 +16,83 @@ package com.android.gallery3d.ui; +import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.RectF; +import com.android.gallery3d.glrenderer.BasicTexture; +import com.android.gallery3d.glrenderer.GLCanvas; +import com.android.gallery3d.glrenderer.GLPaint; +import com.android.gallery3d.glrenderer.RawTexture; + +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + import javax.microedition.khronos.opengles.GL11; -public class GLCanvasStub implements GLCanvas { +public class GLCanvasStub extends GLCanvas { + @Override public void setSize(int width, int height) {} + @Override public void clearBuffer() {} + @Override public void clearBuffer(float[] argb) {} public void setCurrentAnimationTimeMillis(long time) {} public long currentAnimationTimeMillis() { throw new UnsupportedOperationException(); } + @Override public void setAlpha(float alpha) {} + @Override public float getAlpha() { throw new UnsupportedOperationException(); } + @Override public void multiplyAlpha(float alpha) {} + @Override public void translate(float x, float y, float z) {} + @Override public void translate(float x, float y) {} + @Override public void scale(float sx, float sy, float sz) {} + @Override public void rotate(float angle, float x, float y, float z) {} public boolean clipRect(int left, int top, int right, int bottom) { throw new UnsupportedOperationException(); } + @Override public void save() { throw new UnsupportedOperationException(); } + @Override public void save(int saveFlags) { throw new UnsupportedOperationException(); } public void setBlendEnabled(boolean enabled) {} + @Override public void restore() {} + @Override public void drawLine(float x1, float y1, float x2, float y2, GLPaint paint) {} + @Override public void drawRect(float x1, float y1, float x2, float y2, GLPaint paint) {} + @Override public void fillRect(float x, float y, float width, float height, int color) {} + @Override public void drawTexture( BasicTexture texture, int x, int y, int width, int height) {} + @Override public void drawMesh(BasicTexture tex, int x, int y, int xyBuffer, int uvBuffer, int indexBuffer, int indexCount) {} public void drawTexture(BasicTexture texture, int x, int y, int width, int height, float alpha) {} + @Override public void drawTexture(BasicTexture texture, RectF source, RectF target) {} + @Override public void drawTexture(BasicTexture texture, float[] mTextureTransform, int x, int y, int w, int h) {} public void drawMixed(BasicTexture from, BasicTexture to, float ratio, int x, int y, int w, int h) {} + @Override public void drawMixed(BasicTexture from, int to, float ratio, int x, int y, int w, int h) {} public void drawMixed(BasicTexture from, BasicTexture to, @@ -72,17 +103,74 @@ public class GLCanvasStub implements GLCanvas { public GL11 getGLInstance() { throw new UnsupportedOperationException(); } + @Override public boolean unloadTexture(BasicTexture texture) { throw new UnsupportedOperationException(); } + @Override public void deleteBuffer(int bufferId) { throw new UnsupportedOperationException(); } + @Override public void deleteRecycledResources() {} + @Override public void multiplyMatrix(float[] mMatrix, int offset) {} + @Override public void dumpStatisticsAndClear() {} + @Override public void beginRenderTarget(RawTexture texture) {} + @Override public void endRenderTarget() {} + @Override public void drawMixed(BasicTexture from, int toColor, float ratio, RectF src, RectF target) {} + + @Override + public void setTextureParameters(BasicTexture texture) { + } + @Override + public void initializeTextureSize(BasicTexture texture, int format, int type) { + } + @Override + public void initializeTexture(BasicTexture texture, Bitmap bitmap) { + } + @Override + public void texSubImage2D(BasicTexture texture, int xOffset, int yOffset, Bitmap bitmap, + int format, int type) { + } + @Override + public int uploadBuffer(ByteBuffer buffer) { + return 0; + } + @Override + public int uploadBuffer(FloatBuffer buffer) { + return 0; + } + @Override + public void setBlending(Blending blending) { + } + @Override + public void enableStencil() { + } + @Override + public void disableStencil() { + } + @Override + public void clearStencilBuffer() { + } + @Override + public void updateStencil(boolean update) { + } + @Override + public void drawOnlyOutsideStencil(boolean onlyOutside) { + } + @Override + public void initialize(GL11 gl) { + } + @Override + public void recoverFromLightCycle() { + } + @Override + public void getBounds(Rect bounds, int x, int y, int width, int height) { + } } diff --git a/tests/src/com/android/gallery3d/ui/GLViewMock.java b/tests/src/com/android/gallery3d/ui/GLViewMock.java index 7b941daad..9b7488f05 100644 --- a/tests/src/com/android/gallery3d/ui/GLViewMock.java +++ b/tests/src/com/android/gallery3d/ui/GLViewMock.java @@ -16,6 +16,8 @@ package com.android.gallery3d.ui; +import com.android.gallery3d.glrenderer.GLCanvas; + class GLViewMock extends GLView { // onAttachToRoot int mOnAttachCalled; |