-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #71 from jamsinclair/add-qoi-codec
Add the "Quite Ok Image Format" codec
- Loading branch information
Showing
28 changed files
with
683 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
*.cpp | ||
Makefile | ||
node_modules | ||
codec/*package.json | ||
*.d.ts.map | ||
tsconfig.tsbuildinfo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Changelog | ||
|
||
## @jsquash/qoi@1.0.0 | ||
|
||
### Adds | ||
|
||
- Initial release of the QOI codec |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
# @jsquash/qoi | ||
|
||
[![npm version](https://badge.fury.io/js/@jsquash%2Fqoi.svg)](https://badge.fury.io/js/@jsquash%2Fqoi) | ||
|
||
An easy experience for encoding and decoding the "Quite Ok Image Format" in the browser. Powered by WebAssembly ⚡️. | ||
|
||
Uses the [official QOI](https://github.com/phoboslab/qoi) library. | ||
|
||
A [jSquash](https://github.com/jamsinclair/jSquash) package. Codecs and supporting code derived from the [Squoosh](https://github.com/GoogleChromeLabs/squoosh) app. | ||
|
||
## Installation | ||
|
||
```shell | ||
npm install --save @jsquash/qoi | ||
# Or your favourite package manager alternative | ||
``` | ||
|
||
## Usage | ||
|
||
Note: You will need to either manually include the wasm files from the codec directory or use a bundler like WebPack or Rollup to include them in your app/server. | ||
|
||
### decode(data: ArrayBuffer): Promise<ImageData> | ||
|
||
Decodes QOI binary ArrayBuffer to raw RGB image data. | ||
|
||
#### data | ||
Type: `ArrayBuffer` | ||
|
||
#### Example | ||
```js | ||
import { decode } from '@jsquash/qoi'; | ||
|
||
const formEl = document.querySelector('form'); | ||
const formData = new FormData(formEl); | ||
// Assuming user selected an input qoi file | ||
const imageData = await decode(await formData.get('image').arrayBuffer()); | ||
``` | ||
|
||
### encode(data: ImageData): Promise<ArrayBuffer> | ||
|
||
Encodes raw RGB image data to QOI format and resolves to an ArrayBuffer of binary data. | ||
|
||
#### data | ||
Type: `ImageData` | ||
|
||
#### Example | ||
```js | ||
import { encode } from '@jsquash/qoi'; | ||
|
||
async function loadImage(src) { | ||
const img = document.createElement('img'); | ||
img.src = src; | ||
await new Promise(resolve => img.onload = resolve); | ||
const canvas = document.createElement('canvas'); | ||
[canvas.width, canvas.height] = [img.width, img.height]; | ||
const ctx = canvas.getContext('2d'); | ||
ctx.drawImage(img, 0, 0); | ||
return ctx.getImageData(0, 0, img.width, img.height); | ||
} | ||
|
||
const rawImageData = await loadImage('/example.png'); | ||
const qoiBuffer = await encode(rawImageData); | ||
``` | ||
|
||
## Manual WASM initialisation (not recommended) | ||
|
||
In most situations there is no need to manually initialise the provided WebAssembly modules. | ||
The generated glue code takes care of this and supports most web bundlers. | ||
|
||
One situation where this arises is when using the modules in Cloudflare Workers ([See the README for more info](/README.md#usage-in-cloudflare-workers)). | ||
|
||
The `encode` and `decode` modules both export an `init` function that can be used to manually load the wasm module. | ||
|
||
```js | ||
import decode, { init as initQOIDecode } from '@jsquash/qoi/decode'; | ||
|
||
initQOIDecode(WASM_MODULE); // The `WASM_MODULE` variable will need to be sourced by yourself and passed as an ArrayBuffer. | ||
const image = await fetch('./image.qoi').then(res => res.arrayBuffer()).then(decode); | ||
``` | ||
|
||
You can also pass custom options to the `init` function to customise the behaviour of the module. See the [Emscripten documentation](https://emscripten.org/docs/api_reference/module.html#Module) for more information. | ||
|
||
```js | ||
import decode, { init as initQOIDecode } from '@jsquash/qoi/decode'; | ||
|
||
initQOIDecode(null, { | ||
// Customise the path to load the wasm file | ||
locateFile: (path, prefix) => `https://example.com/${prefix}/${path}`, | ||
}); | ||
const image = await fetch('./image.qoi').then(res => res.arrayBuffer()).then(decode); | ||
``` | ||
|
||
## Known Issues | ||
|
||
See [jSquash Project README](https://github.com/jamsinclair/jSquash#known-issues) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2022 Dominic Szablewski | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
CODEC_URL = https://github.com/phoboslab/qoi/archive/8d35d93cdca85d2868246c2a8a80a1e2c16ba2a8.tar.gz | ||
|
||
CODEC_DIR = node_modules/qoi | ||
CODEC_BUILD_DIR:= $(CODEC_DIR)/build | ||
ENVIRONMENT = web,worker | ||
|
||
PRE_JS = pre.js | ||
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) \ | ||
--pre-js $(PRE_JS) \ | ||
--bind \ | ||
-s ENVIRONMENT=$(ENVIRONMENT) \ | ||
-s EXPORT_ES6=1 \ | ||
-s DYNAMIC_EXECUTION=0 \ | ||
-s MODULARIZE=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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# QOI | ||
|
||
- Source: <https://github.com/phoboslab/qoi> | ||
- Version: N/A | ||
- License: MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#include <emscripten/bind.h> | ||
#include <emscripten/val.h> | ||
|
||
#define QOI_IMPLEMENTATION | ||
#include "qoi.h" | ||
|
||
using namespace emscripten; | ||
|
||
thread_local const val Uint8Array = val::global("Uint8Array"); | ||
|
||
val encode(std::string buffer, int width, int height) { | ||
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) { | ||
function("encode", &encode); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export interface QOIModule extends EmscriptenWasm.Module { | ||
encode( | ||
data: BufferSource, | ||
width: number, | ||
height: number | ||
): Uint8Array; | ||
} | ||
|
||
declare var moduleFactory: EmscriptenWasm.ModuleFactory<QOIModule>; | ||
|
||
export default moduleFactory; |
Large diffs are not rendered by default.
Oops, something went wrong.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"scripts": { | ||
"build": "EMSDK_VERSION=3.1.57 ../../../tools/build-cpp.sh" | ||
}, | ||
"type": "module" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const isServiceWorker = globalThis.ServiceWorkerGlobalScope !== undefined; | ||
const isRunningInCloudFlareWorkers = isServiceWorker && typeof self !== 'undefined' && globalThis.caches && globalThis.caches.default !== undefined; | ||
const isRunningInNode = typeof process === 'object' && process.release && process.release.name === 'node'; | ||
|
||
if (isRunningInCloudFlareWorkers || isRunningInNode) { | ||
if (!globalThis.ImageData) { | ||
// Simple Polyfill for ImageData Object | ||
globalThis.ImageData = class ImageData { | ||
constructor(data, width, height) { | ||
this.data = data; | ||
this.width = width; | ||
this.height = height; | ||
} | ||
}; | ||
} | ||
|
||
if (import.meta.url === undefined) { | ||
import.meta.url = 'https://localhost'; | ||
} | ||
|
||
if (typeof self !== 'undefined' && self.location === undefined) { | ||
self.location = { href: '' }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* 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. | ||
*/ | ||
|
||
/** | ||
* Notice: I (Jamie Sinclair) have copied this code from the original and modified | ||
* to align with the jSquash project structure. | ||
*/ | ||
|
||
import type { QOIModule } from './codec/dec/qoi_dec.js'; | ||
import { initEmscriptenModule } from './utils.js'; | ||
|
||
import qoi_dec from './codec/dec/qoi_dec.js'; | ||
|
||
let emscriptenModule: Promise<QOIModule>; | ||
|
||
export async function init( | ||
module?: WebAssembly.Module, | ||
moduleOptionOverrides?: Partial<EmscriptenWasm.ModuleOpts>, | ||
): Promise<void> { | ||
emscriptenModule = initEmscriptenModule( | ||
qoi_dec, | ||
module, | ||
moduleOptionOverrides, | ||
); | ||
} | ||
|
||
export default async function decode(buffer: ArrayBuffer): Promise<ImageData> { | ||
if (!emscriptenModule) await init(); | ||
|
||
const module = await emscriptenModule; | ||
const result = module.decode(buffer); | ||
if (!result) throw new Error('Decoding error'); | ||
return result; | ||
} |
Oops, something went wrong.