view app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt @ 9:884092efe31a concur

Gets gratuitously killed. WTF?
author David Barts <n5jrn@me.com>
date Wed, 17 Feb 2021 13:20:25 -0800
parents 9374d044a132
children
line wrap: on
line source

package com.bartsent.simpleresizer.lib

import android.graphics.Bitmap
import android.graphics.Color
import android.util.Log
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<Worker>(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).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<Worker>(NWORKERS) { workers.read() }
        saved.forEach { workers.write(it) }
        Log.d("Resizer", "draining done")
    }

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

    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 class Worker(private val workers: Channel<Worker>): Thread() {
        private val _input = Channel<() -> Unit>(1)

        override fun run() {
            while (true) {
                val (block: () -> Unit, interrupted: Boolean) = try {
                    Pair(_input.read(), isInterrupted)
                } catch (e: InterruptedException) {
                    Pair({}, true)
                }
                if (interrupted)
                    return
                block()
                workers.write(this)
            }
        }

        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<IndexWeight>, scanLine: IntArray): Unit {
        // 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)
        for (y in 0 until image.height) {
            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)
        for (x in 0 until image.width) {
            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
    }
}