From 4a1bcec5afc4c799fcf303741f19cc6a149e1285 Mon Sep 17 00:00:00 2001 From: robo-mop Date: Mon, 16 Oct 2023 20:22:53 +0530 Subject: [PATCH] Integrate QOI into web-app --- codecs/qoi/Makefile | 54 ++++++++++++++ codecs/qoi/dec/qoi_dec.cpp | 25 +++++++ codecs/qoi/enc/qoi_enc.cpp | 37 ++++++++++ codecs/qoi/enc/qoi_enc.d.ts | 17 +++++ src/client/lazy-app/util/index.ts | 1 + src/features/encoders/qoi/client/index.tsx | 73 +++++++++++++++++++ .../encoders/qoi/client/missing-types.d.ts | 13 ++++ src/features/encoders/qoi/shared/meta.ts | 22 ++++++ .../encoders/qoi/shared/missing-types.d.ts | 13 ++++ .../encoders/qoi/worker/missing-types.d.ts | 13 ++++ src/features/encoders/qoi/worker/qoiEncode.ts | 32 ++++++++ 11 files changed, 300 insertions(+) create mode 100644 codecs/qoi/enc/qoi_enc.d.ts create mode 100644 src/features/encoders/qoi/client/index.tsx create mode 100644 src/features/encoders/qoi/client/missing-types.d.ts create mode 100644 src/features/encoders/qoi/shared/meta.ts create mode 100644 src/features/encoders/qoi/shared/missing-types.d.ts create mode 100644 src/features/encoders/qoi/worker/missing-types.d.ts create mode 100644 src/features/encoders/qoi/worker/qoiEncode.ts diff --git a/codecs/qoi/Makefile b/codecs/qoi/Makefile index e69de29b..b89f8efb 100644 --- a/codecs/qoi/Makefile +++ b/codecs/qoi/Makefile @@ -0,0 +1,54 @@ +CODEC_URL = https://github.com/phoboslab/qoi/archive/8d35d93cdca85d2868246c2a8a80a1e2c16ba2a8.tar.gz + +CODEC_DIR = node_modules/qoi +CODEC_BUILD_DIR:= $(CODEC_DIR)/build +ENVIRONMENT = worker + +OUT_JS = enc/qoi_enc.js dec/qoi_dec.js +OUT_WASM := $(OUT_JS:.js=.wasm) + +.PHONY: all clean + +all: $(OUT_JS) + +# Define dependencies for all variations of build artifacts. +enc/qoi_enc.js dec/qoi_dec.js: $(CODEC_DIR)/qoi.h +$(filter enc/%,$(OUT_JS)): enc/qoi_enc.cpp +$(filter dec/%,$(OUT_JS)): dec/qoi_dec.cpp + +# ALL .js FILES +$(OUT_JS): + @echo ">> Making $@" + $(LD) \ + $(LDFLAGS) \ + --bind \ + -s ENVIRONMENT=$(ENVIRONMENT) \ + -s EXPORT_ES6=1 \ + -o $@ \ + $+ + +# ALL .o FILES +%.o: $(CODEC_DIR) + $(info ) + $(info Making .o files) + $(info $$ + = $+) + $(info $$ @ = $@) + $(info ) + + $(CXX) -c \ + $(CXXFLAGS) \ + -I $(CODEC_DIR) \ + -o $@ \ + $< + +# CREATE DIRECTORY +$(CODEC_DIR): + mkdir -p $(CODEC_DIR) + curl -sL $(CODEC_URL) | tar xz --strip 1 -C $(CODEC_DIR) + +clean: + $(RM) $(OUT_JS) $(OUT_WASM) + $(MAKE) -C $(CODEC_DIR) clean + +test: $(CODEC_BUILD_DIR)/libqoi.a + gcc -o bruh.out test.cpp $+ \ No newline at end of file diff --git a/codecs/qoi/dec/qoi_dec.cpp b/codecs/qoi/dec/qoi_dec.cpp index e69de29b..3c79cae8 100644 --- a/codecs/qoi/dec/qoi_dec.cpp +++ b/codecs/qoi/dec/qoi_dec.cpp @@ -0,0 +1,25 @@ +#include +#include + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +using namespace emscripten; + +thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray"); +thread_local const val ImageData = val::global("ImageData"); + +val decode(std::string qoiimage) { + val result = val::null(); + + const int N = 1000; + int data[N] = {0}; + + result = ImageData.new_(Uint8ClampedArray.new_(typed_memory_view(N, data)), 20, 50); + + return result; +} + +EMSCRIPTEN_BINDINGS(my_module) { + function("decode", &decode); +} diff --git a/codecs/qoi/enc/qoi_enc.cpp b/codecs/qoi/enc/qoi_enc.cpp index e69de29b..384b3e40 100644 --- a/codecs/qoi/enc/qoi_enc.cpp +++ b/codecs/qoi/enc/qoi_enc.cpp @@ -0,0 +1,37 @@ +#include +#include + +#define QOI_IMPLEMENTATION +#include "qoi.h" + +using namespace emscripten; + +struct QoiOptions { + int quality; + bool randombool; +}; + +thread_local const val Uint8Array = val::global("Uint8Array"); + +val encode(std::string buffer, int width, int height, QoiOptions options) { + printf("Starting encode!"); + + printf("quality = %d\n", options.quality); + printf("randombool = %s\n", options.randombool ? "true" : "false"); + + auto js_result = val::null(); + + const int N = 100; + int* data = (int*)malloc(N * sizeof(int)); + + js_result = Uint8Array.new_(typed_memory_view(N, data)); + return js_result; +} + +EMSCRIPTEN_BINDINGS(my_module) { + value_object("QoiOptions") + .field("quality", &QoiOptions::quality) + .field("randombool", &QoiOptions::randombool); + + function("encode", &encode); +} diff --git a/codecs/qoi/enc/qoi_enc.d.ts b/codecs/qoi/enc/qoi_enc.d.ts new file mode 100644 index 00000000..0287cb8e --- /dev/null +++ b/codecs/qoi/enc/qoi_enc.d.ts @@ -0,0 +1,17 @@ +export interface EncodeOptions { + quality: number; + randombool: boolean; +} + +export interface QoiModule extends EmscriptenWasm.Module { + encode( + data: BufferSource, + width: number, + height: number, + options: EncodeOptions, + ): Uint8Array; +} + +declare var moduleFactory: EmscriptenWasm.ModuleFactory; + +export default moduleFactory; diff --git a/src/client/lazy-app/util/index.ts b/src/client/lazy-app/util/index.ts index fb5caea6..a7ae1fed 100644 --- a/src/client/lazy-app/util/index.ts +++ b/src/client/lazy-app/util/index.ts @@ -103,6 +103,7 @@ const magicNumberMapInput = [ [/^\x00\x00\x00 ftypavif\x00\x00\x00\x00/, 'image/avif'], [/^\xff\x0a/, 'image/jxl'], [/^\x00\x00\x00\x0cJXL \x0d\x0a\x87\x0a/, 'image/jxl'], + [/^QOI/, 'image/qoi'], ] as const; export type ImageMimeTypes = typeof magicNumberMapInput[number][1]; diff --git a/src/features/encoders/qoi/client/index.tsx b/src/features/encoders/qoi/client/index.tsx new file mode 100644 index 00000000..8c6d6d4d --- /dev/null +++ b/src/features/encoders/qoi/client/index.tsx @@ -0,0 +1,73 @@ +import { EncodeOptions } from '../shared/meta'; +import type WorkerBridge from 'client/lazy-app/worker-bridge'; +import { h, Component } from 'preact'; +import { + inputFieldChecked, + inputFieldValueAsNumber, + preventDefault, +} from 'client/lazy-app/util'; +import * as style from 'client/lazy-app/Compress/Options/style.css'; +import linkState from 'linkstate'; +import Range from 'client/lazy-app/Compress/Options/Range'; +import Checkbox from 'client/lazy-app/Compress/Options/Checkbox'; +import Expander from 'client/lazy-app/Compress/Options/Expander'; +import Select from 'client/lazy-app/Compress/Options/Select'; +import Revealer from 'client/lazy-app/Compress/Options/Revealer'; + +export function encode( + signal: AbortSignal, + workerBridge: WorkerBridge, + imageData: ImageData, + options: EncodeOptions, +) { + return workerBridge.qoiEncode(signal, imageData, options); +} + +interface Props { + options: EncodeOptions; + onChange(newOptions: EncodeOptions): void; +} + +interface State { + showAdvanced: boolean; +} + +export class Options extends Component { + onChange = (event: Event) => { + const form = (event.currentTarget as HTMLInputElement).closest( + 'form', + ) as HTMLFormElement; + + const options: EncodeOptions = { + quality: inputFieldValueAsNumber(form.quality), + randombool: inputFieldChecked(form.randombool), + }; + this.props.onChange(options); + }; + + render({ options }: Props) { + return ( +
+
+ + Quality: + +
+ +
+ ); + } +} diff --git a/src/features/encoders/qoi/client/missing-types.d.ts b/src/features/encoders/qoi/client/missing-types.d.ts new file mode 100644 index 00000000..c729fd74 --- /dev/null +++ b/src/features/encoders/qoi/client/missing-types.d.ts @@ -0,0 +1,13 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/// diff --git a/src/features/encoders/qoi/shared/meta.ts b/src/features/encoders/qoi/shared/meta.ts new file mode 100644 index 00000000..6e6d58d1 --- /dev/null +++ b/src/features/encoders/qoi/shared/meta.ts @@ -0,0 +1,22 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EncodeOptions } from 'codecs/qoi/enc/qoi_enc'; +export { EncodeOptions }; + +export const label = 'QOI'; +export const mimeType = 'image/qoi'; +export const extension = 'qoi'; +export const defaultOptions: EncodeOptions = { + quality: 75, + randombool: true, +}; diff --git a/src/features/encoders/qoi/shared/missing-types.d.ts b/src/features/encoders/qoi/shared/missing-types.d.ts new file mode 100644 index 00000000..c729fd74 --- /dev/null +++ b/src/features/encoders/qoi/shared/missing-types.d.ts @@ -0,0 +1,13 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/// diff --git a/src/features/encoders/qoi/worker/missing-types.d.ts b/src/features/encoders/qoi/worker/missing-types.d.ts new file mode 100644 index 00000000..c729fd74 --- /dev/null +++ b/src/features/encoders/qoi/worker/missing-types.d.ts @@ -0,0 +1,13 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/// diff --git a/src/features/encoders/qoi/worker/qoiEncode.ts b/src/features/encoders/qoi/worker/qoiEncode.ts new file mode 100644 index 00000000..18018af6 --- /dev/null +++ b/src/features/encoders/qoi/worker/qoiEncode.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2020 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import qoi_enc, { QoiModule } from 'codecs/qoi/enc/qoi_enc'; +import { EncodeOptions } from '../shared/meta'; +import { initEmscriptenModule } from 'features/worker-utils'; + +let emscriptenModule: Promise; + +export default async function encode( + data: ImageData, + options: EncodeOptions, +): Promise { + if (!emscriptenModule) { + emscriptenModule = initEmscriptenModule(qoi_enc); + } + + const module = await emscriptenModule; + const resultView = module.encode(data.data, data.width, data.height, options); + console.log(resultView); + // wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer. + return resultView.buffer as ArrayBuffer; +}