Skip to content

Commit

Permalink
Added extra overload to the compare method to get access to the compa…
Browse files Browse the repository at this point in the history
…re image.
  • Loading branch information
dlemstra committed Aug 1, 2024
1 parent ef8de3e commit 1b68ec5
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export * from './settings/quantize-settings';
export * from './statistics/channel-statistics';
export * from './statistics/statistics';
export * from './types/chromaticity-info';
export * from './types/compare-result';
export * from './types/connected-component';
export * from './types/density';
export * from './types/magick-geometry';
Expand Down
27 changes: 27 additions & 0 deletions src/internal/pointer/double-pointer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/magick-wasm.
// Licensed under the Apache License, Version 2.0.

import { ImageMagick } from '../../image-magick';

/** @internal */
export class DoublePointer {
private readonly instance: number;

private constructor() {
this.instance = ImageMagick._api._malloc(8);
ImageMagick._api.setValue(this.instance, 0, 'double');
}

get ptr(): number { return this.instance; }

get value(): number { return ImageMagick._api.getValue(this.instance, 'double'); }

static use<TReturnType>(func: (pointer: DoublePointer) => TReturnType): TReturnType {
const pointer = new DoublePointer();
try {
return func(pointer);
} finally {
ImageMagick._api._free(pointer.instance);
}
}
}
68 changes: 64 additions & 4 deletions src/magick-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ChromaticityInfo } from './types/chromaticity-info';
import { ClassType } from './enums/class-type';
import { ColorSpace } from './enums/color-space';
import { ColorType } from './enums/color-type';
import { CompareResult } from './types/compare-result';
import { CompositeOperator } from './enums/composite-operator';
import { CompressionMethod } from './enums/compression-method';
import { ConnectedComponent } from './types/connected-component';
Expand All @@ -20,6 +21,7 @@ import { DisposableArray } from './internal/disposable-array';
import { DistortMethod } from './enums/distort-method';
import { DistortSettings } from './settings/distort-settings';
import { DrawingWand } from './drawing/drawing-wand';
import { DoublePointer } from './internal/pointer/double-pointer';
import { Endian } from './enums/endian';
import { ErrorMetric } from './enums/error-metric';
import { EvaluateOperator } from './enums/evaluate-operator';
Expand Down Expand Up @@ -503,6 +505,20 @@ export interface IMagickImage extends IDisposable {
*/
compare(image: IMagickImage, metric: ErrorMetric): number;

/**
* Returns the distortion based on the specified metric.
* @param image - The other image to compare with this image.
* @param metric - The metric to use.
*/
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, func: (compareResult: CompareResult) => TReturnType): TReturnType;

/**
* Returns the distortion based on the specified metric.
* @param image - The other image to compare with this image.
* @param metric - The metric to use.
*/
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, func: (compareResult: CompareResult) => Promise<TReturnType>): Promise<TReturnType>;

/**
* Returns the distortion based on the specified metric.
* @param image - The other image to compare with this image.
Expand All @@ -511,6 +527,20 @@ export interface IMagickImage extends IDisposable {
*/
compare(image: IMagickImage, metric: ErrorMetric, channels: Channels): number;

/**
* Returns the distortion based on the specified metric.
* @param image - The other image to compare with this image.
* @param metric - The metric to use.
*/
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, channels: Channels, func: (compareResult: CompareResult) => TReturnType): TReturnType;

/**
* Returns the distortion based on the specified metric.
* @param image - The other image to compare with this image.
* @param metric - The metric to use.
*/
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, channels: Channels, func: (compareResult: CompareResult) => Promise<TReturnType>): Promise<TReturnType>;

/**
* Compose an image onto another at specified offset using the 'In' operator.
* @param image - The image to composite with this image.
Expand Down Expand Up @@ -2109,10 +2139,40 @@ export class MagickImage extends NativeInstance implements IMagickImage {

compare(image: IMagickImage, metric: ErrorMetric): number;
compare(image: IMagickImage, metric: ErrorMetric, channels: Channels): number;
compare(image: IMagickImage, metric: ErrorMetric, channelsOrUndefined?: Channels): number {
return this.useExceptionPointer(exception => {
const channels = this.valueOrDefault(channelsOrUndefined, Channels.Undefined);
return ImageMagick._api._MagickImage_CompareDistortion(this._instance, image._instance, metric, channels, exception);
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, func: (image: CompareResult) => TReturnType): TReturnType;
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, func: (image: CompareResult) => Promise<TReturnType>): Promise<TReturnType>;
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, channels: Channels, func: (image: CompareResult) => TReturnType): TReturnType;
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, channels: Channels, func: (image: CompareResult) => Promise<TReturnType>): Promise<TReturnType>;
compare<TReturnType>(image: IMagickImage, metric: ErrorMetric, channelsFuncOrUndefined?: Channels | ((image: CompareResult) => TReturnType | Promise<TReturnType>), funcOrUndefined?: (image: CompareResult) => TReturnType | Promise<TReturnType>): number | TReturnType | Promise<TReturnType> {
let func = channelsFuncOrUndefined;
if (funcOrUndefined !== undefined)
func = funcOrUndefined;

let channels = Channels.Undefined;
if (typeof func !== 'function') {
if (func !== undefined)
channels = func;

return this.useExceptionPointer(exception => {
return ImageMagick._api._MagickImage_CompareDistortion(this._instance, image._instance, metric, channels, exception);
});
}

if (channelsFuncOrUndefined !== undefined && typeof channelsFuncOrUndefined !== 'function')
channels = channelsFuncOrUndefined;

const compareResult = DoublePointer.use((pointer) => {
const instance = this.useExceptionPointer(exception => {
return ImageMagick._api._MagickImage_Compare(this._instance, image._instance, metric, channels, pointer.ptr, exception);
});

const distortion = pointer.value;
const difference = MagickImage._createFromImage(instance, this._settings);
return CompareResult._create(distortion, difference);
});

return compareResult.difference._use(() => {
return func(compareResult);
});
}

Expand Down
28 changes: 28 additions & 0 deletions src/types/compare-result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Dirk Lemstra https://github.com/dlemstra/magick-wasm.
// Licensed under the Apache License, Version 2.0.

import { IMagickImage } from '../magick-image';

/**
* Compare result.
*/
export class CompareResult {
private constructor(distortion: number, difference: IMagickImage) {
this.distortion = distortion;
this.difference = difference;
}

/**
* Gets the difference image.
*/
readonly difference;
/**
* Gets the distortion.
*/
readonly distortion;

/** @internal */
static _create(distortion: number, difference: IMagickImage): CompareResult {
return new CompareResult(distortion, difference);
}
}
90 changes: 90 additions & 0 deletions tests/magick-image/compare.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@

