From 004ecb214378d164399cbcdbd02d6e00ff19e359 Mon Sep 17 00:00:00 2001 From: Alex Diller Date: Sat, 13 Aug 2022 18:14:52 +0200 Subject: [PATCH 1/2] Add rotate pipe --- packages/core/src/pipes/index.ts | 10 ++-- packages/core/src/pipes/rotate.test.ts | 71 ++++++++++++++++++++++++++ packages/core/src/pipes/rotate.ts | 43 ++++++++++++++++ 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 packages/core/src/pipes/rotate.test.ts create mode 100644 packages/core/src/pipes/rotate.ts diff --git a/packages/core/src/pipes/index.ts b/packages/core/src/pipes/index.ts index 5dd6307..b8363ae 100644 --- a/packages/core/src/pipes/index.ts +++ b/packages/core/src/pipes/index.ts @@ -5,14 +5,16 @@ * LICENSE file in the root directory of this source tree. */ -import { Pipe } from "@ipp/common"; +import { Pipe } from '@ipp/common'; -import { ConvertPipe } from "./convert"; -import { PassthroughPipe } from "./passthrough"; -import { ResizePipe } from "./resize"; +import { ConvertPipe } from './convert'; +import { PassthroughPipe } from './passthrough'; +import { ResizePipe } from './resize'; +import { RotatePipe } from './rotate'; export const PIPES: { [index: string]: Pipe } = { convert: ConvertPipe, passthrough: PassthroughPipe, resize: ResizePipe, + rotate: RotatePipe, } as const; diff --git a/packages/core/src/pipes/rotate.test.ts b/packages/core/src/pipes/rotate.test.ts new file mode 100644 index 0000000..2c26696 --- /dev/null +++ b/packages/core/src/pipes/rotate.test.ts @@ -0,0 +1,71 @@ +/** + * Image Processing Pipeline - Copyright (c) Marcus Cemes + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { DataObject, sampleMetadata } from '@ipp/common'; + +import { randomBytes } from 'crypto'; +import sharp, { OutputInfo, Sharp } from 'sharp'; + +import { RotateOptions, RotatePipe } from './rotate'; + +jest.mock("sharp"); + +type UnPromise = T extends Promise ? U : never; + +describe("built-in rotate pipe", () => { + /** The input value */ + const data: DataObject = { + buffer: randomBytes(8), + metadata: sampleMetadata(256, "jpeg"), + }; + + const rotateOptions = { angle: 45 }; + + /** The return value of the mocked sharp.toBuffer() function */ + const toBufferResult: UnPromise> = { + data: data.buffer, + info: { + width: 256, + height: 256, + channels: data.metadata.current.channels, + size: data.buffer.length, + format: data.metadata.current.format, + premultiplied: false, + } as OutputInfo, + }; + + /** The expected value */ + const newData: DataObject = { + ...data, + metadata: { + ...data.metadata, + current: { + ...data.metadata.current, + width: toBufferResult.info.width, + height: toBufferResult.info.height, + }, + }, + }; + + const toBufferMock = jest.fn(async () => toBufferResult); + const rotateMock = jest.fn(() => ({ toBuffer: toBufferMock })); + const sharpMock = sharp as unknown as jest.Mock<{ rotate: typeof rotateMock }>; + const mocks = [toBufferMock, rotateMock, sharpMock]; + + beforeAll(() => sharpMock.mockImplementation(() => ({ rotate: rotateMock }))); + afterAll(() => sharpMock.mockRestore()); + afterEach(() => mocks.forEach((m) => m.mockClear())); + + test("rotate image", async () => { + const result = RotatePipe(data, rotateOptions); + + await expect(result).resolves.toMatchObject(newData); + + expect(rotateMock).toHaveBeenCalledWith(rotateOptions.angle, rotateOptions?.rotateOptions); + expect(toBufferMock).toHaveBeenCalledWith({ resolveWithObject: true }); + }); +}); diff --git a/packages/core/src/pipes/rotate.ts b/packages/core/src/pipes/rotate.ts new file mode 100644 index 0000000..eb8659f --- /dev/null +++ b/packages/core/src/pipes/rotate.ts @@ -0,0 +1,43 @@ +/** + * Image Processing Pipeline - Copyright (c) Marcus Cemes + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { Pipe } from '@ipp/common'; + +import produce from 'immer'; +import sharp, { RotateOptions as SharpOptions } from 'sharp'; + +sharp.concurrency(1); + +export interface RotateOptions { + angle?: number; + rotateOptions?: SharpOptions; +} + +/** + * A built-in pipe that lets you rotate an image + * This can be used to rotate the image properly before the EXIF data is removed from CompressPipe + */ +export const RotatePipe: Pipe = async (data, options = {}) => { + const { + data: newBuffer, + info: { width, height, format, channels }, + } = await sharp(data.buffer) + .rotate(options.angle, options.rotateOptions) + .toBuffer({ resolveWithObject: true }); + + const newMetadata = produce(data.metadata, (draft) => { + draft.current.width = width; + draft.current.height = height; + draft.current.channels = channels; + draft.current.format = format; + }); + + return { + buffer: newBuffer, + metadata: newMetadata, + }; +}; From ff5029998ff129b3285f196f9154e5c9a9f8703c Mon Sep 17 00:00:00 2001 From: Alex Diller Date: Sat, 13 Aug 2022 18:34:06 +0200 Subject: [PATCH 2/2] fix formatting issues --- packages/core/src/pipes/index.ts | 10 +++++----- packages/core/src/pipes/rotate.test.ts | 8 ++++---- packages/core/src/pipes/rotate.ts | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/core/src/pipes/index.ts b/packages/core/src/pipes/index.ts index b8363ae..0f37f6e 100644 --- a/packages/core/src/pipes/index.ts +++ b/packages/core/src/pipes/index.ts @@ -5,12 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import { Pipe } from '@ipp/common'; +import { Pipe } from "@ipp/common"; -import { ConvertPipe } from './convert'; -import { PassthroughPipe } from './passthrough'; -import { ResizePipe } from './resize'; -import { RotatePipe } from './rotate'; +import { ConvertPipe } from "./convert"; +import { PassthroughPipe } from "./passthrough"; +import { ResizePipe } from "./resize"; +import { RotatePipe } from "./rotate"; export const PIPES: { [index: string]: Pipe } = { convert: ConvertPipe, diff --git a/packages/core/src/pipes/rotate.test.ts b/packages/core/src/pipes/rotate.test.ts index 2c26696..7f0026d 100644 --- a/packages/core/src/pipes/rotate.test.ts +++ b/packages/core/src/pipes/rotate.test.ts @@ -5,12 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import { DataObject, sampleMetadata } from '@ipp/common'; +import { DataObject, sampleMetadata } from "@ipp/common"; -import { randomBytes } from 'crypto'; -import sharp, { OutputInfo, Sharp } from 'sharp'; +import { randomBytes } from "crypto"; +import sharp, { OutputInfo, Sharp } from "sharp"; -import { RotateOptions, RotatePipe } from './rotate'; +import { RotateOptions, RotatePipe } from "./rotate"; jest.mock("sharp"); diff --git a/packages/core/src/pipes/rotate.ts b/packages/core/src/pipes/rotate.ts index eb8659f..f6fab02 100644 --- a/packages/core/src/pipes/rotate.ts +++ b/packages/core/src/pipes/rotate.ts @@ -5,10 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import { Pipe } from '@ipp/common'; +import { Pipe } from "@ipp/common"; -import produce from 'immer'; -import sharp, { RotateOptions as SharpOptions } from 'sharp'; +import produce from "immer"; +import sharp, { RotateOptions as SharpOptions } from "sharp"; sharp.concurrency(1);