Image Color Number Reduction with alpha support using RGBQuant/NeuQuant/Xiaolin Wu's algorithms and Euclidean/Manhattan/CIEDE2000 color distance formulas in TypeScript
import { PNG } from 'pngjs';
import { buildPaletteSync, utils } from 'image-q';
// read file
const { data, width, height } = PNG.sync.read(fs.readFileSync('file.png'));
const inPointContainer = utils.PointContainer.fromUint8Array(
data,
width,
height,
);
// convert
const palette = buildPaletteSync([inPointContainer]);
const outPointContainer = applyPaletteSync(inPointContainer, palette);
// use outPointContainer.toUint8Array() somehow
This API allows to Build (quantize) palette using Sample Images, returns Palette instance.
import { buildPalette } from 'image-q'; // or const buildPalette = require('image-q').buildPalette
const palette = await buildPalette([pointContainer], {
colorDistanceFormula: 'euclidean', // optional
paletteQuantization: 'neuquant', // optional
colors: 128, // optional
onProgress: (progress) => console.log('applyPalette', progress), // optional
});
import { buildPaletteSync } from 'image-q'; // or const buildPaletteSync = require('image-q').buildPaletteSync
const palette = buildPaletteSync([pointContainer], {
colorDistanceFormula: 'euclidean', // optional
paletteQuantization: 'neuquant', // optional
colors: 128, // optional
});
implementation detail: generator is wrapped with setImmediate (polyfilled)
This API applies given Palette to the PointContainer, returns PointContainer containing new image.
import { applyPalette } from 'image-q'; // or const applyPalette = require('image-q').applyPalette
const outPointContainer = await applyPalette(pointContainer, palette, {
colorDistanceFormula: 'euclidean', // optional
imageQuantization: 'floyd-steinberg', // optional
onProgress: (progress) => console.log('applyPalette', progress), // optional
});
import { applyPaletteSync } from 'image-q'; // or const applyPaletteSync = require('image-q').applyPaletteSync
const outPointContainer = applyPaletteSync(pointContainer, palette, {
colorDistanceFormula: 'euclidean', // optional
imageQuantization: 'floyd-steinberg', // optional
});
See description of string constants in Advanced API Section
export type ColorDistanceFormula =
| 'cie94-textiles'
| 'cie94-graphic-arts'
| 'ciede2000'
| 'color-metric'
| 'euclidean'
| 'euclidean-bt709-noalpha'
| 'euclidean-bt709'
| 'manhattan'
| 'manhattan-bt709'
| 'manhattan-nommyde'
| 'pngquant';
export type PaletteQuantization =
| 'neuquant'
| 'neuquant-float'
| 'rgbquant'
| 'wuquant';
export type ImageQuantization =
| 'nearest'
| 'riemersma'
| 'floyd-steinberg'
| 'false-floyd-steinberg'
| 'stucki'
| 'atkinson'
| 'jarvis'
| 'burkes'
| 'sierra'
| 'two-sierra'
| 'sierra-lite';
API | Source | |
---|---|---|
Canvas related | ||
PointContainer.fromHTMLCanvasElement | HTMLCanvasElement | |
PointContainer.fromImageData | ImageData | ctx.getImageData() |
PointContainer.fromUint8Array | Uint8ClampedArray | ctx.getImageData().data |
PointContainer.fromUint8Array | deprecated CanvasPixelArray | ctx.getImageData().data |
Other | ||
PointContainer.fromHTMLImageElement | HTMLImageElement | |
PointContainer.fromImageData | Array | |
PointContainer.fromUint8Array | Uint8Array | |
PointContainer.fromUint32Array | Uint32Array | |
PointContainer.fromBuffer | Buffer (Node.js) |
Usage:
const canvas = document.querySelector('#canvas');
const pointContainer = PointContainer.fromHTMLCanvasElement(canvas);
| API | Description | Originally used by | | ------------------------- | :------------------------------------------------------------------------------------------------- | ------------------ | --- | | Euclidean | 1/1/1/1 coefficients | WuQuant | | EuclideanBT709 | BT.709 sRGB coefficients | | | Manhattan | 1/1/1/1 coefficients | NeuQuant | | ManhattanBT709 | BT.709 sRGB coefficients | | | CIEDE2000 | CIEDE2000 (very slow) | | | CIE94Textiles | CIE94 implementation for textiles | | | CIE94GraphicArts | CIE94 implementation for graphic arts | | | CMetric | see | | | PNGQuant | used in PNGQuant tools | | | EuclideanBT709NoAlpha | BT.709 sRGB coefficients | RGBQuant | | ManhattanNommyde | discussion | | |
Usage:
const distanceCalculator = new EuclideanBT709();
API | Description |
---|---|
NeuQuant | original code ported, integer calculations |
RGBQuant | |
WuQuant | |
NeuQuantFloat | floating-point calculations |
Usage (sync):
const paletteQuantizer = new WuQuant(distanceCalculator, 256);
paletteQuantizer.sample(pointContainer1);
paletteQuantizer.sample(pointContainer2);
const palette = paletteQuantizer.quantizeSync();
Usage (generator):
// example 1
const paletteQuantizer = new WuQuant(distanceCalculator, 256);
paletteQuantizer.sample(pointContainer1);
paletteQuantizer.sample(pointContainer2);
const generator = paletteQuantizer.quantize();
let palette;
while (true) {
// calling to generator.next() may be easily wrapped with setTimeout to make it async
const result = generator.next();
if (result.done) break;
if (result.value.palette) palette = result.palette;
console.log(`${result.value.progress}% done`);
}
// example 2
const paletteQuantizer = new WuQuant(distanceCalculator, 256);
paletteQuantizer.sample(pointContainer1);
paletteQuantizer.sample(pointContainer2);
const palette = Array.from(paletteQuantizer.quantize()).pop().palette;
API | Description |
---|---|
NearestColor | |
ErrorDiffusionArray | 2 modes of error propagation are supported: xnview and gimp |
- 1. FloydSteinberg | |
- 2. FalseFloydSteinberg | |
- 3. Stucki | |
- 4. Atkinson | |
- 5. Jarvis | |
- 6. Burkes | |
- 7. Sierra | |
- 8. TwoSierra | |
- 9. SierraLite | |
ErrorDiffusionRiemersma | Hilbert space-filling curve is used |
Usage (sync):
const imageQuantizer = new ErrorDiffusionArray(
distanceCalculator,
ErrorDiffusionArrayKernel.Jarvis,
);
const outPointContainer = imageQuantizer.quantizeSync(
inPointContainer,
palette,
);
Usage (generator):
// example 1
const imageQuantizer = new ErrorDiffusionArray(
distanceCalculator,
ErrorDiffusionArrayKernel.Jarvis,
);
const generator = imageQuantizer.quantize(inPointContainer, palette);
let outPointContainer;
while (true) {
// calling to generator.next() may be easily wrapped with setTimeout to make it async
const result = generator.next();
if (result.done) break;
if (result.value.pointContainer) outPointContainer = result.pointContainer;
console.log(`${result.value.progress}% done`);
}
// example 2
const imageQuantizer = new ErrorDiffusionArray(
distanceCalculator,
ErrorDiffusionArrayKernel.Jarvis,
);
const outPointContainer = Array.from(
imageQuantizer.quantize(inPointContainer, palette),
).pop().pointContainer;
API | Description |
---|---|
PointContainer.toUint8Array | Uint8Array |
PointContainer.toUint32Array | Uint32Array |
Usage:
// write PNG using pngjs
png.data = outPointContainer.toUint8Array();
fs.writeFileSync('filename.png', PNG.sync.write(png));
var img = document.createElement("img");
img.onload = function() {
// image is loaded, here should be all code utilizing image
...
}
img.src = "http://pixabay.com/static/uploads/photo/2012/04/11/11/32/letter-a-27580_640.png"
// desired colors number
var targetColors = 256;
// create pointContainer and fill it with image
var pointContainer = iq.utils.PointContainer.fromHTMLImageElement(img);
// create chosen distance calculator (see classes inherited from `iq.distance.AbstractDistanceCalculator`)
var distanceCalculator = new iq.distance.Euclidean();
// create chosen palette quantizer (see classes implementing `iq.palette.AbstractPaletteQuantizer`)
var paletteQuantizer = new iq.palette.RGBQuant(distanceCalculator, targetColors);
// feed out pointContainer filled with image to paletteQuantizer
paletteQuantizer.sample(pointContainer);
... (you may sample more than one image to create mutual palette)
// take generated palette
var palette = paletteQuantizer.quantizeSync();
// create image quantizer (see classes implementing `iq.image.AbstractImageQuantizer`)
var imageQuantizer = new iq.image.NearestColor(distanceCalculator);
// apply palette to image
var resultPointContainer = imageQuantizer.quantizeSync(pointContainer, palette);
You may work with resultPointContainer directly or you may convert it to Uint8Array
/Uint32Array
var uint8array = resultPointContainer.toUint8Array();
please also refer to tests
API | Description |
---|---|
lab2rgb | CIE L*a*b* => CIE RGB |
lab2xyz | CIE L*a*b* => CIE XYZ |
rgb2hsl | CIE RGB => HSL |
rgb2lab | CIE RGB => CIE L*a*b* |
rgb2xyz | CIE RGB => CIE XYZ |
xyz2lab | CIE XYZ => CIE L*a*b* |
xyz2rgb | CIE XYZ => CIE RGB |
https://wolfcrow.com/blog/what-is-the-difference-between-cie-lab-cie-rgb-cie-xyy-and-cie-xyz/
ssim - https://en.wikipedia.org/wiki/Structural_similarity
Usage:
const similarity = ssim(pointContainer1, pointContainer2);
Have fun! Any problems or queries let me know!
-- Igor
Generated using TypeDoc