Skip to content

Commit 0f62f36

Browse files
committed
Add android support
1 parent 358ce26 commit 0f62f36

File tree

36 files changed

+1378
-98
lines changed

36 files changed

+1378
-98
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ cmake-build-*/
66
.gradle/
77
.kotlin/
88
build/
9+
local.properties

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repositories {
1010
}
1111

1212
dependencies {
13-
implementation("com.martmists.ndarray-simd:ndarray-simd:1.4.4")
13+
implementation("com.martmists.ndarray-simd:ndarray-simd:1.5.0")
1414
}
1515
```
1616

build.gradle.kts

Lines changed: 122 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,49 @@
33
import com.vanniktech.maven.publish.SonatypeHost
44
import org.gradle.configurationcache.extensions.capitalized
55
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
6+
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
67
import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
78
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
9+
import org.jetbrains.kotlin.konan.target.KonanTarget
10+
import java.util.Properties
811

912
plugins {
1013
kotlin("multiplatform")
1114
`maven-publish`
15+
id("com.android.library")
1216
id("org.jetbrains.dokka")
1317
id("com.vanniktech.maven.publish")
1418
}
1519

1620
group = "com.martmists.ndarray-simd"
17-
version = "1.4.4"
21+
version = "1.5.0"
1822
val isProduction = (findProperty("production") ?: System.getProperty("production")) != null
1923

2024
repositories {
25+
google()
2126
mavenCentral()
2227
}
2328

29+
android {
30+
compileSdk = 35
31+
namespace = group.toString().replace('-', '.')
32+
ndkVersion = "21.4.7075529"
33+
34+
sourceSets {
35+
getByName("debug").jniLibs.srcDirs(project.layout.buildDirectory.dir("generated/jni/Debug"))
36+
getByName("release").jniLibs.srcDirs(project.layout.buildDirectory.dir("generated/jni/Release"))
37+
}
38+
}
39+
2440
kotlin {
2541
jvmToolchain(21)
2642

2743
withSourcesJar()
2844

29-
jvm()
45+
androidTarget {
46+
publishLibraryVariants("release", "debug")
47+
}
48+
jvm("desktop")
3049

3150
val natives = if (isProduction) {
3251
listOf(
@@ -39,6 +58,9 @@ kotlin {
3958
// Feel free to open a PR
4059
// macosX64(),
4160
// macosArm64(),
61+
62+
androidNativeArm64(),
63+
androidNativeX64(),
4264
)
4365
} else {
4466
when (val osArch = System.getProperty("os.arch")) {
@@ -58,26 +80,42 @@ kotlin {
5880
}
5981
}
6082

83+
applyDefaultHierarchyTemplate()
84+
6185
for (native in natives) {
6286
native.apply {
6387
binaries {
6488
sharedLib {
6589
baseName = "ndarray_simd"
90+
91+
if (native.targetName.startsWith("android")) {
92+
linkTaskProvider.configure {
93+
doLast {
94+
copy {
95+
from(outputFile)
96+
97+
val typeName = if (buildType == NativeBuildType.DEBUG) "Debug" else "Release"
98+
val abiDirName = when (this@sharedLib.target.konanTarget) {
99+
KonanTarget.ANDROID_ARM32 -> "armeabi-v7a"
100+
KonanTarget.ANDROID_ARM64 -> "arm64-v8a"
101+
KonanTarget.ANDROID_X86 -> "x86"
102+
KonanTarget.ANDROID_X64 -> "x86_64"
103+
else -> "unknown"
104+
}
105+
106+
into(project.layout.buildDirectory.dir("generated/jni/$typeName/$abiDirName"))
107+
}
108+
}
109+
}
110+
111+
afterEvaluate {
112+
tasks.getByName("preBuild").dependsOn(linkTaskProvider)
113+
}
114+
}
66115
}
67116
}
68117

69118
compilations.named("main") {
70-
val jni by cinterops.creating {
71-
val javaHome = File(System.getProperty("java.home")!!)
72-
defFile(projectDir.resolve("src/nativeMain/cinterops/jni.def"))
73-
includeDirs(
74-
javaHome.resolve("include"),
75-
javaHome.resolve("include/linux"),
76-
javaHome.resolve("include/darwin"),
77-
javaHome.resolve("include/win32"),
78-
)
79-
}
80-
81119
val simd by cinterops.creating {
82120
defFile(projectDir.resolve("src/nativeMain/cinterops/simd.def"))
83121
includeDirs(
@@ -198,25 +236,43 @@ kotlin {
198236
dependsOn(arTask)
199237
}
200238
}
239+
240+
if (!native.targetName.startsWith("android")) {
241+
val jni by cinterops.creating {
242+
val javaHome = File(System.getProperty("java.home")!!)
243+
defFile(projectDir.resolve("src/desktopNativeMain/cinterops/jni.def"))
244+
245+
includeDirs(
246+
javaHome.resolve("include"),
247+
javaHome.resolve("include/linux"),
248+
javaHome.resolve("include/darwin"),
249+
javaHome.resolve("include/win32"),
250+
)
251+
}
252+
}
201253
}
202254
}
203255
}
204256

205257
sourceSets {
206-
commonTest {
258+
val commonMain by getting
259+
260+
val commonTest by getting {
207261
dependencies {
208262
api(kotlin("test"))
209263
}
210264
}
211265

212-
jvmMain {
266+
val jvmMain by creating {
267+
dependsOn(commonMain)
268+
213269
dependencies {
214270
// Compat: OpenCV
215271
compileOnly("org.openpnp:opencv:4.9.0-0")
216272

217273
// Compat: Exposed+PGVector
218274
compileOnly("com.pgvector:pgvector:0.1.6")
219-
compileOnly("org.jetbrains.exposed:exposed-core:0.52.0")
275+
compileOnly("org.jetbrains.exposed:exposed-core:0.60.0")
220276

221277
// Compat: Image Formats
222278
compileOnly("com.sksamuel.scrimage:scrimage-core:4.1.3")
@@ -226,18 +282,50 @@ kotlin {
226282
compileOnly("dev.langchain4j:langchain4j:0.32.0")
227283

228284
// Compat: kotlinx.dataframe
229-
compileOnly("org.jetbrains.kotlinx:dataframe:0.13.1")
285+
compileOnly("org.jetbrains.kotlinx:dataframe-core:0.13.1")
230286
}
231287
}
232288

233-
jvmTest {
289+
val jvmTest by creating {
290+
dependsOn(commonTest)
291+
234292
dependencies {
235-
runtimeOnly("org.openpnp:opencv:4.9.0-0")
236-
runtimeOnly("com.sksamuel.scrimage:scrimage-core:4.1.3")
237-
runtimeOnly("dev.langchain4j:langchain4j:0.32.0")
238-
runtimeOnly("org.jetbrains.kotlinx:dataframe:0.13.1")
293+
implementation("org.openpnp:opencv:4.9.0-0")
294+
implementation("com.sksamuel.scrimage:scrimage-core:4.1.3")
295+
implementation("dev.langchain4j:langchain4j:0.32.0")
296+
implementation("org.jetbrains.kotlinx:dataframe-core:0.13.1")
297+
}
298+
}
299+
300+
val androidMain by getting {
301+
dependsOn(jvmMain)
302+
}
303+
304+
val desktopMain by getting {
305+
dependsOn(jvmMain)
306+
}
307+
308+
val nativeMain by getting
309+
310+
val desktopNativeMain by creating {
311+
dependsOn(nativeMain)
312+
}
313+
314+
for (native in natives) {
315+
if (!native.targetName.startsWith("android")) {
316+
named("${native.targetName}Main") {
317+
dependsOn(desktopNativeMain)
318+
}
239319
}
240320
}
321+
322+
// val androidUnitTest by getting {
323+
// dependsOn(jvmTest)
324+
// }
325+
326+
val desktopTest by getting {
327+
dependsOn(jvmTest)
328+
}
241329
}
242330
}
243331

@@ -255,14 +343,23 @@ tasks {
255343
}
256344
}
257345

258-
val jvmProcessResources by existing(Copy::class) {
346+
afterEvaluate {
347+
named("testDebugUnitTest") {
348+
enabled = false
349+
}
350+
named("testReleaseUnitTest") {
351+
enabled = false
352+
}
353+
}
354+
355+
val desktopProcessResources by existing(Copy::class) {
259356
val binaryName = if (isProduction) {
260357
"releaseShared"
261358
} else {
262359
"debugShared"
263360
}
264361

265-
for (native in kotlin.targets.withType<KotlinNativeTarget>()) {
362+
for (native in kotlin.targets.withType<KotlinNativeTarget>().filter { !it.name.startsWith("android") }) {
266363
into("META-INF/natives/${native.targetName}") {
267364
from(named(native.binaries.getByName(binaryName).linkTaskName)) {
268365
exclude("**/*.h")
@@ -340,7 +437,7 @@ if (isProduction) {
340437

341438
mavenPublishing {
342439
publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, automaticRelease = true)
343-
coordinates(group as String, name, releaseVersion)
440+
coordinates(group.toString(), name, releaseVersion)
344441
signAllPublications()
345442

346443
pom {

buildSrc/build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ plugins {
55
repositories {
66
gradlePluginPortal()
77
mavenCentral()
8+
google()
89
}
910

1011
dependencies {
11-
implementation(kotlin("gradle-plugin", "2.1.0"))
12-
implementation("com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin:0.30.0")
12+
implementation(kotlin("gradle-plugin", "2.1.20"))
13+
implementation("com.vanniktech.maven.publish:com.vanniktech.maven.publish.gradle.plugin:0.31.0")
1314
implementation("org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:2.0.0")
15+
implementation("com.android.tools.build:gradle:8.9.2")
1416
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Mon Jul 15 14:03:33 CEST 2024
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
55
zipStoreBase=GRADLE_USER_HOME
66
zipStorePath=wrapper/dists
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
@file:JvmName("ImageAndroidKt")
2+
3+
package com.martmists.ndarray.simd.compat
4+
5+
import android.graphics.Bitmap
6+
import com.martmists.ndarray.simd.F64Array
7+
import com.martmists.ndarray.simd.F64Array.Companion.invoke
8+
import com.martmists.ndarray.simd.F64ImageArray
9+
import com.sksamuel.scrimage.ImmutableImage
10+
import com.sksamuel.scrimage.nio.JpegWriter
11+
import com.sksamuel.scrimage.nio.PngWriter
12+
import com.sksamuel.scrimage.webp.WebpWriter
13+
import java.awt.image.BufferedImage
14+
import java.awt.image.DataBufferByte
15+
import java.io.File
16+
17+
private fun Int.asColorDouble(): Double = (if (this < 0) 256 + this else this) / 255.0
18+
19+
/**
20+
* Reads a BufferedImage into an [F64Array].
21+
*
22+
* The resulting [F64Array] will have shape `[width, height, 4]`,
23+
* where the 3rd dimension is in order RGBA. All values are in range `[0..1]`
24+
*
25+
* @param img The image to read from.
26+
* @return The [F64Array] read from the image.
27+
* @since 1.5.0
28+
*/
29+
fun F64Array.Companion.fromImage(img: Bitmap): F64ImageArray {
30+
val w = img.width
31+
val h = img.height
32+
val hasAlpha = img.hasAlpha()
33+
val arr = F64Array(w, h, 4).image
34+
35+
for (y in 0 until h) {
36+
for (x in 0 until w) {
37+
val px = img.getPixel(x, y)
38+
val a = px shr 24
39+
val r = (px shr 16) and 0xff
40+
val g = (px shr 8) and 0xff
41+
val b = px and 0xff
42+
if (hasAlpha) {
43+
arr[x, y, 3] = a.asColorDouble()
44+
} else {
45+
arr[x, y, 3] = 1.0
46+
}
47+
arr[x, y, 0] = r.asColorDouble()
48+
arr[x, y, 1] = g.asColorDouble()
49+
arr[x, y, 2] = b.asColorDouble()
50+
}
51+
}
52+
53+
return arr
54+
}

0 commit comments

Comments
 (0)