Resize image stream dynamically to show image preview #919

This commit is contained in:
J-Jamet
2021-03-11 16:02:15 +01:00
parent fad09b2cd5
commit 5874c5b9cb
9 changed files with 87 additions and 30 deletions

View File

@@ -3,6 +3,7 @@ KeePassDX(2.9.14)
* Dark Themes #532 #714 * Dark Themes #532 #714
* Fix binary deduplication #715 * Fix binary deduplication #715
* Fix IconId #901 * Fix IconId #901
* Resize image stream dynamically to prevent slowdown #919
* Small changes #795 #900 #903 #909 #914 * Small changes #795 #900 #903 #909 #914
KeePassDX(2.9.13) KeePassDX(2.9.13)

View File

@@ -34,6 +34,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Attachment import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.BinaryStreamManager
import kotlin.math.max
class ImageViewerActivity : LockingActivity() { class ImageViewerActivity : LockingActivity() {
@@ -51,6 +53,12 @@ class ImageViewerActivity : LockingActivity() {
val imageView: ImageView = findViewById(R.id.image_viewer_image) val imageView: ImageView = findViewById(R.id.image_viewer_image)
val progressView: View = findViewById(R.id.image_viewer_progress) val progressView: View = findViewById(R.id.image_viewer_progress)
// Approximately, to not OOM and allow a zoom
val mImagePreviewMaxWidth = max(
resources.displayMetrics.widthPixels * 2,
resources.displayMetrics.heightPixels * 2
)
try { try {
progressView.visibility = View.VISIBLE progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment -> intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
@@ -58,7 +66,11 @@ class ImageViewerActivity : LockingActivity() {
supportActionBar?.title = attachment.name supportActionBar?.title = attachment.name
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryFile.length) supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryFile.length)
Attachment.loadBitmap(attachment, Database.getInstance().loadedCipherKey) { bitmapLoaded -> BinaryStreamManager.loadBitmap(
attachment.binaryFile,
Database.getInstance().loadedCipherKey,
mImagePreviewMaxWidth
) { bitmapLoaded ->
if (bitmapLoaded == null) { if (bitmapLoaded == null) {
finish() finish()
} else { } else {

View File

@@ -32,13 +32,14 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.ImageViewerActivity import com.kunzisoft.keepass.activities.ImageViewerActivity
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryStreamManager
import com.kunzisoft.keepass.view.expand import com.kunzisoft.keepass.view.expand
import kotlin.math.max
class EntryAttachmentsItemsAdapter(context: Context) class EntryAttachmentsItemsAdapter(context: Context)
@@ -48,6 +49,11 @@ class EntryAttachmentsItemsAdapter(context: Context)
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null
// Approximately
private val mImagePreviewMaxWidth = max(
context.resources.displayMetrics.widthPixels,
context.resources.getDimensionPixelSize(R.dimen.item_file_info_height)
)
private var mTitleColor: Int private var mTitleColor: Int
init { init {
@@ -76,7 +82,11 @@ class EntryAttachmentsItemsAdapter(context: Context)
if (entryAttachmentState.previewState == AttachmentState.NULL) { if (entryAttachmentState.previewState == AttachmentState.NULL) {
entryAttachmentState.previewState = AttachmentState.IN_PROGRESS entryAttachmentState.previewState = AttachmentState.IN_PROGRESS
// Load the bitmap image // Load the bitmap image
Attachment.loadBitmap(entryAttachmentState.attachment, binaryCipherKey) { imageLoaded -> BinaryStreamManager.loadBitmap(
entryAttachmentState.attachment.binaryFile,
binaryCipherKey,
mImagePreviewMaxWidth
) { imageLoaded ->
if (imageLoaded == null) { if (imageLoaded == null) {
entryAttachmentState.previewState = AttachmentState.ERROR entryAttachmentState.previewState = AttachmentState.ERROR
visibility = View.GONE visibility = View.GONE

View File

@@ -19,12 +19,10 @@
*/ */
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryFile import com.kunzisoft.keepass.database.element.database.BinaryFile
import kotlinx.coroutines.*
data class Attachment(var name: String, data class Attachment(var name: String,
var binaryFile: BinaryFile) : Parcelable { var binaryFile: BinaryFile) : Parcelable {
@@ -68,28 +66,5 @@ data class Attachment(var name: String,
override fun newArray(size: Int): Array<Attachment?> { override fun newArray(size: Int): Array<Attachment?> {
return arrayOfNulls(size) return arrayOfNulls(size)
} }
fun loadBitmap(attachment: Attachment,
binaryCipherKey: Database.LoadedKey?,
actionOnFinish: (Bitmap?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred<Bitmap?> = async {
runCatching {
binaryCipherKey?.let { binaryKey ->
var bitmap: Bitmap?
attachment.binaryFile.getUnGzipInputDataStream(binaryKey).use { bitmapInputStream ->
bitmap = BitmapFactory.decodeStream(bitmapInputStream)
}
bitmap
}
}.getOrNull()
}
withContext(Dispatchers.Main) {
actionOnFinish(asyncResult.await())
}
}
}
}
} }
} }

View File

@@ -9,10 +9,15 @@ import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.database.BinaryFile import com.kunzisoft.keepass.database.element.database.BinaryFile
import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.stream.readAllBytes
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.*
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import kotlin.math.ceil
import kotlin.math.ln
import kotlin.math.max
import kotlin.math.pow
object BinaryStreamManager { object BinaryStreamManager {
@@ -126,6 +131,57 @@ object BinaryStreamManager {
return Bitmap.createScaledBitmap(this, width, height, true) return Bitmap.createScaledBitmap(this, width, height, true)
} }
fun loadBitmap(binaryFile: BinaryFile,
binaryCipherKey: Database.LoadedKey?,
maxWidth: Int,
actionOnFinish: (Bitmap?) -> Unit) {
CoroutineScope(Dispatchers.Main).launch {
withContext(Dispatchers.IO) {
val asyncResult: Deferred<Bitmap?> = async {
runCatching {
binaryCipherKey?.let { binaryKey ->
val bitmap: Bitmap? = decodeSampledBitmap(binaryFile,
binaryKey,
maxWidth)
bitmap
}
}.getOrNull()
}
withContext(Dispatchers.Main) {
actionOnFinish(asyncResult.await())
}
}
}
}
private fun decodeSampledBitmap(binaryFile: BinaryFile,
binaryCipherKey: Database.LoadedKey,
maxWidth: Int): Bitmap? {
// First decode with inJustDecodeBounds=true to check dimensions
return BitmapFactory.Options().run {
try {
inJustDecodeBounds = true
binaryFile.getUnGzipInputDataStream(binaryCipherKey).use {
BitmapFactory.decodeStream(it, null, this)
}
// Calculate inSampleSize
var scale = 1
if (outHeight > maxWidth || outWidth > maxWidth) {
scale = 2.0.pow(ceil(ln(maxWidth / max(outHeight, outWidth).toDouble()) / ln(0.5))).toInt()
}
inSampleSize = scale
// Decode bitmap with inSampleSize set
inJustDecodeBounds = false
binaryFile.getUnGzipInputDataStream(binaryCipherKey).use {
BitmapFactory.decodeStream(it, null, this)
}
} catch (e: Exception) {
null
}
}
}
private const val DEFAULT_ICON_WIDTH = 64 private const val DEFAULT_ICON_WIDTH = 64
private val TAG = BinaryStreamManager::class.java.name private val TAG = BinaryStreamManager::class.java.name

View File

@@ -31,7 +31,7 @@
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/item_attachment_thumbnail" android:id="@+id/item_attachment_thumbnail"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="144dp" android:layout_height="@dimen/image_preview_height"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:layout_centerHorizontal="true" android:layout_centerHorizontal="true"
android:visibility="gone" android:visibility="gone"

View File

@@ -21,6 +21,7 @@
<dimen name="default_margin">18dp</dimen> <dimen name="default_margin">18dp</dimen>
<dimen name="image_button_margin">8dp</dimen> <dimen name="image_button_margin">8dp</dimen>
<dimen name="image_list_margin">12dp</dimen> <dimen name="image_list_margin">12dp</dimen>
<dimen name="image_preview_height">144dp</dimen>
<dimen name="button_margin">6dp</dimen> <dimen name="button_margin">6dp</dimen>
<dimen name="card_view_margin">12dp</dimen> <dimen name="card_view_margin">12dp</dimen>
<dimen name="card_view_padding">14dp</dimen> <dimen name="card_view_padding">14dp</dimen>

View File

@@ -2,4 +2,5 @@
* Dark Themes #532 #714 * Dark Themes #532 #714
* Fix binary deduplication #715 * Fix binary deduplication #715
* Fix IconId #901 * Fix IconId #901
* Resize image stream dynamically to prevent slowdown #919
* Small changes #795 #900 #903 #909 #914 * Small changes #795 #900 #903 #909 #914

View File

@@ -2,4 +2,5 @@
* Thèmes sombres #532 #714 * Thèmes sombres #532 #714
* Correction de la duplication des binaires #715 * Correction de la duplication des binaires #715
* Correction IconId #901 * Correction IconId #901
* Changement de taille dynamique du flux d'image pour prévenir les ralentissements #919
* Petits changements #795 #900 #903 #909 #914 * Petits changements #795 #900 #903 #909 #914