Skip to content

Commit

Permalink
Added fx to the MagickImageCollection.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlemstra committed Aug 8, 2024
1 parent 09d9d70 commit db6b8df
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 3 deletions.
59 changes: 56 additions & 3 deletions src/magick-image-collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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<IMagickImage>, IDisposable {
/** @internal */
Expand Down Expand Up @@ -148,6 +150,36 @@ export interface IMagickImageCollection extends Array<IMagickImage>, IDisposable
*/
flatten<TReturnType>(func: AsyncImageCallback<TReturnType>): Promise<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<TReturnType>(expression: string, func: SyncImageCallback<TReturnType>): 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<TReturnType>(expression: string, func: AsyncImageCallback<TReturnType>): Promise<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<TReturnType>(expression: string, channels: Channels, func: SyncImageCallback<TReturnType>): 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<TReturnType>(expression: string, channels: Channels, func: AsyncImageCallback<TReturnType>): Promise<TReturnType>;

/**
* 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.
Expand Down Expand Up @@ -364,6 +396,27 @@ export class MagickImageCollection extends Array<MagickImage> implements IMagick
return this.mergeImages(LayerMethod.Merge, func);
}

fx<TReturnType>(expression: string, func: SyncImageCallback<TReturnType>): TReturnType;
fx<TReturnType>(expression: string, func: AsyncImageCallback<TReturnType>): Promise<TReturnType>;
fx<TReturnType>(expression: string, channels: Channels, func: SyncImageCallback<TReturnType>): TReturnType;
fx<TReturnType>(expression: string, channels: Channels, func: AsyncImageCallback<TReturnType>): Promise<TReturnType>;
fx<TReturnType>(expression: string, channelsOrFunc: Channels | ImageCallback<TReturnType>, func?: ImageCallback<TReturnType>): TReturnType | Promise<TReturnType> {
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<TReturnType>(settings: MontageSettings, func: SyncImageCallback<TReturnType>): TReturnType;
montage<TReturnType>(settings: MontageSettings, func: AsyncImageCallback<TReturnType>): Promise<TReturnType>;
montage<TReturnType>(settings: MontageSettings, func: ImageCallback<TReturnType>): TReturnType | Promise<TReturnType> {
Expand Down Expand Up @@ -510,13 +563,13 @@ export class MagickImageCollection extends Array<MagickImage> implements IMagick
return Object.create(MagickImageCollection.prototype);
}

private createImage<TReturnType>(createImages: (instance: number, exception: Exception) => number, func: ImageCallback<TReturnType>): TReturnType | Promise<TReturnType> {
private createImage<TReturnType>(createImage: (instance: number, exception: Exception) => number, func: ImageCallback<TReturnType>): TReturnType | Promise<TReturnType> {
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);
});
});

Expand Down
64 changes: 64 additions & 0 deletions tests/magick-image-collection/fx.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
});

0 comments on commit db6b8df

Please sign in to comment.