Skip to content

Commit 0dcf88a

Browse files
committed
Weighted bitmap addition
Signed-off-by: Kyle Corry <[email protected]>
1 parent 2e1d2d4 commit 0dcf88a

File tree

7 files changed

+346
-3
lines changed

7 files changed

+346
-3
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.kylecorry.andromeda.bitmaps
2+
3+
import android.graphics.Bitmap
4+
import android.graphics.Color
5+
import org.junit.Assert.assertEquals
6+
import org.junit.Test
7+
8+
class WeightedAddTest {
9+
10+
11+
@Test
12+
fun weightedAdd() {
13+
val ones = createBitmap()
14+
val expected1 = createBitmap()
15+
val expected2 = createBitmap()
16+
val expected3 = createBitmap()
17+
18+
for (i in 0 until ones.width) {
19+
for (j in 0 until ones.height) {
20+
ones.setPixel(i, j, Color.rgb(1, 1, 1))
21+
expected1.setPixel(i, j, Color.rgb(2, 2, 2))
22+
expected2.setPixel(i, j, Color.argb(0, 0, 0, 0))
23+
expected3.setPixel(i, j, Color.rgb(2, 2, 2))
24+
}
25+
}
26+
27+
val result1 = Toolkit.weightedAdd(ones, ones, 1f, 1f, false)
28+
val result2 = Toolkit.weightedAdd(ones, ones, 1f, -3f, false)
29+
val result3 = Toolkit.weightedAdd(ones, ones, 1f, -3f, true)
30+
31+
bitmapEquals(expected1, result1)
32+
bitmapEquals(expected2, result2)
33+
bitmapEquals(expected3, result3)
34+
}
35+
36+
private fun bitmapEquals(bitmap1: Bitmap, bitmap2: Bitmap) {
37+
assertEquals(bitmap1.width, bitmap2.width)
38+
assertEquals(bitmap1.height, bitmap2.height)
39+
40+
for (i in 0 until bitmap1.width) {
41+
for (j in 0 until bitmap1.height) {
42+
assertEquals(bitmap1.getPixel(i, j), bitmap2.getPixel(i, j))
43+
}
44+
}
45+
}
46+
47+
private fun createBitmap(width: Int = 100, height: Int = 100): Bitmap {
48+
return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
49+
}
50+
}

bitmaps/src/main/cpp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ add_library(# Sets the name of the library.
9494
Moment.cpp
9595
BlobFinder.cpp
9696
GrayLevelCovarianceMatrix.cpp
97+
WeightedAdd.cpp
9798
${ASM_SOURCES})
9899

99100
# Searches for a specified prebuilt library and stores the path as a

bitmaps/src/main/cpp/JniEntryPoints.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,39 @@ Java_com_kylecorry_andromeda_bitmaps_Toolkit_nativeThresholdBitmap(
521521
channel, restrict.get());
522522
}
523523

