<html>
<head>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body> <span id='ditheringstats'>With Dithering</span>
<br>
<canvas id="dither" width="300" height="150"></canvas>
<br>
<span id='noditheringstats'>No Dithering</span>
<br>
<canvas id="nodither" width="300" height="150"></canvas>
<br>
<span id='originalstats'>Original</span>
<br>
<canvas id="original" width="300" height="150"></canvas>
<br>
<script>
window.addEventListener('DOMContentLoaded', function() {
var ditheringstats = document.querySelector('#ditheringstats');
var noditheringstats = document.querySelector('#noditheringstats');
function drawTest(canvas) {
canvas = document.querySelector(canvas);
var context = canvas.getContext('2d');
context.rect(0, 0, canvas.width, canvas.height);
var grd = context.createRadialGradient(238, 50, 10, 238, 50, 300);
grd.addColorStop(0, '#11FF00');
grd.addColorStop(1, '#004CB3');
context.fillStyle = grd;
context.fill();
var grd = context.createRadialGradient(0, 50, 50, 238, 50, 30);
grd.addColorStop(0, '#ffFF00');
grd.addColorStop(1, '#ff00ff');
context.fillStyle = grd;
context.fill();
var grd = context.createRadialGradient(10, 50, 50, 138, 50, 30);
grd.addColorStop(0, '#ff55ff');
grd.addColorStop(1, '#000066');
context.fillStyle = grd;
context.fill();
}
drawTest('#original');
// For those that dont know, the following tag tells Js Bin to put the code from the js panel where that
%code%
drawTest('#nodither');
var t = Date.now();
notdithered = Bitmap('#nodither');
notdithered.createIndexedData(true);
noditheringstats.innerText = 'No Dithering - ' + (Date.now() - t);
notdithered.putImageData();
})
</script>
<script>
function Bitmap(args) {
if (this instanceof Bitmap) {
if (typeof this.init == "function")
this.init.apply(this, args.callee ? args : arguments);
} else
return new Bitmap(arguments);
};
Bitmap.prototype.init = function(canvas) {
if (typeof canvas == 'string') var canvas = document.documentElement.querySelector(canvas);
this.canvas = canvas;
this.context = canvas.getContext("2d");
this.width = canvas.width;
this.height = canvas.height;
this.imageData = this.context.getImageData(0, 0, this.width, this.height);
this.data = this.imageData.data;
this.reduceColor = {};
this.rgbData = null;
this.Data = null;
this.rgbMode = false;
this.pixelWidth = 4;
this.indexedData = null;
this.indexedPalette = []; // dam this could get big ;P
this.cachedColors = {};
};
Bitmap.prototype.getPixel = function(x, y, iNdEx) {
if (this.pixelWidth == 1) {
var idx = this.indexedData[x + (y * this.width)] * 3;
return iNdEx ? idx : [this.indexedPalette[idx], this.indexedPalette[idx + 1], this.indexedPalette[idx + 2]];
} else {
var idx = (x + y * this.width) * this.pixelWidth;
var data = this.pixelWidth == 3 ? this.rgbData : this.data;
return this.pixelWidth == 3 ? [data[idx], data[idx + 1], data[idx + 2]] : [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
}
};
Bitmap.prototype.setPixel = function(x, y, color, iNdEx) {
if (color[0] > 255) color[0] = 255;
else if (color[0] < 0) color[0] = 0;
if (color[1] > 255) color[1] = 255;
else if (color[1] < 0) color[1] = 0;
if (color[2] > 255) color[2] = 255;
else if (color[2] < 0) color[2] = 0;
if (this.pixelWidth == 1) {
if (!iNdEx) color = this.findPaletteColor(color);
this.indexedData[x + (y * this.width)] = color;
} else {
var idx = (x + y * this.width) * this.pixelWidth;
var data = this.pixelWidth == 3 ? this.rgbData : this.data;
data[idx] = color[0];
data[idx + 1] = color[1];
data[idx + 2] = color[2];
if (this.pixelWidth == 4) data[idx + 3] = color[3];
};
}
Bitmap.prototype.putImageData = function(context) {
var data = this.imageData;
if (this.pixelWidth == 3) {
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
var imageData = canvas.getContext("2d").createImageData(this.width, this.height);
data = imageData.data;
var dest = 0;
var len = data.length;
for (var i = 0; i < len; i += 4) {
data[i] = this.rgbData[dest++];
data[i + 1] = this.rgbData[dest++];
data[i + 2] = this.rgbData[dest++];
data[i + 3] = 255;
}
data = imageData;
}
if (this.pixelWidth == 1) {
var canvas = document.createElement('canvas');
canvas.width = this.width;
canvas.height = this.height;
var imageData = canvas.getContext("2d").createImageData(this.width, this.height);
data = imageData.data;
var dest = 0;
var len = data.length;
var idx = null;
for (var i = 0; i < len; i += 4) {
idx = this.indexedData[dest++] * 3;
data[i] = this.indexedPalette[idx];
data[i + 1] = this.indexedPalette[idx + 1];
data[i + 2] = this.indexedPalette[idx + 2];
data[i + 3] = 255;
}
data = imageData;
}
if (context) context.putImageData(data, 0, 0);
else this.context.putImageData(data, 0, 0);
};
Bitmap.prototype.createRgbData = function(matte, transparent) {
this.rgbData = new Uint8Array(this.width * this.height * 3);
var pixels = [];
var count = 0;
var len = this.data.length;
for (var i = 0; i < len; i += 4) {
var r = this.data[i];
var g = this.data[i + 1];
var b = this.data[i + 2];
var a = this.data[i + 3];
if (transparent && a === 0) {
// Use transparent color
r = transparent[0];
g = transparent[1];
b = transparent[2];
thereAreTransparentPixels = true;
} else if (matte && a < 255) {
// Use matte with "over" blend mode
r = ((r * a + (matte[0] * (255 - a))) / 255) | 0;
g = ((g * a + (matte[1] * (255 - a))) / 255) | 0;
b = ((b * a + (matte[2] * (255 - a))) / 255) | 0;
}
this.rgbData[count++] = r;
this.rgbData[count++] = g;
this.rgbData[count++] = b;
}
};
Bitmap.prototype.createIndexedData = function(auto) {
if (!this.rgbData) this.createRgbData();
this.cachedColors = {};
var pixels = this.rgbData;
var len /*int*/ = this.rgbData.length;
var nPix /*int*/ = len / 3;
this.indexedData = new Array(nPix);
var opts = {
colors: 256, // desired palette size
method: 1, // 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
};
var q = new RgbQuant(opts);
q.sample(this.rgbData);
var p = q.palette();
for (var i = 0, end = p.length, off = 0; i < end; i = i + 4) {
this.indexedPalette[off++] = p[i];
this.indexedPalette[off++] = p[i + 1];
this.indexedPalette[off++] = p[i + 2];
}
// map image pixels to new palette
var k /*int*/ = 0;
for (var j /*int*/ = 0; j < nPix; j++) {
var index = this.findPaletteColor([pixels[k++], pixels[k++], pixels[k++]]);
this.indexedData[j] = index;
}
if (auto) this.pixelWidth = 1;
};
Bitmap.prototype.createIndexedDataNQ = function(auto) {
if (!this.rgbData) this.createRgbData();
var pixels = this.rgbData;
var len /*int*/ = this.rgbData.length;
var nPix /*int*/ = len / 3;
this.indexedData = new Array(nPix);
var nq /*NeuQuant*/ = new NeuQuant(this.rgbData, len, 10); // 10 is the sample/quality
this.indexedPalette = nq.process(); // create reduced palette
// map image pixels to new palette
var k /*int*/ = 0;
for (var j /*int*/ = 0; j < nPix; j++) {
var index = this.findPaletteColor([pixels[k++], pixels[k++], pixels[k++]]);
this.indexedData[j] = index;
}
if (auto) this.pixelWidth = 1;
};
Bitmap.prototype.save = function() {
window.open(this.canvas.toDataURL());
}
/**
* Returns index of palette color closest to color
*
*/
Bitmap.prototype.findPaletteColor = function(color) {
if (this.indexedPalette == null) return -1;
var r /*int*/ = color[0];
var g /*int*/ = color[1];
var b /*int*/ = color[2];
var c = b | (g << 8) | (r << 16);
if (this.cachedColors[c]) return this.cachedColors[c];
var minpos /*int*/ = 0;
var dmin /*int*/ = 256 * 256 * 256 * 256;
var len /*int*/ = this.indexedPalette.length;
for (var i /*int*/ = 0; i < len; i = i + 3) {
var dr /*int*/ = r - this.indexedPalette[i];
var dg /*int*/ = g - this.indexedPalette[i + 1];
var db /*int*/ = b - this.indexedPalette[i + 2];
var d /*int*/ = dr * dr + dg * dg + db * db;
var index /*int*/ = i / 3;
if ( /*usedEntry[index] && */ (d < dmin)) {
dmin = d;
minpos = index;
}
}
this.cachedColors[c] = minpos;
return minpos;
}
Bitmap.prototype.findExactPaletteColor = function(color) {
if (this.indexedPalette == null) return -1;
var len /*int*/ = this.indexedPalette.length;
for (var i /*int*/ = 0; i < len; i = i + 3) {
var dr /*int*/ = this.indexedPalette[i];
var dg /*int*/ = this.indexedPalette[i + 1];
var db /*int*/ = this.indexedPalette[i + 2];
if (dr == color[0] && dg == color[1] && db == color[2]) return i / 3;
}
return undefined;
}
</script>
<!-- Quant's -->
<script>
eval((function(s) {
var a, c, e, i, j, o = "",
r, t = "¡¢£¤¥¦§¨©ª«¬•®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ••••••••••••••••••••••#$'@^`•";
for (i = 0; i < s.length; i++) {
r = t + s[i][2];
a = s[i][1].split("•");
for (j = a.length - 1; j >= 0; j--) {
s[i][0] = s[i][0].split(r.charAt(j)).join(a[j]);
}
o += s[i][0];
}
var p = 6663;
var x = function(r) {
var c, p, s, l = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789';
if (r < 63) c = l.charAt(r);
else {
r -= 63;
p = Math.floor(r / 63);
s = r % 63;
c = l.charAt(p) + l.charAt(s)
}
return c;
};
a = o.substr(p).split(':');
r = a[1].split('?');
a = a[0].split('?');
o = o.substr(0, p);
if (!''.replace(/^/, String)) {
var z = {};
for (i = 0; i < 134; i++) {
var y = x(i);
z[y] = r[i] || y
}
t = /\b\w\w?\b/g;
y = function(a) {
return z[a] || a
};
o = o.replace(t, y);
} else {
for (j = a[a.length - 1] - 1; j >= 0; j--) {
if (r[j]) o = o.replace(new RegExp('\b' + (j < 63 ? c.charAt(j) : c.charAt((j - 63) / 63) + c.charAt((j - 63) % 63)) + '\b', 'g'), r[j])
}
}
return o.replace(/•/g, "\n").replace(/•/g, "\"");
})([
["(B(•B t(t•tôÑ{}¥z´zÑ2¥j´jÑbg¥t´tÑ4096¥F´FÑ.05¥G´GÑ.02¥f´f•0´0•2´2•i´iÑ0,J.Z•i?aE i(¦f¥i#¥w´wÑ[64,64]¥P´PÑ2¥e=!1¥nó¥a=[]¥c=[]¥gó¥Bó¥AóÂi£¦lô¥LÊ¥kó•±æ;r<t;r¼)¦k[rµ{aU:0,aY:[]}•¦u=0Âr¤r•ØaQ.sqrt(p*t*t+x*i*i+g*r*r)ÂsÀiÁtû®sÊÐ-tÞeÊÈt[2®n=p*r*r+x*s*s+g*e*e•Øn/65025Âe¤s¬eß,a¸•têiêsêeÚ¤sÃn=aQ.m•¤sÃh=(e+n)/2,e==n)a=o=0•7{ar(uí-n,o=h>.5?u/(2-e-n):u/(e+nÃe•9 t:a=(i-s)/u+(i<s?6:0¾i:a=(s-t)/u+2• s:a=(t-i)/u+4}a/=6}K{h:a,s:o,l:r¤s)}ÂnÀ1/iÉr/2•t>=1-sÑt<=s)Ø0•³e=1;e<i;e¼¬ní*r•t>=n-s&&t<=n+s)Øe}Âa² tÂo² tÂh² a9." +
"W.to÷§N(t).bc(8,æ)Âu(t¬i=h(Jû])••Number•=ÊÑ•÷•=Ê•³rÉ{},e=J._ß»n<e;nÖr=J[n®!s•&&0!==s•)s[rµn•7;á×-s•'}7{V s•6(B² t'•á§m(i)-s§m(r)'}Âf(¬t=•abcdefghijklmnopqrstuvwxyz••K•xyzvwtursopqmnklhijfgdeabc•=ô.split(••)§J©{Kèrþ-èiþ'.jo•(••)Âc£V r,s,eÇo•à{9•HTMLΕdocument.createÅ(•a8•Ãr§q´q,r§C´CÉçÃs.drawÎ(t,0,0ÝHTMLCñ•rÑtÉsÑçÝCñRender•gï2D•:s=sÑt,r=rÑs§8øgetÎData(0,0,r§q,r§CÝÎData•:eíÑtßí.data,ií§q•9•ao•¿tÝab•:9•U•t8ClampedÕ•t,a=Ôn§DÝah•:a=aÑtß=nÑ¿a§DÃiÊÑa._,o=a._/i}K{can:r,ctx:s,imgd:e,buf8:n,aO:a,aq:i,aC:o" +
"}Âl¤r,s•³eô%rßÊ%s,aô-e,oÊ-n,h•u»u<i;u+=s)³f»f<t;f+=r)hú{x:f,y:u,w:f==a?e:r,h:u==o?n:s'•ØhÂv¤r¬sôøy*i+s.xß=(s.y+s.hæ)*i+(s.x+s.wæÃa=0,oÊ-s.w+1,hí•do r§N(J,hÃh+=0==¼a%s.w?o:1•while(h<=n)ÂdÀ[]•³sät)rús)•Øm§N(r,B(r,s•Øi?tå-t•:t•-tå'}t.W§7=B£X(¦e)bb•C•a7 additional images, aØalready assembled.•$r=c(t,i)î9 1:¦T(r§O¾2:¦S(r§O,r§q)}},t.W.•=B£X(!¦e)¦H()•iÊÑ1•±c(tÃs=r§Oø_ß=1=Ê?ÔeÛao(e#,a»a<e;a¼¬o=s[a]•n[aµ1=Ê?¦R(oÛ¦d(o#}Ø1=Ê?¿n§DÛn:aW¢H«X(!¦e¬t•n,i=d(t,!0)•0=Ê._)bb•Noth•g has been `d, aØc•be built.•î9 1:±¦" +
"tÉi[ræ®eô[s®nÊ.bc(0,rÃa=r,oÊ._;a<o&&t[i[a]µí;)núi[a¼])•½V(n¾2:V nÊ}n=n§6(B²+t'¥1(n)¥5()¥e=!0}¢KªØ¦H(Ãt?¦a:¿Ô¦c)§D)¢1ªV i´6(B²õ3¹8,â]}ÃrÊ._,e=rßó•a_•F,e>¦j•Y(;e>¦j;•³a•ö¼¬hÊ[o®u=Ï•h)³f=o+1;f<r;f¼¬cÊ[f®lô[f]•c¬v=100*s(h,c)•v<a_)aú[év]Ãn[lµu,delete i[f®e--}7;}7;}a_+•G}X(e<¦j•m§N(a,B£Øi[3]-t[3]'•³d»e<¦j;)i[a[d]û]µa[d]Þe¼,d¼}}±i._,öÖi[o])¦aúi[o])¥cúÏ)¥g[ϵ¦c._æ¥B[ϵϥA[ϵi[o]•7;³pän¬x•d(p)•¦g[pµx¥B[pµ¦c[x]¥A[pµ¦a[x]}¢Tª³i,r•nÉt._,e»e<s;eÖiô[eüi)>>24º½M(i)•iär)r×¼•7 r[iµ1}7;¢S=BÀ¦wû®s•wÞe=r*sß=l(i,t._" +
"/ÜÃa•n,o=J•n•r¬sßÚ(aQ.round(r.w*r.h/e)*o§P,2Ãhó•v(r,i,B(iºsô[iüs)>>24ºo.Z)o.Z§M(s)•säa)a引7 X(sähº¼hå>=n)a[sµhå}7 h[sµ1}'}ýV(a)¢5ò=J•this§c§J©{V s´g[i®h´g[r®u´a[s®f´a[h®cí(uû®uÞu[2]Ãlí(fû®fÞf[2]Ãv=uûµ=uÐ&&u[1µ=uý(c•d=fûµ=fÐ&&f[1µ=fý(l•p=d-v•p)K-p$x=o(+l.lÆ-o(+c.lÆ•x)K-x$g=a(+l.sÆ-a(+c.sÆ•g)K-g•7 Øvoid 0'¥c§v©{t§a•´A[i®t§g[iµr'¢RªV i•d(t)•Ø¦c×¢dªV i•B[t]•i)ئgו³r,r,e=1e3ß=õ3¹8,â®a•a._,o»o•o¼¬h=s(n¥a[o])•h<e)e=h,r=o}Ør},i.W§MªX(¦u=•l+1)¦M«}$i=ay&t,r=(a3¹8Éâ,aÊ==r&&r==s?æ:n(e(Ü).h¥lÃo•k[a®h•L•o§" +
"U¼,!(o§U>h)ºo§U==h)¦u¼•o§U<=h)¦k[a]§Yút)}},i.W§Vª³i=æ;i<¦l;iÖ¦kקU<•L)à{9•ao•iºæ=´m(i))túi)'••a9•iº!t×)t[iµ1•7 t×¼'}}$p=.2126,x=.7152,g=.0722,m=f()?ao.W§J:u•J.RgbQuantô'§N(JÃa4ò,Ü,eßó,a=bg,o=499,h=491,u=487,f=503,c=3*f,l=aæ,v=4,d=100,p=bh,xëp,g=ba,m=ba,b=x>>m,y=x<<g-m,w=a>>3,C=6,SëC,A=w*S,k=30,G=ba,IëG,D=8,PëD,U=G+D,jëU,E•F•L•M•O=n§4=B(tß,o¬h,uìiô,r=nÉo,e=ao(aÃh»h•h¼)e[hµao(4Ãuí[h®uûµu[1µu[2µ(h<<v+8)/a,L[hµx/a,F[hµ0},z«³t•i=ao(aÃr»r•r¼)i[e•[3]µr•³s=0ß»n•n¼¬oÊ[n]•Ä]û®Ä]ÞÄ][2]}Øt},Hò,Ü߸ìh=0,u=0,t" +
"»t•t¼•Y(ní[t®rôÉnÞiô+1;i•iÖoí[i®oÐ<s)rÊÉoЕoí[r®t!=r)i=oû®oûµnû®nûÌ1®o[1µnÞn[1Ì2®o[2µn[2®n[2Ì3®o[3µn[3®n[3µi•s!=h••t•+1;i<s•t•h=s,uô}}•l•+1;i<bg•l},N«V eÇl,p,x,g,m,b,y,w,S,G,D•r<c)s=1•t=30+(sæ)/3,SÊ,G=0,D=r,w=r/(3*sÃy=0|w/d,m=I,x=A@=0ìe»e<g;e¼)M[eµm*(ù-e*e^•r<c•Ër%o•*oËr%h•*hËr%u•*u•7 b=3*fìe»e<w;ºaÒ+0•,lÒ+1•,pÒ+2•ß=T(a,l,pÃR(mÇl,pÃ0!=g)Q(gÇl,p)•G+=b,G>=D)G-=r•e¼,0==y)y=1•0=í%yºm-=m/t,x-=x/k@=0ìn»n<g;n¼)M[nµm*(ù-n*n^}}}•n§6•¬s߸,f,cìu=1e3,c=æÉE[i®n=sæ;s<aÑn>»ºs<a)X(fí[s®o=fÐ-i,o>=u)s=a•7{X(s¼¯o•" +
"h=fÁtãºh=fÈrã)u=o,c=f[3]}}X(n>=0)X(fí[n®oÊ-fÞo>=u)n=æ•7{X(n--¯o•h=fÁtãºh=fÈrã)u=o,c=f[3]}}}Øc}ß.process«ØN(Ãq(ÃH(Ãz()}$qòìt»t•t¼)e[t]û]Ót]ÐÓt][2]Ót][3µt},Q•,s߬o,h,u,év•uÊ-t,u<æ)u=æ•fÊ+t,f>a)f=aìoÊ+1,hÊæ,l=1;o<fÑh>u;ºc=M[l¼®o<f•ví[o¼]•try{vû¡0]-rÍ1¡1]-sÍ2¡2]-n)/j}bf(d•}}X(h>u•ví[h--]•try{vû¡0]-rÍ1¡1]-sÍ2¡2]-n)/j}bf(d•}}}},R•,s߬aíוaÁô*(aÁr)/I,aÐ-ô*(aÐ-s)/I,aÈô*(aÈn)/I},T•¬s߸,éd,xìl=~(1<<31Ãd=l,f=æ,c=f,s»s•s¼ºxí[s®n=xÁtß<0)n=-n•o=xÐ-i¯o•n+=o,o=xÈr¯o•n+=oß<l)l=n,f=s•h=n-(Få>>p-vÃh<d)d=h,c=s•u=Lå" +
">>m,Lå-=u,Få+=u<<g}ØL[f]+=b,F[f]-=y,c}•ØO§pply(J,argumentsÃn}•0?134:¨¨¨???function¨this?return¨???var?prototype?if?for?hue•?length¨else??case?idxrgb?U•t8Õ?idxi32?nearestIndex?palLocked?hue·i32idx?U•t32Õ?m•HueColsðs?stats?num·•dexOf?histogram?Õ?push?width?switch?toFixed?•itColors?groupsFull?forEach?boxSize?break?255?method?i32rgb?i32i32?height?buffer?new?•itDist?distIncr?buildPal?16711680?sort?palette?m•Cols?check?call?buf32?boxPxls?Math?nearestColorð•2Dð•1D?num?•ject?null?getï?cols?4278190080?th" +
"old?sat·•Pal?lum·65280?NeuQuant?sortPal?map?`?cñ?Object?10?throw?slice?•??catch?256?16",
"]-=c*(v[•},t.W.a•(t,i){•(t,i,•,J.a•J.a•.a•????????•(B(i,r)•=B(t){•=B(){•){V ••X(•],•,o<0)o=-•,h<0)h=•Y(V r=•(t){K•Y(V •=t§•]=•°-h•o+=h•Groups?•,o,h,u•&t)>>•){X(•=0;•++•J.Z)J.Z§•)•ax•9 •aE ab(•£V r=•[0]-•}B •),•t[s¼µe[o•Element•§s(2))•,n,a,•[2]-•,s=•=i••7 X(0!=•µi,i=o[•)/j,v[•Image•t[o]•[1]•||•=(ay&S[G•>>=v,e[•aE ah(•Array•¼)X(•[i]•K •Ø¦J©{Øt(•=aQ.max•):2=Ê?•i,r,s•)•9••[1®•,n•ar(h(t))•Ùi,r)Ñs•(aI¹bh•¶,o<u• bd •[s]•-1•r§X(•2d••~~(t§m(•f,c,l,•/=ay,•=1<<••Y(•=e••ar(¦z){•Context•?color•anvas•«V t•={}•=t•[ay&t,(a•o»o<r;o•String•,e=s.•(g*g•§p(•[0•®0!=(aZ&•[2]?æ:n•)/2.3)•§v(B(•)b=3•<a;•){••:¦kקY••.h,t§fÕ•:n=nÑ•])<<v•=B¤r•[r]•=¦•Y(E[hµu+•>>1,i=h•annot •Stats•Ñba¥•=[®•in•;i¼)E[iµ•,g=x>>C•reduce••ax•9•):aW••V •})••,g<=1)g•)*P/ù))•sample•Å•:r=",
""
]
]));
</script>
</body>
</html>
// The following tells Js Bin to not protect us from infinite loops...
// noprotect
// Dithering an image converted to 256 colours
// I use RgbQuant to convert to 256 colours and then the following function to dither it.
// http://unitzeroone.com/blog/2008/05/06/opensource-image-dithering-for-as3-demosource/
// https://code.google.com/p/imageditheringas3/source/browse/trunk/ImageDitheringAS3/src/com/unitzeroone/fx/ImageDithering.as
Bitmap.prototype.dither = function(kernel, serpentine) {
var kernels = {
FalseFloydSteinberg: [
[3 / 8, 1, 0],
[3 / 8, 0, 1],
[2 / 8, 1, 1]
],
FloydSteinberg: [
[7 / 16, 1, 0],
[3 / 16, -1, 1],
[5 / 16, 0, 1],
[1 / 16, 1, 1]
],
Stucki: [
[8 / 42, 1, 0],
[4 / 42, 2, 0],
[2 / 42, -2, 1],
[4 / 42, -1, 1],
[8 / 42, 0, 1],
[4 / 42, 1, 1],
[2 / 42, 2, 1],
[1 / 42, -2, 2],
[2 / 42, -1, 2],
[4 / 42, 0, 2],
[2 / 42, 1, 2],
[1 / 42, 2, 2]
],
Atkinson: [
[1 / 8, 1, 0],
[1 / 8, 2, 0],
[1 / 8, -1, 1],
[1 / 8, 0, 1],
[1 / 8, 1, 1],
[1 / 8, 0, 2]
]
}
if (!kernel || !kernels[kernel]) return;
var ds = kernels[kernel];
var index = 0,
height = this.height,
width = this.width,
data = this.data;
var direction = serpentine ? -1 : 1;
for (var y = 0; y < height; y++) {
if (serpentine) direction = direction * -1;
for (var x = direction==1?0:width-1, xend = direction==1?width:0; x != xend; x=x+direction){
index = (y * width) + x;
// Get original colour
var idx = index * 4;
var r1 = data[idx];
var g1 = data[idx + 1];
var b1 = data[idx + 2];
// Get converted colour
idx = this.findPaletteColor([r1, g1, b1]);
this.indexedData[index] = idx;
idx = idx * 3;
var r2 = this.indexedPalette[idx];
var g2 = this.indexedPalette[idx + 1];
var b2 = this.indexedPalette[idx + 2];
var er = r1 - r2;
var eg = g1 - g2;
var eb = b1 - b2;
for (var i = direction==1?0:ds.length-1, end = direction==1?ds.length:0; i != end; i=i+direction) {
var x1 = ds[i][1]; // *direction; // Should this by timesd by direction?..to make the kernel go in the opposite direction....got no idea....
var y1 = ds[i][2];
if (x1 + x >= 0 && x1 + x < width && y1 + y >= 0 && y1 + y < height) {
var d = ds[i][0];
idx = index + x1 + (y1 * width);
idx = idx * 4;
r1 = data[idx] + (er * d);
g1 = data[idx + 1] + (eg * d);
b1 = data[idx + 2] + (eb * d);
data[idx] = r1;
data[idx + 1] = g1;
data[idx + 2] = b1;
}
}
}
}
}
drawTest('#dither');
var t = Date.now();
dithered = Bitmap('#dither');
dithered.createIndexedData(true);
dithered.dither('FloydSteinberg', false);
ditheringstats.innerText = 'FloydSteinberg - ' + (Date.now() - t);
dithered.pixelWidth = 1;
dithered.putImageData();
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |