Skip to content

Commit

Permalink
Add webp, avid, png, jpeg WASM format plugin (#1324)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hipstersmoothie authored Sep 3, 2024
1 parent 3d90ad8 commit 3bed692
Show file tree
Hide file tree
Showing 30 changed files with 1,172 additions and 34 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/utils/image-bitmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export async function attemptExifRotate<I extends JimpClass>(
EXIFParser.create(buffer).parse();

exifRotate(image); // EXIF data
} catch (error) {
console.error(error);
} catch {
// do nothing
}
}
5 changes: 5 additions & 0 deletions packages/docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import starlight from "@astrojs/starlight";
import starlightTypeDoc, { typeDocSidebarGroup } from "starlight-typedoc";
import react from "@astrojs/react";
import path from "path";
import { nodePolyfills } from "vite-plugin-node-polyfills";

export default defineConfig({
site: "https://jimp-dev.github.io",
Expand All @@ -24,6 +25,7 @@ export default defineConfig({
{ label: "Writing Plugins", link: "/guides/writing-plugins/" },
{ label: "Custom Jimp", link: "/guides/custom-jimp/" },
{ label: "Migrate to v1", link: "/guides/migrate-to-v1/" },
{ label: "WEBP/WASM", link: "/guides/webp/" },
],
},
typeDocSidebarGroup,
Expand Down Expand Up @@ -54,4 +56,7 @@ export default defineConfig({
],
}),
],
vite: {
plugins: [nodePolyfills({ include: ["buffer"] })],
},
});
4 changes: 3 additions & 1 deletion packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@jimp/core": "workspace:*",
"@jimp/plugin-print": "workspace:*",
"@jimp/wasm-webp": "workspace:*",
"@types/react": "^18.3.5",
"astro": "^4.15.1",
"eslint": "^9.9.1",
Expand All @@ -29,6 +30,7 @@
"typedoc": "^0.26.6",
"typedoc-plugin-markdown": "4.2.6",
"typedoc-plugin-zod": "^1.2.1",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"vite-plugin-node-polyfills": "^0.22.0"
}
}
Binary file added packages/docs/public/tree.webp
Binary file not shown.
81 changes: 81 additions & 0 deletions packages/docs/src/components/webp-example.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React, { useEffect, useState } from "react";

import { defaultFormats, defaultPlugins } from "jimp";
import webp from "@jimp/wasm-webp";
import { createJimp } from "@jimp/core";

const Jimp = createJimp({
formats: [...defaultFormats, webp],
plugins: defaultPlugins,
});

export function WebpExample() {
const [selectedFile, setSelectedFile] = useState("");
const [output, setOutput] = React.useState("");

function handleFile(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];

if (!file) {
return;
}

const reader = new FileReader();

reader.onload = async (e) => {
const data = e.target?.result;

if (!data || !(data instanceof ArrayBuffer)) {
return;
}

// Manipulate images uploaded directly from the website.
const image = await Jimp.fromBuffer(data);
image.quantize({ colors: 16 }).blur(8).pixelate(8);
setSelectedFile(URL.createObjectURL(file));
setOutput(await image.getBase64("image/webp"));
};

reader.readAsArrayBuffer(file);
}

useEffect(() => {
// Or load images hosted on the same domain.
Jimp.read("/jimp/tree.webp").then(async (image) => {
setSelectedFile(await image.getBase64("image/png"));
image.quantize({ colors: 16 }).blur(8).pixelate(8);
setOutput(await image.getBase64("image/png"));
});
}, []);

return (
<div>
{/* A file input that takes a png/jpeg */}
<input type="file" accept="image/webp" onChange={handleFile} />

<div
style={{
display: "flex",
alignItems: "center",
gap: 20,
width: "100%",
}}
>
{selectedFile && (
<img
style={{ flex: 1, minWidth: 0, objectFit: "contain", margin: 0 }}
src={selectedFile}
alt="Input"
/>
)}
{output && (
<img
style={{ flex: 1, minWidth: 0, objectFit: "contain", margin: 0 }}
src={output}
alt="Output"
/>
)}
</div>
</div>
);
}
58 changes: 58 additions & 0 deletions packages/docs/src/content/docs/guides/webp.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: Using WEBP (And other WASM plugins)
description: How to use Jimp WebP and other WASM plugins.
---

import { WebpExample } from "../../../components/webp-example";
import WebpExampleCode from "../../../components/webp-example?raw";
import { Code } from "@astrojs/starlight/components";

The default build of Jimp only includes image formats written in javascript.
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.

```ts
import { createJimp } from "@jimp/core";
import { defaultFormats, defaultPlugins } from "jimp";
import webp from "@jimp/wasm-webp";

// A custom jimp that supports webp
const Jimp = createJimp({
formats: [...defaultFormats, webp],
plugins: defaultPlugins,
});
```

