Мне нужно загрузить очень длинное (около 15 тыс. Пикселей) черно-белое растровое изображение с шириной экрана.
Я пробовал несколько подходов, и лучший вариант - использовать Glide:
private fun loadGlideScreenWideCompress(context: Context, imageView: AppCompatImageView) {
imageView.adjustViewBounds = true
val params = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
imageView.layoutParams = params
GlideApp.with(context)
.load(imageRes)
.encodeFormat(Bitmap.CompressFormat.WEBP)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.into(imageView)
}
Проблема в том - теряется качество изображения. Изображение размыто, текст не читается.
Пробовал использовать BitmapRegionDecoder. Потери качества или проблем с памятью не заметил. Однако он декодирует только часть изображения. Я действительно не понимаю, как его использовать: для декодирования следующей части при прокрутке? Это было бы сложно реализовать. Измерение выдвигаемой высоты и передача полной высоты BitmapRegionDecoder интуитивно кажется неправильной, потому что этот декодер предназначен только для регионов.
Другая проблема в том, что изображение большое, и мне нужно, чтобы оно работало для всех размеров экрана. Если я возьму максимально возможный размер и затем масштабирую его, мне придется выполнять дорогостоящие операции по созданию растрового изображения и потенциально блокировать основной поток.
Обычный подход не работает и дает исключения OOM:
val bitmap = BitmapFactory.Options().run {
inJustDecodeBounds = true
inPreferredConfig = Bitmap.Config.ALPHA_8
inDensity = displayMetrics.densityDpi
BitmapFactory.decodeResource(context.resources, imageRes, this)
}
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
imageView.setImageBitmap(bitmap)
Код с уменьшением масштаба:
val res = context.resources
val display = res.displayMetrics
val dr = res.getDrawable(imageRes!!)
val original = (dr as BitmapDrawable).bitmap
val scale = original.width / display.widthPixels
val scaledBitmap = BitmapDrawable(res, Bitmap.createScaledBitmap(
original,
display.widthPixels,
original.height / scale,
true
))
imageView.adjustViewBounds = true
val bos = ByteArrayOutputStream()
scaledBitmap.bitmap.compress(CompressFormat.WEBP, 100, bos)
val decoder = BitmapRegionDecoder.newInstance(
ByteArrayInputStream(bos.toByteArray()),
false
)
val rect = Rect(
0,
0,
scaledBitmap.intrinsicWidth,
scaledBitmap.intrinsicHeight
)
val bitmapFactoryOptions = BitmapFactory.Options()
bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.ALPHA_8;
bitmapFactoryOptions.inDensity = display.densityDpi;
val bmp = decoder.decodeRegion(rect, bitmapFactoryOptions);
imageView.setImageBitmap(bmp)
Возникает вопрос: что было бы лучше всего в этой ситуации и как правильно использовать BitmapRegionDecoder?
@CommonsWare Спасибо, попробую эту библиотеку
@CommonsWare Я использовал библиотеку - она очень хорошо работает на многих устройствах, но не на всех. У меня проблема с Nexus 5 / Android 5.0, когда декодирование последней части растрового изображения не работает. Сообщение об ошибке следующее: E/SubsamplingScaleImageView: Failed to decode tile java.lang.RuntimeException: Skia image decoder returned null bitmap - image format may not be supported at com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder.decodeRegion(SkiaImageRegionDecoder.java:123)
Рассмотрите возможность публикации образца проекта и подачи отчета об ошибке автору библиотеки. Или посмотрите другие библиотеки, предлагающие масштабируемые изображения, например Вот этот. Или просто посмотрите, как они используют BitmapRegionDecoder, и создайте что-нибудь самостоятельно.
Помогло убрать альфа-канал из PNG
«Это было бы сложно реализовать» - используйте библиотеку, которая сделала это за вас, например Вот этот.