@bashbers/astro-image-dithering
Version:
Astro image dithering plugin - converts png's in project to dithered.pngs and uses them inside markdown files.
93 lines (92 loc) • 4.86 kB
JavaScript
import { cwd } from "node:process";
import { PNG } from 'pngjs';
import { createReadStream, createWriteStream } from "node:fs";
import { BayerDithering, recursiveFindByExtension } from "./lib.js";
import rehypeDitheredImageContainerHtml from "./rehypeDitheredImageContainerHtml.js";
// @ts-ignore
const OptiPng = await import('optipng');
export function ditherImagesIntegration(options = {}) {
const { directoryToTraverse = cwd(), injectCss = true, injectClientsideJs = true } = options;
return {
name: 'dither-images-integration',
hooks: {
'astro:build:start': async ({ logger }) => {
const foundPngFiles = recursiveFindByExtension(directoryToTraverse, 'png', null, []);
logger.debug(foundPngFiles.toString());
const promises = foundPngFiles.map((foundPng) => {
return new Promise((resolve, reject) => {
// We skip dithered.png files, files inside dist folders to prevent double dithering and files from which an existing dithered.png exists
if (foundPng.includes('-dithered.png') || foundPng.includes('dist\\') || foundPngFiles.includes(foundPng + "-dithered.png")) {
return resolve();
}
createReadStream(foundPng)
.pipe(new PNG())
.on('error', reject)
.on('parsed', function () {
// `this` is a PNG instance
const pixels = Array.from(this.data);
const palette = [[25, 25, 25], [75, 75, 75], [125, 125, 125], [175, 175, 175], [225, 225, 225], [250, 250, 250]];
const dithered = BayerDithering(pixels, this.width, this.height, palette);
// The final image
const png = new PNG({
width: this.width,
height: this.height,
});
// Intermittent rgba array with every 4th item set to 255 for opacity.
const rgba = new Uint8Array(this.width * this.height * 4);
// Dithered is actually a index, not concrete values yet.
for (let i = 0; i < dithered.length; i++) {
const color = palette[dithered[i]]; // [R, G, B]
const rgbaOffset = i * 4;
rgba[rgbaOffset] = color[0]; // R
rgba[rgbaOffset + 1] = color[1]; // G
rgba[rgbaOffset + 2] = color[2]; // B
rgba[rgbaOffset + 3] = 255; // A
}
// Fill png.data with the RGBA values
for (let i = 0; i < rgba.length; i++) {
png.data[i] = rgba[i] || 0;
}
//Optimize range is between 1 and 7. Its lossless so we use 7.
const optimizer = new OptiPng.default(['-o7']);
const ditheredFilename = foundPng.split('.')[0] + "-dithered.png";
png.pack()
.pipe(optimizer)
.pipe(createWriteStream(ditheredFilename))
.on('finish', () => {
logger.info(`Processed ${ditheredFilename}!`);
resolve();
});
});
});
});
await Promise.all(promises);
logger.info('🎉 All images dithered and saved before build continues!');
},
'astro:config:setup': ({ updateConfig, injectScript }) => {
if (injectClientsideJs) {
injectScript('page', `
import '@bashbers/astro-image-dithering/dist/dithered-image-container.js';
`);
}
if (injectCss) {
injectScript('page', `
import '@bashbers/astro-image-dithering/dist/dither-image-toggle.css';
`);
}
updateConfig({
markdown: {
rehypePlugins: [
rehypeDitheredImageContainerHtml
]
},
vite: {
ssr: {
noExternal: ['@bashbers/astro-image-dithering']
}
}
});
}
}
};
}