524+
extern "C" JNIEXPORT void JNICALL Java_com_kylecorry_andromeda_bitmaps_Toolkit_nativeWeightedAdd(
525+
JNIEnv *env, jobject /*thiz*/, jlong native_handle, jbyteArray input_array1,
526+
jbyteArray input_array2,
527+
jbyteArray output_array, jint size_x, jint size_y, jfloat weight1, jfloat weight2,
528+
jboolean absolute, jobject restriction) {
529+
RenderScriptToolkit *toolkit = reinterpret_cast<RenderScriptToolkit *>(native_handle);
530+
RestrictionParameter restrict{env, restriction};
531+
532+
ByteArrayGuard input1{env, input_array1};
533+
ByteArrayGuard input2{env, input_array2};
534+
ByteArrayGuard output{env, output_array};
535+
536+
toolkit->weightedAdd(input1.get(), input2.get(), output.get(), size_x, size_y, weight1, weight2,
537+
absolute,
538+
restrict.get());
539+
}
540+
541+
extern "C" JNIEXPORT void JNICALL
542+
Java_com_kylecorry_andromeda_bitmaps_Toolkit_nativeWeightedAddBitmap(
543+
JNIEnv *env, jobject /*thiz*/, jlong native_handle, jobject input_bitmap1,
544+
jobject input_bitmap2, jobject output_bitmap, jfloat weight1, jfloat weight2,
545+
jboolean absolute, jobject restriction) {
546+
RenderScriptToolkit *toolkit = reinterpret_cast<RenderScriptToolkit *>(native_handle);
547+
RestrictionParameter restrict{env, restriction};
548+
BitmapGuard input1{env, input_bitmap1};
549+
BitmapGuard input2{env, input_bitmap2};
550+
BitmapGuard output{env, output_bitmap};
551+
552+
toolkit->weightedAdd(input1.get(), input2.get(), output.get(), input1.width(), input1.height(),
553+
weight1, weight2,
554+
absolute, restrict.get());
555+
}
556+
524557
extern "C" JNIEXPORT void JNICALL Java_com_kylecorry_andromeda_bitmaps_Toolkit_nativeMinMax(
525558
JNIEnv *env, jobject /*thiz*/, jlong native_handle, jbyteArray input_array,
526559
jfloatArray output_array, jint size_x,

bitmaps/src/main/cpp/RenderScriptToolkit.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,21 @@ namespace renderscript {
464464
float threshold, bool binary, uint8_t channel,
465465
const Restriction *restriction);
466466

467+
/**
468+
* Add two images together with a weight.
469+
* @param input1 The buffer of the first image.
470+
* @param input2 The buffer of the second image.
471+
* @param output The buffer that receives the sum.
472+
* @param sizeX The width of both buffers, as a number of 4 byte cells.
473+
* @param sizeY The height of both buffers, as a number of 4 byte cells.
474+
* @param weight1 The weight of the first image.
475+
* @param weight2 The weight of the second image.
476+
* @param absolute Whether to take the absolute value of the sum.
477+
* @param restriction When not null, restricts the operation to a 2D range of pixels.
478+
*/
479+
void weightedAdd(const uint8_t *input1, const uint8_t *input2, uint8_t *output,
480+
size_t sizeX, size_t sizeY, float weight1, float weight2, bool absolute,
481+
const Restriction *restriction);
467482

468483
/**
469484
* Find the minimum or maximum value in an image.

bitmaps/src/main/cpp/WeightedAdd.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include <cstdint>
2+
3+
#include "RenderScriptToolkit.h"
4+
#include "TaskProcessor.h"
5+
#include "Utils.h"
6+
7+
#define LOG_TAG "renderscript.toolkit.WeightedAdd"
8+
9+
namespace renderscript {
10+
11+
class WeightedAddTask : public Task {
12+
const uchar4 *mIn1;
13+
const uchar4 *mIn2;
14+
uchar4 *mOut;
15+
float mWeight1;
16+
float mWeight2;
17+
bool mAbsolute;
18+
19+
// Process a 2D tile of the overall work. threadIndex identifies which thread does the work.
20+
void processData(int threadIndex, size_t startX, size_t startY, size_t endX,
21+
size_t endY) override;
22+
23+
public:
24+
WeightedAddTask(const uint8_t *input1, const uint8_t *input2, uint8_t *output, size_t sizeX,
25+
size_t sizeY,
26+
float weight1, float weight2, bool absolute,
27+
const Restriction *restriction)
28+
: Task{sizeX, sizeY, 4, true, restriction},
29+
mIn1{reinterpret_cast<const uchar4 *>(input1)},
30+
mIn2{reinterpret_cast<const uchar4 *>(input2)},
31+
mOut{reinterpret_cast<uchar4 *>(output)},
32+
mWeight1{weight1},
33+
mWeight2{weight2},
34+
mAbsolute{absolute} {}
35+
};
36+
37+
void
38+
WeightedAddTask::processData(int /* threadIndex */, size_t startX, size_t startY, size_t endX,
39+
size_t endY) {
40+
for (size_t y = startY; y < endY; y++) {
41+
size_t offset = mSizeX * y + startX;
42+
const uchar4 *in1 = mIn1 + offset;
43+
const uchar4 *in2 = mIn2 + offset;
44+
uchar4 *out = mOut + offset;
45+
for (size_t x = startX; x < endX; x++) {
46+
auto v1 = *in1;
47+
double r1 = v1.r;
48+
double g1 = v1.g;
49+
double b1 = v1.b;
50+
double a1 = v1.a;
51+
52+
auto v2 = *in2;
53+
double r2 = v2.r;
54+
double g2 = v2.g;
55+
double b2 = v2.b;
56+
double a2 = v2.a;
57+
58+
double r3 = r1 * mWeight1 + r2 * mWeight2;
59+
double g3 = g1 * mWeight1 + g2 * mWeight2;
60+
double b3 = b1 * mWeight1 + b2 * mWeight2;
61+
double a3 = a1 * mWeight1 + a2 * mWeight2;
62+
if (mAbsolute) {
63+
r3 = std::abs(r3);
64+
g3 = std::abs(g3);
65+
b3 = std::abs(b3);
66+
a3 = std::abs(a3);
67+
}
68+
69+
// Clamp
70+
if (r3 > 255) {
71+
r3 = 255;
72+
} else if (r3 < 0) {
73+
r3 = 0;
74+
}
75+
76+
if (g3 > 255) {
77+
g3 = 255;
78+
} else if (g3 < 0) {
79+
g3 = 0;
80+
}
81+
82+
if (b3 > 255) {
83+
b3 = 255;
84+
} else if (b3 < 0) {
85+
b3 = 0;
86+
}
87+
88+
if (a3 > 255) {
89+
a3 = 255;
90+
} else if (a3 < 0) {
91+
a3 = 0;
92+
}
93+
94+
*out = {static_cast<uint8_t>(r3), static_cast<uint8_t>(g3),
95+
static_cast<uint8_t>(b3),
96+
static_cast<uint8_t>(a3)};
97+
98+
in1++;
99+
in2++;
100+
out++;
101+
}
102+
}
103+
}
104+
105+
void
106+
RenderScriptToolkit::weightedAdd(const uint8_t *input1, const uint8_t *input2, uint8_t *output,
107+
size_t sizeX,
108+
size_t sizeY,
109+
float weight1, float weight2, bool absolute,
110+
const Restriction *restriction) {
111+
#ifdef ANDROID_RENDERSCRIPT_TOOLKIT_VALIDATE
112+
if (!validRestriction(LOG_TAG, sizeX, sizeY, restriction)) {
113+
return;
114+
}
115+
#endif
116+
117+
WeightedAddTask task(input1, input2, output, sizeX, sizeY, weight1, weight2, absolute,
118+
restriction);
119+
processor->doTask(&task);
120+
}
121+
122+
} // namespace renderscript

