mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Resize image stream dynamically to show image preview #919
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user