view app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt @ 6:e8059b166de1

Lanczos works, but is painfully slow.
author David Barts <n5jrn@me.com>
date Tue, 16 Feb 2021 17:29:52 -0800
parents
children 9374d044a132 b1605be35bcc
line wrap: on
line source

package com.bartsent.simpleresizer.lib

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
import kotlin.math.ceil
import kotlin.math.floor

private data class IndexWeight(var index: Int, var weight: Double)

fun Bitmap.getScaledInstance(newWidth: Int, newHeight: Int, kernel: ScalingKernel = LanczosKernel): Bitmap {
    if (newWidth <= 0)
        throw IllegalArgumentException("invalid width: $newWidth")
    if (newHeight <= 0)
        throw IllegalArgumentException("invalid height: $newHeight")
    if (width == newWidth && height == newHeight)
        return Bitmap.createBitmap(this)
    val input = if (config == Bitmap.Config.ARGB_8888)
        this
    else {
        Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also {
            Canvas(it).drawBitmap(this, Matrix(), null)
        }
    }
    if (width != newWidth) {
        if (height != newHeight)
            return resizeVertical(resizeHorizontal(input, newWidth, kernel), newHeight, kernel)
        else
            return resizeHorizontal(input, newWidth, kernel)
    } else {
        return resizeVertical(input, newHeight, kernel)
    }
}

private fun precomputeWeights(dstSize: Int, srcSize: Int, filter: ScalingKernel): Array<Array<IndexWeight>> {
    val du = srcSize.toDouble() / dstSize.toDouble()
    val scale = maxOf(1.0, du)
    val ru = ceil(scale * filter.size)
    val TEMPLATE = arrayOf<IndexWeight>()
    val out = Array<Array<IndexWeight>>(dstSize) { TEMPLATE }
    val tmp = ArrayList<IndexWeight>((ru.toInt()+2)*2)
    val emax = srcSize - 1

    for (v in 0 until dstSize) {
        val fu = (v.toDouble()+0.5)*du - 0.5
        val begin = maxOf(0, ceil(fu - ru).toInt())
        val end = minOf(emax, floor(fu + ru).toInt())
        var sum: Double = 0.0
        for (u in begin..end) {
            val w = filter.weight((u.toDouble() - fu) / scale)
            if (w != 0.0) {
                sum += w
                tmp.add(IndexWeight(u, w))
            }
        }
        if (sum != 0.0) {
            tmp.forEach {
                it.weight /= sum
            }
        }
        out[v] = tmp.toArray(TEMPLATE)
        tmp.clear()
    }
    return out
}

private fun clamp(v: Double): Int = minOf(255, maxOf(0, (v + 0.5).toInt()))

private fun resample(target: Bitmap, x: Int, y: Int, weights: Array<IndexWeight>, scanLine: IntArray): Unit {
    var r = 0.0; var g = 0.0; var b = 0.0; var a = 0.0
    weights.forEach {
        val c = scanLine[it.index]
        val aw = Color.alpha(c).toDouble() * it.weight
        r += Color.red(c).toDouble() * aw
        g += Color.green(c).toDouble() * aw
        b += Color.blue(c).toDouble() * aw
        a += aw
    }
    if (a == 0.0)
        return
    target.setPixel(x, y, Color.argb(clamp(a), clamp(r/a), clamp(g/a), clamp(b/a)))
}

private fun resizeHorizontal(image: Bitmap, newWidth: Int, kernel: ScalingKernel): Bitmap {
    val dst = Bitmap.createBitmap(newWidth, image.height, Bitmap.Config.ARGB_8888)
    val weights = precomputeWeights(newWidth, image.width, kernel)
    val scanLine = IntArray(image.width)
    for (y in 0 until image.height) {
        for (x in 0 until image.width) {
            scanLine[x] = image.getPixel(x, y)
        }
        for (x in weights.indices) {
            resample(dst, x, y, weights[x], scanLine)
        }
    }
    return dst
}

private fun resizeVertical(image: Bitmap, newHeight: Int, kernel: ScalingKernel): Bitmap {
    val dst = Bitmap.createBitmap(image.width, newHeight, Bitmap.Config.ARGB_8888)
    val weights = precomputeWeights(newHeight, image.height, kernel)
    val scanLine = IntArray(image.height)
    for (x in 0 until image.width) {
        for (y in 0 until image.height) {
            scanLine[y] = image.getPixel(x, y)
        }
        for (y in weights.indices) {
            resample(dst, x, y, weights[y], scanLine)
        }
    }
    return dst
}