<br />
<WebpExample client:load />

<details>
<summary>Full code for example</summary>

<Code code={WebpExampleCode} lang="ts" title="example.jsx" />

</details>

## Browser Usage

Since you're no longer using a pre-bundled version of jimp you need configure your bundler to handle the node code.

For example in vite/astro you can use `vite-plugin-node-polyfills`.

```js

import { nodePolyfills } from "vite-plugin-node-polyfills";

export default defineConfig({
plugins: [
// You only need to polyfill buffer if you're using a browser
plugins: [nodePolyfills({ include: ["buffer"] })],
],
});
```

## All WASM Plugins

- [@jimp/wasm-avif](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-avif)
- [@jimp/wasm-jpeg](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-jpeg)
- [@jimp/wasm-png](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-png)
- [@jimp/wasm-webp](https://github.com/jimp-dev/jimp/tree/main/plugins/wasm-webp)
46 changes: 25 additions & 21 deletions packages/jimp/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,29 @@ import * as quantize from "@jimp/plugin-quantize";

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

export const defaultPlugins = [
blit.methods,
blur.methods,
circle.methods,
color.methods,
contain.methods,
cover.methods,
crop.methods,
displace.methods,
dither.methods,
fisheye.methods,
flip.methods,
hash.methods,
mask.methods,
print.methods,
resize.methods,
rotate.methods,
threshold.methods,
quantize.methods,
];

export const defaultFormats = [bmp, msBmp, gif, jpeg, png, tiff];

// TODO: This doesn't document the constructor of the class
/**
* @class
Expand Down Expand Up @@ -101,27 +124,8 @@ import { createJimp } from "@jimp/core";
* ```
*/
export const Jimp = createJimp({
formats: [bmp, msBmp, gif, jpeg, png, tiff],
plugins: [
blit.methods,
blur.methods,
circle.methods,
color.methods,
contain.methods,
cover.methods,
crop.methods,
displace.methods,
dither.methods,
fisheye.methods,
flip.methods,
hash.methods,
mask.methods,
print.methods,
resize.methods,
rotate.methods,
threshold.methods,
quantize.methods,
],
formats: defaultFormats,
plugins: defaultPlugins,
});

export type {
Expand Down
4 changes: 3 additions & 1 deletion plugins/plugin-print/src/load-bitmap-font.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import { BmCharacter, BmKerning, BmFont, BmCommonProps } from "./types.js";
import png from "@jimp/js-png";
import { createJimp } from "@jimp/core";
import path from "path";
import { convertXML } from "simple-xml-to-json";
import xmlPackage from "simple-xml-to-json";

const { convertXML } = xmlPackage;

export const isWebWorker =
typeof self !== "undefined" && self.document === undefined;
Expand Down
19 changes: 19 additions & 0 deletions plugins/wasm-avif/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `@jimp/wasm-avif`

A format plugin for Jimp that adds support for AVIF images using the [libavif](https://github.com/AOMediaCodec/libavif).

> NOTE: Only works in esm environments.
## Usage

```ts
import { createJimp } from "@jimp/core";
import { defaultPlugins } from "jimp";
import avif from "@jimp/wasm-avif";

// A custom jimp that supports webp
const Jimp = createJimp({
formats: [avif],
plugins: defaultPlugins,
});
```
2 changes: 2 additions & 0 deletions plugins/wasm-avif/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import shared from "@jimp/config-eslint/base.js";
export default [...shared];
60 changes: 60 additions & 0 deletions plugins/wasm-avif/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@jimp/wasm-avif",
"version": "1.0.1",
"repository": "jimp-dev/jimp",
"engines": {
"node": ">=18"
},
"scripts": {
"lint": "eslint .",
"build": "tshy",
"dev": "tshy --watch",
"clean": "rm -rf node_modules .tshy .tshy-build dist .turbo"
},
"author": "Andrew Lisowski <[email protected]>",
"license": "MIT",
"devDependencies": {
"@jimp/config-eslint": "workspace:*",
"@jimp/config-typescript": "workspace:*",
"@jimp/core": "workspace:*",
"@jimp/plugin-color": "workspace:*",
"@jimp/test-utils": "workspace:*",
"@jimp/types": "workspace:*",
"@types/node": "^18.19.48",
"eslint": "^9.9.1",
"tshy": "^3.0.2",
"typescript": "^5.5.4",
"vitest": "^2.0.5"
},
"tshy": {
"exclude": [
"**/*.test.ts"
],
"dialects": [
"esm"
],
"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"
}
}
},
"type": "module",
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"dependencies": {
"@jsquash/avif": "^1.3.0",
"zod": "^3.23.8"
},
"module": "./dist/esm/index.js"
}
Loading

0 comments on commit 3bed692

Please sign in to comment.