diff --git a/app/build.gradle b/app/build.gradle index 90458c7c2..a36061bbf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -58,6 +58,7 @@ dependencies { implementation 'com.google.vr:sdk-panowidget:1.150.0' implementation 'org.apache.sanselan:sanselan:0.97-incubator' implementation 'info.androidhive:imagefilters:1.0.7' + implementation 'com.squareup.picasso:picasso:2.71828' kapt "android.arch.persistence.room:compiler:1.1.1" implementation "android.arch.persistence.room:runtime:1.1.1" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 9bd07e413..efaa38cf9 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,3 +1,9 @@ -keep class com.simplemobiletools.** { *; } -dontwarn com.simplemobiletools.** -dontwarn org.apache.** + +# Picasso +-dontwarn javax.annotation.** +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase +-dontwarn org.codehaus.mojo.animal_sniffer.* +-dontwarn okhttp3.internal.platform.ConscryptPlatform diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt index 6bf2dc1d6..149fd53eb 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt @@ -77,7 +77,6 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View companion object { var screenWidth = 0 var screenHeight = 0 - var wasDecodedByGlide = false } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt index d996874c7..d004ee1ea 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/extensions/Activity.kt @@ -61,7 +61,7 @@ fun Activity.launchCamera() { fun SimpleActivity.launchAbout() { val licenses = LICENSE_GLIDE or LICENSE_CROPPER or LICENSE_MULTISELECT or LICENSE_RTL or LICENSE_SUBSAMPLING or LICENSE_PATTERN or - LICENSE_REPRINT or LICENSE_GIF_DRAWABLE or LICENSE_PHOTOVIEW or LICENSE_EXOPLAYER or LICENSE_PANORAMA_VIEW or LICENSE_SANSELAN or LICENSE_FILTERS + LICENSE_REPRINT or LICENSE_GIF_DRAWABLE or LICENSE_PHOTOVIEW or LICENSE_PICASSO or LICENSE_EXOPLAYER or LICENSE_PANORAMA_VIEW or LICENSE_SANSELAN or LICENSE_FILTERS val faqItems = arrayListOf( FAQItem(R.string.faq_5_title_commons, R.string.faq_5_text_commons), diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt index b317132a9..651520dc2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt @@ -9,20 +9,14 @@ import android.graphics.Matrix import android.graphics.drawable.ColorDrawable import android.media.ExifInterface.* import android.net.Uri -import android.os.AsyncTask import android.os.Bundle import android.os.Handler import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.bumptech.glide.Glide -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.DecodeFormat import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.request.RequestListener import com.bumptech.glide.request.RequestOptions -import com.bumptech.glide.request.target.Target import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.simplemobiletools.commons.extensions.* @@ -33,11 +27,10 @@ import com.simplemobiletools.gallery.activities.PanoramaActivity import com.simplemobiletools.gallery.activities.PhotoActivity import com.simplemobiletools.gallery.activities.ViewPagerActivity import com.simplemobiletools.gallery.extensions.* -import com.simplemobiletools.gallery.helpers.GlideRotateTransformation -import com.simplemobiletools.gallery.helpers.MEDIUM -import com.simplemobiletools.gallery.helpers.PATH -import com.simplemobiletools.gallery.helpers.ROTATE_BY_ASPECT_RATIO +import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.models.Medium +import com.squareup.picasso.Callback +import com.squareup.picasso.Picasso import it.sephiroth.android.library.exif2.ExifInterface import kotlinx.android.synthetic.main.pager_photo_item.view.* import org.apache.sanselan.common.byteSources.ByteSourceInputStream @@ -48,12 +41,11 @@ import java.io.FileOutputStream class PhotoFragment : ViewPagerFragment() { private val DEFAULT_DOUBLE_TAP_ZOOM = 2f - private val ZOOMABLE_VIEW_LOAD_DELAY = 1000L + private val ZOOMABLE_VIEW_LOAD_DELAY = 500L private var isFragmentVisible = false private var isFullscreen = false private var wasInit = false - private var useHalfResolution = false private var isPanorama = false private var imageOrientation = -1 private var gifDrawable: GifDrawable? = null @@ -245,48 +237,19 @@ class PhotoFragment : ViewPagerFragment() { private fun loadBitmap(degrees: Int = 0) { if (degrees == 0) { - var targetWidth = if (ViewPagerActivity.screenWidth == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenWidth - var targetHeight = if (ViewPagerActivity.screenHeight == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenHeight - if (useHalfResolution) { - targetWidth /= 2 - targetHeight /= 2 - } - - if (imageOrientation == ORIENTATION_ROTATE_90) { - targetWidth = targetHeight - targetHeight = Target.SIZE_ORIGINAL - } - - val options = RequestOptions() - .signature(medium.path.getFileSignature()) - .format(DecodeFormat.PREFER_ARGB_8888) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .override(targetWidth, targetHeight) - - Glide.with(this) - .asBitmap() - .load(getPathToLoad(medium)) - .apply(options) - .listener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, target: Target?, isFirstResource: Boolean): Boolean { - if (!useHalfResolution && e?.rootCauses?.firstOrNull() is OutOfMemoryError) { - useHalfResolution = true - Handler().post { - if (activity?.isActivityDestroyed() == false) { - loadBitmap(degrees) - } - } - } - return false - } - - override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { + Picasso.get() + .load(File(medium.path)) + .centerInside() + .fit() + .into(view.photo_view, object : Callback { + override fun onSuccess() { if (isFragmentVisible) { scheduleZoomableView() } - return false } - }).into(view.photo_view) + + override fun onError(e: Exception) {} + }) } else { val options = RequestOptions() .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -311,23 +274,24 @@ class PhotoFragment : ViewPagerFragment() { private fun scheduleZoomableView() { loadZoomableViewHandler.removeCallbacksAndMessages(null) loadZoomableViewHandler.postDelayed({ - if (isFragmentVisible && !context!!.config.replaceZoomableImages && medium.isImage() && view.subsampling_view.isGone()) { + if (isFragmentVisible && medium.isImage() && view.subsampling_view.isGone()) { addZoomableView() } }, ZOOMABLE_VIEW_LOAD_DELAY) } private fun addZoomableView() { - ViewPagerActivity.wasDecodedByGlide = false + val rotation = degreesForRotation(imageOrientation) view.subsampling_view.apply { + setBitmapDecoderFactory { PicassoDecoder(medium.path, Picasso.get(), rotation) } + setRegionDecoderFactory { PicassoRegionDecoder() } maxScale = 10f beVisible() isQuickScaleEnabled = context.config.oneFingerZoom setResetScaleOnSizeChange(context.config.screenRotation != ROTATE_BY_ASPECT_RATIO) setImage(ImageSource.uri(getPathToLoad(medium))) - orientation = if (imageOrientation == -1) SubsamplingScaleImageView.ORIENTATION_USE_EXIF else degreesForRotation(imageOrientation) + orientation = rotation setEagerLoadingEnabled(false) - setExecutor(AsyncTask.SERIAL_EXECUTOR) setOnImageEventListener(object : SubsamplingScaleImageView.OnImageEventListener { override fun onImageLoaded() { } @@ -400,8 +364,6 @@ class PhotoFragment : ViewPagerFragment() { return if (context == null || bitmapAspectRatio == screenAspectRatio) { DEFAULT_DOUBLE_TAP_ZOOM - } else if (ViewPagerActivity.wasDecodedByGlide) { - 1f } else if (context!!.portrait && bitmapAspectRatio <= screenAspectRatio) { ViewPagerActivity.screenHeight / height.toFloat() } else if (context!!.portrait && bitmapAspectRatio > screenAspectRatio) { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/PicassoDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/PicassoDecoder.kt new file mode 100644 index 000000000..16dd85760 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/PicassoDecoder.kt @@ -0,0 +1,21 @@ +package com.simplemobiletools.gallery.helpers + +import android.content.Context +import android.graphics.Bitmap +import android.net.Uri +import com.davemorrissey.labs.subscaleview.decoder.ImageDecoder +import com.squareup.picasso.MemoryPolicy +import com.squareup.picasso.Picasso + +class PicassoDecoder(val tag: String, val picasso: Picasso, val degrees: Int) : ImageDecoder { + + override fun decode(context: Context, uri: Uri): Bitmap { + return picasso + .load(uri) + .tag(tag) + .config(Bitmap.Config.ARGB_8888) + .memoryPolicy(MemoryPolicy.NO_CACHE) + .rotate(-degrees.toFloat()) + .get() + } +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/PicassoRegionDecoder.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/PicassoRegionDecoder.kt new file mode 100644 index 000000000..077508770 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/PicassoRegionDecoder.kt @@ -0,0 +1,33 @@ +package com.simplemobiletools.gallery.helpers + +import android.content.Context +import android.graphics.* +import android.net.Uri +import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder + +class PicassoRegionDecoder : ImageRegionDecoder { + private var decoder: BitmapRegionDecoder? = null + private val decoderLock = Any() + + override fun init(context: Context, uri: Uri): Point { + val inputStream = context.contentResolver.openInputStream(uri) + this.decoder = BitmapRegionDecoder.newInstance(inputStream, false) + return Point(this.decoder!!.width, this.decoder!!.height) + } + + override fun decodeRegion(rect: Rect, sampleSize: Int): Bitmap { + synchronized(this.decoderLock) { + val options = BitmapFactory.Options() + options.inSampleSize = sampleSize + options.inPreferredConfig = Bitmap.Config.ARGB_8888 + val bitmap = this.decoder!!.decodeRegion(rect, options) + return bitmap ?: throw RuntimeException("Region decoder returned null bitmap - image format may not be supported") + } + } + + override fun isReady() = this.decoder != null && !this.decoder!!.isRecycled + + override fun recycle() { + this.decoder!!.recycle() + } +}