Mercurial > cgi-bin > hgweb.cgi > SimpleResizer
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 } |