diff --git a/CHANGELOG.md b/CHANGELOG.md index 378e9e588..ac9c25f09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [v0.0.10] - TBD -Uses libvips v8.15.2, compiled with Emscripten v3.1.61. +Uses libvips v8.16.0, compiled with Emscripten v3.1.61. + +### Changed + +- Update methods/enums for libvips 8.16. ## [v0.0.9] - 2024-06-01 diff --git a/build.sh b/build.sh index a7e4c8465..ffcc27564 100755 --- a/build.sh +++ b/build.sh @@ -180,7 +180,7 @@ VERSION_TIFF=4.6.0 # https://gitlab.com/libtiff/libtiff VERSION_RESVG=0.41.0 # https://github.com/RazrFalcon/resvg VERSION_AOM=3.9.0 # https://aomedia.googlesource.com/aom VERSION_HEIF=1.17.6 # https://github.com/strukturag/libheif -VERSION_VIPS=8.15.2 # https://github.com/libvips/libvips +VERSION_VIPS=ec432a5 # https://github.com/libvips/libvips VERSION_EMSCRIPTEN="$(emcc -dumpversion)" @@ -481,12 +481,12 @@ node --version [ -f "$TARGET/lib/pkgconfig/vips.pc" ] || ( stage "Compiling vips" mkdir $DEPS/vips - curl -Ls https://github.com/libvips/libvips/releases/download/v$VERSION_VIPS/vips-$(without_prerelease $VERSION_VIPS).tar.xz | tar xJC $DEPS/vips --strip-components=1 + curl -Ls https://github.com/libvips/libvips/archive/$VERSION_VIPS.tar.gz | tar xzC $DEPS/vips --strip-components=1 cd $DEPS/vips # Emscripten specific patches - curl -Ls https://github.com/libvips/libvips/compare/v$VERSION_VIPS...kleisauke:wasm-vips-8.15.patch | patch -p1 + curl -Ls https://github.com/libvips/libvips/compare/$VERSION_VIPS...kleisauke:wasm-vips-8.16.patch | patch -p1 # Disable HBR support in heifsave - curl -Ls https://github.com/kleisauke/libvips/commit/ad921cf9396dc5a224e93c71b601e87bd3a8a521.patch | patch -p1 + curl -Ls https://github.com/kleisauke/libvips/commit/88b4db95ec6c13860638a0a94e7ef44ea7ce0c19.patch | patch -p1 # Disable building man pages, gettext po files, tools, and (fuzz-)tests sed -i "/subdir('man')/{N;N;N;N;d;}" meson.build meson setup _build --prefix=$TARGET --cross-file=$MESON_CROSS --default-library=static --buildtype=release \ diff --git a/lib/vips.d.ts b/lib/vips.d.ts index 489ba2ab6..6fb092272 100644 --- a/lib/vips.d.ts +++ b/lib/vips.d.ts @@ -3703,6 +3703,14 @@ declare module Vips { * @return Output image. */ static jxlload(filename: string, options?: { + /** + * First page to load. + */ + page?: number + /** + * Number of pages to load, -1 for all. + */ + n?: number /** * Force open via memory. */ @@ -3732,6 +3740,14 @@ declare module Vips { * @return Output image. */ static jxlloadBuffer(buffer: Blob, options?: { + /** + * First page to load. + */ + page?: number + /** + * Number of pages to load, -1 for all. + */ + n?: number /** * Force open via memory. */ @@ -3761,6 +3777,14 @@ declare module Vips { * @return Output image. */ static jxlloadSource(source: Source, options?: { + /** + * First page to load. + */ + page?: number + /** + * Number of pages to load, -1 for all. + */ + n?: number /** * Force open via memory. */ @@ -5862,6 +5886,12 @@ declare module Vips { */ add(right: Image | ArrayConstant): Image; + /** + * Append an alpha channel. + * @return Output image. + */ + addalpha(): Image; + /** * Affine transform of an image. * @param matrix Transformation matrix. @@ -7574,7 +7604,7 @@ declare module Vips { /** * Save image in jpeg2000 format. - * @param filename Filename to load from. + * @param filename Filename to save to. * @param options Optional options. */ jp2ksave(filename: string, options?: { @@ -7945,7 +7975,7 @@ declare module Vips { /** * Save image in jpeg-xl format. - * @param filename Filename to load from. + * @param filename Filename to save to. * @param options Optional options. */ jxlsave(filename: string, options?: { @@ -9022,11 +9052,35 @@ declare module Vips { }): void; /** - * Write raw image to file descriptor. - * @param fd File descriptor to write to. + * Write raw image to buffer. * @param options Optional options. + * @return Buffer to save to. */ - rawsaveFd(fd: number, options?: { + rawsaveBuffer(options?: { + /** + * Which metadata to retain. + */ + keep?: ForeignKeep | Flag + /** + * Background value. + */ + background?: ArrayConstant + /** + * Set page height for multipage save. + */ + page_height?: number + /** + * Filename of icc profile to embed. + */ + profile?: string + }): Uint8Array; + + /** + * Write raw image to target. + * @param target Target to save to. + * @param options Optional options. + */ + rawsaveTarget(target: Target, options?: { /** * Which metadata to retain. */ @@ -10010,10 +10064,18 @@ declare module Vips { * Level of cpu effort to reduce file size. */ effort?: number + /** + * Desired target size in bytes. + */ + target_size?: number /** * Allow mixed encoding (might reduce file size). */ mixed?: boolean + /** + * Number of entropy-analysis passes (in [1..10]). + */ + passes?: number /** * Which metadata to retain. */ @@ -10078,10 +10140,18 @@ declare module Vips { * Level of cpu effort to reduce file size. */ effort?: number + /** + * Desired target size in bytes. + */ + target_size?: number /** * Allow mixed encoding (might reduce file size). */ mixed?: boolean + /** + * Number of entropy-analysis passes (in [1..10]). + */ + passes?: number /** * Which metadata to retain. */ @@ -10145,10 +10215,18 @@ declare module Vips { * Level of cpu effort to reduce file size. */ effort?: number + /** + * Desired target size in bytes. + */ + target_size?: number /** * Allow mixed encoding (might reduce file size). */ mixed?: boolean + /** + * Number of entropy-analysis passes (in [1..10]). + */ + passes?: number /** * Which metadata to retain. */ @@ -10213,10 +10291,18 @@ declare module Vips { * Level of cpu effort to reduce file size. */ effort?: number + /** + * Desired target size in bytes. + */ + target_size?: number /** * Allow mixed encoding (might reduce file size). */ mixed?: boolean + /** + * Number of entropy-analysis passes (in [1..10]). + */ + passes?: number /** * Which metadata to retain. */ diff --git a/src/bindings/vips-operators.cpp b/src/bindings/vips-operators.cpp index 677fd369d..77f09263a 100644 --- a/src/bindings/vips-operators.cpp +++ b/src/bindings/vips-operators.cpp @@ -1465,6 +1465,18 @@ Image Image::abs() const return out; } +Image Image::addalpha() const +{ + Image out; + + this->call("addalpha", + (new Option) + ->set("in", *this) + ->set("out", &out)); + + return out; +} + Image Image::affine(const std::vector &matrix, emscripten::val js_options) const { Image out; @@ -3310,12 +3322,30 @@ void Image::rawsave(const std::string &filename, emscripten::val js_options) con js_options); } -void Image::rawsave_fd(int fd, emscripten::val js_options) const +emscripten::val Image::rawsave_buffer(emscripten::val js_options) const { - this->call("rawsave_fd", + VipsBlob *buffer; + + this->call("rawsave_buffer", (new Option) ->set("in", *this) - ->set("fd", fd), + ->set("buffer", &buffer), + js_options); + + emscripten::val result = BlobVal.new_(emscripten::typed_memory_view( + VIPS_AREA(buffer)->length, + reinterpret_cast(VIPS_AREA(buffer)->data))); + vips_area_unref(VIPS_AREA(buffer)); + + return result; +} + +void Image::rawsave_target(const Target &target, emscripten::val js_options) const +{ + this->call("rawsave_target", + (new Option) + ->set("in", *this) + ->set("target", target), js_options); } diff --git a/src/bindings/vips-operators.h b/src/bindings/vips-operators.h index a5837bb92..9dd6d4465 100644 --- a/src/bindings/vips-operators.h +++ b/src/bindings/vips-operators.h @@ -878,6 +878,12 @@ Image Yxy2XYZ() const; */ Image abs() const; +/** + * Append an alpha channel. + * @return Output image. + */ +Image addalpha() const; + /** * Affine transform of an image. * @param matrix Transformation matrix. @@ -1570,7 +1576,7 @@ Image join(emscripten::val in2, emscripten::val direction, emscripten::val js_op /** * Save image in jpeg2000 format. - * @param filename Filename to load from. + * @param filename Filename to save to. * @param js_options Optional options. */ void jp2ksave(const std::string &filename, emscripten::val js_options = emscripten::val::null()) const; @@ -1618,7 +1624,7 @@ void jpegsave_target(const Target &target, emscripten::val js_options = emscript /** * Save image in jpeg-xl format. - * @param filename Filename to load from. + * @param filename Filename to save to. * @param js_options Optional options. */ void jxlsave(const std::string &filename, emscripten::val js_options = emscripten::val::null()) const; @@ -1961,11 +1967,18 @@ Image rank(int width, int height, int index) const; void rawsave(const std::string &filename, emscripten::val js_options = emscripten::val::null()) const; /** - * Write raw image to file descriptor. - * @param fd File descriptor to write to. + * Write raw image to buffer. + * @param js_options Optional options. + * @return Buffer to save to. + */ +emscripten::val rawsave_buffer(emscripten::val js_options = emscripten::val::null()) const; + +/** + * Write raw image to target. + * @param target Target to save to. * @param js_options Optional options. */ -void rawsave_fd(int fd, emscripten::val js_options = emscripten::val::null()) const; +void rawsave_target(const Target &target, emscripten::val js_options = emscripten::val::null()) const; /** * Linear recombination with matrix. diff --git a/src/vips-emscripten.cpp b/src/vips-emscripten.cpp index 980a22c89..e7a27e944 100644 --- a/src/vips-emscripten.cpp +++ b/src/vips-emscripten.cpp @@ -1474,6 +1474,7 @@ EMSCRIPTEN_BINDINGS(my_module) { .function("XYZ2scRGB", &Image::XYZ2scRGB) .function("Yxy2XYZ", &Image::Yxy2XYZ) .function("abs", &Image::abs) + .function("addalpha", &Image::addalpha) .function("affine", &Image::affine) .function("affine", optional_override([](const Image &image, const std::vector &matrix) { return image.affine(matrix); @@ -1885,9 +1886,13 @@ EMSCRIPTEN_BINDINGS(my_module) { .function("rawsave", optional_override([](const Image &image, const std::string &filename) { image.rawsave(filename); })) - .function("rawsaveFd", &Image::rawsave_fd) - .function("rawsaveFd", optional_override([](const Image &image, int fd) { - image.rawsave_fd(fd); + .function("rawsaveBuffer", &Image::rawsave_buffer) + .function("rawsaveBuffer", optional_override([](const Image &image) { + return image.rawsave_buffer(); + })) + .function("rawsaveTarget", &Image::rawsave_target) + .function("rawsaveTarget", optional_override([](const Image &image, const Target &target) { + image.rawsave_target(target); })) .function("recomb", &Image::recomb) .function("reduce", &Image::reduce) diff --git a/test/unit/test_conversion.js b/test/unit/test_conversion.js index 9aed76741..ef25b3b8a 100644 --- a/test/unit/test_conversion.js +++ b/test/unit/test_conversion.js @@ -117,6 +117,12 @@ describe('conversion', () => { expect(x.extractBand(4).avg()).to.equal(2); }); + it('addalpha', function () { + const x = colour.addalpha(); + expect(x.bands).to.equal(4); + expect(x.extractBand(3).avg()).to.equal(255); + }); + it('bandmean', function () { const bandmean = (x) => x instanceof vips.Image ? x.bandmean() diff --git a/test/unit/test_foreign.js b/test/unit/test_foreign.js index bd4dd43c3..4d54ddd45 100644 --- a/test/unit/test_foreign.js +++ b/test/unit/test_foreign.js @@ -148,7 +148,7 @@ describe('foreign', () => { it('vips', function () { // ftruncate() is not yet available in the Node backend of WasmFS. - // https://github.com/emscripten-core/emscripten/blob/3.1.48/system/lib/wasmfs/backends/node_backend.cpp#L120-L122 + // https://github.com/emscripten-core/emscripten/blob/3.1.61/system/lib/wasmfs/backends/node_backend.cpp#L120-L122 if (typeof vips.FS.statBufToObject === 'function') { return this.skip(); } @@ -400,6 +400,7 @@ describe('foreign', () => { expect(im.height).to.equal(442); expect(im.bands).to.equal(3); expect(im.getInt('bits-per-sample')).to.equal(16); + expect(im.getTypeof('palette')).to.equal(0); }; fileLoader('pngload', Helpers.pngFile, pngValid); @@ -752,6 +753,12 @@ describe('foreign', () => { expect(im.width).to.equal(13); expect(im.height).to.equal(16731); buf = im.webpsaveBuffer(); // eslint-disable-line no-unused-vars + + // target_size should reasonably work, +/- 2% is fine + im = vips.Image.newFromFile(Helpers.webpFile); + buf = im.webpsaveBuffer({ target_size: 20000, keep: vips.ForeignKeep.none }); + expect(buf.byteLength).to.be.below(20400); + expect(buf.byteLength).to.be.above(19600); }); it('gifload', function () { @@ -766,7 +773,6 @@ describe('foreign', () => { expect(im.width).to.equal(159); expect(im.height).to.equal(203); expect(im.bands).to.equal(3); - expect(im.getInt('bits-per-sample')).to.equal(4); }; fileLoader('gifload', Helpers.gifFile, gifValid); @@ -777,6 +783,8 @@ describe('foreign', () => { expect(x1.getInt('n-pages')).to.equal(1); expect(x1.getArrayDouble('background')).to.deep.equal([81.0, 81.0, 81.0]); expect(x1.getInt('interlaced')).to.equal(1); + expect(x1.getInt('bits-per-sample')).to.equal(4); + expect(x1.getInt('palette')).to.equal(1); x1 = vips.Image.newFromFile(Helpers.gifAnimFile, { n: -1 }); // our test gif has delay 0 for the first frame set in error @@ -784,6 +792,7 @@ describe('foreign', () => { expect(x1.getInt('loop')).to.equal(32761); expect(x1.getArrayDouble('background')).to.deep.equal([255.0, 255.0, 255.0]); expect(x1.getTypeof('interlaced')).to.equal(0); + expect(x1.getInt('palette')).to.equal(1); // test deprecated fields too expect(x1.getInt('gif-loop')).to.equal(32760); expect(x1.getInt('gif-delay')).to.equal(0); @@ -883,22 +892,25 @@ describe('foreign', () => { return this.skip(); } - saveLoad('%s.ppm', mono); saveLoad('%s.ppm', colour); - saveLoadFile('%s.ppm', '[ascii]', mono, 0); + saveLoadFile('%s.pgm', '[ascii]', mono, 0); saveLoadFile('%s.ppm', '[ascii]', colour, 0); - saveLoadFile('%s.ppm', '[ascii,bitdepth=1]', onebit, 0); + saveLoadFile('%s.pbm', '[ascii]', onebit, 0); const rgb16 = colour.colourspace('rgb16'); const grey16 = mono.colourspace('rgb16'); - saveLoad('%s.ppm', grey16); saveLoad('%s.ppm', rgb16); saveLoadFile('%s.ppm', '[ascii]', grey16, 0); saveLoadFile('%s.ppm', '[ascii]', rgb16, 0); + + const source = vips.Source.newFromMemory('P1\n#\n#\n1 1\n0\n'); + const im = vips.Image.ppmloadSource(source); + expect(im.width).to.equal(1); + expect(im.height).to.equal(1); }); it('radload', function () { @@ -964,6 +976,18 @@ describe('foreign', () => { im = vips.Image.newFromBuffer(svg, ''); expect(im.width).to.equal(1); expect(im.height).to.equal(1); + + // scale up + svg = ''; + im = vips.Image.newFromBuffer(svg, '', { scale: 10000 }); + expect(im.width).to.equal(10000); + expect(im.height).to.equal(10000); + + // scale down + svg = ''; + im = vips.Image.newFromBuffer(svg, '', { scale: 0.0001 }); + expect(im.width).to.equal(10); + expect(im.height).to.equal(10); }); it('heifload', function () { @@ -1002,10 +1026,8 @@ describe('foreign', () => { return this.skip(); } - // TODO(kleisauke): Remove `subsample_mode: 'off'` when libvips >= 8.16, see: - // https://github.com/libvips/libvips/commit/dbd298cc8c9789dfc0fc6917b2492cb570406a7a saveLoadBuffer('heifsave_buffer', 'heifload_buffer', - colour, 0, { compression: 'av1', lossless: true, subsample_mode: 'off' }); + colour, 0, { compression: 'av1', lossless: true }); saveLoad('%s.avif', colour); }); diff --git a/test/unit/test_iofuncs.js b/test/unit/test_iofuncs.js index 1f2cb5458..b4e2fec49 100644 --- a/test/unit/test_iofuncs.js +++ b/test/unit/test_iofuncs.js @@ -65,7 +65,7 @@ describe('iofuncs', () => { it('revalidate', function () { // ftruncate() is not yet available in the Node backend of WasmFS. - // https://github.com/emscripten-core/emscripten/blob/3.1.48/system/lib/wasmfs/backends/node_backend.cpp#L120-L122 + // https://github.com/emscripten-core/emscripten/blob/3.1.61/system/lib/wasmfs/backends/node_backend.cpp#L120-L122 if (typeof vips.FS.statBufToObject === 'function') { return this.skip(); } diff --git a/test/unit/test_resample.js b/test/unit/test_resample.js index 2cda8f5ba..2ab579a06 100644 --- a/test/unit/test_resample.js +++ b/test/unit/test_resample.js @@ -268,6 +268,13 @@ describe('resample', () => { im2 = vips.Image.newFromFile(Helpers.rgbaCorrectFile); expect(Math.abs(im1.flatten({ background: 255 }).avg() - im2.avg())).to.be.below(1); } + + // thumbnailing a 16-bit image should always make an 8-bit image + const rgb16Buffer = vips.Image.newFromFile(Helpers.jpegFile) + .colourspace(vips.Interpretation.rgb16) + .writeToBuffer('.png'); + const thumb = vips.Image.thumbnailBuffer(rgb16Buffer, 128); + expect(thumb.format).to.equal('uchar'); }); describe('similarity', () => {