7
|
1 package com.bartsent.simpleresizer.lib
|
|
2
|
|
3 import android.graphics.Bitmap
|
|
4 import android.graphics.Color
|
|
5 import java.io.Closeable
|
|
6 import kotlin.math.ceil
|
|
7 import kotlin.math.floor
|
|
8
|
|
9 class Resizer: Closeable {
|
|
10 private val NWORKERS = maxOf(1, Runtime.getRuntime().availableProcessors() - 2)
|
|
11 private val workers = Channel<Worker>(NWORKERS)
|
|
12 init {
|
|
13 for (i in 0 until NWORKERS)
|
|
14 workers.write(Worker(workers))
|
|
15 }
|
|
16
|
|
17 override fun close() {
|
|
18 for (i in 0 until NWORKERS)
|
|
19 workers.read().run {
|
|
20 interrupt()
|
|
21 join()
|
|
22 }
|
|
23 }
|
|
24
|
|
25 private fun drain(): Unit {
|
|
26 val saved = Array<Worker>(NWORKERS) { workers.read() }
|
|
27 saved.forEach { workers.write(it) }
|
|
28 }
|
|
29
|
|
30 private data class IndexWeight(var index: Int, var weight: Double)
|
|
31
|
|
32 private fun precomputeWeights(dstSize: Int, srcSize: Int, filter: ScalingKernel): Array<Array<IndexWeight>> {
|
|
33 val du = srcSize.toDouble() / dstSize.toDouble()
|
|
34 val scale = maxOf(1.0, du)
|
|
35 val ru = ceil(scale * filter.size)
|
|
36 val TEMPLATE = arrayOf<IndexWeight>()
|
|
37 val out = Array<Array<IndexWeight>>(dstSize) { TEMPLATE }
|
|
38 val tmp = ArrayList<IndexWeight>((ru.toInt()+2)*2)
|
|
39 val emax = srcSize - 1
|
|
40
|
|
41 for (v in 0 until dstSize) {
|
|
42 val fu = (v.toDouble()+0.5)*du - 0.5
|
|
43 val begin = maxOf(0, ceil(fu - ru).toInt())
|
|
44 val end = minOf(emax, floor(fu + ru).toInt())
|
|
45 var sum: Double = 0.0
|
|
46 for (u in begin..end) {
|
|
47 val w = filter.weight((u.toDouble() - fu) / scale)
|
|
48 if (w != 0.0) {
|
|
49 sum += w
|
|
50 tmp.add(IndexWeight(u, w))
|
|
51 }
|
|
52 }
|
|
53 if (sum != 0.0) {
|
|
54 tmp.forEach {
|
|
55 it.weight /= sum
|
|
56 }
|
|
57 }
|
|
58 out[v] = tmp.toArray(TEMPLATE)
|
|
59 tmp.clear()
|
|
60 }
|
|
61 return out
|
|
62 }
|
|
63
|
|
64 private data class WorkUnit(val target: Bitmap, val x: Int, val y: Int,
|
|
65 val weights: Array<IndexWeight>, val scanLine: IntArray)
|
|
66
|
|
67 private class Worker(private val workers: Channel<Worker>): Thread() {
|
|
68 private val _input = Channel<WorkUnit>(1)
|
|
69
|
|
70 override fun run() {
|
|
71 while (true) {
|
|
72 val data = _input.read()
|
|
73 if (isInterrupted)
|
|
74 return
|
|
75 resample(data.target, data.x, data.y, data.weights, data.scanLine)
|
|
76 workers.write(this)
|
|
77 }
|
|
78 }
|
|
79
|
|
80 fun send(data: WorkUnit) {
|
|
81 _input.write(data)
|
|
82 }
|
|
83
|
|
84 private fun clamp(v: Double): Int = minOf(255, maxOf(0, (v + 0.5).toInt()))
|
|
85
|
|
86 private fun resample(target: Bitmap, x: Int, y: Int, weights: Array<IndexWeight>, scanLine: IntArray): Unit {
|
|
87 var r = 0.0; var g = 0.0; var b = 0.0; var a = 0.0
|
|
88 weights.forEach {
|
|
89 val c = scanLine[it.index]
|
|
90 val aw = Color.alpha(c).toDouble() * it.weight
|
|
91 r += Color.red(c).toDouble() * aw
|
|
92 g += Color.green(c).toDouble() * aw
|
|
93 b += Color.blue(c).toDouble() * aw
|
|
94 a += aw
|
|
95 }
|
|
96 if (a == 0.0)
|
|
97 return
|
|
98 val argb = Color.argb(clamp(a), clamp(r/a), clamp(g/a), clamp(b/a))
|
|
99 synchronized(target) {
|
|
100 target.setPixel(x, y, argb)
|
|
101 }
|
|
102 }
|
|
103 }
|
|
104
|
|
105 private fun resample(target: Bitmap, x: Int, y: Int, weights: Array<IndexWeight>, scanLine: IntArray): Unit {
|
|
106 workers.read().send(WorkUnit(target, x, y, weights, scanLine))
|
|
107 }
|
|
108
|
|
109 fun horizontal(image: Bitmap, newWidth: Int, kernel: ScalingKernel): Bitmap {
|
|
110 val dst = Bitmap.createBitmap(newWidth, image.height, Bitmap.Config.ARGB_8888)
|
|
111 val weights = precomputeWeights(newWidth, image.width, kernel)
|
|
112 val scanLine = IntArray(image.width)
|
|
113 for (y in 0 until image.height) {
|
|
114 for (x in 0 until image.width) {
|
|
115 scanLine[x] = image.getPixel(x, y)
|
|
116 }
|
|
117 for (x in weights.indices) {
|
|
118 resample(dst, x, y, weights[x], scanLine)
|
|
119 }
|
|
120 }
|
|
121 drain()
|
|
122 return dst
|
|
123 }
|
|
124
|
|
125 fun vertical(image: Bitmap, newHeight: Int, kernel: ScalingKernel): Bitmap {
|
|
126 val dst = Bitmap.createBitmap(image.width, newHeight, Bitmap.Config.ARGB_8888)
|
|
127 val weights = precomputeWeights(newHeight, image.height, kernel)
|
|
128 val scanLine = IntArray(image.height)
|
|
129 for (x in 0 until image.width) {
|
|
130 for (y in 0 until image.height) {
|
|
131 scanLine[y] = image.getPixel(x, y)
|
|
132 }
|
|
133 for (y in weights.indices) {
|
|
134 resample(dst, x, y, weights[y], scanLine)
|
|
135 }
|
|
136 }
|
|
137 drain()
|
|
138 return dst
|
|
139 }
|
|
140 } |