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