GithubHelp home page GithubHelp logo

leeoniya / rgbquant.js Goto Github PK

View Code? Open in Web Editor NEW
381.0 16.0 52.0 3.05 MB

color quantization lib

Home Page: http://leeoniya.github.io/RgbQuant.js/demo/

License: MIT License

JavaScript 100.00%

rgbquant.js's Introduction

RgbQuant.js

an image quantization lib (MIT Licensed)

quantization


Intro

Color quantization is the process of reducing an image with thousands or millions of colors to one with fewer (usually 256). The trick is to balance speed, cpu and memory requirements while minimizing the perceptual loss in output quality. More info can be found on wikipedia. Various algorithms can be found on rosettacode.org.

RgbQuant.js is not a port or implementation of any specific quantization algorithm, though some overlap is inevitable.


Demo: http://leeoniya.github.io/RgbQuant.js/demo/

demo page


Usage

Use Chrome, Firefox or IE10+ since many HTML5/JS features are used. Canvas, Typed Arrays, Array.forEach.

// options with defaults (not required)
var opts = {
    colors: 256,             // desired palette size
    method: 2,               // histogram method, 2: min-population threshold within subregions; 1: global top-population
    boxSize: [64,64],        // subregion dims (if method = 2)
    boxPxls: 2,              // min-population threshold (if method = 2)
    initColors: 4096,        // # of top-occurring colors  to start with (if method = 1)
    minHueCols: 0,           // # of colors per hue group to evaluate regardless of counts, to retain low-count hues
    dithKern: null,          // dithering kernel name, see available kernels in docs below
    dithDelta: 0,            // dithering threshhold (0-1) e.g: 0.05 will not dither colors with <= 5% difference
    dithSerp: false,         // enable serpentine pattern dithering
    palette: [],             // a predefined palette to start with in r,g,b tuple format: [[r,g,b],[r,g,b]...]
    reIndex: false,          // affects predefined palettes only. if true, allows compacting of sparsed palette once target palette size is reached. also enables palette sorting.
    useCache: true,          // enables caching for perf usually, but can reduce perf in some cases, like pre-def palettes
    cacheFreq: 10,           // min color occurance count needed to qualify for caching
    colorDist: "euclidean",  // method used to determine color distance, can also be "manhattan"
};

var q = new RgbQuant(opts);

// analyze histograms
q.sample(imgA);
q.sample(imgB);
q.sample(imgC);

// build palette
var pal = q.palette();

// reduce images
var outA = q.reduce(imgA),
    outB = q.reduce(imgB),
    outC = q.reduce(imgC);

Node.js

npm package: https://www.npmjs.com/package/rgbquant example with https://www.npmjs.com/package/canvas

var fs = require('fs'),
	Canvas = require('canvas'),
	Image = Canvas.Image,
	RgbQuant = require('rgbquant');

var imgPath = "./test.png",
	img, can, ctx, q, pal, out;

fs.readFile(imgPath, function(err, data) {
	img = new Image;
	img.src = data;

	can = new Canvas(img.width, img.height);
	ctx = can.getContext('2d');
	ctx.drawImage(img, 0, 0, img.width, img.height);

	q = new RgbQuant();
	q.sample(can);
	pal = q.palette(true);
	out = q.reduce(can);
});

Docs

.sample(image, width) - Performs histogram analysis.
image may be any of <img>, <canvas>, Context2D, ImageData, Typed Array, Array.
width is required if image is an array.

.palette(tuples, noSort) - Retrieves the palette, building it on first call.
tuples if true will return an array of [r,g,b] triplets, otherwise a Uint8Array is returned by default.
noSort if true will disable palette sorting by hue/luminance and leaves it ordered from highest to lowest color occurrence counts.

.reduce(image, retType, dithKern, dithSerp) - Quantizes an image.
image can be any of the types specified for .sample() above.
retType determines returned type. 1 - Uint8Array (default), 2 - Indexed array.
dithKern is a dithering kernel that can override what was specified in global opts (off by default), available options are:

  • FloydSteinberg
  • FalseFloydSteinberg
  • Stucki
  • Atkinson
  • Jarvis
  • Burkes
  • Sierra
  • TwoSierra
  • SierraLite

