Skip to content

Commit 3bed692

Browse files
Add webp, avid, png, jpeg WASM format plugin (#1324)
* fix import error * export default plugins/formats * don't print noisy exif error * add webp plugin * add docs * add wasm-png/jpeg * add options to webp * add options to jpeg * add avif * add png optimization * get build owrking
1 parent 3d90ad8 commit 3bed692

30 files changed

+1172
-34
lines changed

packages/core/src/utils/image-bitmap.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export async function attemptExifRotate<I extends JimpClass>(
147147
EXIFParser.create(buffer).parse();
148148

149149
exifRotate(image); // EXIF data
150-
} catch (error) {
151-
console.error(error);
150+
} catch {
151+
// do nothing
152152
}
153153
}

packages/docs/astro.config.mjs

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import starlight from "@astrojs/starlight";
33
import starlightTypeDoc, { typeDocSidebarGroup } from "starlight-typedoc";
44
import react from "@astrojs/react";
55
import path from "path";
6+
import { nodePolyfills } from "vite-plugin-node-polyfills";
67

78
export default defineConfig({
89
site: "https://jimp-dev.github.io",
@@ -24,6 +25,7 @@ export default defineConfig({
2425
{ label: "Writing Plugins", link: "/guides/writing-plugins/" },
2526
{ label: "Custom Jimp", link: "/guides/custom-jimp/" },
2627
{ label: "Migrate to v1", link: "/guides/migrate-to-v1/" },
28+
{ label: "WEBP/WASM", link: "/guides/webp/" },
2729
],
2830
},
2931
typeDocSidebarGroup,
@@ -54,4 +56,7 @@ export default defineConfig({
5456
],
5557
}),
5658
],
59+
vite: {
60+
plugins: [nodePolyfills({ include: ["buffer"] })],
61+
},
5762
});

packages/docs/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
1919
"@jimp/core": "workspace:*",
2020
"@jimp/plugin-print": "workspace:*",
21+
"@jimp/wasm-webp": "workspace:*",
2122
"@types/react": "^18.3.5",
2223
"astro": "^4.15.1",
2324
"eslint": "^9.9.1",
@@ -29,6 +30,7 @@
2930
"typedoc": "^0.26.6",
3031
"typedoc-plugin-markdown": "4.2.6",
3132
"typedoc-plugin-zod": "^1.2.1",
32-
"typescript": "^5.5.4"
33+
"typescript": "^5.5.4",
34+
"vite-plugin-node-polyfills": "^0.22.0"
3335
}
3436
}

packages/docs/public/tree.webp

