var images = [ // "https://upload.wikimedia.org/wikipedia/commons/f/fe/Goldcrest_1.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Cistothorus_palustris_CT.jpg/450px-Cistothorus_palustris_CT.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Black-necked_Stilt_%28Himantopus_mexicanus%29%2C_Corte_Madera.jpg/362px-Black-necked_Stilt_%28Himantopus_mexicanus%29%2C_Corte_Madera.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cc/Daurian_redstart_at_Daisen_Park_in_Osaka%2C_January_2016.jpg/573px-Daurian_redstart_at_Daisen_Park_in_Osaka%2C_January_2016.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Myioborus_torquatus_Santa_Elena.JPG/675px-Myioborus_torquatus_Santa_Elena.JPG", "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ef/Great_tit_side-on.jpg/645px-Great_tit_side-on.jpg", "https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Sarcoramphus_papa_%28K%C3%B6nigsgeier_-_King_Vulture%29_-_Weltvogelpark_Walsrode_2013-01.jpg/675px-Sarcoramphus_papa_%28K%C3%B6nigsgeier_-_King_Vulture%29_-_Weltvogelpark_Walsrode_2013-01.jpg",, ]; function loadImageAddBorder(){ if(images.length === 0){ return ; // all done } var imageSrc = images.shift(); imageTools.loadImage( imageSrc,true, function(event){ var pixels, topRGB, c, rImage, wImage, botRGB, grad, i, hsl, h, s, l, hues, hslMap, wHue, hueCount, j, hr, gradCols, border; const IMAGE_WORK_SIZE = 128; const ICOUNT = IMAGE_WORK_SIZE * IMAGE_WORK_SIZE; if(event.type === "load"){ rImage = imageTools.createImage(IMAGE_WORK_SIZE, IMAGE_WORK_SIZE); // reducing image c = rImage.ctx; // This is where you can crop the image. In this example I only look at the center of the image c.drawImage(this,-16,-16,IMAGE_WORK_SIZE + 32, IMAGE_WORK_SIZE + 32); // reduce image size pixels = imageTools.getImageData(rImage).data; h = 0; s = 0; l = 0; // these are the colour ranges you wish to look at hues = [{ lum : { low :20, high : 60, tot : 0, }, sat : { // all saturations low : 0, high : 101, tot : 0, }, count : 0, histo : new Uint16Array(360), }] for(i = 0; i < pixels.length; i += 4){ hsl = imageTools.rgb2hsl(pixels[i],pixels[i + 1],pixels[i + 2]); l += hsl.l * hsl.l; } l = Math.sqrt(l/ICOUNT); hues[0].lum.low = l - 30; hues[0].lum.high = l + 30; for(i = 0; i < pixels.length; i += 4){ hsl = imageTools.rgb2hsl(pixels[i], pixels[i + 1], pixels[i + 2]); for(j = 0; j < hues.length; j ++){ hr = hues[j]; // hue range if(hsl.l >= hr.lum.low && hsl.l < hr.lum.high){ if(hsl.s >= hr.sat.low && hsl.s < hr.sat.high){ hr.histo[hsl.h] += 1; hr.count += 1; hr.lum.tot += hsl.l * hsl.l; hr.sat.tot += hsl.s; } } } } // get weighted hue for image // just to simplify code hue 0 and 1 (reds) can combine for(j = 0; j < hues.length; j += 1){ hr = hues[j]; wHue = 0; hueCount = 0; hr.histo[1] += hr.histo[0]; for(i = 1; i < 360; i ++){ wHue += (i) * hr.histo[i]; hueCount += hr.histo[i]; } h = Math.floor(wHue / hueCount); s = Math.floor(hr.sat.tot / hr.count); l = Math.floor(Math.sqrt(hr.lum.tot / hr.count)); hr.rgb = imageTools.hsl2rgb(h,s,l); hr.rgba = imageTools.hex2RGBA(imageTools.rgba2Hex4(hr.rgb)); } gradCols = hues.map(h=>h.rgba); if(gradCols.length === 1){ gradCols.push(gradCols[0]); // this is a quick fix if only one colour the gradient needs more than one } border = Math.floor(Math.min(this.width / 10,this.height / 10, 64)); wImage = imageTools.padImage(this,border,border); wImage.ctx.fillStyle = imageTools.createGradient( c, "linear", 0, 0, 0, wImage.height,gradCols ); wImage.ctx.fillRect(0, 0, wImage.width, wImage.height); wImage.ctx.fillStyle = "black"; wImage.ctx.fillRect(border - 2, border - 2, wImage.width - border * 2 + 4, wImage.height - border * 2 + 4); wImage.ctx.drawImage(this,border,border); wImage.style.width = (innerWidth -64) + "px"; document.body.appendChild(wImage); setTimeout(loadImageAddBorder,1000); } } ) } setTimeout(loadImageAddBorder,0); /** ImageTools.js begin **/ var imageTools = (function () { // This interface is as is. // No warenties no garenties, and /*****************************/ /* NOT to be used comercialy */ /*****************************/ var workImg,workImg1,keep; // for internal use keep = false; const toHex = v => (v < 0x10 ? "0" : "") + Math.floor(v).toString(16); var tools = { canvas(width, height) { // create a blank image (canvas) var c = document.createElement("canvas"); c.width = width; c.height = height; return c; }, createImage (width, height) { var i = this.canvas(width, height); i.ctx = i.getContext("2d"); return i; }, loadImage (url, crossSite, cb) { // cb is calback. Check first argument for status var i = new Image(); if(crossSite){ i.setAttribute('crossOrigin', 'anonymous'); } i.src = url; i.addEventListener('load', cb); i.addEventListener('error', cb); return i; }, image2Canvas(img) { var i = this.canvas(img.width, img.height); i.ctx = i.getContext("2d"); i.ctx.drawImage(img, 0, 0); return i; }, rgb2hsl(r,g,b){ // integers in the range 0-255 var min, max, dif, h, l, s; h = l = s = 0; r /= 255; // normalize channels g /= 255; b /= 255; min = Math.min(r, g, b); max = Math.max(r, g, b); if(min === max){ // no colour so early exit return { h, s, l : Math.floor(min * 100), // Note there is loss in this conversion } } dif = max - min; l = (max + min) / 2; if (l > 0.5) { s = dif / (2 - max - min) } else { s = dif / (max + min) } if (max === r) { if (g < b) { h = (g - b) / dif + 6.0 } else { h = (g - b) / dif } } else if(max === g) { h = (b - r) / dif + 2.0 } else {h = (r - g) / dif + 4.0 } h = Math.floor(h * 60); s = Math.floor(s * 100); l = Math.floor(l * 100); return {h, s, l}; }, hsl2rgb (h, s, l) { // h in range integer 0-360 (cyclic) and s,l 0-100 both integers var p, q; const hue2Channel = (h) => { h = h < 0.0 ? h + 1 : h > 1 ? h - 1 : h; if (h < 1 / 6) { return p + (q - p) * 6 * h } if (h < 1 / 2) { return q } if (h < 2 / 3) { return p + (q - p) * (2 / 3 - h) * 6 } return p; } s = Math.floor(s)/100; l = Math.floor(l)/100; if (s <= 0){ // no colour return { r : Math.floor(l * 255), g : Math.floor(l * 255), b : Math.floor(l * 255), } } h = (((Math.floor(h) % 360) + 360) % 360) / 360; // normalize if (l < 1 / 2) { q = l * (1 + s) } else { q = l + s - l * s } p = 2 * l - q; return { r : Math.floor(hue2Channel(h + 1 / 3) * 255), g : Math.floor(hue2Channel(h) * 255), b : Math.floor(hue2Channel(h - 1 / 3) * 255), } }, rgba2Hex4(r,g,b,a=255){ if(typeof r === "object"){ g = r.g; b = r.b; a = r.a !== undefined ? r.a : a; r = r.r; } return `#${toHex(r)}${toHex(g)}${toHex(b)}${toHex(a)}`; }, hex2RGBA(hex){ // Not CSS colour as can have extra 2 or 1 chars for alpha // #FFFF & #FFFFFFFF last F and FF are the alpha range 0-F & 00-FF if(typeof hex === "string"){ var str = "rgba("; if(hex.length === 4 || hex.length === 5){ str += (parseInt(hex.substr(1,1),16) * 16) + ","; str += (parseInt(hex.substr(2,1),16) * 16) + ","; str += (parseInt(hex.substr(3,1),16) * 16) + ","; if(hex.length === 5){ str += (parseInt(hex.substr(4,1),16) / 16); }else{ str += "1"; } return str + ")"; } if(hex.length === 7 || hex.length === 9){ str += parseInt(hex.substr(1,2),16) + ","; str += parseInt(hex.substr(3,2),16) + ","; str += parseInt(hex.substr(5,2),16) + ","; if(hex.length === 9){ str += (parseInt(hex.substr(7,2),16) / 255).toFixed(3); }else{ str += "1"; } return str + ")"; } return "rgba(0,0,0,0)"; } }, createGradient(ctx, type, x, y, xx, yy, colours){ // Colours MUST be array of hex colours NOT CSS colours // See this.hex2RGBA for details of format var i,g,c; var len = colours.length; if(type.toLowerCase() === "linear"){ g = ctx.createLinearGradient(x,y,xx,yy); }else{ g = ctx.createRadialGradient(x,y,xx,x,y,yy); } for(i = 0; i < len; i++){ c = colours[i]; if(typeof c === "string"){ if(c[0] === "#"){ c = this.hex2RGBA(c); } g.addColorStop(Math.min(1,i / (len -1)),c); // need to clamp top to 1 due to floating point errors causes addColorStop to throw rangeError when number over 1 } } return g; }, padImage(img,amount){ var image = this.canvas(img.width + amount * 2, img.height + amount * 2); image.ctx = image.getContext("2d"); image.ctx.drawImage(img, amount, amount); return image; }, getImageData(image, w = image.width, h = image.height) { // cut down version to prevent intergration if(image.ctx && image.ctx.imageData){ return image.ctx.imageData; } return (image.ctx || (this.image2Canvas(image).ctx)).getImageData(0, 0, w, h); }, }; return tools; })(); /** ImageTools.js end **/