dithSerp can be true or false and determines if dithering is done in a serpentine pattern.

* Transparent pixels will result in a sparse indexed array.


Caveats & Tips

RgbQuant.js, as any quantizer, makes trade-offs which affect its performance in certain cases. Some parameters may be tweaked to improve the quality of the output at the expense of palette computation and reduction speed. Since the methods used to determine the palette are based on occurrence counts of each pixel's color, three problematic situations can arise.

  • No two pixels are the same color. eg: unscaled bidirectional gradients.
  • Visually distinctive but low-density hues are overwhelmed by dissimilar, dominating hues. (see Quantum Frog)
  • Hues are numerous and densities relatively equal such that choosing the 'top' ones is unpredictable and eliminates a large number of important ones. (see Fish)

The symptom of these issues is a lack of important color groups in the final palette which results in poorly reduced images.

Frequently, the solution is to set minHueCols: 256 during instantiation. What this will do is inject the first 256 encountered distinct colors for each hue group (by default there are 10) into the initial palette for analysis. In effect, it forces each encountered hue group to be represented, regardless of specific color counts. If using method: 1, you may additionally increase initColors to advance the slicing point of the frequency-sorted initial histogram.

These adjustments come with a (often significant) speed penalty for palette generation. Reduction passes may also be affected because of the internal memoization/caching used during palette building.


Why?

Let me acknowledge the elephant in the room: why not just use or port an existing quantization algorithm? As far as JS ports go, there are really only 3.5 options (which implement 2.5 algos).

My original goal was to upscale frames from <canvas> graphics animations and pixelated SNES-style games for GIFter.js. It became apparent after trying the first three options that I would need something different.

rgbquant.js's People

Contributors

forresto avatar leeoniya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rgbquant.js's Issues

Uint8Array image and serpentine dithering not supported?

Hi Leon,

After spending countless hours trying to find coding errors on my side, I finally decided to look at your code for serpentine dithering. Looks like dithering is only supported with Uin8Array images rgbQuant? Is this correct? I need to convert a Uint8Array to a data.buf32 ? Google?

if (!kernel || !kernels[kernel]) { throw 'Unknown dithering kernel: ' + kernel; }

var ds = kernels[kernel];

	var data = getImageData(img),

