fix displaying of large images

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2021-05-21 15:39:17 +02:00
parent ec976fcbdd
commit e03f048126
No known key found for this signature in database
GPG Key ID: C793F8B59F43CE7B
3 changed files with 144 additions and 9 deletions

View File

@ -186,6 +186,7 @@ dependencies {
implementation "androidx.work:work-runtime:${workVersion}"
implementation "androidx.work:work-rxjava2:${workVersion}"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.exifinterface:exifinterface:1.3.2'
androidTestImplementation "androidx.work:work-testing:${workVersion}"
implementation 'com.google.android:flexbox:2.0.1'
implementation ('com.gitlab.bitfireAT:dav4jvm:2.1.2', {

View File

@ -25,29 +25,26 @@
package com.nextcloud.talk.activities
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.databinding.ActivityFullScreenImageBinding
import com.nextcloud.talk.utils.BitmapShrinker
import pl.droidsonroids.gif.GifDrawable
import java.io.File
class FullScreenImageActivity : AppCompatActivity() {
lateinit var binding: ActivityFullScreenImageBinding
private lateinit var path: String
private var showFullscreen = false
private val maxScale = 6.0f
private val mediumScale = 2.45f
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_preview, menu)
return true
@ -98,8 +95,8 @@ class FullScreenImageActivity : AppCompatActivity() {
// Enable enlarging the image more than default 3x maximumScale.
// Medium scale adapted to make double-tap behaviour more consistent.
binding.photoView.maximumScale = maxScale
binding.photoView.mediumScale = mediumScale
binding.photoView.maximumScale = MAX_SCALE
binding.photoView.mediumScale = MEDIUM_SCALE
val fileName = intent.getStringExtra("FILE_NAME")
val isGif = intent.getBooleanExtra("IS_GIF", false)
@ -116,7 +113,25 @@ class FullScreenImageActivity : AppCompatActivity() {
} else {
binding.gifView.visibility = View.INVISIBLE
binding.photoView.visibility = View.VISIBLE
binding.photoView.setImageURI(Uri.parse(path))
displayImage(path)
}
}
private fun displayImage(path: String) {
val displayMetrics = applicationContext.resources.displayMetrics
val doubleScreenWidth = displayMetrics.widthPixels * 2
val doubleScreenHeight = displayMetrics.heightPixels * 2
val bitmap = BitmapShrinker.shrinkBitmap(path, doubleScreenWidth, doubleScreenHeight)
val bitmapSize: Int = bitmap.byteCount
// info that 100MB is the limit comes from https://stackoverflow.com/a/53334563
if (bitmapSize > HUNDRED_MB) {
Log.e(TAG, "bitmap will be too large to display. It won't be displayed to avoid RuntimeException")
Toast.makeText(this, R.string.nc_common_error_sorry, Toast.LENGTH_LONG).show()
} else {
binding.photoView.setImageBitmap(bitmap)
}
}
@ -149,4 +164,11 @@ class FullScreenImageActivity : AppCompatActivity() {
or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
)
}
companion object {
private val TAG = "FullScreenImageActivity"
private const val HUNDRED_MB = 100 * 1024 * 1024
private const val MAX_SCALE = 6.0f
private const val MEDIUM_SCALE = 2.45f
}
}

View File

@ -0,0 +1,112 @@
/*
* Nextcloud Talk application
*
* @author Marcel Hibbe
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.util.Log
import androidx.exifinterface.media.ExifInterface
import java.io.IOException
object BitmapShrinker {
private val TAG = "BitmapShrinker"
private const val DEGREES_90 = 90f
private const val DEGREES_180 = 180f
private const val DEGREES_270 = 270f
fun shrinkBitmap(
path: String,
reqWidth: Int,
reqHeight: Int
): Bitmap {
val bitmap = decodeBitmap(path, reqWidth, reqHeight)
return rotateBitmap(path, bitmap)
}
// solution inspired by https://developer.android.com/topic/performance/graphics/load-bitmap
private fun decodeBitmap(
path: String,
requestedWidth: Int,
requestedHeight: Int
): Bitmap {
return BitmapFactory.Options().run {
inJustDecodeBounds = true
BitmapFactory.decodeFile(path, this)
inSampleSize = getInSampleSize(this, requestedWidth, requestedHeight)
inJustDecodeBounds = false
BitmapFactory.decodeFile(path, this)
}
}
// solution inspired by https://developer.android.com/topic/performance/graphics/load-bitmap
private fun getInSampleSize(
options: BitmapFactory.Options,
requestedWidth: Int,
requestedHeight: Int
): Int {
val (height: Int, width: Int) = options.run { outHeight to outWidth }
var inSampleSize = 1
if (height > requestedHeight || width > requestedWidth) {
val halfHeight: Int = height / 2
val halfWidth: Int = width / 2
// "||" was used instead of "&&". Otherwise it would still crash for wide panorama photos.
while (halfHeight / inSampleSize >= requestedHeight || halfWidth / inSampleSize >= requestedWidth) {
inSampleSize *= 2
}
}
return inSampleSize
}
// solution inspired by https://stackoverflow.com/a/15341203
private fun rotateBitmap(path: String, bitmap: Bitmap): Bitmap {
try {
val exif = ExifInterface(path)
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1)
val matrix = Matrix()
when (orientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> {
matrix.postRotate(DEGREES_90)
}
ExifInterface.ORIENTATION_ROTATE_180 -> {
matrix.postRotate(DEGREES_180)
}
ExifInterface.ORIENTATION_ROTATE_270 -> {
matrix.postRotate(DEGREES_270)
}
}
val rotatedBitmap = Bitmap.createBitmap(
bitmap,
0,
0,
bitmap.getWidth(),
bitmap.getHeight(),
matrix,
true
)
return rotatedBitmap
} catch (e: IOException) {
Log.e(TAG, "error while rotating image", e)
}
return bitmap
}
}