Mercurial > cgi-bin > hgweb.cgi > SimpleResizer
view app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt @ 13:b1605be35bcc memo.oo
Dumping Bitmap yields 2x improvement!
author | David Barts <n5jrn@me.com> |
---|---|
date | Thu, 18 Feb 2021 14:15:26 -0800 |
parents | |
children | 86740f593b6c |
line wrap: on
line source
package com.bartsent.simpleresizer.lib import android.graphics.Bitmap import android.graphics.Color import kotlin.math.ceil import kotlin.math.floor /** * Bitmapped images designed for efficient resampling and per-pixel access via * subscripting. */ class Resizer(private val bits: IntArray, val width: Int, val height: Int) { constructor(width: Int, height: Int) : this(IntArray(width * height), width, height) companion object { /** * Make a Resizer from a standard Bitmap. * @param image Bitmap object. * @return A new Resizer. */ fun fromBitmap(image: Bitmap): Resizer { val basedOn = IntArray(image.width * image.height) image.getPixels(basedOn, 0, image.width, 0, 0, image.width, image.height) return Resizer(basedOn, image.width, image.height) } } private interface Band { operator fun get(i: Int): Int operator fun set(i: Int, v: Int): Unit val index: Int val size: Int } private class Row(val basedOn: Resizer, override val index: Int): Band { override operator fun get(i: Int) = basedOn[i, index] override operator fun set(i: Int, v: Int): Unit { basedOn[i, index] = v } override val size; get() = basedOn.width } private class Column(val basedOn: Resizer, override val index: Int): Band { override operator fun get(i: Int) = basedOn[index, i] override operator fun set(i: Int, v: Int): Unit { basedOn[index, i] = v } override val size; get() = basedOn.height } private data class IndexWeight(var index: Int, var weight: Double) operator fun get(x: Int, y: Int): Int = bits[x + y*width] operator fun set(x: Int, y: Int, v: Int): Unit { bits[x + y*width] = v } /** * Return a standard Bitmap based on this Resizer's image. * @return A new Bitmap, which may share this Resizer's image storage. */ fun toBitmap(): Bitmap = Bitmap.createBitmap(bits, width, height, Bitmap.Config.ARGB_8888) 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: Band, i: Int, weights: Array<IndexWeight>, source: Band): Unit { var r = 0.0; var g = 0.0; var b = 0.0; var a = 0.0 weights.forEach { val c = source[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[i] = Color.argb(clamp(a), clamp(r/a), clamp(g/a), clamp(b/a)) } private fun resampleBand(target: Band, weights: Array<Array<IndexWeight>>, source: Band) { for (i in 0 until target.size) { resample(target, i, weights[i], source) } } private val DEFAULT_KERNEL = LanczosKernel() /** * Resize the horizontal aspect. * @param newWidth Width of new image * @param kernel Scaling kernel to use (default: LanczosKernel) * @return A new Resizer object, resampled per specifications */ fun horizontal(newWidth: Int, kernel: ScalingKernel = DEFAULT_KERNEL): Resizer { val dst = Resizer(newWidth, height) val weights = precomputeWeights(newWidth, width, kernel) for (y in 0 until height) { resampleBand(Row(dst, y), weights, Row(this, y)) } return dst } /** * Resize the vertical aspect. * @param newHeight Height of new image * @param kernel Scaling kernel to use (default: LanczosKernel) * @return A new Resizer object, resampled per specifications */ fun vertical(newHeight: Int, kernel: ScalingKernel = DEFAULT_KERNEL): Resizer { val dst = Resizer(width, newHeight) val weights = precomputeWeights(newHeight, height, kernel) for (x in 0 until width) { resampleBand(Column(dst, x), weights, Column(this, x)) } return dst } }