bitmaps/src/main/java/com/kylecorry/andromeda/bitmaps/BitmapUtils.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,12 @@ object BitmapUtils {
178178
return Toolkit.convolve(this, kernel)
179179
}
180180

181-
fun Bitmap.gray(): Bitmap {
182-
return Toolkit.colorMatrix(this, Toolkit.greyScaleColorMatrix)
181+
fun Bitmap.gray(average: Boolean = false, inPlace: Boolean = false): Bitmap {
182+
return Toolkit.colorMatrix(
183+
this,
184+
if (average) Toolkit.averageColorMatrix else Toolkit.greyScaleColorMatrix,
185+
inPlace = inPlace
186+
)
183187
}
184188

185189
fun Bitmap.histogram(): IntArray {
@@ -384,6 +388,16 @@ object BitmapUtils {
384388
)
385389
}
386390

391+
fun Bitmap.add(
392+
bitmap: Bitmap,
393+
selfWeight: Float = 1f,
394+
otherWeight: Float = 1f,
395+
absolute: Boolean = false,
396+
inPlace: Boolean = false
397+
): Bitmap {
398+
return Toolkit.weightedAdd(this, bitmap, selfWeight, otherWeight, absolute, inPlace)
399+
}
400+
387401
private fun quantize(arr: ByteArray, bins: Int) {
388402
if (bins >= 256 || bins <= 0) {
389403
return

0 commit comments

Comments
 (0)