mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-15 18:19:47 +00:00
Integrate QOI codec completely
* Adds code for encoders and decoders * Cleans up some obsolete QOI-related code
This commit is contained in:
@@ -10,12 +10,16 @@ thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
|
|||||||
thread_local const val ImageData = val::global("ImageData");
|
thread_local const val ImageData = val::global("ImageData");
|
||||||
|
|
||||||
val decode(std::string qoiimage) {
|
val decode(std::string qoiimage) {
|
||||||
val result = val::null();
|
qoi_desc desc;
|
||||||
|
uint8_t* rgba = (uint8_t*)qoi_decode(qoiimage.c_str(), qoiimage.length(), &desc, 4);
|
||||||
|
|
||||||
const int N = 1000;
|
// Resultant width and height stored in descriptor
|
||||||
int data[N] = {0};
|
int decodedWidth = desc.width;
|
||||||
|
int decodedHeight = desc.height;
|
||||||
|
|
||||||
result = ImageData.new_(Uint8ClampedArray.new_(typed_memory_view(N, data)), 20, 50);
|
val result = ImageData.new_(
|
||||||
|
Uint8ClampedArray.new_(typed_memory_view(4 * decodedWidth * decodedHeight, rgba)),
|
||||||
|
decodedWidth, decodedHeight);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
7
codecs/qoi/dec/qoi_dec.d.ts
vendored
Normal file
7
codecs/qoi/dec/qoi_dec.d.ts
vendored
Normal file
@@ -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
codecs/qoi/dec/qoi_dec.js
generated
Normal file
16
codecs/qoi/dec/qoi_dec.js
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/qoi/dec/qoi_dec.wasm
Normal file
BIN
codecs/qoi/dec/qoi_dec.wasm
Normal file
Binary file not shown.
@@ -6,32 +6,29 @@
|
|||||||
|
|
||||||
using namespace emscripten;
|
using namespace emscripten;
|
||||||
|
|
||||||
struct QoiOptions {
|
struct QoiOptions {};
|
||||||
int quality;
|
|
||||||
bool randombool;
|
|
||||||
};
|
|
||||||
|
|
||||||
thread_local const val Uint8Array = val::global("Uint8Array");
|
thread_local const val Uint8Array = val::global("Uint8Array");
|
||||||
|
|
||||||
val encode(std::string buffer, int width, int height, QoiOptions options) {
|
val encode(std::string buffer, int width, int height, QoiOptions options) {
|
||||||
printf("Starting encode!");
|
int compressedSizeInBytes;
|
||||||
|
qoi_desc desc;
|
||||||
|
desc.width = width;
|
||||||
|
desc.height = height;
|
||||||
|
desc.channels = 4;
|
||||||
|
desc.colorspace = QOI_SRGB;
|
||||||
|
|
||||||
printf("quality = %d\n", options.quality);
|
void* encodedData = qoi_encode(buffer.c_str(), &desc, &compressedSizeInBytes);
|
||||||
printf("randombool = %s\n", options.randombool ? "true" : "false");
|
if (encodedData == NULL)
|
||||||
|
return val::null();
|
||||||
|
|
||||||
auto js_result = val::null();
|
auto js_result =
|
||||||
|
Uint8Array.new_(typed_memory_view(compressedSizeInBytes, (const uint8_t*)encodedData));
|
||||||
const int N = 100;
|
|
||||||
int* data = (int*)malloc(N * sizeof(int));
|
|
||||||
|
|
||||||
js_result = Uint8Array.new_(typed_memory_view(N, data));
|
|
||||||
return js_result;
|
return js_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
EMSCRIPTEN_BINDINGS(my_module) {
|
EMSCRIPTEN_BINDINGS(my_module) {
|
||||||
value_object<QoiOptions>("QoiOptions")
|
value_object<QoiOptions>("QoiOptions");
|
||||||
.field("quality", &QoiOptions::quality)
|
|
||||||
.field("randombool", &QoiOptions::randombool);
|
|
||||||
|
|
||||||
function("encode", &encode);
|
function("encode", &encode);
|
||||||
}
|
}
|
||||||
|
|||||||
5
codecs/qoi/enc/qoi_enc.d.ts
vendored
5
codecs/qoi/enc/qoi_enc.d.ts
vendored
@@ -1,7 +1,4 @@
|
|||||||
export interface EncodeOptions {
|
export interface EncodeOptions {}
|
||||||
quality: number;
|
|
||||||
randombool: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QoiModule extends EmscriptenWasm.Module {
|
export interface QoiModule extends EmscriptenWasm.Module {
|
||||||
encode(
|
encode(
|
||||||
|
|||||||
16
codecs/qoi/enc/qoi_enc.js
generated
Normal file
16
codecs/qoi/enc/qoi_enc.js
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
codecs/qoi/enc/qoi_enc.wasm
Normal file
BIN
codecs/qoi/enc/qoi_enc.wasm
Normal file
Binary file not shown.
@@ -111,6 +111,9 @@ async function decodeImage(
|
|||||||
if (mimeType === 'image/webp2') {
|
if (mimeType === 'image/webp2') {
|
||||||
return await workerBridge.wp2Decode(signal, blob);
|
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.
|
// Otherwise fall through and try built-in decoding for a laugh.
|
||||||
return await builtinDecode(signal, blob);
|
return await builtinDecode(signal, blob);
|
||||||
|
|||||||
20
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
20
src/features/decoders/qoi/worker/qoiDecode.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { 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) {
|
||||||
|
const decoder = await import('codecs/qoi/dec/qoi_dec');
|
||||||
|
emscriptenModule = initEmscriptenModule(decoder.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [module, data] = await Promise.all([
|
||||||
|
emscriptenModule,
|
||||||
|
blobToArrayBuffer(blob),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = module.decode(data);
|
||||||
|
if (!result) throw new Error('Decoding error');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -1,18 +1,6 @@
|
|||||||
import { EncodeOptions } from '../shared/meta';
|
import { EncodeOptions } from '../shared/meta';
|
||||||
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
import type WorkerBridge from 'client/lazy-app/worker-bridge';
|
||||||
import { h, Component } from 'preact';
|
import { h, Component, Fragment } 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(
|
export function encode(
|
||||||
signal: AbortSignal,
|
signal: AbortSignal,
|
||||||
@@ -28,46 +16,8 @@ interface Props {
|
|||||||
onChange(newOptions: EncodeOptions): void;
|
onChange(newOptions: EncodeOptions): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
|
||||||
showAdvanced: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Options extends Component<Props, {}> {
|
export class Options extends Component<Props, {}> {
|
||||||
onChange = (event: Event) => {
|
render() {
|
||||||
const form = (event.currentTarget as HTMLInputElement).closest(
|
return <Fragment></Fragment>;
|
||||||
'form',
|
|
||||||
) as HTMLFormElement;
|
|
||||||
|
|
||||||
const options: EncodeOptions = {
|
|
||||||
quality: inputFieldValueAsNumber(form.quality),
|
|
||||||
randombool: inputFieldChecked(form.randombool),
|
|
||||||
};
|
|
||||||
this.props.onChange(options);
|
|
||||||
};
|
|
||||||
|
|
||||||
render({ options }: Props) {
|
|
||||||
return (
|
|
||||||
<form class={style.optionsSection} onSubmit={preventDefault}>
|
|
||||||
<div class={style.optionOneCell}>
|
|
||||||
<Range
|
|
||||||
name="quality"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
value={options.quality}
|
|
||||||
onInput={this.onChange}
|
|
||||||
>
|
|
||||||
Quality:
|
|
||||||
</Range>
|
|
||||||
</div>
|
|
||||||
<label class={style.optionToggle}>
|
|
||||||
Random Bool
|
|
||||||
<Checkbox
|
|
||||||
name="randombool"
|
|
||||||
checked={options.randombool}
|
|
||||||
onChange={this.onChange}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,4 @@ export { EncodeOptions };
|
|||||||
export const label = 'QOI';
|
export const label = 'QOI';
|
||||||
export const mimeType = 'image/qoi';
|
export const mimeType = 'image/qoi';
|
||||||
export const extension = 'qoi';
|
export const extension = 'qoi';
|
||||||
export const defaultOptions: EncodeOptions = {
|
export const defaultOptions: EncodeOptions = {};
|
||||||
quality: 75,
|
|
||||||
randombool: true,
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ export default async function encode(
|
|||||||
|
|
||||||
const module = await emscriptenModule;
|
const module = await emscriptenModule;
|
||||||
const resultView = module.encode(data.data, data.width, data.height, options);
|
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.
|
// wasm can’t run on SharedArrayBuffers, so we hard-cast to ArrayBuffer.
|
||||||
return resultView.buffer as ArrayBuffer;
|
return resultView.buffer as ArrayBuffer;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user