From 3b20dcb41a23238434dd04bb84f7e36e01c259e1 Mon Sep 17 00:00:00 2001 From: Stefan Oltmann Date: Sat, 16 Dec 2023 00:20:45 +0100 Subject: [PATCH] WASM Zlib support (#47) Use pako library for WASM zlib to fully support PNG --- README.md | 3 +-- build.gradle.kts | 20 +++++++++++++- kotlin-js-store/yarn.lock | 23 ++++++++++++++++ .../com/ashampoo/kim/common/ZLib.wasm.kt | 27 +++++++++++++++++-- 4 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 kotlin-js-store/yarn.lock diff --git a/README.md b/README.md index 55383a7a..1cc7dbe5 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ of Ashampoo Photos, which, in turn, is driven by user community feedback. ## Installation ``` -implementation("com.ashampoo:kim:0.8") +implementation("com.ashampoo:kim:0.8.1") ``` ## Sample usages @@ -139,7 +139,6 @@ val newBytes = Kim.updateThumbnail( ## Limitations * Inability to update EXIF, IPTC and XMP in JPG files simultaneously. -* There is no implementation of WebAssembly (WASM) for ZLib compression, which means that PNG files utilizing this compression cannot be processed. ## Contributions diff --git a/build.gradle.kts b/build.gradle.kts index d4a2d1e8..dbacf8e2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension plugins { kotlin("multiplatform") version "1.9.21" @@ -146,7 +147,11 @@ kotlin { } @OptIn(ExperimentalWasmDsl::class) - wasmJs() + wasmJs { + // All tests reading from files fail, because kotlinx-io + // has no Path support for WASM (yet?). + // nodejs() + } // Note: Missing support in kotlinx-datetime // @OptIn(ExperimentalWasmDsl::class) @@ -278,6 +283,10 @@ kotlin { wasmJsMain.dependsOn(this) // wasmWasiMain.dependsOn(this) + + dependencies { + implementation(npm("pako", "2.1.0")) + } } } @@ -450,3 +459,12 @@ publishing { } } // endregion + +//rootProject.the().apply { +// nodeVersion = "21.0.0-v8-canary202309143a48826a08" +// nodeDownloadBaseUrl = "https://nodejs.org/download/v8-canary" +//} +// +//tasks.withType().configureEach { +// args.add("--ignore-engines") +//} diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock new file mode 100644 index 00000000..87aee93f --- /dev/null +++ b/kotlin-js-store/yarn.lock @@ -0,0 +1,23 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@js-joda/core@3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" + integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== + +format-util@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +pako@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" + integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug== + +typescript@5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" + integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== diff --git a/src/wasmMain/kotlin/com/ashampoo/kim/common/ZLib.wasm.kt b/src/wasmMain/kotlin/com/ashampoo/kim/common/ZLib.wasm.kt index b848d948..173cee90 100644 --- a/src/wasmMain/kotlin/com/ashampoo/kim/common/ZLib.wasm.kt +++ b/src/wasmMain/kotlin/com/ashampoo/kim/common/ZLib.wasm.kt @@ -15,8 +15,31 @@ */ package com.ashampoo.kim.common +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.get +import org.khronos.webgl.set + actual fun compress(input: String): ByteArray = - TODO("NOT implemented!") // FIXME + Pako.deflate(input).toByteArray() actual fun decompress(byteArray: ByteArray): String = - TODO("NOT implemented!") // FIXME + Pako.inflate(byteArray.toUint8Array(), toStringOptions) + +private val toStringOptions: JsAny = js("({to: 'string'})") + +private fun Uint8Array.toByteArray(): ByteArray = + ByteArray(length) { this[it] } + +private fun ByteArray.toUint8Array(): Uint8Array { + val result = Uint8Array(size) + forEachIndexed { index, byte -> + result[index] = byte + } + return result +} + +@JsModule("pako") +private external object Pako { + fun deflate(data: String): Uint8Array + fun inflate(data: Uint8Array, options: JsAny): String +}