Android 图片解码并裁剪

193 阅读1分钟

将图片裁剪为合适的大小,以适应View的宽高。

1、自适应拉伸填满宽,再保持图片宽高比拉伸高,并垂直居中显示。

private fun ImageView.loadImageByDrawableId(id: Int) {
        doOnLayout {
            kotlin.runCatching {
                decodeBitmap(id, width, height)
            }.getOrNull()?.also {
                setImageBitmap(it)
            }
        }
    }

    private fun decodeBitmap(id: Int, targetWidth: Int, targetHeight: Int): Bitmap {
        val options = BitmapFactory.Options()
        options.inScaled = false
        options.inMutable = true
        options.inPreferredConfig = Bitmap.Config.RGB_565
        options.inDither = true
        options.inJustDecodeBounds = true
        BitmapFactory.decodeResource(resources, id, options)

        val sourceWidth = options.outWidth
        val sourceHeight = options.outHeight

        options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight)

        options.inJustDecodeBounds = false
        var scaleHeight = targetHeight.times(sourceWidth.toFloat().div(targetWidth)).toInt()
        var bitmap: Bitmap? = null
        if (sourceHeight > scaleHeight) {
            val top = sourceHeight.minus(scaleHeight).div(2)
            val rect = Rect(0, top, sourceWidth, top + scaleHeight)
            bitmap = kotlin.runCatching {
                contentResolver.openInputStream(toUri(id))?.use {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                        BitmapRegionDecoder.newInstance(it)?.decodeRegion(rect, options)
                    } else {
                        BitmapRegionDecoder.newInstance(it, false)?.decodeRegion(rect, options)
                    }
                }
            }.getOrNull()
        }
        val newBitmap = bitmap ?: BitmapFactory.decodeResource(resources, id, options)
        if (newBitmap.width == targetWidth) {
            return newBitmap
        }
        val matrix = Matrix()
        val sx = targetWidth.toFloat().div(newBitmap.width)
        matrix.setScale(sx, sx)
        scaleHeight = targetHeight.times(newBitmap.width.toFloat().div(targetWidth)).toInt()
        return if (newBitmap.height > scaleHeight) {
            Bitmap.createBitmap(newBitmap, 0, newBitmap.height.minus(scaleHeight).div(2), newBitmap.width, newBitmap.height, matrix, false)
        } else {
            Bitmap.createBitmap(newBitmap, 0, 0, newBitmap.width, newBitmap.height, matrix, false)
        }
    }

    private fun toUri(resID: Int): Uri {
        val path = buildString {
            append(ContentResolver.SCHEME_ANDROID_RESOURCE)
            append("://")
            append(resources.getResourcePackageName(resID))
            append("/")
            append(resources.getResourceTypeName(resID))
            append("/")
            append(resources.getResourceEntryName(resID))
        }
        return Uri.parse(path)
    }

    private fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
        // Raw height and width of image
        val (height: Int, width: Int) = options.run { outHeight to outWidth }
        var inSampleSize = 1

        if (height > reqHeight || width > reqWidth) {

            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 height and width.
            while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) {
                inSampleSize *= 2
            }
        }

        return inSampleSize
    }

1、首先解码出图片文件的宽高

2、再对比目标view的宽高和源文件宽高,确定是否需要裁剪和计算出采样率。

3、如需裁剪,则使用BitmapRegionDecoder解码图片,否则使用BitmapFactory解码图片。

4、再缩放解码后的图片至和目标view的宽一致即可。