20.3 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { useEffect, useState } from "react";
2+
3+
import { defaultFormats, defaultPlugins } from "jimp";
4+
import webp from "@jimp/wasm-webp";
5+
import { createJimp } from "@jimp/core";
6+
7+
const Jimp = createJimp({
8+
formats: [...defaultFormats, webp],
9+
plugins: defaultPlugins,
10+
});
11+
12+
export function WebpExample() {
13+
const [selectedFile, setSelectedFile] = useState("");
14+
const [output, setOutput] = React.useState("");
15+
16+
function handleFile(e: React.ChangeEvent<HTMLInputElement>) {
17+
const file = e.target.files?.[0];
18+
19+
if (!file) {
20+
return;
21+
}
22+
23+
const reader = new FileReader();
24+
25+
reader.onload = async (e) => {
26+
const data = e.target?.result;
27+
28+
if (!data || !(data instanceof ArrayBuffer)) {
29+
return;
30+
}
31+
32+
// Manipulate images uploaded directly from the website.
33+
const image = await Jimp.fromBuffer(data);
34+
image.quantize({ colors: 16 }).blur(8).pixelate(8);
35+
setSelectedFile(URL.createObjectURL(file));
36+
setOutput(await image.getBase64("image/webp"));
37+
};
38+
39+
reader.readAsArrayBuffer(file);
40+
}
41+
42+
useEffect(() => {
43+
// Or load images hosted on the same domain.
44+
Jimp.read("/jimp/tree.webp").then(async (image) => {
45+
setSelectedFile(await image.getBase64("image/png"));
46+
image.quantize({ colors: 16 }).blur(8).pixelate(8);
47+
setOutput(await image.getBase64("image/png"));
48+
});
49+
}, []);
50+
51+
return (
52+
<div>
53+
{/* A file input that takes a png/jpeg */}
54+
<input type="file" accept="image/webp" onChange={handleFile} />
55+
56+
<div
57+
style={{
58+
display: "flex",
59+
alignItems: "center",
60+
gap: 20,
61+
width: "100%",
62+
}}
63+
>
64+
{selectedFile && (
65+
<img
66+
style={{ flex: 1, minWidth: 0, objectFit: "contain", margin: 0 }}
67+
src={selectedFile}
68+
alt="Input"
69+
/>
70+
)}
71+
{output && (
72+
<img
73+
style={{ flex: 1, minWidth: 0, objectFit: "contain", margin: 0 }}
74+
src={output}
75+
alt="Output"
76+
/>
77+
)}
78+
</div>
79+
</div>
80+
);
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
title: Using WEBP (And other WASM plugins)
3+
description: How to use Jimp WebP and other WASM plugins.
4+
---
5+
6+
import { WebpExample } from "../../../components/webp-example";
7+
import WebpExampleCode from "../../../components/webp-example?raw";
8+
import { Code } from "@astrojs/starlight/components";
9+
10+
The default build of Jimp only includes image formats written in javascript.
11+
To utilize webp (and anything else we don't have a JS implementation for) we need to use format plugins and create a custom jimp.
12+
13+
```ts
14+
import { createJimp } from "@jimp/core";
15+
import { defaultFormats, defaultPlugins } from "jimp";
16+
import webp from "@jimp/wasm-webp";
17+
18+
// A custom jimp that supports webp
19+
const Jimp = createJimp({
20+
formats: [...defaultFormats, webp],
21+
plugins: defaultPlugins,
22+
});
23+
```
24+
25+
<br />
26+
<WebpExample client:load />
27+
28+
<details>
29+
<summary>Full code for example</summary>
30+
31+
<Code code={WebpExampleCode} lang="ts" title="example.jsx" />
32+
33+
</details>
34+
35+
## Browser Usage
36+
37+
Since you're no longer using a pre-bundled version of jimp you need configure your bundler to handle the node code.
38+
39+
For example in vite/astro you can use `vite-plugin-node-polyfills`.
40+
41+
```js
42+
43+
import { nodePolyfills } from "vite-plugin-node-polyfills";
44+
45+
export default defineConfig({
46+
plugins: [
47+
// You only need to polyfill buffer if you're using a browser
48+
plugins: [nodePolyfills({ include: ["buffer"] })],
49+
],
50+
});
51+
```
52+
53+
## All WASM Plugins
54+
55+
- [@jimp/wasm-avif](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-avif)
56+
- [@jimp/wasm-jpeg](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-jpeg)
57+
- [@jimp/wasm-png](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-png)
58+
- [@jimp/wasm-webp](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-webp)

packages/jimp/src/index.ts

+25-21
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,29 @@ import * as quantize from "@jimp/plugin-quantize";
3131

3232
import { createJimp } from "@jimp/core";
3333

34+
export const defaultPlugins = [
35+
blit.methods,
36+
blur.methods,
37+
circle.methods,
38+
color.methods,
39+
contain.methods,
40+
cover.methods,
41+
crop.methods,
42+
displace.methods,
43+
dither.methods,
44+
fisheye.methods,
45+
flip.methods,
46+
hash.methods,
47+
mask.methods,
48+
print.methods,
49+
resize.methods,
50+
rotate.methods,
51+
threshold.methods,
52+
quantize.methods,
53+
];
54+
55+
export const defaultFormats = [bmp, msBmp, gif, jpeg, png, tiff];
56+
3457
// TODO: This doesn't document the constructor of the class
3558
/**
3659
* @class
@@ -101,27 +124,8 @@ import { createJimp } from "@jimp/core";
101124
* ```
102125
*/
103126
export const Jimp = createJimp({
104-
formats: [bmp, msBmp, gif, jpeg, png, tiff],
105-
plugins: [
106-
blit.methods,
107-
blur.methods,
108-
circle.methods,
109-
color.methods,
110-
contain.methods,
111-
cover.methods,
112-
crop.methods,
113-
displace.methods,
114-
dither.methods,
115-
fisheye.methods,
116-
flip.methods,
117-
hash.methods,
118-
mask.methods,
119-
print.methods,
120-
resize.methods,
121-
rotate.methods,
122-
threshold.methods,
123-
quantize.methods,
124-
],
127+
formats: defaultFormats,
128+
plugins: defaultPlugins,
125129
});
126130

127131
export type {

plugins/plugin-print/src/load-bitmap-font.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import { BmCharacter, BmKerning, BmFont, BmCommonProps } from "./types.js";
55
import png from "@jimp/js-png";
66
import { createJimp } from "@jimp/core";
77
import path from "path";
8-
import { convertXML } from "simple-xml-to-json";
8+
import xmlPackage from "simple-xml-to-json";
9+
10+
const { convertXML } = xmlPackage;
911

1012
export const isWebWorker =
1113
typeof self !== "undefined" && self.document === undefined;

plugins/wasm-avif/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# `@jimp/wasm-avif`
2+
3+
A format plugin for Jimp that adds support for AVIF images using the [libavif](https://github.com/AOMediaCodec/libavif).
4+
5+
> NOTE: Only works in esm environments.
6+
7+
## Usage
8+
9+
```ts
10+
import { createJimp } from "@jimp/core";
11+
import { defaultPlugins } from "jimp";
12+
import avif from "@jimp/wasm-avif";
13+
14+
// A custom jimp that supports webp
15+
const Jimp = createJimp({
16+
formats: [avif],
17+
plugins: defaultPlugins,
18+
});
19+
```

plugins/wasm-avif/eslint.config.mjs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import shared from "@jimp/config-eslint/base.js";
2+
export default [...shared];

plugins/wasm-avif/package.json

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "@jimp/wasm-avif",
3+
"version": "1.0.1",
4+
"repository": "jimp-dev/jimp",
5+
"engines": {
6+
"node": ">=18"
7+
},
8+
"scripts": {
9+
"lint": "eslint .",
10+
"build": "tshy",
11+
"dev": "tshy --watch",
12+
"clean": "rm -rf node_modules .tshy .tshy-build dist .turbo"
13+
},
14+
"author": "Andrew Lisowski <[email protected]>",
15+
"license": "MIT",
16+
"devDependencies": {
17+
"@jimp/config-eslint": "workspace:*",
18+
"@jimp/config-typescript": "workspace:*",
19+
"@jimp/core": "workspace:*",
20+
"@jimp/plugin-color": "workspace:*",
21+
"@jimp/test-utils": "workspace:*",
22+
"@jimp/types": "workspace:*",
23+
"@types/node": "^18.19.48",
24+
"eslint": "^9.9.1",
25+
"tshy": "^3.0.2",
26+
"typescript": "^5.5.4",
27+
"vitest": "^2.0.5"
28+
},
29+
"tshy": {
30+
"exclude": [
31+
"**/*.test.ts"
32+
],
33+
"dialects": [
34+
"esm"
35+
],
36+
"exports": {
37+
"./package.json": "./package.json",
38+
".": "./src/index.ts"
39+
}
40+
},
41+
"exports": {
42+
"./package.json": "./package.json",
43+
".": {
44+
"import": {
45+
"types": "./dist/esm/index.d.ts",
46+
"default": "./dist/esm/index.js"
47+
}
48+
}
49+
},
50+
"type": "module",
51+
"publishConfig": {
52+
"access": "public"
53+
},
54+
"sideEffects": false,
55+
"dependencies": {
56+
"@jsquash/avif": "^1.3.0",
57+
"zod": "^3.23.8"
58+
},
59+
"module": "./dist/esm/index.js"
60+
}

0 commit comments

Comments
 (0)