From db6b8dfa8a94a395a24568cf05d6b5ab51dc5415 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Thu, 8 Aug 2024 23:08:26 +0200 Subject: [PATCH] Added fx to the MagickImageCollection. --- src/magick-image-collection.ts | 59 ++++++++++++++++++++-- tests/magick-image-collection/fx.spec.ts | 64 ++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 tests/magick-image-collection/fx.spec.ts diff --git a/src/magick-image-collection.ts b/src/magick-image-collection.ts index 0d641056..bfeafffb 100644 --- a/src/magick-image-collection.ts +++ b/src/magick-image-collection.ts @@ -5,6 +5,7 @@ import { AsyncImageCallback, AsyncImageCollectionCallback, ImageCallback, ImageCollectionCallback, SyncImageCallback, SyncImageCollectionCallback } from './types/callbacks'; import { ByteArray } from './byte-array'; +import { Channels } from './enums/channels'; import { ColorSpace } from './enums/color-space'; import { ComplexSettings } from './settings/complex-settings'; import { Disposable } from './internal/disposable'; @@ -23,6 +24,7 @@ import { MagickReadSettings } from './settings/magick-read-settings'; import { MagickSettings } from './settings/magick-settings'; import { MontageSettings } from './settings/montage-settings'; import { TemporaryDefines } from './helpers/temporary-defines'; +import { _withString } from './internal/native/string'; export interface IMagickImageCollection extends Array, IDisposable { /** @internal */ @@ -148,6 +150,36 @@ export interface IMagickImageCollection extends Array, IDisposable */ flatten(func: AsyncImageCallback): Promise; + /** + * Apply a mathematical expression to the image or image channels. + * @param expression - The expression to apply. + * @param func - The function to execute with the image. + */ + fx(expression: string, func: SyncImageCallback): TReturnType; + + /** + * Apply a mathematical expression to the image or image channels. + * @param expression - The expression to apply. + * @param func - The function to execute with the image. + */ + fx(expression: string, func: AsyncImageCallback): Promise; + + /** + * Apply a mathematical expression to the image or image channels. + * @param expression - The expression to apply. + * @param channels - The channels to apply the expression to. + * @param func - The function to execute with the image. + */ + fx(expression: string, channels: Channels, func: SyncImageCallback): TReturnType; + + /** + * Apply a mathematical expression to the image or image channels. + * @param expression - The expression to apply. + * @param channels - The channels to apply the expression to. + * @param func - The function to execute with the image. + */ + fx(expression: string, channels: Channels, func: AsyncImageCallback): Promise; + /** * Merge all layers onto a canvas just large enough to hold all the actual images. The virtual * canvas of the first image is preserved but otherwise ignored. @@ -364,6 +396,27 @@ export class MagickImageCollection extends Array implements IMagick return this.mergeImages(LayerMethod.Merge, func); } + fx(expression: string, func: SyncImageCallback): TReturnType; + fx(expression: string, func: AsyncImageCallback): Promise; + fx(expression: string, channels: Channels, func: SyncImageCallback): TReturnType; + fx(expression: string, channels: Channels, func: AsyncImageCallback): Promise; + fx(expression: string, channelsOrFunc: Channels | ImageCallback, func?: ImageCallback): TReturnType | Promise { + this.throwIfEmpty(); + + let channels = Channels.All; + let callback = func; + if (typeof channelsOrFunc === 'number') + channels = channelsOrFunc; + else + callback = channelsOrFunc; + + return _withString(expression, expressionPtr => { + return this.createImage((instance, exception) => { + return ImageMagick._api._MagickImageCollection_Fx(instance, expressionPtr, channels, exception.ptr); + }, callback!); + }); + } + montage(settings: MontageSettings, func: SyncImageCallback): TReturnType; montage(settings: MontageSettings, func: AsyncImageCallback): Promise; montage(settings: MontageSettings, func: ImageCallback): TReturnType | Promise { @@ -510,13 +563,13 @@ export class MagickImageCollection extends Array implements IMagick return Object.create(MagickImageCollection.prototype); } - private createImage(createImages: (instance: number, exception: Exception) => number, func: ImageCallback): TReturnType | Promise { + private createImage(createImage: (instance: number, exception: Exception) => number, func: ImageCallback): TReturnType | Promise { this.throwIfEmpty(); const result = this.attachImages((instance) => { return Exception.use(exception => { - const images = createImages(instance, exception); - return this.checkResult(images, exception); + const image = createImage(instance, exception); + return this.checkResult(image, exception); }); }); diff --git a/tests/magick-image-collection/fx.spec.ts b/tests/magick-image-collection/fx.spec.ts new file mode 100644 index 00000000..4fdcd4a1 --- /dev/null +++ b/tests/magick-image-collection/fx.spec.ts @@ -0,0 +1,64 @@ +/* + Copyright Dirk Lemstra https://github.com/dlemstra/magick-wasm. + Licensed under the Apache License, Version 2.0. +*/ + +import { Channels } from '@src/enums/channels'; +import { MagickColor } from '@src/magick-color'; +import { MagickImage } from '@src/magick-image'; +import { bogusAsyncMethod } from '@test/bogus-async'; +import { TestImages } from '@test/test-images'; + +describe('MagickImageCollection#fx', () => { + it('should throw exception when collection is empty', () => { + TestImages.emptyCollection.use((images) => { + expect(() => { + images.fx('test', () => { /* never reached */ }); + }).toThrowError('operation requires at least one image'); + }); + }); + + it('apply a mathematical expression', () => { + TestImages.emptyCollection.use((images) => { + images.push(MagickImage.create(new MagickColor('#ff0040'), 1, 1)); + images.push(MagickImage.create(new MagickColor('#00ff40'), 1, 1)); + images.fx('(u+v)/2', (image) => { + expect(image).toHavePixelWithColor(0, 0, new MagickColor('#808040ff')); + }); + }); + }); + + it('apply a mathematical expression async', async () => { + await TestImages.emptyCollection.use(async (images) => { + images.push(MagickImage.create(new MagickColor('#ff0040'), 1, 1)); + images.push(MagickImage.create(new MagickColor('#00ff40'), 1, 1)); + await images.fx('(u+v)/2', async (image) => { + expect(image).toHavePixelWithColor(0, 0, new MagickColor('#808040ff')); + + await bogusAsyncMethod(); + }); + }); + }); + + it('apply a mathematical expression to the specified channels', () => { + TestImages.emptyCollection.use((images) => { + images.push(MagickImage.create(new MagickColor('#ff0040'), 1, 1)); + images.push(MagickImage.create(new MagickColor('#00ff40'), 1, 1)); + images.fx('(u+v)/2', Channels.Red | Channels.Blue, (image) => { + expect(image).toHavePixelWithColor(0, 0, new MagickColor('#800040ff')); + }); + }); + }); + + it('apply a mathematical expression to the specified channels async', async () => { + await TestImages.emptyCollection.use(async (images) => { + images.push(MagickImage.create(new MagickColor('#aa00bb'), 1, 1)); + images.push(MagickImage.create(new MagickColor('#00ff40'), 1, 1)); + await images.fx('(u+v)/2', Channels.Green, async (image) => { + expect(image).toHavePixelWithColor(0, 0, new MagickColor('#aa80bbff')); + + await bogusAsyncMethod(); + }); + }); + }); +});