diff --git a/html/bezier-easing.js b/html/bezier-easing.js new file mode 100644 index 0000000..b355f04 --- /dev/null +++ b/html/bezier-easing.js @@ -0,0 +1,104 @@ +/** + * https://github.com/gre/bezier-easing + * BezierEasing - use bezier curve for transition easing function + * by Gaëtan Renaudeau 2014 - 2015 – MIT License + */ + +// These values are established by empiricism with tests (tradeoff: performance VS precision) +var NEWTON_ITERATIONS = 4; +var NEWTON_MIN_SLOPE = 0.001; +var SUBDIVISION_PRECISION = 0.0000001; +var SUBDIVISION_MAX_ITERATIONS = 10; + +var kSplineTableSize = 11; +var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0); + +var float32ArraySupported = typeof Float32Array === 'function'; + +function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; } +function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; } +function C (aA1) { return 3.0 * aA1; } + +// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. +function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; } + +// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2. +function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); } + +function binarySubdivide (aX, aA, aB, mX1, mX2) { + var currentX, currentT, i = 0; + do { + currentT = aA + (aB - aA) / 2.0; + currentX = calcBezier(currentT, mX1, mX2) - aX; + if (currentX > 0.0) { + aB = currentT; + } else { + aA = currentT; + } + } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS); + return currentT; +} + +function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) { + for (var i = 0; i < NEWTON_ITERATIONS; ++i) { + var currentSlope = getSlope(aGuessT, mX1, mX2); + if (currentSlope === 0.0) { + return aGuessT; + } + var currentX = calcBezier(aGuessT, mX1, mX2) - aX; + aGuessT -= currentX / currentSlope; + } + return aGuessT; +} + +function LinearEasing (x) { + return x; +} + +function bezier (mX1, mY1, mX2, mY2) { + if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { + throw new Error('bezier x values must be in [0, 1] range'); + } + + if (mX1 === mY1 && mX2 === mY2) { + return LinearEasing; + } + + // Precompute samples table + var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize); + for (var i = 0; i < kSplineTableSize; ++i) { + sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2); + } + + function getTForX (aX) { + var intervalStart = 0.0; + var currentSample = 1; + var lastSample = kSplineTableSize - 1; + + for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) { + intervalStart += kSampleStepSize; + } + --currentSample; + + // Interpolate to provide an initial guess for t + var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]); + var guessForT = intervalStart + dist * kSampleStepSize; + + var initialSlope = getSlope(guessForT, mX1, mX2); + if (initialSlope >= NEWTON_MIN_SLOPE) { + return newtonRaphsonIterate(aX, guessForT, mX1, mX2); + } else if (initialSlope === 0.0) { + return guessForT; + } else { + return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2); + } + } + + return function BezierEasing (x) { + // Because JavaScript number are imprecise, we should guarantee the extremes are right. + if (x === 0 || x === 1) { + return x; + } + return calcBezier(getTForX(x), mY1, mY2); + }; +}; \ No newline at end of file diff --git a/html/color.js b/html/color.js new file mode 100644 index 0000000..eef8a81 --- /dev/null +++ b/html/color.js @@ -0,0 +1,111 @@ + + +const 转换十进制=function(num){ + return parseInt(num,16) +}; +const 十六进制转换十进制=function(color){ + if(!color) + color='EEEEEE' + return color.match(/\w\w/g).map(转换十进制) +}; +const 转换十六进制=function(num){ + num=num.toString(16); + return num.length==2?num:('0'+num) +}; +const 十进制颜色转换十六进制=function(arr){ + return arr.map(转换十六进制).join(''); +}; + + + + +const rgb2hsl=function(o){ + var + r=o[0], + g=o[1], + b=o[2]; + + r /= 255, g /= 255, b /= 255; + var + max = Math.max(r, g, b), + min = Math.min(r, g, b); + var + h, + s, + l = (max + min) / 2; + + if(max == min){ + h = s = 0; // achromatic + }else{ + var d = max - min; + s = l > 0.5 ? d / (2 - max - min) : d / (max + min); + switch(max){ + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + } + h /= 6; + //h *= 60; + } + + // h=Math.round(h*14); + // s=Math.round(s*7); + // l=Math.round(l*7); + + + return [h, s, l]; +}; +const hsl2rgb=function(hsl) { + // const hsl = /hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(hslValue); + var h = hsl[0];// / 360; + var s = hsl[1];// / 100; + var l = hsl[2];// / 100; + // console.log(h,s,l); + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1/6) return p + (q - p) * 6 * t; + if (t < 1/2) return q; + if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + var r, g, b; + if (s == 0) { + r = g = b = l; + } else { + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + return [ + Math.round(r*255), + Math.round(g*255), + Math.round(b*255) + ] + // return `rgb(${r * 255},${g * 255},${b * 255})`; +}; + +const hax2burn=function(hax,burn){ + const hsl=rgb2hsl(十六进制转换十进制(hax)) + + hsl[1] = hsl[1]/5 + 0.6 + hsl[2]= burn + // console.log(hsl) + return 十进制颜色转换十六进制(hsl2rgb(hsl)) +}; +const hax2light=function(hax,l){ + const hsl=rgb2hsl(十六进制转换十进制(hax)) + + hsl[2]= l + // console.log(hsl) + return 十进制颜色转换十六进制(hsl2rgb(hsl)) +}; + + +// export { +// 十六进制转换十进制, +// hax2burn, +// hax2light +// } \ No newline at end of file diff --git a/html/document.js b/html/document.js new file mode 100644 index 0000000..8daec37 --- /dev/null +++ b/html/document.js @@ -0,0 +1,229 @@ + +const readFileToURL = (file,onOver)=>{ + var reader = new FileReader(); + reader.onload = ()=>{ + const src = reader.result; + onOver(src); + }; + reader.readAsDataURL(file); +}; + +const readFileAndSetIMGSrc = file=>{ + readFileToURL(file,src=>{ + // const img = new Image(); + const img = document.querySelector('img') + img.src = src; + img.onload = ()=>{ + app.img = img; + louvre(img,app.style, src=>{ + app.src = src; + }); + } + }); +}; + +const isImageRegex = /^image\/(jpeg|gif|png|bmp|webp)$/; + +document.addEventListener('paste',e=>{ + // console.log(e.clipboardData,e.clipboardData.files); + + const clipboardData = e.clipboardData; + if(clipboardData.items[0]){ + let file = clipboardData.items[0].getAsFile(); + + if(file && isImageRegex.test(file.type)){ + return readFileAndSetIMGSrc(file); + } + } + + if(clipboardData.files.length){ + for(let i = 0;i{ + e.preventDefault(); +}); +document.addEventListener('drop',e=>{ + e.preventDefault(); + + const file = e.dataTransfer.files[0]; + + if(file && file.type.match(isImageRegex)){ + readFileAndSetIMGSrc(file); + } +}); + +const _louvre = (img,style,callback)=>{ + + clearTimeout(louvre.T); + louvre.T = setTimeout(()=>{ + louvre(img,style,callback); + app.saveData(); + },100); +}; + +const deepCopy = o=>JSON.parse(JSON.stringify(o)); + + + + + +const creatConvoluteCenterHigh = (w,centerV)=>{ + const arr = []; + const c = Math.floor((w*w)/2); + + for(let x = 0; x < w; x++){ + for(let y = 0; y < w; y++){ + let i = x * w + y; + arr[i] = -1; + + if(i===c){ + arr[i] = centerV; + } + } + } + return arr; +} +const creatConvoluteAverage = (w)=>new Array(w*w).fill(1/(w*w)) + + +const Convolutes = { + // '右倾': [ + // 0, -1, 0, + // -1, 2, 2, + // 0, -1, 0 + // ], + // '左倾': [ + // 0, -1, 0, + // 3, 2, -2, + // 0, -1, 0 + // ], + '极精细': creatConvoluteAverage(3), + '精细': creatConvoluteAverage(5), + '一般': creatConvoluteAverage(7), + '粗线条': creatConvoluteAverage(9), + '超粗': creatConvoluteAverage(11), + // '12421': [ + // -3,2,-3, + // 2,4, 2, + // -3,2,-3, + // ], + // '9,-1,8': [ + // -1 ,-1 ,-1 , + // -1 , 9 ,-1 , + // -1 ,-1 ,-1 , + // ], + '25,-1,24':creatConvoluteCenterHigh(5,24), + // '25,-1,25': creatConvoluteCenterHigh(5,25), + // '25,-1,26': [ + // -1 , -1 , -1 , -1 , -1 , + // -1 , -1 , -1 , -1 , -1 , + // -1 , -1 , 26 , -1 , -1 , + // -1 , -1 , -1 , -1 , -1 , + // -1 , -1 , -1 , -1 , -1 , + // ], + // '-1,0,16': [ + // -1 , -1 , -1 , -1 , -1 , + // -1 , 0 , 0 , 0 , -1 , + // -1 , 0 , 17 , 0 , -1 , + // -1 , 0 , 0 , 0 , -1 , + // -1 , -1 , -1 , -1 , -1 , + // ], + '浮雕': [ + 1, 1, 1, + 1, 1, -1, + -1, -1, -1 + ] +} + +const style = { + zoom:1.3, + light:0, + s:80, + l:50, + black:true, + convoluteName: '一般', + convolute1Diff: true, + convoluteName2: null, + Convolutes, + contrast: 30, + invertLight: false, + hue:false, + hueGroup:255, + lightGroup:1, + lightCut: 128, + darkCut: 120, +}; + + + +const data = { + img:null, + style +}; + + + + + +const chooseFile = callback=>{ + chooseFile.form.reset(); + chooseFile.input.onchange = function(){ + if(!this.files||!this.files[0])return; + callback(this.files[0]); + }; + chooseFile.input.click(); +}; +chooseFile.form = document.createElement('form'); +chooseFile.input = document.createElement('input'); +chooseFile.input.type = 'file'; +chooseFile.form.appendChild(chooseFile.input); + + + +const app = new Vue({ + el:'.app', + data, + methods: { + _louvre(){ + clearTimeout(app.T) + app.T = setTimeout(this.louvre,300) + }, + louvre(){ + app.src = louvre(this.img,{ + ...this.style, + Convolutes, + }); + }, + setImageAndDraw(e){ + let img = e.target; + + console.log(img); + this.img = img; + this.louvre(); + }, + output(){ + const a = document.createElement('a'); + a.href = this.src; + a.download = `[lab.magiconch.com][90s-time-machine]-${+Date.now()}.jpg`; + a.click(); + }, + chooseFile(){ + chooseFile(readFileAndSetIMGSrc) + }, + }, + watch:{ + style:{ + deep:true, + handler(){ + this._louvre(); + } + } + } +}); \ No newline at end of file diff --git a/html/images/asuka.jpg b/html/images/asuka.jpg new file mode 100644 index 0000000..59c46c1 Binary files /dev/null and b/html/images/asuka.jpg differ diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..a44d3bb --- /dev/null +++ b/html/index.html @@ -0,0 +1,120 @@ + + + + +卷积 + + + + + +
+
+ + + +
+ light + + +
+ +
+ s + + +
+
+ l + + +
+
+ 线条方案1 + + +
+ + + +
+ +
+ + + + + + + + + +
+ 黑场切断 + + +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/html/louvre.js b/html/louvre.js new file mode 100644 index 0000000..fc8102d --- /dev/null +++ b/html/louvre.js @@ -0,0 +1,541 @@ +/** + * @author itorr + * @date 2020-04-08 + * @Description 蒸汽波风格化处理 + * */ + + + +const rgb2yuv = (r, g, b) => { + var y, u, v; + + y = r * .299000 + g * .587000 + b * .114000; + u = r * -.168736 + g * -.331264 + b * .500000 + 128; + v = r * .500000 + g * -.418688 + b * -.081312 + 128; + + y = Math.floor(y); + u = Math.floor(u); + v = Math.floor(v); + + return [y, u, v]; +}; + +const yuv2rgb = (y, u, v) => { + var r, g, b; + + r = y + 1.4075 * (v - 128); + g = y - 0.3455 * (u - 128) - (0.7169 * (v - 128)); + b = y + 1.7790 * (u - 128); + + r = Math.floor(r); + g = Math.floor(g); + b = Math.floor(b); + + r = (r < 0) ? 0 : r; + r = (r > 255) ? 255 : r; + + g = (g < 0) ? 0 : g; + g = (g > 255) ? 255 : g; + + b = (b < 0) ? 0 : b; + b = (b > 255) ? 255 : b; + + return [r, g, b]; +}; + +const randRange = (a, b) => Math.floor(Math.random() * (b - a) + a); + +const inputImageEl = document.querySelector('#input'); + +const canvas = document.createElement('canvas'); +const ctx = canvas.getContext('2d'); + +document.body.appendChild(canvas) + +let width = 640; +let height = 480; +let scale = width / height; + + + +let lastConfigString = null; + + +const canvasBlack = document.createElement('canvas'); +const canvasBlackMin = document.createElement('canvas'); +const canvasMin = document.createElement('canvas'); + +const louvre = (img, config, callback) => { + if (!img || !config) return; + + const configString = [ + JSON.stringify(config), + img.src, + ].join('-'); + + if (lastConfigString === configString) return; + + console.time('louvre'); + + lastConfigString = configString; + + const oriWidth = img.naturalWidth; + const oriHeight = img.naturalHeight; + + let oriScale = oriWidth / oriHeight; + + + + // const _width = Math.floor( width / config.zoom ); + // const _height = Math.floor( height / config.zoom ); + + const _width = Math.floor( oriWidth / config.zoom ); + const _height = Math.floor( oriHeight / config.zoom ); + + // const _width = 800; + // const _height = 800; + + + canvas.width = _width; + canvas.height = _height; + + let cutLeft = 0; + let cutTop = 0; + + let calcWidth = oriWidth; + let calcHeight = oriHeight; + + + // if(oriScale > 1){ + // cutLeft = (oriScale - 1) * oriHeight / 2; + // calcWidth = oriHeight; + // }else{ + // cutTop = (1 - oriScale) * oriHeight / 2; + // calcHeight = oriWidth; + // } + + let setLeft = 0; + let setTop = 0; + + let setWidth = _width; + let setHeight = _height; + + + + + ctx.drawImage( + img, + cutLeft, cutTop, + calcWidth, calcHeight, + + setLeft, setTop, + setWidth, setHeight + ); + + + let pixel = ctx.getImageData(0, 0, _width, _height); + + + + let pixelData = pixel.data; + + + for (let i = 0; i < pixelData.length; i += 4) { + // let yuv = rgb2yuv( + // pixelData[i], + // pixelData[i + 1], + // pixelData[i + 2], + // ); + const r = pixelData[i]; + const g = pixelData[i + 1]; + const b = pixelData[i + 2]; + + let y = r * .299000 + g * .587000 + b * .114000; + y = Math.floor(y); + + // if(i%10000) console.log(y); + + pixelData[i ] = y; + pixelData[i + 1] = y; + pixelData[i + 2] = y; + } + let blackPixel; + + if(config.black){ + // 处理暗面 + blackPixel = ctx.createImageData(_width, _height); + + for (let i = 0; i < pixelData.length; i += 4) { + let y = pixelData[i]; + + y = y > 80 ? 0 : (40 + Math.random() * 40 - 20); + + // y = Math.max(255-y) * 0.6; + + blackPixel.data[i ] = y; + blackPixel.data[i + 1] = 128; + blackPixel.data[i + 2] = 128; + blackPixel.data[i + 3] = Math.floor(Math.random() * 255)//Math.ceil( y + Math.random() * 40 - 20); + } + + // /* + // document.body.appendChild(canvasBlack) + + const ctxBlack = canvasBlack.getContext('2d'); + const ctxBlackMin = canvasBlackMin.getContext('2d'); + + canvasBlack.width = _width; + canvasBlack.height = _height; + + console.log({blackPixel}) + + ctxBlack.putImageData(blackPixel, 0, 0); + + // ctxBlack.fillText('123233',50,50); + const blackZoom = 4; + canvasBlackMin.width = Math.floor(_width/blackZoom); + canvasBlackMin.height = Math.floor(_height/blackZoom); + + ctxBlackMin.drawImage( + canvasBlack, + 0,0, + canvasBlackMin.width,canvasBlackMin.height + ); + + ctxBlack.clearRect(0,0,_width,_height) + ctxBlack.drawImage( + canvasBlackMin, + 0,0, + _width,_height + ); + blackPixel = ctxBlack.getImageData(0,0,_width,_height); + + } + + const { + light = 0, + } = config; + if(light){ + + + for (let i = 0; i < pixelData.length; i += 4) { + let y = pixelData[i]; + + y = y + y * (light/100); + + pixelData[i ] = y; + pixelData[i + 1] = y; + pixelData[i + 2] = y; + } + + // ctx.putImageData(pixel, 0, 0); + // pixel = ctx.getImageData(0, 0, _width, _height); + } + + + + + let pixel1 = config.convoluteName ? convoluteY( + pixel, + config.Convolutes[config.convoluteName] + ) : pixel; + + // if(config.contrast){ + // for (let i = 0; i < pixel1.data.length; i +=4) { + // let r = (pixel1.data[i]-128) * config.contrast + 128; + // pixel1.data[i ] = r; + // pixel1.data[i+1] = r; + // pixel1.data[i+2] = r; + // pixel1.data[i+3] = 255; + // } + // } + + if(config.convolute1Diff){ + let pixel2 = config.convoluteName2 ? convoluteY( + pixel, + config.Convolutes[config.convoluteName2] + ) : pixel; + + console.log(/pixel2/,config.Convolutes[config.convoluteName2],pixel2); + // pixelData + for (let i = 0; i < pixel2.data.length; i +=4) { + let r = 128 + pixel2.data[i ] - pixel1.data[i ]; + pixel2.data[i ] = r; + pixel2.data[i+1] = r; + pixel2.data[i+2] = r; + pixel2.data[i+3] = 255; + } + pixel = pixel2; + }else{ + // 不对比 + pixel = pixel1; + } + + pixelData = pixel.data; + + if(config.invertLight){ + for (let i = 0; i < pixelData.length; i += 4) { + let r = 255 - pixelData[i] + pixelData[i ] = r + pixelData[i+1 ] = r + pixelData[i+2 ] = r + } + } + + if(config.lightGroup!==1){ + for (let i = 0; i < pixelData.length; i += 4) { + let y = pixelData[i]; + + const isOdd = Math.floor(y / (255/config.lightGroup)) % 2 === 0; + + + y = y % (255/config.lightGroup) * config.lightGroup; + + if(isOdd) y = 255 - y; + + pixelData[i+0 ] = y + pixelData[i+1 ] = y + pixelData[i+2 ] = y + } + } + + + if(config.lightCut || config.darkCut){ + const scale = 255 / (255 - config.lightCut - config.darkCut); + for (let i = 0; i < pixelData.length; i += 4) { + let y = pixelData[i]; + + y = (y - config.darkCut) * scale; + + y = Math.max(0,y); + + pixelData[i+0 ] = y + pixelData[i+1 ] = y + pixelData[i+2 ] = y + pixelData[i+3 ] = 255 + } + } + const hStart = 30; + const hEnd = -184; + + const be = bezier(0.57, 0.01, 0.43, 0.99); + const s = config.s/100; + + + const gradient = ctx.createLinearGradient(0,0, _width,_height); + + // Add three color stops + gradient.addColorStop(0, '#fbba30'); + gradient.addColorStop(0.4, '#fc7235'); + gradient.addColorStop(.6, '#fc354e'); + gradient.addColorStop(0.7, '#cf36df'); + gradient.addColorStop(.8, '#37b5d9'); + gradient.addColorStop(1, '#3eb6da'); + + // Set the fill style and draw a rectangle + ctx.fillStyle = gradient; + ctx.fillRect(0, 0, _width, _height); + let gradientPixel = ctx.getImageData(0, 0, _width, _height); + + for (let i = 0; i < pixelData.length; i += 4) { + let y = pixelData[i]; + let p = Math.floor(i / 4); + + let _h = Math.floor(p/_width); + let _w = p % _width; + + /* + + // const + // hScale = hScale * hScale; + + let hScale = (_h + _w)/(_width + _height); + + hScale = hScale * hScale; + hScale = be(hScale); + + // let h = Math.floor((hStart + (hScale) * (hEnd - hStart))); + let [h] = rgb2hsl([ + gradientPixel.data[i + 0], + gradientPixel.data[i + 1], + gradientPixel.data[i + 2], + ]); + const l = y/255; + const rgb = hsl2rgb([h, s, l * (1 - config.l/100) + (config.l/100)]); + + if(i%5677===0){ + // console.log(h,y,l,l * (config.l/100) + (1 - config.l/100)) + // console.log((_h + _w)/(_width + _height),hScale) + } + + pixelData[i+0 ] = rgb[0]; + pixelData[i+1 ] = rgb[1]; + pixelData[i+2 ] = rgb[2]; + pixelData[i+3 ] = 255; + */ + + pixelData[i+0 ] = gradientPixel.data[i + 0]; + pixelData[i+1 ] = gradientPixel.data[i + 1]; + pixelData[i+2 ] = gradientPixel.data[i + 2]; + + y = 255 - y; + if(config.black){ + y = Math.max( + y, + blackPixel.data[i] + ); + } + pixelData[i+3 ] = y + } + + + if(config.hue){ + for (let i = 0; i < pixelData.length; i += 4) { + let y = pixelData[i ] + y = y % config.hueGroup * (255/config.hueGroup) /255; + const [r,g,b] = hsl2rgb([y,.7,.5]) + pixelData[i+0 ] = r + pixelData[i+1 ] = g + pixelData[i+2 ] = b + } + } + + // for(let i = 0;i < pixelData.length;i += 4){ + + // let _rgb = yuv2rgb( + // pixelData[i], + // pixelData[i+1], + // pixelData[i+2], + // ); + + // pixelData[i ] = _rgb[0]; + // pixelData[i+1 ] = _rgb[1]; + // pixelData[i+2 ] = _rgb[2]; + // } + + // blurC(); + ctx.putImageData(pixel, 0, 0); + + const ctxMin = canvasMin.getContext('2d'); + + canvasMin.width = Math.floor(_width/1.4); + canvasMin.height = Math.floor(_height/1.3); + + ctxMin.clearRect(0,0,canvasMin.width,canvasMin.height) + ctxMin.drawImage( + canvas, + 0,0, + canvasMin.width,canvasMin.height + ); + + ctx.clearRect(0,0,_width,_height) + ctx.drawImage( + canvasMin, + 0,0, + canvasMin.width,canvasMin.height, + 0,0,_width,_height + ); + + console.timeEnd('louvre'); + // return canvas.toDataURL('image/png'); +}; + + + +let convolute = (pixels, weights) => { + const side = Math.round(Math.sqrt(weights.length)); + const halfSide = Math.floor(side / 2); + + const src = pixels.data; + const sw = pixels.width; + const sh = pixels.height; + + const w = sw; + const h = sh; + const output = ctx.createImageData(w, h); + const dst = output.data; + + + for (let y = 0; y < h; y++) { + for (let x = 0; x < w; x++) { + const sy = y; + const sx = x; + const dstOff = (y * w + x) * 4; + let r = 0, g = 0, b = 0; + for (let cy = 0; cy < side; cy++) { + for (let cx = 0; cx < side; cx++) { + const scy = Math.min(sh - 1, Math.max(0, sy + cy - halfSide)); + const scx = Math.min(sw - 1, Math.max(0, sx + cx - halfSide)); + const srcOff = (scy * sw + scx) * 4; + const wt = weights[cy * side + cx]; + r += src[srcOff] * wt; + g += src[srcOff + 1] * wt; + b += src[srcOff + 2] * wt; + } + } + dst[dstOff] = r; + dst[dstOff + 1] = g; + dst[dstOff + 2] = b; + dst[dstOff + 3] = 255; + } + } + + + // for (let y=0; y { + const side = Math.round( Math.sqrt( weights.length ) ); + const halfSide = Math.floor(side / 2); + + const src = pixels.data; + + const w = pixels.width; + const h = pixels.height; + const output = ctx.createImageData(w, h); + const dst = output.data; + + for (let sy = 0; sy < h; sy++) { + for (let sx = 0; sx < w; sx++) { + const dstOff = (sy * w + sx) * 4; + let r = 0, g = 0, b = 0; + + for (let cy = 0; cy < side; cy++) { + for (let cx = 0; cx < side; cx++) { + + const scy = Math.min(h - 1, Math.max(0, sy + cy - halfSide)); + const scx = Math.min(w - 1, Math.max(0, sx + cx - halfSide)); + + const srcOff = (scy * w + scx) * 4; + const wt = weights[cy * side + cx]; + + r += src[srcOff] * wt; + // g += src[srcOff + 1] * wt; + // b += src[srcOff + 2] * wt; + } + } + dst[dstOff] = r; + dst[dstOff + 1] = r; + dst[dstOff + 2] = r; + dst[dstOff + 3] = 255; + } + } + + + // for (let y=0; y