# HG changeset patch # User David Barts # Date 1613575466 28800 # Node ID 9374d044a132ee4c017c287a2bc8751c7298d20e # Parent e8059b166de1a53ad49bbdef62e79905f53e8e67 Concurrency attempt, NOT WORKING. diff -r e8059b166de1 -r 9374d044a132 app/src/main/java/com/bartsent/simpleresizer/lib/Channel.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/bartsent/simpleresizer/lib/Channel.kt Wed Feb 17 07:24:26 2021 -0800 @@ -0,0 +1,41 @@ +package com.bartsent.simpleresizer.lib + +import java.util.concurrent.Semaphore + +/** + * A typed, buffered communications channel a'la C.A.R. Hoare's communicating sequential + * processes (CSP). + */ +class Channel(capacity: Int) { + private val buffer = Array(capacity) { null } + private var start = 0 + private val wSem = Semaphore(capacity) + private val rSem = Semaphore(0) + + /** + * Write a single item to this channel. + * @param item Item to write + */ + fun write(item: T): Unit { + wSem.acquire() + synchronized(this) { + buffer[(start + rSem.availablePermits()) % buffer.size] = item + rSem.release() + } + } + + /** + * Read a single item from this channel. + * @return The item read + */ + fun read(): T { + rSem.acquire() + synchronized(this) { + val ret = buffer[start]!! as T + buffer[start] = null // unref + start = (start + 1) % buffer.size + wSem.release() + return ret + } + } +} diff -r e8059b166de1 -r 9374d044a132 app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt Wed Feb 17 07:24:26 2021 -0800 @@ -0,0 +1,140 @@ +package com.bartsent.simpleresizer.lib + +import android.graphics.Bitmap +import android.graphics.Color +import java.io.Closeable +import kotlin.math.ceil +import kotlin.math.floor + +class Resizer: Closeable { + private val NWORKERS = maxOf(1, Runtime.getRuntime().availableProcessors() - 2) + private val workers = Channel(NWORKERS) + init { + for (i in 0 until NWORKERS) + workers.write(Worker(workers)) + } + + override fun close() { + for (i in 0 until NWORKERS) + workers.read().run { + interrupt() + join() + } + } + + private fun drain(): Unit { + val saved = Array(NWORKERS) { workers.read() } + saved.forEach { workers.write(it) } + } + + private data class IndexWeight(var index: Int, var weight: Double) + + private fun precomputeWeights(dstSize: Int, srcSize: Int, filter: ScalingKernel): Array> { + val du = srcSize.toDouble() / dstSize.toDouble() + val scale = maxOf(1.0, du) + val ru = ceil(scale * filter.size) + val TEMPLATE = arrayOf() + val out = Array>(dstSize) { TEMPLATE } + val tmp = ArrayList((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 data class WorkUnit(val target: Bitmap, val x: Int, val y: Int, + val weights: Array, val scanLine: IntArray) + + private class Worker(private val workers: Channel): Thread() { + private val _input = Channel(1) + + override fun run() { + while (true) { + val data = _input.read() + if (isInterrupted) + return + resample(data.target, data.x, data.y, data.weights, data.scanLine) + workers.write(this) + } + } + + fun send(data: WorkUnit) { + _input.write(data) + } + + 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, 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 + val argb = Color.argb(clamp(a), clamp(r/a), clamp(g/a), clamp(b/a)) + synchronized(target) { + target.setPixel(x, y, argb) + } + } + } + + private fun resample(target: Bitmap, x: Int, y: Int, weights: Array, scanLine: IntArray): Unit { + workers.read().send(WorkUnit(target, x, y, weights, scanLine)) + } + + fun horizontal(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) + } + } + drain() + return dst + } + + fun vertical(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) + } + } + drain() + return dst + } +} \ No newline at end of file diff -r e8059b166de1 -r 9374d044a132 app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt --- a/app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt Tue Feb 16 17:29:52 2021 -0800 +++ b/app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt Wed Feb 17 07:24:26 2021 -0800 @@ -2,10 +2,7 @@ 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) @@ -23,91 +20,14 @@ 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) + Resizer().use { resize -> + if (width != newWidth) { + if (height != newHeight) + return resize.vertical(resize.horizontal(input, newWidth, kernel), newHeight, kernel) + else + return resize.horizontal(input, newWidth, kernel) + } else { + return resize.vertical(input, newHeight, kernel) + } } } - -private fun precomputeWeights(dstSize: Int, srcSize: Int, filter: ScalingKernel): Array> { - val du = srcSize.toDouble() / dstSize.toDouble() - val scale = maxOf(1.0, du) - val ru = ceil(scale * filter.size) - val TEMPLATE = arrayOf() - val out = Array>(dstSize) { TEMPLATE } - val tmp = ArrayList((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, 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 -}