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
* Fix binary deduplication #715
* Fix IconId #901
* Resize image stream dynamically to prevent slowdown #919
* Small changes #795 #900 #903 #909 #914
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.database.element.Attachment
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.tasks.BinaryStreamManager
import kotlin.math.max
class ImageViewerActivity : LockingActivity() {
@@ -51,6 +53,12 @@ class ImageViewerActivity : LockingActivity() {
val imageView: ImageView = findViewById(R.id.image_viewer_image)
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 {
progressView.visibility = View.VISIBLE
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
@@ -58,7 +66,11 @@ class ImageViewerActivity : LockingActivity() {
supportActionBar?.title = attachment.name
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) {
finish()
} else {

View File

@@ -32,13 +32,14 @@ import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
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.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachmentState
import com.kunzisoft.keepass.model.StreamDirection
import com.kunzisoft.keepass.tasks.BinaryStreamManager
import com.kunzisoft.keepass.view.expand
import kotlin.math.max
class EntryAttachmentsItemsAdapter(context: Context)
@@ -48,6 +49,11 @@ class EntryAttachmentsItemsAdapter(context: Context)
var onItemClickListener: ((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
init {
@@ -76,7 +82,11 @@ class EntryAttachmentsItemsAdapter(context: Context)
if (entryAttachmentState.previewState == AttachmentState.NULL) {
entryAttachmentState.previewState = AttachmentState.IN_PROGRESS
// Load the bitmap image
Attachment.loadBitmap(entryAttachmentState.attachment, binaryCipherKey) { imageLoaded ->
BinaryStreamManager.loadBitmap(
entryAttachmentState.attachment.binaryFile,
binaryCipherKey,
mImagePreviewMaxWidth
) { imageLoaded ->
if (imageLoaded == null) {
entryAttachmentState.previewState = AttachmentState.ERROR
visibility = View.GONE

View File

@@ -19,12 +19,10 @@
*/
package com.kunzisoft.keepass.database.element
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.BinaryFile
import kotlinx.coroutines.*
data class Attachment(var name: String,
var binaryFile: BinaryFile) : Parcelable {
@@ -68,28 +66,5 @@ data class Attachment(var name: String,
override fun newArray(size: Int): Array<Attachment?> {
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.stream.readAllBytes
import com.kunzisoft.keepass.utils.UriUtil
import kotlinx.coroutines.*
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.io.OutputStream
import kotlin.math.ceil
import kotlin.math.ln
import kotlin.math.max
import kotlin.math.pow
object BinaryStreamManager {
@@ -126,6 +131,57 @@ object BinaryStreamManager {
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 val TAG = BinaryStreamManager::class.java.name

View File

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

View File

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

View File

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

View File

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