Mercurial > cgi-bin > hgweb.cgi > SimpleResizer
view app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt @ 35:6607f675a5f7
Add licensing.
author | David Barts <n5jrn@me.com> |
---|---|
date | Thu, 11 Mar 2021 22:41:48 -0800 |
parents | eedf995462d9 |
children |
line wrap: on
line source
package com.bartsent.simpleresizer.lib /* * Some code in this file has been adapted from: https://github.com/disintegration/imaging/ . */ import android.graphics.Bitmap import android.graphics.Color import android.util.Log import java.util.concurrent.Callable 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 makeResampler(target: Band, weights: Array<Array<IndexWeight>>, source: Band): Callable<Exception?> { return Callable<Exception?> { try { for (i in 0 until target.size) { resample(target, i, weights[i], source) } null } catch (e: Exception) { e } } } private fun runAndReap(jobs: ArrayList<Callable<Exception?>>) { ThreadPools.WORKERS.invokeAll(jobs).forEach { val exc = it.get() if (exc != null) Log.e("Resizer", "worker thread threw exception", exc) } } /** * 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): Resizer { val dst = Resizer(newWidth, height) val weights = precomputeWeights(newWidth, width, kernel) val jobs = ArrayList<Callable<Exception?>>(height) for (y in 0 until height) { jobs.add(makeResampler(Row(dst, y), weights, Row(this, y))) } runAndReap(jobs) 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): Resizer { val dst = Resizer(width, newHeight) val weights = precomputeWeights(newHeight, height, kernel) val jobs = ArrayList<Callable<Exception?>>(width) for (x in 0 until width) { jobs.add(makeResampler(Column(dst, x), weights, Column(this, x))) } runAndReap(jobs) return dst } }