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 0023d8a99..f566b6333 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/activities/ViewPagerActivity.kt @@ -5,7 +5,10 @@ import android.content.Intent import android.content.pm.ActivityInfo import android.content.res.Configuration import android.database.Cursor +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.graphics.Color +import android.graphics.Matrix import android.graphics.drawable.ColorDrawable import android.hardware.SensorManager import android.media.ExifInterface @@ -24,6 +27,7 @@ import com.simplemobiletools.gallery.R import com.simplemobiletools.gallery.activities.MediaActivity.Companion.mMedia import com.simplemobiletools.gallery.adapters.MyPagerAdapter import com.simplemobiletools.gallery.asynctasks.GetMediaAsynctask +import com.simplemobiletools.gallery.dialogs.SaveAsDialog import com.simplemobiletools.gallery.extensions.* import com.simplemobiletools.gallery.fragments.PhotoFragment import com.simplemobiletools.gallery.fragments.ViewPagerFragment @@ -31,6 +35,7 @@ import com.simplemobiletools.gallery.helpers.* import com.simplemobiletools.gallery.models.Medium import kotlinx.android.synthetic.main.activity_medium.* import java.io.File +import java.io.OutputStream import java.util.* class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, ViewPagerFragment.FragmentListener { @@ -41,6 +46,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View private var mIsFullScreen = false private var mPos = -1 private var mShowAll = false + private var mRotationDegrees = 0f private var mLastHandledOrientation = 0 private var mPrevHashcode = 0 @@ -182,15 +188,15 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_viewpager, menu) - if (getCurrentMedium() == null) - return true + val currentMedium = getCurrentMedium() ?: return true menu.apply { - findItem(R.id.menu_set_as).isVisible = getCurrentMedium()!!.isImage() == true - findItem(R.id.menu_edit).isVisible = getCurrentMedium()!!.isImage() == true - findItem(R.id.menu_rotate).isVisible = getCurrentMedium()!!.isImage() == true - findItem(R.id.menu_hide).isVisible = !getCurrentMedium()!!.name.startsWith('.') - findItem(R.id.menu_unhide).isVisible = getCurrentMedium()!!.name.startsWith('.') + findItem(R.id.menu_set_as).isVisible = currentMedium.isImage() + findItem(R.id.menu_edit).isVisible = currentMedium.isImage() + findItem(R.id.menu_rotate).isVisible = currentMedium.isImage() + findItem(R.id.menu_save_as).isVisible = mRotationDegrees != 0f + findItem(R.id.menu_hide).isVisible = !currentMedium.name.startsWith('.') + findItem(R.id.menu_unhide).isVisible = currentMedium.name.startsWith('.') } return true @@ -214,6 +220,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View R.id.menu_properties -> showProperties() R.id.show_on_map -> showOnMap() R.id.menu_rotate -> rotateImage() + R.id.menu_save_as -> saveImageAs() R.id.settings -> launchSettings() else -> return super.onOptionsItemSelected(item) } @@ -256,6 +263,15 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } private fun rotateImage() { + val currentMedium = getCurrentMedium() ?: return + if (currentMedium.isJpg() && !isPathOnSD(currentMedium.path)) { + rotateByExif() + } else { + rotateByDegrees() + } + } + + private fun rotateByExif() { val exif = ExifInterface(getCurrentPath()) val rotation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) val newRotation = getNewRotation(rotation) @@ -274,6 +290,54 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View }.toString() } + private fun rotateByDegrees() { + mRotationDegrees = (mRotationDegrees + 90) % 360 + getCurrentFragment()?.let { + (it as? PhotoFragment)?.rotateImageViewBy(mRotationDegrees) + } + supportInvalidateOptionsMenu() + } + + private fun saveImageAs() { + val currPath = getCurrentPath() + SaveAsDialog(this, currPath) { + Thread({ + toast(R.string.saving) + val selectedFile = File(it) + val tmpFile = File(selectedFile.parent, "tmp_${it.getFilenameFromPath()}") + try { + val bitmap = BitmapFactory.decodeFile(currPath) + getFileOutputStream(tmpFile) { + saveFile(tmpFile, bitmap, it) + if (needsStupidWritePermissions(selectedFile.absolutePath)) { + deleteFile(selectedFile) {} + } + + renameFile(tmpFile, selectedFile) { + deleteFile(tmpFile) {} + } + } + } catch (e: OutOfMemoryError) { + toast(R.string.out_of_memory_error) + deleteFile(tmpFile) {} + } catch (e: Exception) { + toast(R.string.unknown_error_occurred) + deleteFile(tmpFile) {} + } + }).start() + } + } + + private fun saveFile(file: File, bitmap: Bitmap, out: OutputStream) { + val matrix = Matrix() + matrix.postRotate(mRotationDegrees) + val bmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + bmp.compress(file.getCompressionFormat(), 90, out) + out.flush() + toast(R.string.file_saved) + out.close() + } + private fun getCurrentFragment() = (view_pager.adapter as MyPagerAdapter).getCurrentFragment(view_pager.currentItem) private fun showProperties() { @@ -493,6 +557,7 @@ class ViewPagerActivity : SimpleActivity(), ViewPager.OnPageChangeListener, View } mPos = position updateActionbarTitle() + mRotationDegrees = 0f supportInvalidateOptionsMenu() } 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 d5747ad86..f6d8b1a80 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/PhotoFragment.kt @@ -28,6 +28,7 @@ import com.simplemobiletools.gallery.extensions.config import com.simplemobiletools.gallery.extensions.getFileSignature import com.simplemobiletools.gallery.extensions.getRealPathFromURI import com.simplemobiletools.gallery.extensions.portrait +import com.simplemobiletools.gallery.helpers.GlideRotateTransformation import com.simplemobiletools.gallery.helpers.MEDIUM import com.simplemobiletools.gallery.models.Medium import it.sephiroth.android.library.exif2.ExifInterface @@ -154,28 +155,38 @@ class PhotoFragment : ViewPagerFragment() { } } - private fun loadBitmap() { - val targetWidth = if (ViewPagerActivity.screenWidth == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenWidth - val targetHeight = if (ViewPagerActivity.screenHeight == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenHeight + private fun loadBitmap(degrees: Float = 0f) { + if (degrees == 0f) { + val targetWidth = if (ViewPagerActivity.screenWidth == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenWidth + val targetHeight = if (ViewPagerActivity.screenHeight == 0) Target.SIZE_ORIGINAL else ViewPagerActivity.screenHeight - Glide.with(this) - .load(medium.path) - .asBitmap() - .signature(activity.getFileSignature(medium.path)) - .format(if (medium.isPng()) DecodeFormat.PREFER_ARGB_8888 else DecodeFormat.PREFER_RGB_565) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .override(targetWidth, targetHeight) - .listener(object : RequestListener { - override fun onException(e: Exception?, model: String?, target: Target?, isFirstResource: Boolean): Boolean { - return false - } + Glide.with(this) + .load(medium.path) + .asBitmap() + .signature(activity.getFileSignature(medium.path)) + .format(if (medium.isPng()) DecodeFormat.PREFER_ARGB_8888 else DecodeFormat.PREFER_RGB_565) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .override(targetWidth, targetHeight) + .listener(object : RequestListener { + override fun onException(e: Exception?, model: String?, target: Target?, isFirstResource: Boolean): Boolean { + return false + } - override fun onResourceReady(resource: Bitmap, model: String?, target: Target?, isFromMemoryCache: Boolean, isFirstResource: Boolean): Boolean { - if (isFragmentVisible) - addZoomableView() - return false - } - }).into(view.photo_view) + override fun onResourceReady(resource: Bitmap, model: String?, target: Target?, isFromMemoryCache: Boolean, isFirstResource: Boolean): Boolean { + if (isFragmentVisible) + addZoomableView() + return false + } + }).into(view.photo_view) + } else { + Glide.with(this) + .load(medium.path) + .asBitmap() + .transform(GlideRotateTransformation(context, degrees)) + .thumbnail(0.2f) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .into(view.photo_view) + } } private fun addZoomableView() { @@ -239,6 +250,11 @@ class PhotoFragment : ViewPagerFragment() { loadBitmap() } + fun rotateImageViewBy(degrees: Float) { + view.subsampling_view.beGone() + loadBitmap(degrees) + } + override fun onDestroyView() { super.onDestroyView() Glide.clear(view.photo_view) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt new file mode 100644 index 000000000..4e347dd9e --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/helpers/GlideRotateTransformation.kt @@ -0,0 +1,21 @@ +package com.simplemobiletools.gallery.helpers + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Matrix +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation + +class GlideRotateTransformation(context: Context, val rotateRotationAngle: Float) : BitmapTransformation(context) { + + override fun transform(pool: BitmapPool, bitmap: Bitmap, outWidth: Int, outHeight: Int): Bitmap { + if (rotateRotationAngle % 360 == 0f) + return bitmap + + val matrix = Matrix() + matrix.postRotate(rotateRotationAngle) + return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) + } + + override fun getId() = "GlideRotateTransformation $rotateRotationAngle" +} diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt index c9afe29f0..0fb1aa59a 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/models/Medium.kt @@ -20,6 +20,8 @@ data class Medium(var name: String, var path: String, val video: Boolean, val mo fun isGif() = path.isGif() + fun isJpg() = path.endsWith(".jpg", true) || path.endsWith(".jpeg", true) + fun isImage() = !isGif() && !video fun getMimeType() = File(path).getMimeType() diff --git a/app/src/main/res/menu/menu_viewpager.xml b/app/src/main/res/menu/menu_viewpager.xml index e66a7b835..c668eab9c 100644 --- a/app/src/main/res/menu/menu_viewpager.xml +++ b/app/src/main/res/menu/menu_viewpager.xml @@ -6,6 +6,12 @@ android:icon="@drawable/ic_rotate_right" android:title="@string/rotate" app:showAsAction="ifRoom"/> +