Android - сжатие фото возвращает все черные растровые карты на некоторых устройствахAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Android - сжатие фото возвращает все черные растровые карты на некоторых устройствах

Сообщение Anonymous »

В моем приложении мне нужно сфотографировать -> Сжатие -> Загрузка, чтобы быть. После успешного результата фотография сжата. Большая часть сжатия времени в порядке, но одни некоторые редкие устройства (например, MPTECH Hammer_blade_3 (Android 10)) приводит к сжатию во всех черных изображениях, и я не могу найти никаких проблем в моем коде.

Код: Выделить всё

class ImageCompressor(
private val context: Context,
private val dispatcher: CoroutineDispatcher = Dispatchers.Default
) {

private var calculatedInSampleSize: Int = 1

/**
* Compress image represented by [sourceUri] and save it to [targetFile].
*
* Decoding strategy:
*  - **API 28+**: uses the Android 9.0+ `ImageDecoder` API for all formats
*    including HEIC/HEIF, applying EXIF rotation and down-sampling in one step.
*  - **API 26–27**: falls back to `BitmapFactory.decodeFileDescriptor`
*
* @param sourceUri [Uri] to source image.
* @param targetFile [File] to which compressed image will be saved.
* @param compressRatio The [CompressRatio].
* @return true
if successfully compressed, otherwise */
suspend fun compress(
sourceUri: Uri,
targetFile: File,
compressRatio: CompressRatio
): Boolean = withContext(dispatcher) {
calculatedInSampleSize = 1

val contentResolver = context.contentResolver
val descriptor = contentResolver.openFileDescriptor(sourceUri, "r", null)

val statSize = descriptor?.statSize ?: 0L
if (statSize 0L) statSize else getFileSize(sourceUri)
if (queriedSize compressRatio.maxSizeBytes) {
Timber.d("File still too large, will try with higher compression in next iteration")
}
} while (compressedSize > compressRatio.maxSizeBytes)

val success = compressedSize != 0L && targetFile.exists() && targetFile.length() > 0
Timber.d("Compression ${if (success) "succeeded" else "failed"} after $iterationCount iterations. Final size: $compressedSize bytes")
success
} == true
}

private fun Uri.decodeSampledBitmap(
contentResolver: ContentResolver,
maxSize: Int,
calculateSampleSize: Boolean
): Bitmap? {
val bitmapUri = this
var sampledBitmap: Bitmap? = null
try {
BitmapFactory.Options().run {
val descriptor = contentResolver.openFileDescriptor(
bitmapUri,
"r",
null
)?.fileDescriptor
descriptor?.let {
if (calculateSampleSize) {
inJustDecodeBounds = true
BitmapFactory.decodeFileDescriptor(it, null, this)
calculateInSampleSize(this, maxSize)
}

inSampleSize = calculatedInSampleSize

inJustDecodeBounds = false
sampledBitmap = BitmapFactory.decodeFileDescriptor(it, null, this)
}
}
} catch (e: Exception) {
Timber.w(e, "Could not decode sampled bitmap.")
}

calculatedInSampleSize *= 2 // for next iteration if file still too big
return sampledBitmap?.rotate(contentResolver, this@decodeSampledBitmap)
}

/**
* Decodes this Uri into a Bitmap, down-sampling so both dimensions ≤ maxSize.
* On API 28+ it uses ImageDecoder (native HEIC/HEIF support + all other formats).
* On older APIs it falls back to BitmapFactory sampling.
*/
private fun Uri.decodeBitmapCompat(
resolver: ContentResolver,
maxSize: Int,
calculateSampleSize: Boolean
): Bitmap? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val src = ImageDecoder.createSource(resolver, this)
try {
ImageDecoder.decodeBitmap(src) { decoder, info, _ ->
if (!calculateSampleSize) {
val w = info.size.width
val h = info.size.height
val origMax = maxOf(w, h).toFloat()
if (origMax > maxSize) {
val ratio = origMax / maxSize
decoder.setTargetSize((w / ratio).toInt(), (h / ratio).toInt())
}
}
}
} catch (e: IOException) {
Timber.w(e, "ImageDecoder failed — falling back to BitmapFactory")
// fallback to old path
this.decodeSampledBitmap(resolver, maxSize, calculateSampleSize)
}
} else {
decodeSampledBitmap(resolver, maxSize, calculateSampleSize)
}
}

private fun calculateInSampleSize(options: BitmapFactory.Options, maxSize: Int) {
// Raw height and width of image
val (height: Int, width: Int) = options.run { outHeight to outWidth }
if (height > maxSize || width > maxSize) {

val halfHeight: Int = height / 2
val halfWidth: Int = width / 2

// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested maxSize.
while (halfHeight / calculatedInSampleSize >= maxSize && halfWidth / calculatedInSampleSize >= maxSize) {
calculatedInSampleSize *= 2
}
}
}

private fun Bitmap.rotate(contentResolver: ContentResolver, contentUri: Uri): Bitmap {
var rotatedBitmap: Bitmap? = null
try {
val fd = contentResolver.openFileDescriptor(contentUri, "r", null)
fd?.fileDescriptor?.let {
val exif = ExifInterface(it)
val matrix: Matrix? = when (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
ExifInterface.ORIENTATION_ROTATE_90 -> Matrix().apply { postRotate(90f) }
ExifInterface.ORIENTATION_ROTATE_180 -> Matrix().apply { postRotate(180f) }
ExifInterface.ORIENTATION_ROTATE_270 -> Matrix().apply { postRotate(270f) }
else -> null
}
if (matrix != null) {
rotatedBitmap = Bitmap.createBitmap(this, 0, 0, this.width, this.height, matrix, true)
}
}
} catch (e: Exception) {
Timber.w(e, "Could not rotate bitmap.")
}

return rotatedBitmap ?: this
}

/**
* Save bitmap and return its size or 0 is save was not successful.
*/
private fun Bitmap.save(destination: File, quality: Int): Long {
var fileOutputStream: FileOutputStream? = null
try {
fileOutputStream = FileOutputStream(destination.absolutePath)
compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream)
} catch (e: Exception) {
Timber.e(e, "Unable to decode stream.")
} finally {
fileOutputStream?.run {
try {
flush()
close()
} catch (e: Exception) {
// just ignore
}
}
}

return try {
destination.length()
} catch (e: Exception) {
0
}
}

private fun getFileSize(uri: Uri): Long =
context.contentResolver.query(uri, arrayOf(OpenableColumns.SIZE), null, null, null)
?.use { cursor ->
val idx = cursor.getColumnIndex(OpenableColumns.SIZE)
if (cursor.moveToFirst() && idx != -1) cursor.getLong(idx) else -1L
} ?: -1L
}
< /code>
Журнал с затронутого устройства < /p>
[2025-08-18 11:49:44][DEBUG] Compressing file with original size=5619029 bytes, maxSizeBytes=3000000, maxWidthOrHeight=600, quality=90
[2025-08-18 11:49:45][DEBUG] Iteration 1: Decoded bitmap with inSampleSize=1, bitmap size=4160x3120
[2025-08-18 11:49:45][DEBUG] Iteration 1: Compressed file size=76888 bytes (1% of original), target max size=3000000
[2025-08-18 11:49:45][DEBUG] Compression succeeded after 1 iterations. Final size: 76888 bytes


Подробнее здесь: https://stackoverflow.com/questions/797 ... me-devices
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»