Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## v0.8.1

- Support WASM AVIF encoding and decoding (AV1 only, with libavif+aom).

## v0.8.0

- Bump the version of the libwebp2 dependency.
Expand Down
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ cmake_minimum_required(VERSION 3.20)
project(
codec-compare-gen
LANGUAGES CXX
VERSION 0.8.0)
VERSION 0.8.1)
set(CMAKE_CXX_STANDARD 17)

# Options
Expand Down Expand Up @@ -424,7 +424,8 @@ if(CCGEN_WASM)
set_target_properties(codec_wasm PROPERTIES SUFFIX ".js")
target_link_options(
codec_wasm PRIVATE "-sMODULARIZE=1" "-sEXPORT_NAME=loadCodecWasm"
"-sENVIRONMENT=web,worker" "-sALLOW_MEMORY_GROWTH=1" "--bind")
"-sENVIRONMENT=web,worker" "-sALLOW_MEMORY_GROWTH=1" "-sSTACK_SIZE=5MB"
"--bind")
# Making the following work would be nicer for TypeScript integration:
# "--emit-tsd=${CMAKE_CURRENT_BINARY_DIR}/codec_wasm_bin.d.ts"
endif()
Expand Down
34 changes: 28 additions & 6 deletions cmake/Modules/LocalLibavif.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,27 @@ set(AVIF_BUILD_TESTS
set(AVIF_CODEC_AOM
LOCAL
CACHE INTERNAL "")
set(AVIF_CODEC_DAV1D
LOCAL
CACHE INTERNAL "")
set(AVIF_CODEC_AVM
LOCAL
CACHE INTERNAL "")
if(CCGEN_WASM)
set(AVIF_CODEC_DAV1D
OFF
CACHE INTERNAL "")
set(AVIF_CODEC_AVM
OFF
CACHE INTERNAL "")
set(AOM_TARGET_CPU
"generic"
CACHE INTERNAL "")
set(AOM_LIB_LINK_TYPE
"PRIVATE"
CACHE INTERNAL "")
else()
set(AVIF_CODEC_DAV1D
LOCAL
CACHE INTERNAL "")
set(AVIF_CODEC_AVM
LOCAL
CACHE INTERNAL "")
endif()
set(AVIF_LIBYUV
LOCAL
CACHE INTERNAL "")
Expand All @@ -39,8 +54,15 @@ set(AVIF_ENABLE_EXPERIMENTAL_MINI

include_directories("${CMAKE_BINARY_DIR}/flatbuffers/include")

set(CMAKE_CXX_FLAGS_ORIG "${CMAKE_CXX_FLAGS}")
set(CMAKE_C_FLAGS_ORIG "${CMAKE_C_FLAGS}")

ccgen_fetchcontent_makeavailable(libavif)

# libaom modifies CMAKE_CXX_FLAGS/CMAKE_C_FLAGS with CACHE FORCE. Restore them.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_ORIG}" CACHE STRING "" FORCE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_ORIG}" CACHE STRING "" FORCE)

if(CCGEN_ENABLE_WEBP)
# To avoid building libsharpyuv twice.
add_dependencies(avif webp)
Expand Down
2 changes: 1 addition & 1 deletion src/result_json.cc
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ Status TasksToJson(const std::string& batch_pretty_name, CodecSettings settings,
/*prefix=*/encoded_common_parent.parent_path(), encoded_common_parent));

const std::string build_cmd =
"git clone -b v0.8.0 --depth 1"
"git clone -b v0.8.1 --depth 1"
" https://github.com/webmproject/codec-compare-gen.git ccgen"
" && cmake -S ccgen -B ccgen/build" +
std::string(GetCodecMetadata(settings.codec).build_options) +
Expand Down
2 changes: 1 addition & 1 deletion wasm/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ NUM_THREADS=$(($(nproc) - 1))
emcmake cmake \
-S "${SOURCE_DIR}" \
-B "${BUILD_DIR}" \
-DCCGEN_ENABLE_AVIF=OFF \
-DCCGEN_ENABLE_AVIF=ON \
-DCCGEN_ENABLE_AVIF_LIBHEIF=OFF \
-DCCGEN_ENABLE_JPEG=ON \
-DCCGEN_ENABLE_JPEGLI=OFF \
Expand Down
18 changes: 14 additions & 4 deletions wasm/codec_wasm.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <vector>

#include "src/base.h"
Expand All @@ -26,7 +27,7 @@

namespace codec_compare_gen {
struct EncodedBytesAndDecodeStats {
std::vector<uint8_t> encoded_bytes;
emscripten::val encoded_bytes;
std::string encoded_bytes_mime_type;

// Same fields as TaskOutput.
Expand All @@ -45,8 +46,10 @@ struct EncodedBytesAndDecodeStats {
};

EncodedBytesAndDecodeStats WasmGetEncodedBytesAndDecodeStats(
const std::vector<uint8_t>& bytes, Codec codec, Subsampling subsampling,
const emscripten::val& bytes_val, Codec codec, Subsampling subsampling,
int effort, int quality) {
const std::vector<uint8_t> bytes =
emscripten::convertJSArrayToNumberVector<uint8_t>(bytes_val);
auto status_or = GetEncodedBytesAndDecodeStats(
bytes.data(), bytes.size(), codec, subsampling, effort, quality,
/*quiet=*/false);
Expand All @@ -69,7 +72,14 @@ EncodedBytesAndDecodeStats WasmGetEncodedBytesAndDecodeStats(
description += " q";
description += std::to_string(codec_settings.quality);
}
return {std::move(encoded_bytes),

emscripten::val js_encoded_bytes =
emscripten::val::global("Uint8Array").new_(encoded_bytes.size());
js_encoded_bytes.call<void>(
"set", emscripten::val(emscripten::typed_memory_view(
encoded_bytes.size(), encoded_bytes.data())));

return {std::move(js_encoded_bytes),
encoded_bytes_mime_type,
task.image_width,
task.image_height,
Expand All @@ -90,6 +100,7 @@ EMSCRIPTEN_BINDINGS(codec_compare_gen) {
using namespace codec_compare_gen;

emscripten::enum_<Codec>("Codec")
.value("Avif", Codec::kAvif)
.value("Webp", Codec::kWebp)
.value("Webp2", Codec::kWebp2)
.value("Jpegturbo", Codec::kJpegturbo)
Expand All @@ -101,7 +112,6 @@ EMSCRIPTEN_BINDINGS(codec_compare_gen) {
.value("Yuv444", Subsampling::k444)
.value("Yuv420", Subsampling::k420);

emscripten::register_vector<uint8_t>("ByteVector");
emscripten::value_object<EncodedBytesAndDecodeStats>(
"EncodedBytesAndDecodeStats")
.field("encoded_bytes", &EncodedBytesAndDecodeStats::encoded_bytes)
Expand Down
44 changes: 18 additions & 26 deletions wasm/tests/test_wasm_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@ async function fetchBytes(url: string): Promise<Uint8Array> {
return new Uint8Array(await response.arrayBuffer());
}

function toByteVector(module: any, bytes: Uint8Array): any {
const v = new module.ByteVector();
// Using a loop for simplicity, can be optimized later
for (let i = 0; i < bytes.length; i++) {
v.push_back(bytes[i]);
}
return v;
}

// Loading codec_wasm_bin.wasm can take a long time, especially if sanitizers
// were enabled during the build. See b/514217988.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
Expand Down Expand Up @@ -73,14 +64,13 @@ describe('Codec WASM', () => {
});

it('should encode and decode gradient32x32.png with WebP', async () => {
const pngVector = toByteVector(module, pngBytes);
const result = module.getEncodedBytesAndDecodeStats(
pngVector, module.Codec.Webp, module.Subsampling.Default, /*effort=*/ 0,
pngBytes, module.Codec.Webp, module.Subsampling.Default, /*effort=*/ 0,
/*quality=*/ -1);
expect(result.width).toBe(32);
expect(result.height).toBe(32);
expect(result.encoded_bytes.size()).toBeGreaterThan(0);
expect(result.encoded_size).toBe(result.encoded_bytes.size());
expect(result.encoded_bytes.length).toBeGreaterThan(0);
expect(result.encoded_size).toBe(result.encoded_bytes.length);
expect(result.psnr).toBe(99);

const resultAgain = module.getEncodedBytesAndDecodeStats(
Expand All @@ -89,10 +79,18 @@ describe('Codec WASM', () => {
expect(resultAgain.width).toBe(32);
expect(resultAgain.height).toBe(32);
expect(resultAgain.psnr).toBe(99);
});

pngVector.delete();
result.encoded_bytes.delete();
resultAgain.encoded_bytes.delete();
it('should encode and decode gradient32x32.png with AVIF', async () => {
const result = module.getEncodedBytesAndDecodeStats(
pngBytes, module.Codec.Avif, module.Subsampling.Default, /*effort=*/ 0,
/*quality=*/ 90);
expect(result.width).toBe(32);
expect(result.height).toBe(32);
expect(result.encoded_bytes.length).toBeGreaterThan(0);
expect(result.encoded_size).toBe(result.encoded_bytes.length);
expect(result.psnr).toBeGreaterThan(20);
expect(result.psnr).toBeLessThan(99);
});

it('should encode and decode with JPEG libraries', async () => {
Expand All @@ -101,14 +99,13 @@ describe('Codec WASM', () => {
module.Codec.Jpegsimple,
module.Codec.Jpegmoz,
]) {
const pngVector = toByteVector(module, pngBytes);
const result = module.getEncodedBytesAndDecodeStats(
pngVector, codec, module.Subsampling.Default, /*effort=*/ 0,
pngBytes, codec, module.Subsampling.Default, /*effort=*/ 0,
/*quality=*/ 90);
expect(result.width).toBe(32);
expect(result.height).toBe(32);
expect(result.encoded_bytes.size()).toBeGreaterThan(0);
expect(result.encoded_size).toBe(result.encoded_bytes.size());
expect(result.encoded_bytes.length).toBeGreaterThan(0);
expect(result.encoded_size).toBe(result.encoded_bytes.length);
console.log(`${codec} PSNR:`, result.psnr);
expect(result.psnr).toBeGreaterThan(20);
expect(result.psnr).toBeLessThan(99);
Expand All @@ -118,20 +115,15 @@ describe('Codec WASM', () => {
/*effort=*/ 0, /*quality=*/ 90);
expect(resultAgain.width).toBe(32);
expect(resultAgain.height).toBe(32);

pngVector.delete();
result.encoded_bytes.delete();
resultAgain.encoded_bytes.delete();
}
});

it('should throw an error on invalid bytes', () => {
const invalidBytes = toByteVector(module, new Uint8Array([1, 2, 3]));
const invalidBytes = new Uint8Array([1, 2, 3]);
expect(() => {
module.getEncodedBytesAndDecodeStats(
invalidBytes, module.Codec.Webp, module.Subsampling.Default,
/*effort=*/ 0, /*quality=*/ 90);
}).toThrowError(/Failed to decode image/);
invalidBytes.delete();
});
});