// @ts-nocheck window.GPUImagesService = {}; const colorPalettes = [ { id: 0, name: 'Gray', f: gray }, { id: 1, name: 'Cold & Hot', f: coldhot }, { id: 2, name: 'AMF hot', f: afmhot }, { id: 3, name: 'Rainbow HC', f: rainbowhc }, { id: 4, name: 'Rainbow', f: rainbow }, { id: 5, name: 'Arctic', f: arctic }, { id: 6, name: 'Lava', f: lava }, { id: 7, name: 'Jet', f: jet }, ]; window.GPUImagesService.colorPalettes = colorPalettes; function imageKernel(imageData, palette, minTempFilter, maxTempFilter, scaling) { const n = 4 * (this.thread.x + this.constants.w * (this.constants.h - this.thread.y)); let normalizedTemp = imageData[n] / 256; // 0...1 if (scaling == 1) { normalizedTemp = (normalizedTemp - minTempFilter) / (maxTempFilter - minTempFilter); } else { if (normalizedTemp < minTempFilter) normalizedTemp = 0; // minTempFilter 0 if (normalizedTemp > maxTempFilter) normalizedTemp = 1; // maxTempFilter 1 } if (palette === 0) { const [r, g, b] = gray(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 1) { const [r, g, b] = afmhot(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 2) { const [r, g, b] = coldhot(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 3) { const [r, g, b] = rainbowhc(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 4) { const [r, g, b] = rainbow(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 5) { const [r, g, b] = arctic(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 6) { const [r, g, b] = lava(normalizedTemp); this.color(r, g, b, 1); } else if (palette === 7) { const [r, g, b] = jet(normalizedTemp); this.color(r, g, b, 1); } } function maskKernel(imageData, minThreshold, maxThreshold) { const n = 4 * (this.thread.x + this.constants.w * (this.constants.h - this.thread.y)); const pixelValue = imageData[n] / 255; // 0...1 if (pixelValue <= minThreshold || pixelValue > maxThreshold) { this.color(0, 0, 0, 0); } else { const [r, g, b] = jet(pixelValue); this.color(r, g, b, 1); } } function sliderKernelFunction(palette, minTempFilter, maxTempFilter, scaling) { let t = this.thread.y / this.constants.h; if (scaling == 1) { t = (t - minTempFilter) / (maxTempFilter - minTempFilter); } else { if (t < minTempFilter) t = 0; // minTempFilter 0 if (t > maxTempFilter) t = 1; // maxTempFilter 1 } if (palette === 0) { const [r, g, b] = gray(t); this.color(r, g, b, 1); } else if (palette === 1) { const [r, g, b] = afmhot(t); this.color(r, g, b, 1); } else if (palette === 2) { const [r, g, b] = coldhot(t); this.color(r, g, b, 1); } else if (palette === 3) { const [r, g, b] = rainbowhc(t); this.color(r, g, b, 1); } else if (palette === 4) { const [r, g, b] = rainbow(t); this.color(r, g, b, 1); } else if (palette === 5) { const [r, g, b] = arctic(t); this.color(r, g, b, 1); } else if (palette === 6) { const [r, g, b] = lava(t); this.color(r, g, b, 1); } else if (palette === 7) { const [r, g, b] = jet(t); this.color(r, g, b, 1); } } function maskSliderKernel(minThreshold, maxThreshold) { let t = this.thread.y / this.constants.h; if (t < minThreshold) t = 0; if (t > maxThreshold) t = 0; if (t == 0) { this.color(0, 0, 0, 0); } else { const [r, g, b] = jet(t); this.color(r, g, b, 1); } } class ThermalSliderHelper { sliderGpu; sliderKernel; constructor(sliderCanvas) { const width = 1; const height = 500; this.sliderGpu = new GPU({ canvas: sliderCanvas }); colorPalettes.forEach(p => { this.sliderGpu.addFunction(p.f); }); this.sliderKernel = this.sliderGpu.createKernel(sliderKernelFunction) .setConstants({ w: width, h: height }) .setGraphical(true) .setOutput([ width, height ]); } updateSlider(params) { this.sliderKernel( params.palette.id, params.paletteNormalMinTemp, params.paletteNormalMaxTemp, params.scaling ? 1 : 0 ); } } window.GPUImagesService.ThermalSliderHelper = ThermalSliderHelper; class ThermalImageHelper { canvas; kernel; imageData; w; h; context; minTemp; maxTemp; img; constructor(img, params) { this.img = img; this.w = img.width; this.h = img.height; this.minTemp = params.minTemp; this.maxTemp = params.maxTemp; const inputImageCanvas = document.createElement('canvas'); inputImageCanvas.setAttribute('width', this.w); inputImageCanvas.setAttribute('height', this.h); this.context = inputImageCanvas.getContext('2d', { willReadFrequently: true }); this.context.willReadFrequently = true; this.context.drawImage(img, 0, 0, this.w, this.h); this.imageData = this.context.getImageData(0, 0, this.w, this.h); inputImageCanvas.remove(); this.canvas = document.createElement('canvas'); this.canvas.width = img.width; this.canvas.height = img.height; this.canvas.style.visibility = 'hidden'; const thermalImageGpu = new GPU({ canvas: this.canvas }); colorPalettes.forEach(p => { thermalImageGpu.addFunction(p.f); }); this.kernel = thermalImageGpu.createKernel(imageKernel) .setConstants({ w: img.width, h: img.height }) .setGraphical(true) .setOutput([ img.width, img.height ]); } updateThermalImage(params) { this.kernel( this.imageData.data, params.palette ? params.palette.id : 0, params.paletteNormalMinTemp, params.paletteNormalMaxTemp, params.scaling ? 1 : 0 ); return this.canvas; } getTemperature(x, y, emissivity = 1) { if (x >= this.w || y >= this.h || x < 0 || y < 0) return; x = Math.round(x); y = Math.round(y); return (this.context.getImageData(x, y, 1, 1).data[0] / 255) * (this.maxTemp * emissivity - this.minTemp) + this.minTemp; } getTemperatureFromRectangle(x1, y1, width, height, emissivity = 1) { x1 = Math.round(x1); y1 = Math.round(y1); width = Math.round(width); height = Math.round(height); const t = { min: { value: 99999, pos: { x: 0, y: 0 } }, max: { value: -273, pos: { x: 0, y: 0 } }, avg: 0 }; const imageData = Array.from(this.context.getImageData(x1, y1, width, height).data); imageData.forEach((pixel, pixelIndex) => { if (pixelIndex % 4 === 0) { // only one channel (it's grayscale image) const pixelNumber = pixelIndex / 4; const offsetX = pixelNumber % width; const offsetY = Math.floor(pixelNumber / width); const pixelTemp = (pixel / 255) * (this.maxTemp * emissivity - this.minTemp) + this.minTemp; t.avg += pixelTemp; if (pixelTemp > t.max.value) { t.max = { value: pixelTemp, pos: { x: x1 + offsetX, y: y1 + offsetY } }; } if (pixelTemp < t.min.value) { t.min = { value: pixelTemp, pos: { x: x1 + offsetX, y: y1 + offsetY } }; } } }); t.avg /= imageData.length / 4; return t; } getTemperaturePolygon(points, emissivity = 1) { const box = { x1: 999999, y1: 999999, x2: 0, y2: 0 }; points.forEach(point => { const x = Math.round(point[0]); const y = Math.round(point[1]); box.x1 = Math.min(x, box.x1); box.x2 = Math.max(x, box.x2); box.y1 = Math.min(y, box.y1); box.y2 = Math.max(y, box.y2); }); const rows = box.y2 - box.y1; const cols = box.x2 - box.x1; const shiftedPoints = points.map(p => [Math.round(p[0] - box.x1), Math.round(p[1] - box.y1)]); const mask = PolygonHelper.getMask(rows + 1, cols + 1, shiftedPoints); const t = { min: { value: 99999, pos: { x: 0, y: 0 } }, max: { value: -273, pos: { x: 0, y: 0 } }, avg: 0 }; let polygonPixelsCount = 0; const imgDataArray = this.imageData.data; for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (mask[r][c] === 1) { polygonPixelsCount++; const absoluteRowNumber = r + box.y1; const absoluteColNumber = c + box.x1; const n = (absoluteRowNumber * this.imageData.width + absoluteColNumber) * 4; const pixelTemp = (imgDataArray[n] / 255) * (this.maxTemp * emissivity - this.minTemp) + this.minTemp; t.avg += pixelTemp; if (pixelTemp > t.max.value) { t.max = { value: pixelTemp, pos: { x: box.x1 + c, y: box.y1 + r } }; } if (pixelTemp < t.min.value) { t.min = { value: pixelTemp, pos: { x: box.x1 + c, y: box.y1 + r } }; } } } } t.avg /= polygonPixelsCount; return t; } } window.GPUImagesService.ThermalImageHelper = ThermalImageHelper; class MaskSliderHelper { sliderGpu; sliderKernel; constructor(sliderCanvas) { const width = 1; const height = 500; this.sliderGpu = new GPU({ canvas: sliderCanvas }); this.sliderGpu.addFunction(jet); this.sliderKernel = this.sliderGpu.createKernel(maskSliderKernel) .setConstants({ w: width, h: height }) .setGraphical(true) .setOutput([ width, height ]); } update(minThreshold, maxThreshold) { this.sliderKernel(minThreshold, maxThreshold); } } window.GPUImagesService.MaskSliderHelper = MaskSliderHelper; class MaskImageHelper { img; w; h; GPUcanvas; imageData; context; constructor(img) { this.img = img; this.w = img.width; this.h = img.height; // create original image data const inputImageCanvas = document.createElement('canvas'); inputImageCanvas.setAttribute('width', this.w); inputImageCanvas.setAttribute('height', this.h); // document.body.appendChild(inputImageCanvas); // inputImageCanvas.id = 'inputImageCanvas'; // inputImageCanvas.style.cssText = 'position: absolute; top: 0; right: 0; z-index: 1000;'; this.context = inputImageCanvas.getContext('2d', { willReadFrequently: true }); this.context.drawImage(img, 0, 0, this.w, this.h); this.imageData = this.context.getImageData(0, 0, this.w, this.h); inputImageCanvas.remove(); this.GPUcanvas = document.createElement('canvas'); // this.GPUcanvas.style.cssText = 'position: absolute; top: 0; right: 0; z-index: 1000;'; // document.body.appendChild(this.GPUcanvas); this.GPUcanvas.width = img.width; this.GPUcanvas.height = img.height; const maskGpu = new GPU({ canvas: this.GPUcanvas }); maskGpu.addFunction(jet); this.maskKernel = maskGpu.createKernel(maskKernel) .setConstants({ w: img.width, h: img.height }) .setGraphical(true) .setOutput([ img.width, img.height ]); } updateMask(minThreshold, maxThreshold) { this.maskKernel(this.imageData.data, minThreshold, maxThreshold); return this.removeBlack(); } removeBlack() { return new Promise((resolve, reject) => { const image = new Image(); image.src = this.GPUcanvas.toDataURL(); const canvasBuffer = document.createElement('canvas'); const ctx = canvasBuffer.getContext('2d'); canvasBuffer.setAttribute('width', this.w); canvasBuffer.setAttribute('height', this.h); image.onload = () => { ctx.drawImage(image, 0, 0, this.w, this.h); const imageData = ctx.getImageData(0, 0, this.w, this.h); const data = imageData.data; for (let i = 0; i < data.length; i += 4) { if (data[i] + data[i + 1] + data[i + 2] < 10) { data[i + 3] = 0; // alpha } } ctx.putImageData(imageData, 0, 0); resolve(canvasBuffer); } }); } } window.GPUImagesService.MaskImageHelper = MaskImageHelper; ////////////////////////////////// // colors ////////////////////////////////// function coldhot(normTemp) { let r = 2 * normTemp; let g = 0; let b = 2 - normTemp * 2; return [r, g, b]; } function afmhot(normTemp) { let r = 2 * normTemp; let g = 2 * normTemp - 0.5; let b = 2 * normTemp - 1; return [r, g, b]; } function gray(normTemp) { let r = normTemp; let g = normTemp; let b = normTemp; return [r, g, b]; } function rainbowhc(normTemp) { let colorCount = 7; let floorColorNumber = Math.floor(normTemp * colorCount); let startColor = [0, 0, 0]; let endColor = [0, 0, 0]; if (floorColorNumber == 0) { startColor = [0, 0, 0]; endColor = [0.7, 0, 0.7]; } else if (floorColorNumber == 1) { startColor = [0.7, 0, 0.7]; endColor = [0, 0, 0.5]; } else if (floorColorNumber == 2) { startColor = [0, 0, 0.5]; endColor = [0, 0.8, 0.8]; } else if (floorColorNumber == 3) { startColor = [0, 0.8, 0.8]; endColor = [0, 0.4, 0]; } else if (floorColorNumber == 4) { startColor = [0, 0.4, 0]; endColor = [0.8, 0.8, 0]; } else if (floorColorNumber == 5) { startColor = [0.8, 0.8, 0]; endColor = [0.8, 0.0, 0]; } else if (floorColorNumber >= 6) { startColor = [0.8, 0, 0]; endColor = [1, 1, 1]; } const colorProgress = (normTemp * colorCount) - floorColorNumber; let r = endColor[0] * colorProgress + startColor[0] * (1 - colorProgress); let g = endColor[1] * colorProgress + startColor[1] * (1 - colorProgress); let b = endColor[2] * colorProgress + startColor[2] * (1 - colorProgress); return [r, g, b]; } function rainbow(normalizedTemp) { let colorCount = 5; let floorColorNumber = Math.floor(normalizedTemp * colorCount); let startColor = [0, 0, 0]; let endColor = [0, 0, 0]; if (floorColorNumber == 0) { startColor = [0, 0, 0.3]; endColor = [0, 0.5, 0.8]; } else if (floorColorNumber == 1) { startColor = [0, 0.5, 0.8]; endColor = [0.2, 0.6, 0.4]; } else if (floorColorNumber == 2) { startColor = [0.2, 0.6, 0.4]; endColor = [0.8, 0.8, 0]; } else if (floorColorNumber == 3) { startColor = [0.8, 0.8, 0]; endColor = [0.8, 0, 0]; } else if (floorColorNumber >= 4) { startColor = [0.8, 0, 0]; endColor = [1, 1, 1]; } const colorProgress = (normalizedTemp * colorCount) - floorColorNumber; let r = endColor[0] * colorProgress + startColor[0] * (1 - colorProgress); let g = endColor[1] * colorProgress + startColor[1] * (1 - colorProgress); let b = endColor[2] * colorProgress + startColor[2] * (1 - colorProgress); return [r, g, b]; } function arctic(normTemp) { let colorCount = 5; let floorColorNumber = Math.floor(normTemp * colorCount); let startColor = [0, 0, 0]; let endColor = [0, 0, 0]; if (floorColorNumber == 0) { startColor = [0, 0, 0.8]; endColor = [0.1, 0.4, 0.9]; } else if (floorColorNumber == 1) { startColor = [0.1, 0.4, 0.9]; endColor = [0.2, 0.8, 1]; } else if (floorColorNumber == 2) { startColor = [0.2, 0.8, 1]; endColor = [1, 0.4, 0]; } else if (floorColorNumber == 3) { startColor = [1, 0.4, 0]; endColor = [1, 1, 0]; } else if (floorColorNumber >= 4) { startColor = [1, 1, 0]; endColor = [1, 1, 1]; } const colorProgress = (normTemp * colorCount) - floorColorNumber; let r = endColor[0] * colorProgress + startColor[0] * (1 - colorProgress); let g = endColor[1] * colorProgress + startColor[1] * (1 - colorProgress); let b = endColor[2] * colorProgress + startColor[2] * (1 - colorProgress); return [r, g, b]; } function lava(normTemp) { let colorCount = 6; let floorColorNumber = Math.floor(normTemp * colorCount); let startColor = [0, 0, 0]; let endColor = [0, 0, 0]; if (floorColorNumber == 0) { startColor = [0, 0, 0]; endColor = [0.1, 0.25, 0.6]; } else if (floorColorNumber == 1) { startColor = [0.1, 0.25, 0.6]; endColor = [0, 0.6, 0.6]; } else if (floorColorNumber == 2) { startColor = [0, 0.6, 0.6]; endColor = [0.5, 0.2, 0.4]; } else if (floorColorNumber == 3) { startColor = [0.5, 0.2, 0.4]; endColor = [1, 0.2, 0.2]; } else if (floorColorNumber == 4) { startColor = [1, 0.2, 0.2]; endColor = [1, 1, 0.1]; } else if (floorColorNumber >= 5) { startColor = [1, 1, 0.1]; endColor = [1, 1, 1]; } const colorProgress = (normTemp * colorCount) - floorColorNumber; let r = endColor[0] * colorProgress + startColor[0] * (1 - colorProgress); let g = endColor[1] * colorProgress + startColor[1] * (1 - colorProgress); let b = endColor[2] * colorProgress + startColor[2] * (1 - colorProgress); return [r, g, b]; } function jet(normalValue) { let colorCount = 5; let floorColorNumber = Math.floor(normalValue * colorCount); let startColor = [0, 0, 0]; let endColor = [0, 0, 0]; if (floorColorNumber == 0) { startColor = [0, 0, 0.5]; endColor = [0, 0, 1]; } else if (floorColorNumber == 1) { startColor = [0, 0, 1]; endColor = [0.05, 1, 1]; } else if (floorColorNumber == 2) { startColor = [0.05, 1, 1]; endColor = [1, 1, 0]; } else if (floorColorNumber == 3) { startColor = [1, 1, 0]; endColor = [1, 0, 0]; } else if (floorColorNumber >= 4) { startColor = [1, 0, 0]; endColor = [0.5, 0, 0]; } const colorProgress = (normalValue * colorCount) - floorColorNumber; let r = endColor[0] * colorProgress + startColor[0] * (1 - colorProgress); let g = endColor[1] * colorProgress + startColor[1] * (1 - colorProgress); let b = endColor[2] * colorProgress + startColor[2] * (1 - colorProgress); return [r, g, b]; } class PolygonHelper { /** * getMask returns a matrix [row x cols], with such values * -1: outside of polygon * 0: edges of the polygon * 1: polygon internal point * For example, a triangle [[1, 3], [4, 6], [4, 0]] can be represented as following: * [ * [-1, -1, -1, -1, -1, -1, -1], * [-1, -1, -1, 0, -1, -1, -1], * [-1, -1, 0, 1, 0, -1, -1], * [-1, 0, 1, 1, 1, 0, -1], * [ 0, 0, 0, 0, 0, 0, 0], * [-1, -1, -1, -1, -1, -1, -1], * ] */ static getMask(rows, cols, points) { const mask = new Array(rows).fill(0).map(() => new Array(cols).fill(1)); for (let i = 1; i < points.length; i++) { PolygonHelper.line(points[i][1], points[i][0], points[i - 1][1], points[i - 1][0], mask); } PolygonHelper.line(points[0][1], points[0][0], points[points.length - 1][1], points[points.length - 1][0], mask); const fillLocations = []; for (let r = 0; r < rows; r++) { fillLocations.push([r, 0]); fillLocations.push([r, cols - 1]); } for (let c = 0; c < cols; c++) { fillLocations.push([0, c]); fillLocations.push([rows - 1, c]); } PolygonHelper.fillOutsidesOfPolygon(mask, fillLocations, rows, cols); return mask; } static line(x0, y0, x1, y1, mask) { const dx = Math.abs(x1 - x0); const dy = Math.abs(y1 - y0); const sx = (x0 < x1) ? 1 : -1; const sy = (y0 < y1) ? 1 : -1; let err = dx - dy; let x = x0; let y = y0; while (true) { mask[x][y] = 0; if (Math.abs(x - x1) < 0.0001 && Math.abs(y - y1) < 0.0001) { break; } const e2 = 2 * err; if (e2 > -dy) { err -= dy; x += sx; } if (e2 < dx) { err += dx; y += sy; } } } // Recursion emulation // Fills all the area within rectangle but outside of the polygon with "-1" values static fillOutsidesOfPolygon(mask, fillLocations, rows, cols) { while (fillLocations.length > 0) { const [r, c] = fillLocations.pop(); if (r < 0 || c < 0 || r >= rows || c >= cols || mask[r][c] !== 1) { continue; } mask[r][c] = -1; fillLocations.push([r - 1, c]); fillLocations.push([r, c - 1]); fillLocations.push([r + 1, c]); fillLocations.push([r, c + 1]); } } } // const tempCanvas = document.createElement('canvas'); // function DrawEdgeBorders(imageData, canvas) { // const size = 1; // const color = 'black'; // const SMOOTH_MIN_THRESHOLD = 3; // const SMOOTH_MAX_THRESHOLD = 10; // const bordersCoordinates = []; // { x, y } // // - first set correct dimensions for canvases // canvas.width = imageData.width; // canvas.height = imageData.height; // tempCanvas.width = imageData.width; // tempCanvas.height = imageData.height; // // - the draw original shape into temp canvas // tempCanvas.getContext('2d').putImageData(imageData, 0, 0); // const finalCtx = canvas.getContext('2d'); // // 3. we will use shadow as border // // so we just need apply shadow on the original image // finalCtx.save(); // finalCtx.shadowColor = color; // finalCtx.shadowBlur = size; // finalCtx.drawImage(tempCanvas, 0, 0); // finalCtx.restore(); // // - Then we will dive in into image data of [original image + shadow] // // and remove transparency from shadow // const tempImageData = finalCtx.getImageData(0, 0, canvas.width, canvas.height); // let alphaColor, hasValue; // for (let i = 3; i < imageData.data.length; i += 4) { // // skip opaque pixels // if (imageData.data[i] === 255) continue; // alphaColor = tempImageData.data[i]; // hasValue = alphaColor !== 0; // if (!hasValue) continue; // if (alphaColor > SMOOTH_MAX_THRESHOLD) { // alphaColor = 255; // tempImageData.data[i - 3] = 255; // bordersCoordinates.push({ x: (i - 3) / 4 % imageData.width, y: Math.floor((i - 3) / 4 / imageData.width) }); // } else if (alphaColor < SMOOTH_MIN_THRESHOLD) { // alphaColor = 0; // } else { // alphaColor = 0; // } // tempImageData.data[i] = alphaColor; // } // // draw resulted image (original + shadow without opacity) into canvas // finalCtx.putImageData(tempImageData, 0, 0); // return bordersCoordinates; // }