view app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt @ 7:9374d044a132 concur

Concurrency attempt, NOT WORKING.
author David Barts <n5jrn@me.com>
date Wed, 17 Feb 2021 07:24:26 -0800
parents
children 884092efe31a
line wrap: on
line source

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<Worker>(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<Worker>(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<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 data class WorkUnit(val target: Bitmap, val x: Int, val y: Int,
                                val weights: Array<IndexWeight>, val scanLine: IntArray)

    private class Worker(private val workers: Channel<Worker>): Thread() {
        private val _input = Channel<WorkUnit>(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<IndexWeight>, 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<IndexWeight>, 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
    }
}