/* * Copyright 2015 Daniel Dittmar * * 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 dan.dit.whatsthat.util.mosaic; import android.app.Activity; import android.content.ClipData; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.provider.MediaStore; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; import java.text.NumberFormat; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.Map; import dan.dit.whatsthat.R; import dan.dit.whatsthat.image.Image; import dan.dit.whatsthat.image.ImageBitmapSource; import dan.dit.whatsthat.system.RiddleFragment; import dan.dit.whatsthat.system.store.WorkshopView; import dan.dit.whatsthat.util.general.PercentProgressListener; import dan.dit.whatsthat.util.general.VersionSafe; import dan.dit.whatsthat.util.image.BitmapUtil; import dan.dit.whatsthat.util.image.ColorMetric; import dan.dit.whatsthat.util.image.Dimension; import dan.dit.whatsthat.util.image.ImageUtil; import dan.dit.whatsthat.util.mosaic.data.MosaicMaker; import dan.dit.whatsthat.util.mosaic.matching.SimpleLinearTileMatcher; import dan.dit.whatsthat.util.mosaic.matching.TileMatcher; import dan.dit.whatsthat.util.mosaic.reconstruction.pattern.CirclePatternReconstructor; import dan.dit.whatsthat.util.mosaic.reconstruction.pattern.LegoPatternReconstructor; /** * Created by daniel on 07.10.15. */ public class MosaicGeneratorUi { private static final double DEFAULT_ROWS_COLUMNS = 30; private static final double MAX_ROWS_COLUMNS = 100; private static final ColorMetric DEFAULT_COLOR_METRIC = ColorMetric.Euclid2.INSTANCE; private static final boolean DEFAULT_USE_ALPHA = true; private static final int MAX_IMAGE_WIDTH_HEIGHT = 1024; // else we run into out of memory // errors really quick since most cameras produce high resolution images and we only get // around 50mb ram from JVM by default private final Activity mActivity; private final View mView; private final Spinner mMosaicTypes; private final List<ColorMetric> mMetrics; private final ImageView mSelectedBitmapState; private final ImageView mMosaicImageView; private final View mWorkingIndicator; private final PercentProgressListener mProgress; private final View mParameterUseAlphaContainer; private final View mParameterUseColorMetricContainer; private File mMediaDirectory; private String mSelectedBitmapName; private String mMosaicBitmapName; private Bitmap mSelectedBitmap; private List<MosaicType> mTypes; private List<TextView> mParameterName; private List<SeekBar> mParameterValue; private List<View> mParameterContainer; private CheckBox mParameterUseAlpha; private Spinner mParameterColorMetric; private MosaicMaker<String> mMosaicMaker; private int mIgnoreParameterChange; private AsyncTask<Void, Integer, Bitmap> mMosaicTask; private Bitmap mMosaicBitmap; private File mMosaicFile; private View mShare; private SVDMaker mSVDMaker; private int mSVDLastRank; public MosaicGeneratorUi(Activity activity) { mActivity = activity; mSVDLastRank = -1; Map<String, Image> images = RiddleFragment.ALL_IMAGES; ImageBitmapSource source = new ImageBitmapSource(mActivity.getResources(), images); TileMatcher<String> matcher = new SimpleLinearTileMatcher<>(images.values(), DEFAULT_USE_ALPHA, DEFAULT_COLOR_METRIC); mMosaicMaker = new MosaicMaker<>(matcher, source, DEFAULT_USE_ALPHA, DEFAULT_COLOR_METRIC); mTypes = new ArrayList<>(10); mTypes.add(new MosaicType(MosaicType.RECT, R.string.mosaic_generator_mosaic_type_rect, true, true) .addParameter(R.string.mosaic_generator_param_rows, 1, MAX_ROWS_COLUMNS, DEFAULT_ROWS_COLUMNS, true) .addParameter(R.string.mosaic_generator_param_columns, 1, MAX_ROWS_COLUMNS, DEFAULT_ROWS_COLUMNS, true)); mTypes.add(new MosaicType(MosaicType.MULTI_RECT, R.string.mosaic_generator_mosaic_type_multirect, true, true) .addParameter(R.string.mosaic_generator_param_rows, 1, MAX_ROWS_COLUMNS, DEFAULT_ROWS_COLUMNS, true) .addParameter(R.string.mosaic_generator_param_columns, 1, MAX_ROWS_COLUMNS, DEFAULT_ROWS_COLUMNS, true) .addParameter(R.string.mosaic_generator_param_merge_factor, 0., 1., 0.5, false)); mTypes.add(new MosaicType(MosaicType.FIXED_LAYER, R.string .mosaic_generator_mosaic_type_fixedlayer, true, true) .addParameter(R.string.mosaic_generator_param_layer_count, 1, 10, 3, true)); mTypes.add(new MosaicType(MosaicType.SVD, R.string.mosaic_generator_mosaic_type_svd, false, false) .addParameter(R.string.mosaic_generator_mosaic_type_svd_approx, 1, 100, 20, true)); mTypes.add(new MosaicType(MosaicType.PATTERN_CIRCLE, R.string .mosaic_generator_mosaic_type_pattern_circle, false, false) .addParameter(R.string.mosaic_generator_param_rows, 1, MAX_ROWS_COLUMNS, DEFAULT_ROWS_COLUMNS, true) .addParameter(R.string.mosaic_generator_param_columns, 1, MAX_ROWS_COLUMNS, DEFAULT_ROWS_COLUMNS, true)); mTypes.add(new MosaicType(MosaicType.PATTERN_LEGO, R.string .mosaic_generator_mosaic_type_pattern_lego, true, true) .addParameter(R.string.mosaic_generator_param_rows, 1, MAX_ROWS_COLUMNS, 25, true) .addParameter(R.string.mosaic_generator_param_columns, 1, MAX_ROWS_COLUMNS, 25, true)); LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mView = inflater.inflate(R.layout.workshop_mosaic_generator, null); mMosaicTypes = (Spinner) mView.findViewById(R.id.mosaic_types); mMosaicTypes.setAdapter(new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_dropdown_item, mTypes)); mMosaicTypes.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { MosaicType type = mTypes.get(position); type.applyUi(); type.makeMosaic(); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); mView.findViewById(R.id.parameter_title).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mIgnoreParameterChange++; View container = mView.findViewById(R.id.parameter_inner_container); if (container.getVisibility() == View.VISIBLE) { container.setVisibility(View.GONE); ((TextView) v).setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.achievement_list_group_indicator_down, 0); } else { container.setVisibility(View.VISIBLE); ((TextView) v).setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.achievement_list_group_indicator_up, 0); } mIgnoreParameterChange--; } }); mParameterName = new ArrayList<>(3); mParameterValue = new ArrayList<>(3); mParameterContainer = new ArrayList<>(3); mParameterName.add((TextView) mView.findViewById(R.id.parameter1_name)); mParameterName.add((TextView) mView.findViewById(R.id.parameter2_name)); mParameterName.add((TextView) mView.findViewById(R.id.parameter3_name)); mParameterValue.add((SeekBar) mView.findViewById(R.id.parameter1_value)); mParameterValue.add((SeekBar) mView.findViewById(R.id.parameter2_value)); mParameterValue.add((SeekBar) mView.findViewById(R.id.parameter3_value)); SeekBar.OnSeekBarChangeListener seekChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { int index = mParameterValue.indexOf(seekBar); if (index >= 0) { MosaicType type = getCurrentMosaicType(); if (type != null) { type.barToValue(seekBar, index); } } onParameterChanged(false); } } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { onParameterChanged(true); } }; for (int i = 0; i < mParameterValue.size(); i++) { mParameterValue.get(i).setOnSeekBarChangeListener(seekChangeListener); } mParameterContainer.add(mView.findViewById(R.id.parameter1)); mParameterContainer.add(mView.findViewById(R.id.parameter2)); mParameterContainer.add(mView.findViewById(R.id.parameter3)); mParameterUseAlphaContainer = mView.findViewById(R.id.parameterUseAlpha); mParameterUseAlpha = (CheckBox) mView.findViewById(R.id.parameterUseAlpha_value); mParameterUseColorMetricContainer = mView.findViewById(R.id.parameterColorMetric); mParameterColorMetric = (Spinner) mView.findViewById(R.id.parameterColorMetric_value); mMetrics = ColorMetric.makeAll(); mParameterColorMetric.setAdapter(new ArrayAdapter<>(mActivity, android.R.layout.simple_spinner_dropdown_item, mMetrics)); mParameterUseAlpha.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { mMosaicMaker.setUseAlpha(isChecked); onParameterChanged(true); } }); mParameterColorMetric.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { if (mMetrics.get(position).equals(mMosaicMaker.getColorMetric())) { return; } mMosaicMaker.setColorMetric(mMetrics.get(position)); onParameterChanged(true); } @Override public void onNothingSelected(AdapterView<?> parent) { } }); mSelectedBitmapState = (ImageView) mView.findViewById(R.id.select_image_state); applySelectImageState(); mView.findViewById(R.id.select_image).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { openSelectBitmap(); } }); mMosaicImageView = (ImageView) mView.findViewById(R.id.mosaic_image); mWorkingIndicator = mView.findViewById(R.id.progress_is_working); mProgress = (PercentProgressListener) mView.findViewById(R.id.progress_bar); mView.findViewById(R.id.save_image).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bitmap bitmap = mMosaicBitmap; if (bitmap != null) { ensureMediaDirectory(); mMosaicFile = ImageUtil.saveToMediaFile(mActivity, bitmap, mMosaicBitmapName); applyShare(); if (mMosaicFile != null) { Toast.makeText(mActivity, R.string.mosaic_generator_image_save_success, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(mActivity, R.string.mosaic_generator_image_save_failed, Toast.LENGTH_SHORT).show(); } } } }); mShare = mView.findViewById(R.id.share_image); mShare.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { shareMosaic(); } }); applyShare(); } private void applyShare() { mShare.setEnabled(mMosaicFile != null); } private void shareMosaic() { if (mMosaicFile != null) { Intent share = new Intent(Intent.ACTION_SEND); share.setType("image/*"); share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(mMosaicFile)); mActivity.startActivity(Intent.createChooser(share, mActivity.getResources().getString(R.string.mosaic_generator_share_mosaic))); } } private void onBitmapSelected() { applySelectImageState(); Log.d("HomeStuff", "Bitmap selected: " + mSelectedBitmap.getWidth() + "/" + mSelectedBitmap.getHeight()); mMosaicImageView.setImageBitmap(mSelectedBitmap); MosaicType curr = getCurrentMosaicType(); if (curr != null) { curr.makeMosaic(); } } private void onMosaicDone(@NonNull Bitmap mosaic) { mMosaicBitmap = mosaic; mMosaicImageView.setImageBitmap(mMosaicBitmap); } private void applySelectImageState() { if (mSelectedBitmap == null) { mSelectedBitmapState.setImageResource(R.drawable.x_red); } else { mSelectedBitmapState.setImageResource(R.drawable.a_green); } } private MosaicType getCurrentMosaicType() { if (mMosaicTypes == null || mMosaicTypes.getSelectedItemPosition() == AdapterView.INVALID_POSITION) { return null; } return mTypes.get(mMosaicTypes.getSelectedItemPosition()); } private void onParameterChanged(boolean updateMosaic) { if (mIgnoreParameterChange > 0) { return; } MosaicType type = getCurrentMosaicType(); if (type != null) { type.applyUi(); if (updateMosaic) { type.makeMosaic(); } } } public View getView() { return mView; } public void clear() { // to release memory mSelectedBitmapName = null; mSelectedBitmap = null; mMosaicImageView.setImageResource(0); mMosaicBitmap = null; mSVDMaker = null; } private class MosaicType { public static final int RECT = 0; public static final int MULTI_RECT = 1; public static final int FIXED_LAYER = 2; public static final int SVD = 3; public static final int PATTERN_CIRCLE = 4; public static final int PATTERN_LEGO = 5; private static final boolean SVD_RANK_PARAMETER_LOGARITHMIC_SCALE = false; private final int mNameResId; private final List<Double> mMinValues; private final List<Double> mMaxValues; private final List<Double> mValues; private final List<Integer> mParameterNameResIds; private final int mType; private List<Boolean> mOnlyIntegers; private boolean mIsUsingAlpha; private boolean mIsUsingColorMetric; public MosaicType(int type, int nameResId, boolean isUsingAlpha, boolean useColorMetric) { mType = type; mNameResId = nameResId; mMinValues = new LinkedList<>(); mMaxValues = new LinkedList<>(); mValues = new LinkedList<>(); mOnlyIntegers = new LinkedList<>(); mParameterNameResIds = new LinkedList<>(); mIsUsingAlpha = isUsingAlpha; mIsUsingColorMetric = useColorMetric; } public MosaicType addParameter(int nameResId, double minValue, double maxValue, double defaultValue, boolean onlyIntegers) { mParameterNameResIds.add(nameResId); mMinValues.add(minValue); mMaxValues.add(maxValue); mValues.add(defaultValue); mOnlyIntegers.add(onlyIntegers); return this; } private void valueToBar(SeekBar bar, double minValue, double maxValue, double value) { // interval [minValue, maxValue] mapped to [0, bar.max()] int progress = (int) ((value - minValue) / (maxValue - minValue) * bar.getMax()); bar.setProgress(progress); } private void barToValue(SeekBar bar, int index) { double value = bar.getProgress() / (double) bar.getMax() * (mMaxValues.get(index) - mMinValues.get(index)) + mMinValues.get(index); if (mOnlyIntegers.get(index)) { value = Math.round(value); } mValues.set(index, value); } public void applyUi() { mIgnoreParameterChange++; if (mIsUsingAlpha) { mParameterUseAlpha.setChecked(mMosaicMaker.usesAlpha()); mParameterUseAlphaContainer.setVisibility(View.VISIBLE); } else { mParameterUseAlphaContainer.setVisibility(View.GONE); } if (mIsUsingColorMetric) { mParameterColorMetric.setSelection(mMetrics.indexOf(mMosaicMaker.getColorMetric())); mParameterUseColorMetricContainer.setVisibility(View.VISIBLE); } else { mParameterUseColorMetricContainer.setVisibility(View.GONE); } for (int i = 0; i < mParameterContainer.size(); i++) { if (i < mMinValues.size()) { // got that parameter mParameterContainer.get(i).setVisibility(View.VISIBLE); SeekBar bar = mParameterValue.get(i); double max = mMaxValues.get(i); double min = mMinValues.get(i); int steps; if (mOnlyIntegers.get(i)) { steps = (int) (max - min + 1); } else { steps = 100; } bar.setMax(steps); valueToBar(bar, min, max, mValues.get(i)); mParameterName.get(i).setText(mActivity.getResources().getString(mParameterNameResIds.get(i), mValues.get(i))); } else { mParameterContainer.get(i).setVisibility(View.GONE); } } mIgnoreParameterChange--; } public void makeMosaic() { Log.d("Riddle", "Start making mosaic " + mType + " task was: " + mMosaicTask); if (mMosaicTask != null) { mMosaicTask.cancel(true); mMosaicTask = null; } if (mType != SVD) { mSVDMaker = null; // clear memory } mMosaicTask = new AsyncTask<Void, Integer, Bitmap>() { @Override public void onPreExecute() { mWorkingIndicator.setVisibility(View.VISIBLE); mProgress.onProgressUpdate(0); ImageUtil.CACHE.makeReusable(mMosaicBitmap); mMosaicImageView.setImageBitmap(mSelectedBitmap); mMosaicBitmap = null; mMosaicBitmapName = null; mMosaicFile = null; applyShare(); } @Override public void onProgressUpdate(Integer... progress) { mProgress.onProgressUpdate(progress[0]); } public AsyncTask<Void, Integer, Bitmap> getTask() { return this; // to be accessible in the inner nested anonymous class } @Override protected Bitmap doInBackground(Void... params) { Bitmap base = mSelectedBitmap; if (base == null) { return null; } int rows = 0, columns = 0; if (mType == RECT || mType == MULTI_RECT || mType == PATTERN_CIRCLE || mType == PATTERN_LEGO) { //make sure that image dimensions are dividable by the given columns/rows values by resizing rows = (int) Math.round(mValues.get(0)); columns = (int) Math.round(mValues.get(1)); Dimension targetDim = new Dimension(base.getWidth(), base.getHeight()); targetDim.ensureDivisibleBy(columns, rows, true); if (base.getWidth() != targetDim.getWidth() || base.getHeight() != targetDim.getHeight()) { Log.d("Riddle", "Need to resize base image before doing rect or multirect mosaic: rows=" + rows + ", columns=" + columns + " and bitmap was " + base.getWidth() + "/" + base.getHeight() + " and now is " + targetDim); base = BitmapUtil.resize(base, targetDim.getWidth(), targetDim.getHeight()); } } if (isCancelled()) { return null; } Bitmap result = null; Log.d("Riddle", "Really starting to make mosaic " + mType + " task was: " + mMosaicTask + " for bitmap dimension " + base.getWidth() + "/" + base.getHeight()); MosaicMaker.ProgressCallback callback = new MosaicMaker.ProgressCallback() { @Override public void onProgressUpdate(int progress) { publishProgress(progress); } @Override public boolean isCancelled() { return getTask().isCancelled(); } }; try { switch (mType) { case RECT: result = mMosaicMaker.makeRect(base, rows, columns, callback); break; case MULTI_RECT: result = mMosaicMaker.makeMultiRect(base, rows, columns, mValues.get(2), callback); break; case FIXED_LAYER: result = mMosaicMaker.makeFixedLayer(base, (int) Math.round(mValues.get(0)), callback); break; case PATTERN_CIRCLE: result = MosaicMaker.makePattern(mActivity.getResources(), base, CirclePatternReconstructor.NAME, mMosaicMaker.usesAlpha(), mMosaicMaker.getColorMetric(), rows, columns, callback); break; case PATTERN_LEGO: result = MosaicMaker.makePattern(mActivity.getResources(), base, LegoPatternReconstructor.NAME, mMosaicMaker.usesAlpha(), mMosaicMaker.getColorMetric(), rows, columns, callback); break; case SVD: if (mSVDMaker == null) { mSVDMaker = new SVDMaker(base, SVDMaker.MODE_ARGB_BITMAP, callback); } // use a logarithmic scale as the interesting effects appear in // the higher value regions int wantedRank; if (SVD_RANK_PARAMETER_LOGARITHMIC_SCALE) { wantedRank = (int) ( Math.log(1. + mValues.get(0) / 100.) / Math.log(2) * mSVDMaker.getMaxRank()); } else { wantedRank = (int) (mValues.get(0) / 100. * mSVDMaker .getMaxRank()); wantedRank = Math.max(1, wantedRank); } if (mMosaicBitmap == null || mSVDLastRank != wantedRank) { result = mSVDMaker.getRankApproximation(wantedRank); if (result != null) { mSVDLastRank = wantedRank; } } break; } } catch (OutOfMemoryError error) { // well I know I am catching an error, but since there is this stupid (48mb to 128mb) restriction we move at the edge of what is possible clear(); result = null; Toast.makeText(mActivity, R.string.mosaic_generator_memory_error, Toast.LENGTH_LONG).show(); } return result; } @Override public void onPostExecute(Bitmap result) { mMosaicTask = null; mWorkingIndicator.setVisibility(View.INVISIBLE); mProgress.onProgressUpdate(0); if (result != null) { StringBuilder name = new StringBuilder(); MosaicType.this.addConfigPrefix(name); name.append(mSelectedBitmapName); mMosaicBitmapName = name.toString(); onMosaicDone(result); } Runtime.getRuntime().gc(); } }.execute(); } @Override public String toString() { return mActivity.getResources().getString(mNameResId); } public void addConfigPrefix(StringBuilder builder) { builder.append(toString()); for (int i = 0; i < mMinValues.size(); i++) { builder.append('_'); if (mOnlyIntegers.get(i)) { builder.append((int) Math.round(mValues.get(i))); } else { builder.append(NumberFormat.getInstance().format(mValues.get(i))); } } builder.append(mMosaicMaker.usesAlpha() ? "A" : "-") .append(mMosaicMaker.getColorMetric()); } } private void openSelectBitmap() { Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT); getIntent.setType("image/*"); Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); pickIntent.setType("image/*"); Intent chooserIntent = Intent.createChooser(getIntent, mActivity.getResources().getString(R.string.select_mosaic_image_from_gallery)); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{pickIntent}); mActivity.startActivityForResult(chooserIntent, WorkshopView.PICK_IMAGE_FOR_MOSAIC); } public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != Activity.RESULT_OK || requestCode != WorkshopView.PICK_IMAGE_FOR_MOSAIC || data == null) { return; } ClipData clipData = VersionSafe.getClipData(data); Uri[] selectedImageUris; if (clipData == null) { selectedImageUris = new Uri[] {data.getData()}; } else { selectedImageUris = new Uri[clipData.getItemCount()]; for (int i = 0; i < clipData.getItemCount(); i++) { ClipData.Item curr = clipData.getItemAt(i); selectedImageUris[i] = curr.getUri(); } } new AsyncTask<Uri, Bitmap, Void>() { @Override public void onPreExecute() { mWorkingIndicator.setVisibility(View.VISIBLE); clear(); } @Override protected Void doInBackground(Uri... params) { String[] columns = { MediaStore.Images.Media.DATA, MediaStore.Images.Media.DISPLAY_NAME}; for (Uri selectedImages : params) { if (selectedImages == null) { continue; } Cursor cursor = null; try { cursor = mActivity.getContentResolver().query(selectedImages, columns, null, null, null); if (cursor == null) { continue; } cursor.moveToFirst(); if (cursor.isAfterLast()) { continue; // empty cursor } int pathIndex = cursor.getColumnIndexOrThrow(columns[0]); int nameIndex = cursor.getColumnIndex(columns[1]); do { String picturePath = cursor.getString(pathIndex); if (picturePath == null) { InputStream input = null; try { input = mActivity.getContentResolver().openInputStream(selectedImages); } catch (FileNotFoundException e1) { Log.e("HomeStuff", "File not found when trying to decode bitmap from stream: " + e1); } mSelectedBitmapName = null; if (nameIndex != -1) { mSelectedBitmapName = cursor.getString(nameIndex); } if (TextUtils.isEmpty(mSelectedBitmapName)) { mSelectedBitmapName = String.valueOf(System.currentTimeMillis()); } publishProgress(ImageUtil.loadBitmap(input, MAX_IMAGE_WIDTH_HEIGHT, MAX_IMAGE_WIDTH_HEIGHT, BitmapUtil.MODE_FIT_NO_GROW)); } else { File path = new File(picturePath); mSelectedBitmapName = path.getName(); if (TextUtils.isEmpty(mSelectedBitmapName) && nameIndex != -1) { mSelectedBitmapName = cursor.getString(nameIndex); } else if (TextUtils.isEmpty(mSelectedBitmapName)) { mSelectedBitmapName = String.valueOf(System.currentTimeMillis()); } publishProgress(ImageUtil.loadBitmap(path, MAX_IMAGE_WIDTH_HEIGHT, MAX_IMAGE_WIDTH_HEIGHT, BitmapUtil.MODE_FIT_NO_GROW)); } cursor.moveToNext(); } while (!cursor.isAfterLast()); } catch (Exception e) { Log.e("HomeStuff", "Error retrieving images from cursor " + cursor + ": " + e); } finally { if (cursor != null) { cursor.close(); } } } return null; } @Override public void onProgressUpdate(Bitmap... bitmap) { ImageUtil.CACHE.makeReusable(mSelectedBitmap); mSelectedBitmap = bitmap[0]; mWorkingIndicator.setVisibility(View.INVISIBLE); onBitmapSelected(); } }.execute(selectedImageUris); } private void ensureMediaDirectory() { if (mMediaDirectory == null) { mMediaDirectory = ImageUtil.getMediaDirectory(); if (mMediaDirectory == null) { throw new IllegalStateException("No media directory available"); } } } }