UNPKG

@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
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'] } } }); } } }; }