Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webp, avid, png, jpeg WASM format plugin #1324

Merged
merged 11 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading