Build webp

This commit is contained in:
Surma
2024-08-07 18:48:23 +01:00
parent a0f9fea679
commit b5bd766a4e
37 changed files with 473 additions and 6253 deletions

View File

@@ -18,6 +18,14 @@ LIBWEBP_FLAGS = -I${WEBP}/include -L${WEBP}/lib -lwebp
$(OUT_JS): $(OUT_JS):
$(LD) \ $(LD) \
-O3 \
-flto \
-std=c++17 \
-s FILESYSTEM=0 \
-s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
-s ALLOW_MEMORY_GROWTH=1 \
-s TEXTDECODER=2 \
-s NODEJS_CATCH_EXIT=0 -s NODEJS_CATCH_REJECTION=0 \
$(LIBWEBP_FLAGS) \ $(LIBWEBP_FLAGS) \
$(LDFLAGS) \ $(LDFLAGS) \
-lembind \ -lembind \
@@ -28,6 +36,14 @@ $(OUT_JS):
%.o: %.cpp %.o: %.cpp
$(CXX) -c \ $(CXX) -c \
-O3 \
-flto \
-std=c++17 \
-s FILESYSTEM=0 \
-s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency \
-s ALLOW_MEMORY_GROWTH=1 \
-s TEXTDECODER=2 \
-s NODEJS_CATCH_EXIT=0 -s NODEJS_CATCH_REJECTION=0 \
$(LIBWEBP_FLAGS) \ $(LIBWEBP_FLAGS) \
$(CXXFLAGS) \ $(CXXFLAGS) \
-o $@ \ -o $@ \

View File

@@ -14,17 +14,27 @@
flake-utils, flake-utils,
webp-src, webp-src,
}: }:
let
optionSets = {
base = {
simd = false;
};
simd = {
simd = true;
};
};
in
flake-utils.lib.eachDefaultSystem ( flake-utils.lib.eachDefaultSystem (
system: system:
let let
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
in packageBuilder =
with pkgs; with pkgs;
rec { name:
packages = rec { { simd }:
default = webp-squoosh; {
webp-squoosh = stdenv.mkDerivation { "webp-squoosh-${name}" = stdenv.mkDerivation {
name = "mozjpeg-squoosh"; name = "webp-squoosh-${name}";
# Only copy files that are actually relevant to avoid unnecessary # Only copy files that are actually relevant to avoid unnecessary
# cache invalidations. # cache invalidations.
src = runCommand "src" { } '' src = runCommand "src" { } ''
@@ -35,9 +45,9 @@
''; '';
nativeBuildInputs = [ nativeBuildInputs = [
emscripten emscripten
webp self.packages.${system}."webp-${name}"
]; ];
WEBP = webp; WEBP = self.packages.${system}."webp-${name}";
dontConfigure = true; dontConfigure = true;
buildPhase = '' buildPhase = ''
export HOME=$TMPDIR export HOME=$TMPDIR
@@ -48,15 +58,11 @@
cp -r enc dec $out cp -r enc dec $out
''; '';
}; };
webp = stdenv.mkDerivation { "webp-${name}" = stdenv.mkDerivation {
name = "webp"; name = "webp-${name}";
src = webp-src; src = webp-src;
nativeBuildInputs = [ nativeBuildInputs = [
# autoconf
# automake
# libtool
emscripten emscripten
# pkg-config
cmake cmake
]; ];
configurePhase = '' configurePhase = ''
@@ -76,6 +82,7 @@
-DWEBP_BUILD_WEBPINFO=0 \ -DWEBP_BUILD_WEBPINFO=0 \
-DWEBP_BUILD_WEBPMUX=0 \ -DWEBP_BUILD_WEBPMUX=0 \
-DWEBP_BUILD_EXTRAS=0 \ -DWEBP_BUILD_EXTRAS=0 \
${if simd then "-DWEBP_ENABLE_SIMD=1" else ""} \
-B $TMPDIR/build \ -B $TMPDIR/build \
. .
''; '';
@@ -90,16 +97,29 @@
''; '';
dontFixup = true; dontFixup = true;
}; };
"install-${name}" = writeShellScriptBin "install.sh" ''
${pkgs.coreutils}/bin/mkdir -p wasm_build/${name}
${pkgs.rsync}/bin/rsync --chmod=u+w -r ${
self.packages.${system}."webp-squoosh-${name}"
}/* wasm_build/${name}
'';
};
forEachOption = pkgs.callPackage (import ../../nix/for-each-option.nix) { };
packageVariants = forEachOption packageBuilder optionSets;
in
with pkgs;
{
packages = packageVariants // {
installScript = writeShellScriptBin "install.sh" '' installScript = writeShellScriptBin "install.sh" ''
${pkgs.coreutils}/bin/rm -rf wasm_build ${self.packages.${system}.install-base}/bin/install.sh
${pkgs.coreutils}/bin/mkdir -p wasm_build ${self.packages.${system}.install-simd}/bin/install.sh
${pkgs.rsync}/bin/rsync --chmod=u+w -r ${webp-squoosh}/* wasm_build/
''; '';
}; };
apps = { apps = {
install = { install = {
type = "app"; type = "app";
program = "${packages.installScript}/bin/install.sh"; program = "${self.packages.${system}.installScript}/bin/install.sh";
}; };
}; };
} }

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
# WebP decoder
- Source: <https://github.com/webmproject/libwebp>
- Version: v1.0.2
## Example
See `example.html`
## API
### `int version()`
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
### `RawImage decode(std::string buffer)`
Decodes the given webp buffer into raw RGBA. `RawImage` is a class with 3 fields: `buffer`, `width`, and `height`.

View File

@@ -0,0 +1,20 @@
<!doctype html>
<script src='webp_dec.js'></script>
<script>
async function loadFile(src) {
const resp = await fetch(src);
return await resp.arrayBuffer();
}
webp_dec().then(async module => {
console.log('Version:', module.version().toString(16));
const image = await loadFile('../../example.webp');
const imageData = module.decode(image);
const canvas = document.createElement('canvas');
canvas.width = imageData.width;
canvas.height = imageData.height;
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
ctx.putImageData(imageData, 0, 0);
});
</script>

View File

@@ -0,0 +1,29 @@
#include <string>
#include "emscripten/bind.h"
#include "emscripten/val.h"
#include "webp/decode.h"
#include "webp/demux.h"
using namespace emscripten;
int version() {
return WebPGetDecoderVersion();
}
thread_local const val Uint8ClampedArray = val::global("Uint8ClampedArray");
thread_local const val ImageData = val::global("ImageData");
val decode(std::string buffer) {
int width, height;
std::unique_ptr<uint8_t[]> rgba(
WebPDecodeRGBA((const uint8_t*)buffer.c_str(), buffer.size(), &width, &height));
return rgba ? ImageData.new_(
Uint8ClampedArray.new_(typed_memory_view(width * height * 4, rgba.get())),
width, height)
: val::null();
}
EMSCRIPTEN_BINDINGS(my_module) {
function("decode", &decode);
function("version", &version);
}

View File

@@ -0,0 +1,7 @@
export interface WebPModule extends EmscriptenWasm.Module {
decode(data: BufferSource): ImageData | null;
}
declare var moduleFactory: EmscriptenWasm.ModuleFactory<WebPModule>;
export default moduleFactory;

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,22 @@
# WebP encoder
- Source: <https://github.com/webmproject/libwebp>
- Version: v1.0.2
## Dependencies
- Docker
## Example
See `example.html`
## API
### `int version()`
Returns the version of libwebp as a number. va.b.c is encoded as 0x0a0b0c
### `UInt8Array encode(uint8_t* image_buffer, int image_width, int image_height, WebPConfig config)`
Encodes the given image with given dimension to WebP.

View File

@@ -0,0 +1,58 @@
<!doctype html>
<script src='webp_enc.js'></script>
<script>
async function loadImage(src) {
// Load image
const img = document.createElement('img');
img.src = src;
await new Promise(resolve => img.onload = resolve);
// Make canvas same size as image
const canvas = document.createElement('canvas');
[canvas.width, canvas.height] = [img.width, img.height];
// Draw image onto canvas
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
return ctx.getImageData(0, 0, img.width, img.height);
}
webp_enc().then(async module => {
console.log('Version:', module.version().toString(16));
const image = await loadImage('../../example.png');
const result = module.encode(image.data, image.width, image.height, {
quality: 75,
target_size: 0,
target_PSNR: 0,
method: 4,
sns_strength: 50,
filter_strength: 60,
filter_sharpness: 0,
filter_type: 1,
partitions: 0,
segments: 4,
pass: 1,
show_compressed: 0,
preprocessing: 0,
autofilter: 0,
partition_limit: 0,
alpha_compression: 1,
alpha_filtering: 1,
alpha_quality: 100,
lossless: 0,
exact: 0,
image_hint: 0,
emulate_jpeg_size: 0,
thread_level: 0,
low_memory: 0,
near_lossless: 100,
use_delta_palette: 0,
use_sharp_yuv: 0,
});
console.log('size', result.length);
const blob = new Blob([result], {type: 'image/webp'});
const blobURL = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = blobURL;
document.body.appendChild(img);
});
</script>

View File

@@ -0,0 +1,85 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <stdlib.h>
#include <string.h>
#include <stdexcept>
#include "webp/encode.h"
using namespace emscripten;
int version() {
return WebPGetEncoderVersion();
}
thread_local const val Uint8Array = val::global("Uint8Array");
val encode(std::string img, int width, int height, WebPConfig config) {
auto img_in = (uint8_t*)img.c_str();
// A lot of this is duplicated from Encode in picture_enc.c
WebPPicture pic;
WebPMemoryWriter wrt;
int ok;
if (!WebPPictureInit(&pic)) {
// shouldn't happen, except if system installation is broken
return val::null();
}
// Allow quality to go higher than 0.
config.qmax = 100;
// Only use use_argb if we really need it, as it's slower.
pic.use_argb = config.lossless || config.use_sharp_yuv || config.preprocessing > 0;
pic.width = width;
pic.height = height;
pic.writer = WebPMemoryWrite;
pic.custom_ptr = &wrt;
WebPMemoryWriterInit(&wrt);
ok = WebPPictureImportRGBA(&pic, img_in, width * 4) && WebPEncode(&config, &pic);
WebPPictureFree(&pic);
val js_result = ok ? Uint8Array.new_(typed_memory_view(wrt.size, wrt.mem)) : val::null();
WebPMemoryWriterClear(&wrt);
return js_result;
}
EMSCRIPTEN_BINDINGS(my_module) {
enum_<WebPImageHint>("WebPImageHint")
.value("WEBP_HINT_DEFAULT", WebPImageHint::WEBP_HINT_DEFAULT)
.value("WEBP_HINT_PICTURE", WebPImageHint::WEBP_HINT_PICTURE)
.value("WEBP_HINT_PHOTO", WebPImageHint::WEBP_HINT_PHOTO)
.value("WEBP_HINT_GRAPH", WebPImageHint::WEBP_HINT_GRAPH);
value_object<WebPConfig>("WebPConfig")
.field("lossless", &WebPConfig::lossless)
.field("quality", &WebPConfig::quality)
.field("method", &WebPConfig::method)
.field("image_hint", &WebPConfig::image_hint)
.field("target_size", &WebPConfig::target_size)
.field("target_PSNR", &WebPConfig::target_PSNR)
.field("segments", &WebPConfig::segments)
.field("sns_strength", &WebPConfig::sns_strength)
.field("filter_strength", &WebPConfig::filter_strength)
.field("filter_sharpness", &WebPConfig::filter_sharpness)
.field("filter_type", &WebPConfig::filter_type)
.field("autofilter", &WebPConfig::autofilter)
.field("alpha_compression", &WebPConfig::alpha_compression)
.field("alpha_filtering", &WebPConfig::alpha_filtering)
.field("alpha_quality", &WebPConfig::alpha_quality)
.field("pass", &WebPConfig::pass)
.field("show_compressed", &WebPConfig::show_compressed)
.field("preprocessing", &WebPConfig::preprocessing)
.field("partitions", &WebPConfig::partitions)
.field("partition_limit", &WebPConfig::partition_limit)
.field("emulate_jpeg_size", &WebPConfig::emulate_jpeg_size)
.field("low_memory", &WebPConfig::low_memory)
.field("near_lossless", &WebPConfig::near_lossless)
.field("exact", &WebPConfig::exact)
.field("use_delta_palette", &WebPConfig::use_delta_palette)
.field("use_sharp_yuv", &WebPConfig::use_sharp_yuv);
function("version", &version);
function("encode", &encode);
}

View File

@@ -0,0 +1,42 @@
export interface EncodeOptions {
quality: number;
target_size: number;
target_PSNR: number;
method: number;
sns_strength: number;
filter_strength: number;
filter_sharpness: number;
filter_type: number;
partitions: number;
segments: number;
pass: number;
show_compressed: number;
preprocessing: number;
autofilter: number;
partition_limit: number;
alpha_compression: number;
alpha_filtering: number;
alpha_quality: number;
lossless: number;
exact: number;
image_hint: number;
emulate_jpeg_size: number;
thread_level: number;
low_memory: number;
near_lossless: number;
use_delta_palette: number;
use_sharp_yuv: number;
}
export interface WebPModule extends EmscriptenWasm.Module {
encode(
data: BufferSource,
width: number,
height: number,
options: EncodeOptions,
): Uint8Array | null;
}
declare var moduleFactory: EmscriptenWasm.ModuleFactory<WebPModule>;
export default moduleFactory;

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1 @@
export { default } from './webp_enc.js';

6
nix/for-each-option.nix Normal file
View File

@@ -0,0 +1,6 @@
{ lib }:
f: opts:
let
derivations = (lib.attrsets.mapAttrsToList f opts);
in
lib.lists.fold (a: b: a // b) { } derivations

View File

@@ -10,14 +10,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import type { WebPModule } from 'codecs/webp/dec/webp_dec'; import type { WebPModule } from 'codecs/webp/wasm_build/base/dec/webp_dec';
import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils'; import { initEmscriptenModule, blobToArrayBuffer } from 'features/worker-utils';
let emscriptenModule: Promise<WebPModule>; let emscriptenModule: Promise<WebPModule>;
export default async function decode(blob: Blob): Promise<ImageData> { export default async function decode(blob: Blob): Promise<ImageData> {
if (!emscriptenModule) { if (!emscriptenModule) {
const decoder = await import('codecs/webp/dec/webp_dec'); const decoder = await import('codecs/webp/wasm_build/base/dec/webp_dec');
emscriptenModule = initEmscriptenModule(decoder.default); emscriptenModule = initEmscriptenModule(decoder.default);
} }

View File

@@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import type { EncodeOptions } from 'codecs/webp/enc/webp_enc'; import type { EncodeOptions } from 'codecs/webp/wasm_build/base/enc/webp_enc';
export { EncodeOptions }; export { EncodeOptions };

View File

@@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import type { WebPModule } from 'codecs/webp/enc/webp_enc'; import type { WebPModule } from 'codecs/webp/wasm_build/base/enc/webp_enc';
import type { EncodeOptions } from '../shared/meta'; import type { EncodeOptions } from '../shared/meta';
import { initEmscriptenModule } from 'features/worker-utils'; import { initEmscriptenModule } from 'features/worker-utils';
@@ -20,10 +20,12 @@ let emscriptenModule: Promise<WebPModule>;
async function init() { async function init() {
if (await simd()) { if (await simd()) {
const webpEncoder = await import('codecs/webp/enc/webp_enc_simd'); const webpEncoder = await import(
'codecs/webp/wasm_build/simd/enc/webp_enc'
);
return initEmscriptenModule(webpEncoder.default); return initEmscriptenModule(webpEncoder.default);
} }
const webpEncoder = await import('codecs/webp/enc/webp_enc'); const webpEncoder = await import('codecs/webp/wasm_build/base/enc/webp_enc');
return initEmscriptenModule(webpEncoder.default); return initEmscriptenModule(webpEncoder.default);
} }

View File

@@ -25,7 +25,7 @@ import * as featuresWorker from 'entry-data:../features-worker';
// Decoders (some are feature detected) // Decoders (some are feature detected)
import * as avifDec from 'entry-data:codecs/avif/dec/avif_dec'; import * as avifDec from 'entry-data:codecs/avif/dec/avif_dec';
import * as webpDec from 'entry-data:codecs/webp/dec/webp_dec'; import * as webpDec from 'entry-data:codecs/webp/wasm_build/base/dec/webp_dec';
// AVIF // AVIF
import * as avifEncMt from 'entry-data:codecs/avif/enc/avif_enc_mt'; import * as avifEncMt from 'entry-data:codecs/avif/enc/avif_enc_mt';
@@ -41,8 +41,8 @@ import * as oxiMt from 'entry-data:codecs/oxipng/pkg-parallel/squoosh_oxipng';
import * as oxi from 'entry-data:codecs/oxipng/pkg/squoosh_oxipng'; import * as oxi from 'entry-data:codecs/oxipng/pkg/squoosh_oxipng';
// WebP // WebP
import * as webpEncSimd from 'entry-data:codecs/webp/enc/webp_enc_simd'; import * as webpEncSimd from 'entry-data:codecs/webp/wasm_build/simd/enc/webp_enc';
import * as webpEnc from 'entry-data:codecs/webp/enc/webp_enc'; import * as webpEnc from 'entry-data:codecs/webp/wasm_build/base/enc/webp_enc';
// WP2 // WP2
import * as wp2EncMtSimd from 'entry-data:codecs/wp2/enc/wp2_enc_mt_simd'; import * as wp2EncMtSimd from 'entry-data:codecs/wp2/enc/wp2_enc_mt_simd';