comparison app/src/main/java/com/bartsent/simpleresizer/lib/Resizer.kt @ 13:b1605be35bcc memo.oo

Dumping Bitmap yields 2x improvement!
author David Barts <n5jrn@me.com>
date Thu, 18 Feb 2021 14:15:26 -0800
parents
children 86740f593b6c
comparison
equal deleted inserted replaced
11:678adef4774f 13:b1605be35bcc
1 package com.bartsent.simpleresizer.lib
2
3 import android.graphics.Bitmap
4 import android.graphics.Color
5 import kotlin.math.ceil
6 import kotlin.math.floor
7
8 /**
9 * Bitmapped images designed for efficient resampling and per-pixel access via
10 * subscripting.
11 */
12 class Resizer(private val bits: IntArray, val width: Int, val height: Int) {
13 constructor(width: Int, height: Int) : this(IntArray(width * height), width, height)
14
15 companion object {
16 /**
17 * Make a Resizer from a standard Bitmap.
18 * @param image Bitmap object.
19 * @return A new Resizer.
20 */
21 fun fromBitmap(image: Bitmap): Resizer {
22 val basedOn = IntArray(image.width * image.height)
23 image.getPixels(basedOn, 0, image.width, 0, 0, image.width, image.height)
24 return Resizer(basedOn, image.width, image.height)
25 }
26 }
27
28 private interface Band {
29 operator fun get(i: Int): Int
30 operator fun set(i: Int, v: Int): Unit
31 val index: Int
32 val size: Int
33 }
34
35 private class Row(val basedOn: Resizer, override val index: Int): Band {
36 override operator fun get(i: Int) = basedOn[i, index]
37 override operator fun set(i: Int, v: Int): Unit {
38 basedOn[i, index] = v
39 }
40 override val size; get() = basedOn.width
41 }
42
43 private class Column(val basedOn: Resizer, override val index: Int): Band {
44 override operator fun get(i: Int) = basedOn[index, i]
45 override operator fun set(i: Int, v: Int): Unit {
46 basedOn[index, i] = v
47 }
48 override val size; get() = basedOn.height
49 }
50
51 private data class IndexWeight(var index: Int, var weight: Double)
52
53 operator fun get(x: Int, y: Int): Int = bits[x + y*width]
54
55 operator fun set(x: Int, y: Int, v: Int): Unit {
56 bits[x + y*width] = v
57 }
58
59 /**
60 * Return a standard Bitmap based on this Resizer's image.
61 * @return A new Bitmap, which may share this Resizer's image storage.
62 */
63 fun toBitmap(): Bitmap = Bitmap.createBitmap(bits, width, height, Bitmap.Config.ARGB_8888)
64
65 private fun precomputeWeights(dstSize: Int, srcSize: Int, filter: ScalingKernel): Array<Array<IndexWeight>> {
66 val du = srcSize.toDouble() / dstSize.toDouble()
67 val scale = maxOf(1.0, du)
68 val ru = ceil(scale * filter.size)
69 val TEMPLATE = arrayOf<IndexWeight>()
70 val out = Array<Array<IndexWeight>>(dstSize) { TEMPLATE }
71 val tmp = ArrayList<IndexWeight>((ru.toInt()+2)*2)
72 val emax = srcSize - 1
73
74 for (v in 0 until dstSize) {
75 val fu = (v.toDouble()+0.5)*du - 0.5
76 val begin = maxOf(0, ceil(fu - ru).toInt())
77 val end = minOf(emax, floor(fu + ru).toInt())
78 var sum: Double = 0.0
79 for (u in begin..end) {
80 val w = filter.weight((u.toDouble() - fu) / scale)
81 if (w != 0.0) {
82 sum += w
83 tmp.add(IndexWeight(u, w))
84 }
85 }
86 if (sum != 0.0) {
87 tmp.forEach {
88 it.weight /= sum
89 }
90 }
91 out[v] = tmp.toArray(TEMPLATE)
92 tmp.clear()
93 }
94 return out
95 }
96
97 private fun clamp(v: Double): Int = minOf(255, maxOf(0, (v + 0.5).toInt()))
98
99 private fun resample(target: Band, i: Int, weights: Array<IndexWeight>, source: Band): Unit {
100 var r = 0.0; var g = 0.0; var b = 0.0; var a = 0.0
101 weights.forEach {
102 val c = source[it.index]
103 val aw = Color.alpha(c).toDouble() * it.weight
104 r += Color.red(c).toDouble() * aw
105 g += Color.green(c).toDouble() * aw
106 b += Color.blue(c).toDouble() * aw
107 a += aw
108 }
109 if (a == 0.0)
110 return
111 target[i] = Color.argb(clamp(a), clamp(r/a), clamp(g/a), clamp(b/a))
112 }
113
114 private fun resampleBand(target: Band, weights: Array<Array<IndexWeight>>, source: Band) {
115 for (i in 0 until target.size) {
116 resample(target, i, weights[i], source)
117 }
118 }
119
120 private val DEFAULT_KERNEL = LanczosKernel()
121
122 /**
123 * Resize the horizontal aspect.
124 * @param newWidth Width of new image
125 * @param kernel Scaling kernel to use (default: LanczosKernel)
126 * @return A new Resizer object, resampled per specifications
127 */
128 fun horizontal(newWidth: Int, kernel: ScalingKernel = DEFAULT_KERNEL): Resizer {
129 val dst = Resizer(newWidth, height)
130 val weights = precomputeWeights(newWidth, width, kernel)
131 for (y in 0 until height) {
132 resampleBand(Row(dst, y), weights, Row(this, y))
133 }
134 return dst
135 }
136
137 /**
138 * Resize the vertical aspect.
139 * @param newHeight Height of new image
140 * @param kernel Scaling kernel to use (default: LanczosKernel)
141 * @return A new Resizer object, resampled per specifications
142 */
143 fun vertical(newHeight: Int, kernel: ScalingKernel = DEFAULT_KERNEL): Resizer {
144 val dst = Resizer(width, newHeight)
145 val weights = precomputeWeights(newHeight, height, kernel)
146 for (x in 0 until width) {
147 resampleBand(Column(dst, x), weights, Column(this, x))
148 }
149 return dst
150 }
151 }