Expose some options for AVIF

This commit is contained in:
Surma
2020-02-06 09:21:43 -08:00
committed by Ingvar Stepanyan
parent c29006d593
commit ac9a7767d2
8 changed files with 114 additions and 18 deletions

View File

@@ -4,10 +4,41 @@
using namespace emscripten; using namespace emscripten;
struct AvifOptions {
// [0 - 63]
// 0 = lossless
// 63 = worst quality
int minQuantizer;
int maxQuantizer;
// [0 - 6]
// Creates 2^n tiles in that dimension
int tileRowsLog2;
int tileColsLog2;
// [0 - 10]
// 0 = slowest
// 10 = fastest
int speed;
// 0 = 4:2:0
// 1 = 4:2:2
// 2 = 4:4:4
int subsample;
};
avifRWData output = AVIF_DATA_EMPTY; avifRWData output = AVIF_DATA_EMPTY;
val encode(std::string buffer, int width, int height) { val encode(std::string buffer, int width, int height, AvifOptions options) {
int depth = 8; int depth = 8;
avifPixelFormat format = AVIF_PIXEL_FORMAT_YUV420; avifPixelFormat format;
switch (options.subsample) {
case 0:
format = AVIF_PIXEL_FORMAT_YUV420;
break;
case 1:
format = AVIF_PIXEL_FORMAT_YUV422;
break;
case 2:
format = AVIF_PIXEL_FORMAT_YUV444;
break;
}
avifImage *image = avifImageCreate(width, height, depth, format); avifImage *image = avifImageCreate(width, height, depth, format);
uint8_t *rgba = (uint8_t *)buffer.c_str(); uint8_t *rgba = (uint8_t *)buffer.c_str();
@@ -27,15 +58,15 @@ val encode(std::string buffer, int width, int height) {
avifEncoder *encoder = avifEncoderCreate(); avifEncoder *encoder = avifEncoderCreate();
encoder->maxThreads = 1; encoder->maxThreads = 1;
encoder->minQuantizer = AVIF_QUANTIZER_LOSSLESS; encoder->minQuantizer = options.minQuantizer;
encoder->maxQuantizer = AVIF_QUANTIZER_LOSSLESS; encoder->maxQuantizer = options.maxQuantizer;
encoder->tileRowsLog2 = options.tileRowsLog2;
encoder->tileColsLog2 = options.tileColsLog2;
encoder->speed = options.speed;
avifResult encodeResult = avifEncoderWrite(encoder, image, &output); avifResult encodeResult = avifEncoderWrite(encoder, image, &output);
if (encodeResult != AVIF_RESULT_OK) { if (encodeResult != AVIF_RESULT_OK) {
return val::null(); return val::null();
} }
// output contains a valid .avif file's contents
// ... output.data;
// ... output.size;
avifImageDestroy(image); avifImageDestroy(image);
avifEncoderDestroy(encoder); avifEncoderDestroy(encoder);
return val(typed_memory_view(output.size, output.data)); return val(typed_memory_view(output.size, output.data));
@@ -44,6 +75,14 @@ val encode(std::string buffer, int width, int height) {
void free_result() { avifRWDataFree(&output); } void free_result() { avifRWDataFree(&output); }
EMSCRIPTEN_BINDINGS(my_module) { EMSCRIPTEN_BINDINGS(my_module) {
value_object<AvifOptions>("AvifOptions")
.field("minQuantizer", &AvifOptions::minQuantizer)
.field("maxQuantizer", &AvifOptions::maxQuantizer)
.field("tileRowsLog2", &AvifOptions::tileRowsLog2)
.field("tileColsLog2", &AvifOptions::tileColsLog2)
.field("speed", &AvifOptions::speed)
.field("subsample", &AvifOptions::subsample);
function("encode", &encode); function("encode", &encode);
function("free_result", &free_result); function("free_result", &free_result);
} }

View File

@@ -1,7 +1,7 @@
// import { EncodeOptions } from '../../src/codecs/webp/encoder-meta'; import { EncodeOptions } from '../../src/codecs/avif/encoder-meta';
interface AVIFModule extends EmscriptenWasm.Module { interface AVIFModule extends EmscriptenWasm.Module {
encode(data: BufferSource, width: number, height: number): Uint8Array; encode(data: BufferSource, width: number, height: number, options: EncodeOptions): Uint8Array;
free_result(): void; free_result(): void;
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -1,10 +1,23 @@
export interface EncodeOptions { } export interface EncodeOptions {
minQuantizer: number;
maxQuantizer: number;
tileRowsLog2: number;
tileColsLog2: number;
speed: number;
subsample: number;
}
export const type = 'avif'; export const type = 'avif';
export const label = 'AVIF'; export const label = 'AVIF';
export const mimeType = 'image/avif'; export const mimeType = 'image/avif';
export const extension = 'avif'; export const extension = 'avif';
export const defaultOptions: EncodeOptions = { export const defaultOptions: EncodeOptions = {
minQuantizer: 16,
maxQuantizer: 16,
tileColsLog2: 0,
tileRowsLog2: 0,
speed: 10,
subsample: 0,
}; };
export interface EncoderState { type: typeof type; options: EncodeOptions; } export interface EncoderState { type: typeof type; options: EncodeOptions; }

View File

@@ -9,7 +9,7 @@ export async function encode(data: ImageData, options: EncodeOptions): Promise<A
if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl); if (!emscriptenModule) emscriptenModule = initEmscriptenModule(avif_enc, wasmUrl);
const module = await emscriptenModule; const module = await emscriptenModule;
const resultView = module.encode(data.data, data.width, data.height); const resultView = module.encode(data.data, data.width, data.height, options);
const result = new Uint8Array(resultView); const result = new Uint8Array(resultView);
module.free_result(); module.free_result();

View File

@@ -1,12 +1,15 @@
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import { bind } from '../../lib/initial-util'; import { bind } from '../../lib/initial-util';
import { /*inputFieldCheckedAsNumber, inputFieldValueAsNumber,*/ preventDefault } from '../../lib/util'; import {
/*inputFieldCheckedAsNumber,*/ inputFieldValueAsNumber,
preventDefault,
} from '../../lib/util';
import { EncodeOptions } from './encoder-meta'; import { EncodeOptions } from './encoder-meta';
import * as style from '../../components/Options/style.scss'; import * as style from '../../components/Options/style.scss';
// import Checkbox from '../../components/checkbox'; // import Checkbox from '../../components/checkbox';
// import Expander from '../../components/expander'; // import Expander from '../../components/expander';
// import Select from '../../components/select'; // import Select from '../../components/select';
// import Range from '../../components/range'; import Range from '../../components/range';
// import linkState from 'linkstate'; // import linkState from 'linkstate';
interface Props { interface Props {
@@ -14,20 +17,58 @@ interface Props {
onChange(newOptions: EncodeOptions): void; onChange(newOptions: EncodeOptions): void;
} }
interface State{} interface State {}
export default class AVIFEncoderOptions extends Component<Props, State> { export default class AVIFEncoderOptions extends Component<Props, State> {
state: State = { state: State = {};
};
@bind @bind
onChange(event: Event) { onChange(event: Event) {
const form = (event.currentTarget as HTMLInputElement).closest(
'form',
) as HTMLFormElement;
const { options } = this.props;
const newOptions: EncodeOptions = {
// Copy over options the form doesn't currently care about, eg arithmetic
...this.props.options,
minQuantizer: inputFieldValueAsNumber(
form.quantizer,
options.minQuantizer,
),
maxQuantizer: inputFieldValueAsNumber(
form.quantizer,
options.maxQuantizer,
),
speed: inputFieldValueAsNumber(form.speed, options.speed),
};
this.props.onChange(newOptions);
} }
render({ options }: Props) { render({ options }: Props) {
return ( return (
<form class={style.optionsSection} onSubmit={preventDefault}> <form class={style.optionsSection} onSubmit={preventDefault}>
Lol <div class={style.optionOneCell}>
<Range
name="quantizer"
min="0"
max="63"
value={options.minQuantizer}
onInput={this.onChange}
>
Quantizer
</Range>
</div>
<div class={style.optionOneCell}>
<Range
name="speed"
min="0"
max="10"
value={options.speed}
onInput={this.onChange}
>
Speed
</Range>
</div>
</form> </form>
); );
} }

View File

@@ -7,6 +7,7 @@ import OxiPNGEncoderOptions from '../../codecs/oxipng/options';
import MozJpegEncoderOptions from '../../codecs/mozjpeg/options'; import MozJpegEncoderOptions from '../../codecs/mozjpeg/options';
import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options'; import BrowserJPEGEncoderOptions from '../../codecs/browser-jpeg/options';
import WebPEncoderOptions from '../../codecs/webp/options'; import WebPEncoderOptions from '../../codecs/webp/options';
import AvifEncoderOptions from '../../codecs/avif/options';
import BrowserWebPEncoderOptions from '../../codecs/browser-webp/options'; import BrowserWebPEncoderOptions from '../../codecs/browser-webp/options';
import QuantizerOptionsComponent from '../../codecs/imagequant/options'; import QuantizerOptionsComponent from '../../codecs/imagequant/options';
@@ -16,6 +17,7 @@ import * as identity from '../../codecs/identity/encoder-meta';
import * as oxiPNG from '../../codecs/oxipng/encoder-meta'; import * as oxiPNG from '../../codecs/oxipng/encoder-meta';
import * as mozJPEG from '../../codecs/mozjpeg/encoder-meta'; import * as mozJPEG from '../../codecs/mozjpeg/encoder-meta';
import * as webP from '../../codecs/webp/encoder-meta'; import * as webP from '../../codecs/webp/encoder-meta';
import * as avif from '../../codecs/avif/encoder-meta';
import * as browserPNG from '../../codecs/browser-png/encoder-meta'; import * as browserPNG from '../../codecs/browser-png/encoder-meta';
import * as browserJPEG from '../../codecs/browser-jpeg/encoder-meta'; import * as browserJPEG from '../../codecs/browser-jpeg/encoder-meta';
import * as browserWebP from '../../codecs/browser-webp/encoder-meta'; import * as browserWebP from '../../codecs/browser-webp/encoder-meta';
@@ -47,6 +49,7 @@ const encoderOptionsComponentMap: {
[oxiPNG.type]: OxiPNGEncoderOptions, [oxiPNG.type]: OxiPNGEncoderOptions,
[mozJPEG.type]: MozJpegEncoderOptions, [mozJPEG.type]: MozJpegEncoderOptions,
[webP.type]: WebPEncoderOptions, [webP.type]: WebPEncoderOptions,
[avif.type]: AvifEncoderOptions,
[browserPNG.type]: undefined, [browserPNG.type]: undefined,
[browserJPEG.type]: BrowserJPEGEncoderOptions, [browserJPEG.type]: BrowserJPEGEncoderOptions,
[browserWebP.type]: BrowserWebPEncoderOptions, [browserWebP.type]: BrowserWebPEncoderOptions,