// buf8 = data.buf8, << Uint8Array not supported ????

		buf32 = data.buf32,
		width = data.width,
		height = data.height,
		len = buf32.length;
	var dir = serpentine ? -1 : 1;
	for (var y = 0; y < height; y++) {
		if (serpentine)
			dir = dir * -1;

Thank you,
Wayne

Rebuilding the palette

I am using rgbquant.js on a page where I upload images and reduce amount of colours. It works great.

However, I don't understand the palette locking & regeneration. How do I programmatically clear & rebuild the palette from javascript when a new picture has been uploaded? I don't see a function that clears the palLocked flag. What was the intention of the authors in this regard?

I can't seem to get the "right" pallete

I'm running rgbquant on the following image in order to get the 10 most dominant colours out:

screenshot 68

but instead most of the settings I've tried seem to come up with the following quantization:

screenshot 69

Are there specific settings that work best for quantizing such that the final palette is closest to the original image?

(the orange is obvious, but also the little yellow bit in the original image has become pink in the quantized result, and the tiny plushy at the bottom has become half pink, too =)

palette length for GIF

Hi there, thank you for this library. I have a usage question - I am trying to use it to quantize a canvas image down to 256 colors before I pass it to GifWriter (to create an animated GIF).

var cv = document.getElementById("canvas");
 var ctx = cv.getContext("2d");
var data = ctx.getImageData(0, 0, w, h).data;
 var q = new RgbQuant(opts);
 q.sample(data);
var palette = q.palette(false);
console.log ("QUANTING WITH PALETTE="+palette.length);

This produces a palette length of 1024 for an image on the canvas. I need it to be 256, which is what GifWriter needs. What am I doing wrong?

opts is exactly the same as your example

 var opts = {
    colors: 256,             // desired palette size
    method: 2,               // histogram method, 2: min-population threshold within subregions; 1: global top-population
    boxSize: [64,64],        // subregion dims (if method = 2)
    boxPxls: 2,              // min-population threshold (if method = 2)
    initColors: 4096,        // # of top-occurring colors  to start with (if method = 1)
    minHueCols: 0,           // # of colors per hue group to evaluate regardless of counts, to retain low-count hues
    dithKern: null,          // dithering kernel name, see available kernels in docs below
    dithDelta: 0,            // dithering threshhold (0-1) e.g: 0.05 will not dither colors with <= 5% difference
    dithSerp: false,         // enable serpentine pattern dithering
    palette: [],             // a predefined palette to start with in r,g,b tuple format: [[r,g,b],[r,g,b]...]
    reIndex: false,          // affects predefined palettes only. if true, allows compacting of sparsed palette once target palette size is reached. also enables palette sorting.
    useCache: true,          // enables caching for perf usually, but can reduce perf in some cases, like pre-def palettes
    cacheFreq: 10,           // min color occurance count needed to qualify for caching
    colorDist: "euclidean",  // method used to determine color distance, can also be "manhattan"
};```

Unexpected results for 2 color reduction

I'm using a fairly simple piece of code to reduce a small array of RGBA values to only 2 colors:

var quant = new RgbQuant({
  colors: 2
});
quant.sample([99, 88, 77, 1, 66, 55, 44, 1...]);
var pal = quant.palette(true);
console.log(pal)

The palette returned has only one color in it. What other params do I need to provide to get the right behavior?

set color

Hi,
I just go through your library and its really great stuff. And it reduce the image perfectly. But is there any way i can change the color.
Suppose i give option.color = 4;
and after update click i need to show 4 color pallets and by clicking on them i want to change that color on image.
is it possible ??

Emit "progress" events to notify users in a web app use-case

I'm using RgbQuant on a website, to extract brand colours from an image provided by a user. The algorithm is quite fast, but it can still take a while for huge images, and we all know users are weird creatures. In order to tell a user that the process is still running and didn't hang, I'd like to have an event that is emitted frequently, that could look like:

q.on('progress', function(progress) {
  // ...
});

Error: Uncaught Nothing has been sampled, palette cannot be built

Given I draw to the canvas an image 1x3 pixels with blue, green and red dots
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAADCAIAAADdv/LVAAAADUlEQVQI12NgYPgPQwAR+gL+H61IqAAAAABJRU5ErkJggg==

when I try to get a palette:

var q = new RgbQuant({colors: 3}),
      paletteArray;
q.sample(canvas);
paletteArray = q.palette();

then I see an exception:

Error: Uncaught Nothing has been sampled, palette cannot be built.

Why? Is there any limitations for images?

dithSerp palette issue

If dithSerp is set to true it seems to introduce colors that are not in the palette for small palettes, repro:

const createCanvas = require('canvas');
const canvas = createCanvas(204, 204);
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.font = '30px Impact';
ctx.rotate(0.1);
ctx.fillStyle = 'black';
ctx.fillText('Awesome!', 50, 100);

// quantitize
const RgbQuant = require('rgbquant');
const q = new RgbQuant({colors: 16, dithKern: 'FloydSteinberg', dithSerp: true});
q.sample(canvas);
const pal = q.palette(true);
const out = q.reduce(canvas);

The palette now contains:

[ [ 0, 0, 0 ],
  [ 17, 17, 17 ],
  [ 27, 27, 27 ],
  [ 38, 38, 38 ],
  [ 54, 54, 54 ],
  [ 75, 75, 75 ],
  [ 86, 86, 86 ],
  [ 103, 103, 103 ],
  [ 126, 126, 126 ],
  [ 137, 137, 137 ],
  [ 154, 154, 154 ],
  [ 169, 169, 169 ],
  [ 194, 194, 194 ],
  [ 218, 218, 218 ],
  [ 231, 231, 231 ],
  [ 255, 255, 255 ] ]

the colors of out are (unordered):

[ [ 255, 255, 255, 255 ],
  [ 231, 231, 231, 255 ],
  [ 0, 0, 0, 255 ],
  [ 17, 17, 17, 255 ],
  [ 38, 38, 38, 255 ],
  [ 54, 54, 54, 255 ],
  [ 86, 86, 86, 255 ],
  [ 103, 103, 103, 255 ],
  [ 137, 137, 137, 255 ],
  [ 169, 169, 169, 255 ],
  [ 194, 194, 194, 255 ],
  [ 126, 126, 126, 255 ],
  [ 218, 218, 218, 255 ],
  [ 75, 75, 75, 255 ],
  [ 154, 154, 154, 255 ],
  [ 27, 27, 27, 255 ],
  [ 253, 253, 253, 255 ] ]

The last entry is not in the palette. If you add more colors or gradients to the original image, more spurious colors will pop up. Is this a missing/misplaced nearest color matching in reduce?

RgbQuant.js as a node.js module

First, I <3 RgbQuant, I use it for analysing web page screenshots.

Have you ever thought about making this as a node.js module, where you can overgive a path to the image on the filesystem. This would be pretty awesome...

Best Regards,
Frank

Does not work for tiny images

I am trying to get colors of images whose one dimension is 1px wide.
For a 1x1px image nothing is sampled.
For a 7x1px image it nondeterministicaly fails to sample ~50% of the time.

Is there an option to make it work for tiny images? Or at least to add determinism?
Or was this never designed to work with 1px wide images?

inconsistent sampling for population by palette size

RgbQuant is awesome!

I am using it in an application for beginning artists (painters) - reducing an image to a minimal number of colors...etc.

The strange behavior I find is that when I get a palette size of 10 - orange (in this particular case) is one of the most predominant color - and green doesn't show in the top 10 colors.

But when I get a palette size of 15 - green (which is far more visually correct) is the predominant color (by population).

Can you give me any advice on this?

Thanks so much!

var q = new RgbQuant({ colors: getSlice });
q.sample(ctx);
var color = q.palette(true, true);

slice15
slice10

Description of algorithm?

It'd be nice if the README or another file had an in-depth description of the algorithm, so it could be implemented in other programming languages.

Question on usage

Hi there, I am trying to use this library to quantize an image, but I'm probably doing something wrong
My code with comments:

var cv = document.getElementById("canvas");
ctx.drawImage(img, 0, 0); // works fine, I can see the image
var q = new RgbQuant();
q.sample(cv); 
var pal = q.palette(true);
var rimg = q.reduce(cv); // I assume rimg should be the quantized version?

// next step  produces an error
//Failed to execute 'drawImage' on 'CanvasRenderingContext2D': The provided value is not of type 
//'(HTMLImageElement or HTMLVideoElement or HTMLCanvasElement or ImageBitmap)'
ctx.drawImage(rimg, 0, 0);

Make it async

Are there plans to make reduce, pallete, .. asynchronous?

Ordered Dithering

Hi Leon,

I am interested in adding some ordered dithering options to RgbQuant.js. I am trying to implement, to start, a 2x2 Bayer Matrix. I am trying to do this by adding the following to the kernels array:

Bayer2x2: [
[3 / 6, 0, 0],
[1 / 6, 1, 0],
[0 / 6, 0, 1],
[2 / 6, 1, 1],
],

However, it seems that setting the first value at 0,0 is preventing colors from getting applied. Having a little trouble understanding the code to debug, I can dig in more but thought you might have some quick ideas of where to look.

thanks, love the script,

Alex

Visual artifacts

I'm trying to apply RgbQuant.js's quantization on a canvas where I've draw some PNGs (tiles from a mapping application).

These PNGs have some holes (transparency).

Also they not fill the canvas, leading to lots of "white pixels".
captura de tela 2018-02-23 as 18 38 15
Left is the canvas before quantization. Right is after quantization.

After q.reduce(canvas) I'm using drawPixels function I found on RgbQuant.js's demo to render a canvas with quantization result.

But I'm getting lots of artifacts on the most dark pixels.

Tried with: method: 1 and method: 2, initColors: 8128 or with default initColors...
Using options: colors: 32, minHueCols: 0

Would you/someone have any idea how to getting it to work properly?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.