import { Channels } from '@src/enums/channels';
import { ErrorMetric } from '@src/enums/error-metric';
import { MagickColor } from '@src/magick-color';
import { MagickColors } from '@src/magick-colors';
import { TestImages } from '@test/test-images';
import { bogusAsyncMethod } from '@test/bogus-async';

describe('MagickImage#compare', () => {
it('should return 0 for same image', () => {
Expand All @@ -24,6 +26,49 @@ describe('MagickImage#compare', () => {
});
});

it('should call function with compare result', () => {
TestImages.empty.use(image => {
TestImages.empty.use(other => {
image.read(MagickColors.Red, 1, 1);
other.read(MagickColors.RosyBrown, 1, 1);
const result = image.compare(other, ErrorMetric.RootMeanSquared, compareResult => {
expect(compareResult.difference).not.toBeNull();
expect(compareResult.difference.width).toBeCloseTo(1);
expect(compareResult.difference.height).toBeCloseTo(1);
expect(compareResult.difference).toHavePixelWithColor(0, 0, new MagickColor('#f40018'));

return compareResult;
});

expect(() => { result.difference._instance }).toThrowError('instance is disposed');
expect(result.distortion).toBeCloseTo(0.48);
});
});
});

it('should call function with compare result async', async () => {
await TestImages.empty.use(async image => {
await TestImages.empty.use(async other => {
image.read(MagickColors.Red, 1, 1);
other.read(MagickColors.RosyBrown, 1, 1);
const result = await image.compare(other, ErrorMetric.RootMeanSquared, async compareResult => {
expect(compareResult.difference).not.toBeNull();
expect(compareResult.difference.width).toBeCloseTo(1);
expect(compareResult.difference.height).toBeCloseTo(1);

await bogusAsyncMethod();

expect(compareResult.difference).toHavePixelWithColor(0, 0, new MagickColor('#f40018'));

return compareResult;
});

expect(() => { result.difference._instance }).toThrowError('instance is disposed');
expect(result.distortion).toBeCloseTo(0.48);
});
});
});

it('should compare the specified channels', () => {
TestImages.empty.use(image => {
TestImages.empty.use(other => {
Expand All @@ -33,4 +78,49 @@ describe('MagickImage#compare', () => {
});
});
});

it('should compare the specified channels and call function with compare result', () => {
TestImages.empty.use(image => {
TestImages.empty.use(other => {
image.read(MagickColors.Red, 1, 1);
other.read(MagickColors.RosyBrown, 1, 1);

const result = image.compare(other, ErrorMetric.RootMeanSquared, Channels.Red, compareResult => {
expect(compareResult.difference).not.toBeNull();
expect(compareResult.difference.width).toBeCloseTo(1);
expect(compareResult.difference.height).toBeCloseTo(1);
expect(compareResult.difference).toHavePixelWithColor(0, 0, new MagickColor('#f40018'));

return compareResult;
});

expect(() => { result.difference._instance }).toThrowError('instance is disposed');
expect(result.distortion).toBeCloseTo(0.15);
});
});
});

it('should compare the specified channels and call function with compare result async', async () => {
await TestImages.empty.use(async image => {
await TestImages.empty.use(async other => {
image.read(MagickColors.Red, 1, 1);
other.read(MagickColors.RosyBrown, 1, 1);

const result = await image.compare(other, ErrorMetric.RootMeanSquared, Channels.Red, async compareResult => {
expect(compareResult.difference).not.toBeNull();
expect(compareResult.difference.width).toBeCloseTo(1);
expect(compareResult.difference.height).toBeCloseTo(1);

await bogusAsyncMethod();

expect(compareResult.difference).toHavePixelWithColor(0, 0, new MagickColor('#f40018'));

return compareResult;
});

expect(() => { result.difference._instance }).toThrowError('instance is disposed');
expect(result.distortion).toBeCloseTo(0.15);
});
});
});
});

0 comments on commit 1b68ec5

Please sign in to comment.