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-runtime:${workVersion}"
implementation "androidx.work:work-rxjava2:${workVersion}" implementation "androidx.work:work-rxjava2:${workVersion}"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.exifinterface:exifinterface:1.3.2'
androidTestImplementation "androidx.work:work-testing:${workVersion}" androidTestImplementation "androidx.work:work-testing:${workVersion}"
implementation 'com.google.android:flexbox:2.0.1' implementation 'com.google.android:flexbox:2.0.1'
implementation ('com.gitlab.bitfireAT:dav4jvm:2.1.2', { implementation ('com.gitlab.bitfireAT:dav4jvm:2.1.2', {

View File

@ -25,29 +25,26 @@
package com.nextcloud.talk.activities package com.nextcloud.talk.activities
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.databinding.ActivityFullScreenImageBinding import com.nextcloud.talk.databinding.ActivityFullScreenImageBinding
import com.nextcloud.talk.utils.BitmapShrinker
import pl.droidsonroids.gif.GifDrawable import pl.droidsonroids.gif.GifDrawable
import java.io.File import java.io.File
class FullScreenImageActivity : AppCompatActivity() { class FullScreenImageActivity : AppCompatActivity() {
lateinit var binding: ActivityFullScreenImageBinding lateinit var binding: ActivityFullScreenImageBinding
private lateinit var path: String private lateinit var path: String
private var showFullscreen = false private var showFullscreen = false
private val maxScale = 6.0f
private val mediumScale = 2.45f
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_preview, menu) menuInflater.inflate(R.menu.menu_preview, menu)
return true return true
@ -98,8 +95,8 @@ class FullScreenImageActivity : AppCompatActivity() {
// Enable enlarging the image more than default 3x maximumScale. // Enable enlarging the image more than default 3x maximumScale.
// Medium scale adapted to make double-tap behaviour more consistent. // Medium scale adapted to make double-tap behaviour more consistent.
binding.photoView.maximumScale = maxScale binding.photoView.maximumScale = MAX_SCALE
binding.photoView.mediumScale = mediumScale binding.photoView.mediumScale = MEDIUM_SCALE
val fileName = intent.getStringExtra("FILE_NAME") val fileName = intent.getStringExtra("FILE_NAME")
val isGif = intent.getBooleanExtra("IS_GIF", false) val isGif = intent.getBooleanExtra("IS_GIF", false)
@ -116,7 +113,25 @@ class FullScreenImageActivity : AppCompatActivity() {
} else { } else {
binding.gifView.visibility = View.INVISIBLE binding.gifView.visibility = View.INVISIBLE
binding.photoView.visibility = View.VISIBLE 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 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
}
}