Skip to content

Commit

Permalink
Adding the Quite OK Image (QOI) Codec (GoogleChromeLabs#1384)
Browse files Browse the repository at this point in the history
  • Loading branch information
aryanpingle authored Oct 20, 2023
1 parent f374068 commit d87eff7
Show file tree
Hide file tree
Showing 19 changed files with 295 additions and 0 deletions.
42 changes: 42 additions & 0 deletions codecs/qoi/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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)

$(filter enc/%,$(OUT_JS)): enc/qoi_enc.o
$(filter dec/%,$(OUT_JS)): dec/qoi_dec.o

# ALL .js FILES
$(OUT_JS):
$(LD) \
$(LDFLAGS) \
--bind \
-s ENVIRONMENT=$(ENVIRONMENT) \
-s EXPORT_ES6=1 \
-o $@ \
$+

# ALL .o FILES
%.o: %.cpp $(CODEC_DIR)
$(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
30 changes: 30 additions & 0 deletions codecs/qoi/dec/qoi_dec.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>

#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) {
qoi_desc desc;
uint8_t* rgba = (uint8_t*)qoi_decode(qoiimage.c_str(), qoiimage.length(), &desc, 4);

// Resultant width and height stored in descriptor
int decodedWidth = desc.width;
int decodedHeight = desc.height;

val result = ImageData.new_(
Uint8ClampedArray.new_(typed_memory_view(4 * decodedWidth * decodedHeight, rgba)),
decodedWidth, decodedHeight);
free(rgba);

return result;
}

EMSCRIPTEN_BINDINGS(my_module) {
function("decode", &decode);
}
7 changes: 7 additions & 0 deletions codecs/qoi/dec/qoi_dec.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface QOIModule extends EmscriptenWasm.Module {
decode(data: BufferSource): ImageData | null;
}

declare var moduleFactory: EmscriptenWasm.ModuleFactory<QOIModule>;

export default moduleFactory;
16 changes: 16 additions & 0 deletions codecs/qoi/dec/qoi_dec.js

Large diffs are not rendered by default.

Binary file added codecs/qoi/dec/qoi_dec.wasm
Binary file not shown.
36 changes: 36 additions & 0 deletions codecs/qoi/enc/qoi_enc.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>

#define QOI_IMPLEMENTATION
#include "qoi.h"

using namespace emscripten;

struct QoiOptions {};

thread_local const val Uint8Array = val::global("Uint8Array");

val encode(std::string buffer, int width, int height, QoiOptions options) {
int compressedSizeInBytes;
qoi_desc desc;
desc.width = width;
desc.height = height;
desc.channels = 4;
desc.colorspace = QOI_SRGB;

uint8_t* encodedData = (uint8_t*)qoi_encode(buffer.c_str(), &desc, &compressedSizeInBytes);
if (encodedData == NULL)
return val::null();

auto js_result =
Uint8Array.new_(typed_memory_view(compressedSizeInBytes, (const uint8_t*)encodedData));
free(encodedData);

return js_result;
}

EMSCRIPTEN_BINDINGS(my_module) {
value_object<QoiOptions>("QoiOptions");

function("encode", &encode);
}
14 changes: 14 additions & 0 deletions codecs/qoi/enc/qoi_enc.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface EncodeOptions {}

export interface QoiModule extends EmscriptenWasm.Module {
encode(
data: BufferSource,
width: number,
height: number,
options: EncodeOptions,
): Uint8Array;
}

declare var moduleFactory: EmscriptenWasm.ModuleFactory<QoiModule>;

export default moduleFactory;
16 changes: 16 additions & 0 deletions codecs/qoi/enc/qoi_enc.js

Large diffs are not rendered by default.

Binary file added codecs/qoi/enc/qoi_enc.wasm
Binary file not shown.
7 changes: 7 additions & 0 deletions codecs/qoi/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "qoi",
"scripts": {
"build": "../build-cpp.sh"
}
}

3 changes: 3 additions & 0 deletions src/client/lazy-app/Compress/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ async function decodeImage(
if (mimeType === 'image/webp2') {
return await workerBridge.wp2Decode(signal, blob);
}
if (mimeType === 'image/qoi') {
return await workerBridge.qoiDecode(signal, blob);
}
}
// Otherwise fall through and try built-in decoding for a laugh.
return await builtinDecode(signal, blob);
Expand Down
1 change: 1 addition & 0 deletions src/client/lazy-app/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
[/^qoif/, 'image/qoi'],
] as const;

export type ImageMimeTypes = typeof magicNumberMapInput[number][1];
Expand Down
19 changes: 19 additions & 0 deletions src/features/decoders/qoi/worker/qoiDecode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import qoiDecoder, { QOIModule } from 'codecs/qoi/dec/qoi_dec';
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';

let emscriptenModule: Promise<QOIModule>;

export default async function decode(blob: Blob): Promise<ImageData> {
if (!emscriptenModule) {
emscriptenModule = initEmscriptenModule(qoiDecoder);
}

const [module, data] = await Promise.all([
emscriptenModule,
blobToArrayBuffer(blob),
]);

const result = module.decode(data);
if (!result) throw new Error('Decoding error');
return result;
}
11 changes: 11 additions & 0 deletions src/features/encoders/qoi/client/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { EncodeOptions } from '../shared/meta';
import type WorkerBridge from 'client/lazy-app/worker-bridge';

export function encode(
signal: AbortSignal,
workerBridge: WorkerBridge,
imageData: ImageData,
options: EncodeOptions,
) {
return workerBridge.qoiEncode(signal, imageData, options);
}
13 changes: 13 additions & 0 deletions src/features/encoders/qoi/client/missing-types.d.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
/// <reference path="../../../../../missing-types.d.ts" />
19 changes: 19 additions & 0 deletions src/features/encoders/qoi/shared/meta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* 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 = {};
13 changes: 13 additions & 0 deletions src/features/encoders/qoi/shared/missing-types.d.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
/// <reference path="../../../../../missing-types.d.ts" />
13 changes: 13 additions & 0 deletions src/features/encoders/qoi/worker/missing-types.d.ts
Original file line number Diff line number Diff line change
@@ -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.
*/
/// <reference path="../../../../../missing-types.d.ts" />
35 changes: 35 additions & 0 deletions src/features/encoders/qoi/worker/qoiEncode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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 qoiEncoder, { QoiModule } from 'codecs/qoi/enc/qoi_enc';
import type { EncodeOptions } from '../shared/meta';
import { initEmscriptenModule } from 'features/worker-utils';

let emscriptenModule: Promise<QoiModule>;

async function init() {
return initEmscriptenModule(qoiEncoder);
}

export default async function encode(
data: ImageData,
options: EncodeOptions,
): Promise<ArrayBuffer> {
if (!emscriptenModule) {
emscriptenModule = init();
}

const module = await emscriptenModule;
const resultView = module.encode(data.data, data.width, data.height, options);
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
return resultView.buffer as ArrayBuffer;
}

0 comments on commit d87eff7

Please sign in to comment.