Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b0a2dc56 authored by Michael Jurka's avatar Michael Jurka Committed by Android Git Automerger
Browse files

am ac132888: am 9a4c6988: am 0a1988b6: Merge "Make wallpaper cropper more robust " into klp-dev

* commit 'ac132888':
  Make wallpaper cropper more robust
parents 824913c9 ac132888
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -17,4 +17,9 @@
    <string name="crop_wallpaper">Crop wallpaper</string>
    <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper -->
    <string name="wallpaper_instructions">Set wallpaper</string>
    <!-- Error message when an image is selected as a wallpaper,
         but the wallpaper cropper cannot load it. The user will
         usually see this when using another app and trying to set
         an image as the wallpaper -->
    <string name="wallpaper_load_fail">Couldn\'t load image as wallpaper</string>
</resources>
+169 −56
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.os.Build.VERSION_CODES;
import android.util.Log;

import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.gallery3d.glrenderer.BasicTexture;
import com.android.gallery3d.glrenderer.BitmapTexture;
@@ -41,6 +42,85 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

interface SimpleBitmapRegionDecoder {
    int getWidth();
    int getHeight();
    Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options);
}

class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder {
    BitmapRegionDecoder mDecoder;
    private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) {
        mDecoder = decoder;
    }
    public static SimpleBitmapRegionDecoderWrapper newInstance(String pathName, boolean isShareable) {
        try {
            BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable);
            if (d != null) {
                return new SimpleBitmapRegionDecoderWrapper(d);
            }
        } catch (IOException e) {
            Log.w("BitmapRegionTileSource", "getting decoder failed for path " + pathName, e);
            return null;
        }
        return null;
    }
    public static SimpleBitmapRegionDecoderWrapper newInstance(InputStream is, boolean isShareable) {
        try {
            BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable);
            if (d != null) {
                return new SimpleBitmapRegionDecoderWrapper(d);
            }
        } catch (IOException e) {
            Log.w("BitmapRegionTileSource", "getting decoder failed", e);
            return null;
        }
        return null;
    }
    public int getWidth() {
        return mDecoder.getWidth();
    }
    public int getHeight() {
        return mDecoder.getHeight();
    }
    public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
        return mDecoder.decodeRegion(wantRegion, options);
    }
}

class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder {
    //byte[] streamCopy;
    Bitmap mBuffer;
    private DumbBitmapRegionDecoder(Bitmap b) {
        mBuffer = b;
    }
    public static DumbBitmapRegionDecoder newInstance(String pathName) {
        Bitmap b = BitmapFactory.decodeFile(pathName);
        if (b != null) {
            return new DumbBitmapRegionDecoder(b);
        }
        return null;
    }
    public static DumbBitmapRegionDecoder newInstance(InputStream is) {
        Bitmap b = BitmapFactory.decodeStream(is);
        if (b != null) {
            return new DumbBitmapRegionDecoder(b);
        }
        return null;
    }
    public int getWidth() {
        return mBuffer.getWidth();
    }
    public int getHeight() {
        return mBuffer.getHeight();
    }
    public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) {
        System.out.println("DECODING WITH SAMPLE LEVEL OF " + options.inSampleSize);
        return Bitmap.createBitmap(
                mBuffer, wantRegion.left, wantRegion.top, wantRegion.width(), wantRegion.height());
    }
}

