From 296d4b2104942a0d58485b7e76beaec5cc8db98b Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Sat, 16 Mar 2024 14:56:05 -0700 Subject: [PATCH] so close --- packages/core/src/index.ts | 8 +-- packages/jimp/package.json | 1 + packages/jimp/src/index.test.ts | 20 +++++- packages/jimp/src/index.ts | 3 +- packages/utils/src/index.ts | 14 +++- plugins/plugin-blit/package.json | 47 +++++++++++++ plugins/plugin-blit/src/index.test.ts | 5 ++ plugins/plugin-blit/src/index.ts | 97 +++++++++++++++++++++++++++ plugins/plugin-blit/tsconfig.json | 8 +++ plugins/plugin-crop/src/index.ts | 2 +- pnpm-lock.yaml | 31 +++++++++ 11 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 plugins/plugin-blit/package.json create mode 100644 plugins/plugin-blit/src/index.test.ts create mode 100644 plugins/plugin-blit/src/index.ts create mode 100644 plugins/plugin-blit/tsconfig.json diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 91d74475..21b7a63d 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -49,10 +49,10 @@ export type JimpFormatArray = export type JimpFormatMime = T extends Format ? M : never; -export type JimpMethod = ( - img: Jimp, - ...args: Args -) => Jimp; +export type JimpMethod< + Args extends any[] = any[], + J extends JimpClass = JimpClass, +> = (img: J, ...args: Args) => J; type JimpInstanceMethod = T extends JimpMethod diff --git a/packages/jimp/package.json b/packages/jimp/package.json index ac802e82..e4569ecb 100644 --- a/packages/jimp/package.json +++ b/packages/jimp/package.json @@ -32,6 +32,7 @@ "author": "Andrew Lisowski ", "license": "MIT", "dependencies": { + "@jimp/plugin-blit": "workspace:*", "@jimp/plugin-crop": "workspace:*", "@jimp/js-png": "workspace:*", "@jimp/core": "workspace:*" diff --git a/packages/jimp/src/index.test.ts b/packages/jimp/src/index.test.ts index 4a41c3e9..04be178e 100644 --- a/packages/jimp/src/index.test.ts +++ b/packages/jimp/src/index.test.ts @@ -9,15 +9,33 @@ import { Jimp } from "./index.js"; // }); async function run() { - const image = new Jimp(); const imageBuffer = await fs.readFile( path.join(__dirname, "../../../../images/GIgFDCFbAAA0zlg.png"), ); + const image = new Jimp(); await image.fromBuffer(imageBuffer); + const image2 = new Jimp(); + await image2.fromBuffer(imageBuffer); + + // works + image.blit({ + src: image2, + x: 100, + y: 100, + }); + + // works image.crop(100, 100, 150, 100); + // doesn't work + image.crop(100, 100, 150, 100).blit({ + src: image2, + x: 100, + y: 100, + }); + const outputBuffer = await image.toBuffer("image/png"); const outPath = path.join(__dirname, "./out.png"); diff --git a/packages/jimp/src/index.ts b/packages/jimp/src/index.ts index 6fd7bffb..d597d857 100644 --- a/packages/jimp/src/index.ts +++ b/packages/jimp/src/index.ts @@ -1,7 +1,8 @@ import { Jimp as JimpCustom } from "@jimp/core"; import crop from "@jimp/plugin-crop"; +import blit from "@jimp/plugin-blit"; import png from "@jimp/js-png"; -export const Jimp = JimpCustom.addFormat(png).plugin(crop); +export const Jimp = JimpCustom.addFormat(png).plugin(blit).plugin(crop); diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index f2663916..dacf47c3 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -6,7 +6,7 @@ export function scan( y: number, w: number, h: number, - cb: (image: I, x: number, y: number, idx: number) => any, + cb: (x: number, y: number, idx: number) => any, ) { // round input x = Math.round(x); @@ -17,7 +17,7 @@ export function scan( for (let _y = y; _y < y + h; _y++) { for (let _x = x; _x < x + w; _x++) { const idx = (image.bitmap.width * _y + _x) << 2; - cb(image, _x, _y, idx); + cb(_x, _y, idx); } } @@ -108,3 +108,13 @@ export function colorDiff(rgba1: RGBAColor, rgba2: RGBAColor) { maxVal ); } + +/** + * Limits a number to between 0 or 255 + */ +export function limit255(n: number) { + n = Math.max(n, 0); + n = Math.min(n, 255); + + return n; +} diff --git a/plugins/plugin-blit/package.json b/plugins/plugin-blit/package.json new file mode 100644 index 00000000..e4edeecb --- /dev/null +++ b/plugins/plugin-blit/package.json @@ -0,0 +1,47 @@ +{ + "name": "@jimp/plugin-blit", + "version": "1.0.0", + "repository": "jimp-dev/jimp", + "scripts": { + "lint": "eslint .", + "test": "vitest", + "build": "tshy", + "dev": "tshy --watch" + }, + "author": "Andrew Lisowski ", + "license": "MIT", + "dependencies": { + "@jimp/utils": "workspace:*", + "@jimp/types": "workspace:*" + }, + "devDependencies": { + "@jimp/config-eslint": "workspace:*", + "@jimp/config-typescript": "workspace:*", + "eslint": "^8.57.0", + "tshy": "^1.12.0", + "typescript": "^5.4.2", + "vitest": "^1.4.0" + }, + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts" + } + }, + "exports": { + "./package.json": "./package.json", + ".": { + "import": { + "types": "./dist/esm/index.d.ts", + "default": "./dist/esm/index.js" + }, + "require": { + "types": "./dist/commonjs/index.d.ts", + "default": "./dist/commonjs/index.js" + } + } + }, + "main": "./dist/commonjs/index.js", + "types": "./dist/commonjs/index.d.ts", + "type": "module" +} diff --git a/plugins/plugin-blit/src/index.test.ts b/plugins/plugin-blit/src/index.test.ts new file mode 100644 index 00000000..d3b08e22 --- /dev/null +++ b/plugins/plugin-blit/src/index.test.ts @@ -0,0 +1,5 @@ +import { expect, test } from "vitest"; + +test("adds 1 + 2 to equal 3", () => { + expect(1 + 2).toBe(3); +}); diff --git a/plugins/plugin-blit/src/index.ts b/plugins/plugin-blit/src/index.ts new file mode 100644 index 00000000..509cb534 --- /dev/null +++ b/plugins/plugin-blit/src/index.ts @@ -0,0 +1,97 @@ +import { JimpClass } from "@jimp/types"; +import { limit255, scan } from "@jimp/utils"; + +/** + * Blits a source image on to this image + */ +function blit( + image: I, + { + src, + x = 0, + y = 0, + srcX = 0, + srcY = 0, + srcW = src.bitmap.width, + srcH = src.bitmap.height, + }: { + /** This image to blit on to the current image */ + src: I; + /** the x position to blit the image */ + x: number; + /** the y position to blit the image */ + y: number; + /** the x position from which to crop the source image */ + srcX?: number; + /** the y position from which to crop the source image */ + srcY?: number; + /** the width to which to crop the source image */ + srcW?: number; + /** the height to which to crop the source image */ + srcH?: number; + }, +) { + if (!("bitmap" in src)) { + throw new Error("The source must be a Jimp image"); + } + + if (typeof x !== "number" || typeof y !== "number") { + throw new Error("x and y must be numbers"); + } + + // round input + x = Math.round(x); + y = Math.round(y); + + // round input + srcX = Math.round(srcX); + srcY = Math.round(srcY); + srcW = Math.round(srcW); + srcH = Math.round(srcH); + + const maxWidth = image.bitmap.width; + const maxHeight = image.bitmap.height; + + scan(src, srcX, srcY, srcW, srcH, function (sx, sy, idx) { + const xOffset = x + sx - srcX; + const yOffset = y + sy - srcY; + + if ( + xOffset >= 0 && + yOffset >= 0 && + maxWidth - xOffset > 0 && + maxHeight - yOffset > 0 + ) { + const dstIdx = image.getPixelIndex(xOffset, yOffset); + const srcColor = { + r: src.bitmap.data[idx] || 0, + g: src.bitmap.data[idx + 1] || 0, + b: src.bitmap.data[idx + 2] || 0, + a: src.bitmap.data[idx + 3] || 0, + }; + + const dst = { + r: image.bitmap.data[dstIdx] || 0, + g: image.bitmap.data[dstIdx + 1] || 0, + b: image.bitmap.data[dstIdx + 2] || 0, + a: image.bitmap.data[dstIdx + 3] || 0, + }; + + image.bitmap.data[dstIdx] = + ((srcColor.a * (srcColor.r - dst.r) - dst.r + 255) >> 8) + dst.r; + image.bitmap.data[dstIdx + 1] = + ((srcColor.a * (srcColor.g - dst.g) - dst.g + 255) >> 8) + dst.g; + image.bitmap.data[dstIdx + 2] = + ((srcColor.a * (srcColor.b - dst.b) - dst.b + 255) >> 8) + dst.b; + image.bitmap.data[dstIdx + 3] = limit255(dst.a + srcColor.a); + } + }); + + return image; +} + +export default function blitPlugin() { + return { + blit, + }; +} diff --git a/plugins/plugin-blit/tsconfig.json b/plugins/plugin-blit/tsconfig.json new file mode 100644 index 00000000..de37516b --- /dev/null +++ b/plugins/plugin-blit/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@jimp/config-typescript/base.json", + "compilerOptions": { + "outDir": "dist" + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/plugins/plugin-crop/src/index.ts b/plugins/plugin-crop/src/index.ts index e8eee94b..0e66957d 100644 --- a/plugins/plugin-crop/src/index.ts +++ b/plugins/plugin-crop/src/index.ts @@ -34,7 +34,7 @@ export function crop( const bitmap = Buffer.allocUnsafe(w * h * 4); let offset = 0; - scan(image, x, y, w, h, function (i, x, y, idx) { + scan(image, x, y, w, h, function (x, y, idx) { const data = image.bitmap.data.readUInt32BE(idx); bitmap.writeUInt32BE(data, offset); offset += 4; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c7b25ab..c5f102ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: '@jimp/js-png': specifier: workspace:* version: link:../../plugins/js-png + '@jimp/plugin-blit': + specifier: workspace:* + version: link:../../plugins/plugin-blit '@jimp/plugin-crop': specifier: workspace:* version: link:../../plugins/plugin-crop @@ -186,6 +189,34 @@ importers: specifier: ^1.4.0 version: 1.4.0(@types/node@20.11.28) + plugins/plugin-blit: + dependencies: + '@jimp/types': + specifier: workspace:* + version: link:../../packages/types + '@jimp/utils': + specifier: workspace:* + version: link:../../packages/utils + devDependencies: + '@jimp/config-eslint': + specifier: workspace:* + version: link:../../packages/config-eslint + '@jimp/config-typescript': + specifier: workspace:* + version: link:../../packages/config-typescript + eslint: + specifier: ^8.57.0 + version: 8.57.0 + tshy: + specifier: ^1.12.0 + version: 1.12.0 + typescript: + specifier: ^5.4.2 + version: 5.4.2 + vitest: + specifier: ^1.4.0 + version: 1.4.0(@types/node@20.11.28) + plugins/plugin-crop: dependencies: '@jimp/types':