# HG changeset patch # User David Barts # Date 1613596825 28800 # Node ID 884092efe31a640c25d7bd9783d7dd14ce3ebaca # Parent 9374d044a132ee4c017c287a2bc8751c7298d20e Gets gratuitously killed. WTF? diff -r 9374d044a132 -r 884092efe31a app/src/main/java/com/bartsent/simpleresizer/EditImage.kt --- a/app/src/main/java/com/bartsent/simpleresizer/EditImage.kt Wed Feb 17 07:24:26 2021 -0800 +++ b/app/src/main/java/com/bartsent/simpleresizer/EditImage.kt Wed Feb 17 13:20:25 2021 -0800 @@ -12,7 +12,9 @@ import android.provider.OpenableColumns import android.view.MenuItem import android.view.View +import android.view.Window import android.widget.EditText +import android.widget.ProgressBar import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu @@ -20,6 +22,7 @@ import com.bartsent.simpleresizer.lib.getScaledInstance import java.io.File import java.io.IOException +import kotlin.concurrent.thread import kotlin.math.roundToInt class EditImage : AppCompatActivity() { @@ -140,11 +143,16 @@ if (factor >= 1.0) { throw IllegalArgumentException("can only scale down") } - val newBitmap = Bitmap.createBitmap((oldBitmap.width * factor).roundToInt(), (oldBitmap.height * factor).roundToInt(), oldBitmap.config) - copyColorSpace(oldBitmap, newBitmap) - setImage(oldBitmap.getScaledInstance( - (oldBitmap.width.toDouble() * factor + 0.5).toInt(), - (oldBitmap.height.toDouble() * factor + 0.5).toInt())) + binding.progressBar.visibility = ProgressBar.VISIBLE + thread { + val newBitmap = oldBitmap.getScaledInstance( + (oldBitmap.width.toDouble() * factor + 0.5).toInt(), + (oldBitmap.height.toDouble() * factor + 0.5).toInt()) + runOnUiThread { + binding.progressBar.visibility = ProgressBar.INVISIBLE + setImage(newBitmap) + } + } } // is there any way to remember the last scale value? @@ -215,10 +223,16 @@ setRotate(deg.toFloat(), oldBitmap.width.toFloat()/2.0f, oldBitmap.height.toFloat()/2.0f) postTranslate((w - oldBitmap.width).toFloat()/2.0f, (h - oldBitmap.height).toFloat()/2.0f) } - Canvas(newBitmap).run { - drawBitmap(oldBitmap, rotater, null) + binding.progressBar.visibility = ProgressBar.VISIBLE + thread { + Canvas(newBitmap).run { + drawBitmap(oldBitmap, rotater, null) + } + runOnUiThread { + binding.progressBar.visibility = ProgressBar.INVISIBLE + setImage(newBitmap) + } } - setImage(newBitmap) } fun cancelClicked(view: View): Unit { diff -r 9374d044a132 -r 884092efe31a app/src/main/java/com/bartsent/simpleresizer/lib/Channel.kt --- a/app/src/main/java/com/bartsent/simpleresizer/lib/Channel.kt Wed Feb 17 07:24:26 2021 -0800 +++ b/app/src/main/java/com/bartsent/simpleresizer/lib/Channel.kt Wed Feb 17 13:20:25 2021 -0800 @@ -1,5 +1,6 @@ package com.bartsent.simpleresizer.lib +import android.util.Log import java.util.concurrent.Semaphore /** @@ -17,11 +18,13 @@ * @param item Item to write */ fun write(item: T): Unit { + Log.d("Channel<${hashCode()}>", "write…") wSem.acquire() synchronized(this) { buffer[(start + rSem.availablePermits()) % buffer.size] = item rSem.release() } + Log.d("Channel<${hashCode()}>", "write done") } /** @@ -29,12 +32,14 @@ * @return The item read */ fun read(): T { + Log.d("Channel<${hashCode()}>", "read…") rSem.acquire() synchronized(this) { val ret = buffer[start]!! as T buffer[start] = null // unref start = (start + 1) % buffer.size wSem.release() + Log.d("Channel<${hashCode()}>", "read done") return ret } } diff -r 9374d044a132 -r 884092efe31a app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt --- a/app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt Wed Feb 17 07:24:26 2021 -0800 +++ b/app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt Wed Feb 17 13:20:25 2021 -0800 @@ -2,6 +2,7 @@ import android.graphics.Bitmap import android.graphics.Color +import android.util.Log import java.io.Closeable import kotlin.math.ceil import kotlin.math.floor @@ -10,21 +11,28 @@ private val NWORKERS = maxOf(1, Runtime.getRuntime().availableProcessors() - 2) private val workers = Channel(NWORKERS) init { + Log.d("Resizer", "workers channel is ${workers.hashCode()}") + Log.d("Resizer", "writing workers…") for (i in 0 until NWORKERS) - workers.write(Worker(workers)) + workers.write(Worker(workers).apply { start() }) + Log.d("Resizer", "writing done") } override fun close() { + Log.d("Resizer", "closing, stack trace follows…", Exception("dummy exception")) for (i in 0 until NWORKERS) workers.read().run { interrupt() join() } + Log.d("Resizer", "closing done") } private fun drain(): Unit { + Log.d("Resizer", "draining workers…") val saved = Array(NWORKERS) { workers.read() } saved.forEach { workers.write(it) } + Log.d("Resizer", "draining done") } private data class IndexWeight(var index: Int, var weight: Double) @@ -61,80 +69,88 @@ 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) + private val _input = Channel<() -> Unit>(1) override fun run() { while (true) { - val data = _input.read() - if (isInterrupted) + val (block: () -> Unit, interrupted: Boolean) = try { + Pair(_input.read(), isInterrupted) + } catch (e: InterruptedException) { + Pair({}, true) + } + if (interrupted) return - resample(data.target, data.x, data.y, data.weights, data.scanLine) + block() 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) - } + fun send(block: () -> Unit) { + _input.write(block) } } + private fun clamp(v: Double): Int = minOf(255, maxOf(0, (v + 0.5).toInt())) + + fun parallel(block: () -> Unit) = workers.read().send(block) + private fun resample(target: Bitmap, x: Int, y: Int, weights: Array, scanLine: IntArray): Unit { - workers.read().send(WorkUnit(target, x, y, weights, scanLine)) + // Log.d("Resizer", "resample: x=$x, y=$y") + 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) { + Log.d("Resizer", "resample: a=0.0, done") + return + } + val argb = Color.argb(clamp(a), clamp(r/a), clamp(g/a), clamp(b/a)) + // Log.d("Resizer", "setting pixel…") + synchronized(target) { + target.setPixel(x, y, argb) + } + // Log.d("Resizer", "resample: a=$a, done") } fun horizontal(image: Bitmap, newWidth: Int, kernel: ScalingKernel): Bitmap { + Log.d("Resizer", "horizontal…") 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) + val scanLine = IntArray(image.width) { image.getPixel(it, y) } + parallel { + Log.d("Resizer", "horizontal: y=$y") + for (x in weights.indices) { + resample(dst, x, y, weights[x], scanLine) + } } } drain() + Log.d("Resizer", "horizontal done") return dst } fun vertical(image: Bitmap, newHeight: Int, kernel: ScalingKernel): Bitmap { + Log.d("Resizer", "vertical…") 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) + val scanLine = IntArray(image.height) { image.getPixel(x, it) } + parallel { + Log.d("Resizer", "vertical: x=$x") + for (y in weights.indices) { + resample(dst, x, y, weights[y], scanLine) + } } } drain() + Log.d("Resizer", "vertical done") return dst } } \ No newline at end of file diff -r 9374d044a132 -r 884092efe31a app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt --- a/app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt Wed Feb 17 07:24:26 2021 -0800 +++ b/app/src/main/java/com/bartsent/simpleresizer/lib/getScaledInstance.kt Wed Feb 17 13:20:25 2021 -0800 @@ -2,7 +2,10 @@ 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) @@ -20,14 +23,91 @@ Canvas(it).drawBitmap(this, Matrix(), null) } } - 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) + 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> { + 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 +} diff -r 9374d044a132 -r 884092efe31a app/src/main/res/layout/activity_edit_image.xml --- a/app/src/main/res/layout/activity_edit_image.xml Wed Feb 17 07:24:26 2021 -0800 +++ b/app/src/main/res/layout/activity_edit_image.xml Wed Feb 17 13:20:25 2021 -0800 @@ -72,4 +72,15 @@ app:layout_constraintStart_toEndOf="@id/cancel_button" app:layout_constraintTop_toBottomOf="@id/rotate_button" /> + + \ No newline at end of file