/**
 * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using
 * {@link BitmapRegionDecoder} to wrap a local file
@@ -58,14 +138,16 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
    public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2;

    public static abstract class BitmapSource {
        private BitmapRegionDecoder mDecoder;
        private SimpleBitmapRegionDecoder mDecoder;
        private Bitmap mPreview;
        private int mPreviewSize;
        private int mRotation;
        public enum State { NOT_LOADED, LOADED, ERROR_LOADING };
        private State mState = State.NOT_LOADED;
        public BitmapSource(int previewSize) {
            mPreviewSize = previewSize;
        }
        public void loadInBackground() {
        public boolean loadInBackground() {
            ExifInterface ei = new ExifInterface();
            if (readExif(ei)) {
                Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
@@ -74,6 +156,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
                }
            }
            mDecoder = loadBitmapRegionDecoder();
            if (mDecoder == null) {
                mState = State.ERROR_LOADING;
                return false;
            } else {
                int width = mDecoder.getWidth();
                int height = mDecoder.getHeight();
                if (mPreviewSize != 0) {
@@ -87,9 +173,16 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
                    opts.inJustDecodeBounds = false;
                    mPreview = loadPreviewBitmap(opts);
                }
                mState = State.LOADED;
                return true;
            }
        }

        public BitmapRegionDecoder getBitmapRegionDecoder() {
        public State getLoadingState() {
            return mState;
        }

        public SimpleBitmapRegionDecoder getBitmapRegionDecoder() {
            return mDecoder;
        }

@@ -106,7 +199,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        }

        public abstract boolean readExif(ExifInterface ei);
        public abstract BitmapRegionDecoder loadBitmapRegionDecoder();
        public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
        public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
    }

@@ -117,13 +210,13 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
            mPath = path;
        }
        @Override
        public BitmapRegionDecoder loadBitmapRegionDecoder() {
            try {
                return BitmapRegionDecoder.newInstance(mPath, true);
            } catch (IOException e) {
                Log.w("BitmapRegionTileSource", "getting decoder failed", e);
                return null;
        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
            SimpleBitmapRegionDecoder d;
            d = SimpleBitmapRegionDecoderWrapper.newInstance(mPath, true);
            if (d == null) {
                d = DumbBitmapRegionDecoder.newInstance(mPath);
            }
            return d;
        }
        @Override
        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
@@ -154,9 +247,17 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
            return new BufferedInputStream(is);
        }
        @Override
        public BitmapRegionDecoder loadBitmapRegionDecoder() {
        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
            try {
                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
                InputStream is = regenerateInputStream();
                SimpleBitmapRegionDecoder regionDecoder =
                        SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
                Utils.closeSilently(is);
                if (regionDecoder == null) {
                    is = regenerateInputStream();
                    regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
                }
                return regionDecoder;
            } catch (FileNotFoundException e) {
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return null;
@@ -168,7 +269,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
            try {
                return BitmapFactory.decodeStream(regenerateInputStream(), null, options);
                InputStream is = regenerateInputStream();
                Bitmap b = BitmapFactory.decodeStream(is, null, options);
                Utils.closeSilently(is);
                return b;
            } catch (FileNotFoundException e) {
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return null;
@@ -177,13 +281,15 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public boolean readExif(ExifInterface ei) {
            try {
                ei.readExif(regenerateInputStream());
                InputStream is = regenerateInputStream();
                ei.readExif(is);
                Utils.closeSilently(is);
                return true;
            } catch (FileNotFoundException e) {
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return false;
            } catch (IOException e) {
                Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e);
                Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
                return false;
            }
        }
@@ -202,13 +308,16 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
            return new BufferedInputStream(is);
        }
        @Override
        public BitmapRegionDecoder loadBitmapRegionDecoder() {
            try {
                return BitmapRegionDecoder.newInstance(regenerateInputStream(), true);
            } catch (IOException e) {
                Log.e("BitmapRegionTileSource", "Error reading resource", e);
                return null;
        public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() {
            InputStream is = regenerateInputStream();
            SimpleBitmapRegionDecoder regionDecoder =
                    SimpleBitmapRegionDecoderWrapper.newInstance(is, false);
            Utils.closeSilently(is);
            if (regionDecoder == null) {
                is = regenerateInputStream();
                regionDecoder = DumbBitmapRegionDecoder.newInstance(is);
            }
            return regionDecoder;
        }
        @Override
        public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
@@ -217,7 +326,9 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        @Override
        public boolean readExif(ExifInterface ei) {
            try {
                ei.readExif(regenerateInputStream());
                InputStream is = regenerateInputStream();
                ei.readExif(is);
                Utils.closeSilently(is);
                return true;
            } catch (IOException e) {
                Log.e("BitmapRegionTileSource", "Error reading resource", e);
@@ -226,7 +337,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        }
    }

    BitmapRegionDecoder mDecoder;
    SimpleBitmapRegionDecoder mDecoder;
    int mWidth;
    int mHeight;
    int mTileSize;
@@ -243,6 +354,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
        mTileSize = TiledImageRenderer.suggestedTileSize(context);
        mRotation = source.getRotation();
        mDecoder = source.getBitmapRegionDecoder();
        if (mDecoder != null) {
            mWidth = mDecoder.getWidth();
            mHeight = mDecoder.getHeight();
            mOptions = new BitmapFactory.Options();
@@ -267,6 +379,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
                }
            }
        }
    }

    @Override
    public int getTileSize() {
+66 −31
Original line number Diff line number Diff line
@@ -41,10 +41,12 @@ import android.util.Log;
import android.view.Display;
import android.view.View;
import android.view.WindowManager;
import android.widget.Toast;

import com.android.gallery3d.common.Utils;
import com.android.gallery3d.exif.ExifInterface;
import com.android.photos.BitmapRegionTileSource;
import com.android.photos.BitmapRegionTileSource.BitmapSource;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
@@ -109,12 +111,24 @@ public class WallpaperCropActivity extends Activity {
                });

        // Load image in background
        setCropViewTileSource(
                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024), true, false);
        final BitmapRegionTileSource.UriBitmapSource bitmapSource =
                new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024);
        Runnable onLoad = new Runnable() {
            public void run() {
                if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) {
                    Toast.makeText(WallpaperCropActivity.this,
                            getString(R.string.wallpaper_load_fail),
                            Toast.LENGTH_LONG).show();
                    finish();
                }
            }
        };
        setCropViewTileSource(bitmapSource, true, false, onLoad);
    }

    public void setCropViewTileSource(final BitmapRegionTileSource.BitmapSource bitmapSource,
            final boolean touchEnabled, final boolean moveToLeft) {
    public void setCropViewTileSource(
            final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled,
            final boolean moveToLeft, final Runnable postExecute) {
        final Context context = WallpaperCropActivity.this;
        final View progressView = findViewById(R.id.loading);
        final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() {
@@ -127,6 +141,7 @@ public class WallpaperCropActivity extends Activity {
            protected void onPostExecute(Void arg) {
                if (!isCancelled()) {
                    progressView.setVisibility(View.INVISIBLE);
                    if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) {
                        mCropView.setTileSource(
                                new BitmapRegionTileSource(context, bitmapSource), null);
                        mCropView.setTouchEnabled(touchEnabled);
@@ -135,6 +150,10 @@ public class WallpaperCropActivity extends Activity {
                        }
                    }
                }
                if (postExecute != null) {
                    postExecute.run();
                }
            }
        };
        // We don't want to show the spinner every time we load an image, because that would be
        // annoying; instead, only start showing the spinner if loading the image has taken
@@ -235,10 +254,12 @@ public class WallpaperCropActivity extends Activity {
                InputStream is = context.getContentResolver().openInputStream(uri);
                BufferedInputStream bis = new BufferedInputStream(is);
                ei.readExif(bis);
                bis.close();
            } else {
                InputStream is = res.openRawResource(resId);
                BufferedInputStream bis = new BufferedInputStream(is);
                ei.readExif(bis);
                bis.close();
            }
            Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
            if (ori != null) {
@@ -408,7 +429,6 @@ public class WallpaperCropActivity extends Activity {
        String mInFilePath;
        byte[] mInImageBytes;
        int mInResId = 0;
        InputStream mInStream;
        RectF mCropBounds = null;
        int mOutWidth, mOutHeight;
        int mRotation;
@@ -481,37 +501,36 @@ public class WallpaperCropActivity extends Activity {
        }

        // Helper to setup input stream
        private void regenerateInputStream() {
        private InputStream regenerateInputStream() {
            if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
                Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
                        "image byte array given");
            } else {
                Utils.closeSilently(mInStream);
                try {
                    if (mInUri != null) {
                        mInStream = new BufferedInputStream(
                        return new BufferedInputStream(
                                mContext.getContentResolver().openInputStream(mInUri));
                    } else if (mInFilePath != null) {
                        mInStream = mContext.openFileInput(mInFilePath);
                        return mContext.openFileInput(mInFilePath);
                    } else if (mInImageBytes != null) {
                        mInStream = new BufferedInputStream(
                                new ByteArrayInputStream(mInImageBytes));
                        return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
                    } else {
                        mInStream = new BufferedInputStream(
                                mResources.openRawResource(mInResId));
                        return new BufferedInputStream(mResources.openRawResource(mInResId));
                    }
                } catch (FileNotFoundException e) {
                    Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
                }
            }
            return null;
        }

        public Point getImageBounds() {
            regenerateInputStream();
            if (mInStream != null) {
            InputStream is = regenerateInputStream();
            if (is != null) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeStream(mInStream, null, options);
                BitmapFactory.decodeStream(is, null, options);
                Utils.closeSilently(is);
                if (options.outWidth != 0 && options.outHeight != 0) {
                    return new Point(options.outWidth, options.outHeight);
                }
@@ -529,22 +548,26 @@ public class WallpaperCropActivity extends Activity {
        public boolean cropBitmap() {
            boolean failure = false;

            regenerateInputStream();

            WallpaperManager wallpaperManager = null;
            if (mSetWallpaper) {
                wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
            }
            if (mSetWallpaper && mNoCrop && mInStream != null) {


            if (mSetWallpaper && mNoCrop) {
                try {
                    wallpaperManager.setStream(mInStream);
                    InputStream is = regenerateInputStream();
                    if (is != null) {
                        wallpaperManager.setStream(is);
                        Utils.closeSilently(is);
                    }
                } catch (IOException e) {
                    Log.w(LOGTAG, "cannot write stream to wallpaper", e);
                    failure = true;
                }
                return !failure;
            }
            if (mInStream != null) {
            } else {
                // Find crop bounds (scaled to original image size)
                Rect roundedTrueCrop = new Rect();
                Matrix rotateMatrix = new Matrix();
@@ -557,6 +580,11 @@ public class WallpaperCropActivity extends Activity {
                    mCropBounds = new RectF(roundedTrueCrop);

                    Point bounds = getImageBounds();
                    if (bounds == null) {
                        Log.w(LOGTAG, "cannot get bounds for image");
                        failure = true;
                        return false;
                    }

                    float[] rotatedBounds = new float[] { bounds.x, bounds.y };
                    rotateMatrix.mapPoints(rotatedBounds);
@@ -567,7 +595,6 @@ public class WallpaperCropActivity extends Activity {
                    inverseRotateMatrix.mapRect(mCropBounds);
                    mCropBounds.offset(bounds.x/2, bounds.y/2);

                    regenerateInputStream();
                }

                mCropBounds.roundOut(roundedTrueCrop);
@@ -585,7 +612,14 @@ public class WallpaperCropActivity extends Activity {
                // Attempt to open a region decoder
                BitmapRegionDecoder decoder = null;
                try {
                    decoder = BitmapRegionDecoder.newInstance(mInStream, true);
                    InputStream is = regenerateInputStream();
                    if (is == null) {
                        Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
                        failure = true;
                        return false;
                    }
                    decoder = BitmapRegionDecoder.newInstance(is, false);
                    Utils.closeSilently(is);
                } catch (IOException e) {
                    Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
                }
@@ -603,14 +637,15 @@ public class WallpaperCropActivity extends Activity {

                if (crop == null) {
                    // BitmapRegionDecoder has failed, try to crop in-memory
                    regenerateInputStream();
                    InputStream is = regenerateInputStream();
                    Bitmap fullSize = null;
                    if (mInStream != null) {
                    if (is != null) {
                        BitmapFactory.Options options = new BitmapFactory.Options();
                        if (scaleDownSampleSize > 1) {
                            options.inSampleSize = scaleDownSampleSize;
                        }
                        fullSize = BitmapFactory.decodeStream(mInStream, null, options);
                        fullSize = BitmapFactory.decodeStream(is, null, options);
                        Utils.closeSilently(is);
                    }
                    if (fullSize != null) {
                        mCropBounds.left /= scaleDownSampleSize;