Improve media resizing (#722)
* improve MediaUtils.getImageThumbnail so it does not load the whole bitmap into memory * load thumbnails in device specific sizes
This commit is contained in:
parent
67f4479e86
commit
61f3f6c928
5 changed files with 73 additions and 63 deletions
|
@ -154,8 +154,6 @@ public final class ComposeActivity
|
|||
private static final int MEDIA_PICK_RESULT = 1;
|
||||
private static final int MEDIA_TAKE_PHOTO_RESULT = 2;
|
||||
private static final int PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1;
|
||||
@Px
|
||||
private static final int THUMBNAIL_SIZE = 128;
|
||||
|
||||
private static final String SAVED_TOOT_UID_EXTRA = "saved_toot_uid";
|
||||
private static final String SAVED_TOOT_TEXT_EXTRA = "saved_toot_text";
|
||||
|
@ -210,6 +208,7 @@ public final class ComposeActivity
|
|||
private int savedTootUid = 0;
|
||||
private List<Emoji> emojiList;
|
||||
private int maximumTootCharacters = STATUS_CHARACTER_LIMIT;
|
||||
private @Px int thumbnailViewSize;
|
||||
|
||||
private SaveTootHelper saveTootHelper;
|
||||
|
||||
|
@ -341,6 +340,8 @@ public final class ComposeActivity
|
|||
actionPhotoTake.setOnClickListener(v -> initiateCameraApp());
|
||||
actionPhotoPick.setOnClickListener(v -> onMediaPick());
|
||||
|
||||
thumbnailViewSize = getResources().getDimensionPixelSize(R.dimen.compose_media_preview_size);
|
||||
|
||||
/* Initialise all the state, or restore it from a previous run, to determine a "starting"
|
||||
* state. */
|
||||
Status.Visibility startingVisibility = Status.Visibility.UNKNOWN;
|
||||
|
@ -527,7 +528,7 @@ public final class ComposeActivity
|
|||
}
|
||||
} else if (savedMediaQueued != null) {
|
||||
for (SavedQueuedMedia item : savedMediaQueued) {
|
||||
Bitmap preview = MediaUtils.getImageThumbnail(getContentResolver(), item.uri, THUMBNAIL_SIZE);
|
||||
Bitmap preview = MediaUtils.getImageThumbnail(getContentResolver(), item.uri, thumbnailViewSize);
|
||||
addMediaToQueue(item.id, item.type, preview, item.uri, item.mediaSize, item.readyStage, item.description);
|
||||
}
|
||||
} else if (intent != null && savedInstanceState == null) {
|
||||
|
@ -1033,11 +1034,10 @@ public final class ComposeActivity
|
|||
item.readyStage = readyStage;
|
||||
ImageView view = item.preview;
|
||||
Resources resources = getResources();
|
||||
int side = resources.getDimensionPixelSize(R.dimen.compose_media_preview_side);
|
||||
int margin = resources.getDimensionPixelSize(R.dimen.compose_media_preview_margin);
|
||||
int marginBottom = resources.getDimensionPixelSize(
|
||||
R.dimen.compose_media_preview_margin_bottom);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(side, side);
|
||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize);
|
||||
layoutParams.setMargins(margin, 0, margin, marginBottom);
|
||||
view.setLayoutParams(layoutParams);
|
||||
view.setScaleType(ImageView.ScaleType.CENTER_CROP);
|
||||
|
@ -1343,7 +1343,7 @@ public final class ComposeActivity
|
|||
displayTransientError(R.string.error_media_upload_image_or_video);
|
||||
return;
|
||||
}
|
||||
Bitmap bitmap = MediaUtils.getVideoThumbnail(this, uri, THUMBNAIL_SIZE);
|
||||
Bitmap bitmap = MediaUtils.getVideoThumbnail(this, uri, thumbnailViewSize);
|
||||
if (bitmap != null) {
|
||||
addMediaToQueue(QueuedMedia.Type.VIDEO, bitmap, uri, mediaSize);
|
||||
} else {
|
||||
|
@ -1352,7 +1352,7 @@ public final class ComposeActivity
|
|||
break;
|
||||
}
|
||||
case "image": {
|
||||
Bitmap bitmap = MediaUtils.getImageThumbnail(contentResolver, uri, THUMBNAIL_SIZE);
|
||||
Bitmap bitmap = MediaUtils.getImageThumbnail(contentResolver, uri, thumbnailViewSize);
|
||||
if (bitmap != null) {
|
||||
addMediaToQueue(QueuedMedia.Type.IMAGE, bitmap, uri, mediaSize);
|
||||
} else {
|
||||
|
|
|
@ -36,6 +36,7 @@ import com.keylesspalace.tusky.di.Injectable
|
|||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.network.MastodonApi
|
||||
import com.keylesspalace.tusky.util.IOUtils
|
||||
import com.keylesspalace.tusky.util.MediaUtils
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.theartofdev.edmodo.cropper.CropImage
|
||||
import kotlinx.android.synthetic.main.activity_edit_profile.*
|
||||
|
@ -446,23 +447,9 @@ class EditProfileActivity : BaseActivity(), Injectable {
|
|||
|
||||
override fun doInBackground(vararg uris: Uri): Boolean? {
|
||||
val uri = uris[0]
|
||||
val inputStream: InputStream?
|
||||
try {
|
||||
inputStream = contentResolver.openInputStream(uri)
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.d(TAG, Log.getStackTraceString(e))
|
||||
return false
|
||||
}
|
||||
|
||||
val sourceBitmap: Bitmap?
|
||||
try {
|
||||
sourceBitmap = BitmapFactory.decodeStream(inputStream, null, null)
|
||||
} catch (error: OutOfMemoryError) {
|
||||
Log.d(TAG, Log.getStackTraceString(error))
|
||||
return false
|
||||
} finally {
|
||||
IOUtils.closeQuietly(inputStream)
|
||||
}
|
||||
val sourceBitmap = MediaUtils.getSampledBitmap(contentResolver, uri, resizeWidth, resizeHeight)
|
||||
|
||||
if (sourceBitmap == null) {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -131,21 +131,6 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
|||
return orientation;
|
||||
}
|
||||
|
||||
private static int calculateInSampleSize(int width, int height, int requiredScale) {
|
||||
int inSampleSize = 1;
|
||||
if (height > requiredScale || width > requiredScale) {
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
/* Calculate the largest inSampleSize value that is a power of 2 and keeps both height
|
||||
* and width larger than the requested height and width. */
|
||||
while (halfHeight / inSampleSize >= requiredScale
|
||||
&& halfWidth / inSampleSize >= requiredScale) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
return inSampleSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Uri... uris) {
|
||||
resultList = new ArrayList<>();
|
||||
|
@ -160,8 +145,6 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
|||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(inputStream, null, options);
|
||||
int beforeWidth = options.outWidth;
|
||||
int beforeHeight = options.outHeight;
|
||||
IOUtils.closeQuietly(inputStream);
|
||||
// Get EXIF data, for orientation info.
|
||||
int orientation = getOrientation(uri, contentResolver);
|
||||
|
@ -180,8 +163,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
|
|||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
options.inSampleSize = calculateInSampleSize(beforeWidth, beforeHeight,
|
||||
scaledImageSize);
|
||||
options.inSampleSize = MediaUtils.calculateInSampleSize(options, scaledImageSize, scaledImageSize);
|
||||
options.inJustDecodeBounds = false;
|
||||
Bitmap scaledBitmap;
|
||||
try {
|
||||
|
|
|
@ -27,6 +27,7 @@ import android.provider.OpenableColumns;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.Px;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
@ -34,11 +35,10 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Class who will have all the code link with Media
|
||||
* <p>
|
||||
* Motivation : try to keep the ComposeActivity "smaller" and make modular method
|
||||
* Class with helper methods for obtaining and resizing media files
|
||||
*/
|
||||
public class MediaUtils {
|
||||
private static final String TAG = "MediaUtils";
|
||||
public static final int MEDIA_SIZE_UNKNOWN = -1;
|
||||
|
||||
/**
|
||||
|
@ -88,30 +88,51 @@ public class MediaUtils {
|
|||
}
|
||||
|
||||
@Nullable
|
||||
public static Bitmap getImageThumbnail(ContentResolver contentResolver, Uri uri,
|
||||
@Px int thumbnailSize) {
|
||||
public static Bitmap getSampledBitmap(ContentResolver contentResolver, Uri uri, @Px int reqWidth, @Px int reqHeight) {
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
InputStream stream;
|
||||
try {
|
||||
stream = contentResolver.openInputStream(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
Bitmap source = BitmapFactory.decodeStream(stream);
|
||||
if (source == null) {
|
||||
|
||||
BitmapFactory.decodeStream(stream, null, options);
|
||||
|
||||
IOUtils.closeQuietly(stream);
|
||||
return null;
|
||||
}
|
||||
Bitmap bitmap = ThumbnailUtils.extractThumbnail(source, thumbnailSize, thumbnailSize);
|
||||
source.recycle();
|
||||
|
||||
// Calculate inSampleSize
|
||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
|
||||
|
||||
// Decode bitmap with inSampleSize set
|
||||
options.inJustDecodeBounds = false;
|
||||
try {
|
||||
if (stream != null) {
|
||||
stream.close();
|
||||
stream = contentResolver.openInputStream(uri);
|
||||
return BitmapFactory.decodeStream(stream, null, options);
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
} catch (OutOfMemoryError e) {
|
||||
Log.e(TAG, "OutOfMemoryError while trying to get sampled Bitmap", e);
|
||||
return null;
|
||||
} finally {
|
||||
IOUtils.closeQuietly(stream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
bitmap.recycle();
|
||||
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Bitmap getImageThumbnail(ContentResolver contentResolver, Uri uri,
|
||||
@Px int thumbnailSize) {
|
||||
Bitmap source = getSampledBitmap(contentResolver, uri, thumbnailSize, thumbnailSize);
|
||||
if(source != null) {
|
||||
return ThumbnailUtils.extractThumbnail(source, thumbnailSize, thumbnailSize, ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
@ -128,8 +149,7 @@ public class MediaUtils {
|
|||
}
|
||||
|
||||
public static long getImageSquarePixels(ContentResolver contentResolver, Uri uri) throws FileNotFoundException {
|
||||
InputStream input;
|
||||
input = contentResolver.openInputStream(uri);
|
||||
InputStream input = contentResolver.openInputStream(uri);
|
||||
|
||||
final BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
|
@ -139,4 +159,25 @@ public class MediaUtils {
|
|||
|
||||
return (long) options.outWidth * options.outHeight;
|
||||
}
|
||||
|
||||
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
|
||||
// Raw height and width of image
|
||||
final int height = options.outHeight;
|
||||
final int width = options.outWidth;
|
||||
int inSampleSize = 1;
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
|
||||
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
|
||||
// height and width larger than the requested height and width.
|
||||
while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {
|
||||
inSampleSize *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return inSampleSize;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<dimen name="status_detail_media_preview_height">130dp</dimen>
|
||||
<dimen name="compose_media_preview_margin">8dp</dimen>
|
||||
<dimen name="compose_media_preview_margin_bottom">0dp</dimen>
|
||||
<dimen name="compose_media_preview_side">120dp</dimen>
|
||||
<dimen name="compose_media_preview_size">120dp</dimen>
|
||||
<dimen name="compose_options_margin">8dp</dimen>
|
||||
<dimen name="account_avatar_margin">14dp</dimen>
|
||||
<dimen name="tab_page_margin">8dp</dimen>
|
||||
|
|
Loading…
Reference in a new issue