Spaces:
Sleeping
Sleeping
| /* eslint-disable @typescript-eslint/no-explicit-any */ | |
| /** | |
| * Copyright (c) 2015 Guyon Roche | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a copy | |
| * of this software and associated documentation files (the "Software"), to deal | |
| * in the Software without restriction, including without limitation the rights | |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| * copies of the Software, and to permit persons to whom the Software is | |
| * furnished to do so, subject to the following conditions:</p> | |
| * | |
| * The above copyright notice and this permission notice shall be included in | |
| * all copies or substantial portions of the Software. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
| * THE SOFTWARE. | |
| */ | |
| export const operations = { | |
| nearestNeighbor(src: any, dst: any) { | |
| const wSrc = src.width; | |
| const hSrc = src.height; | |
| const wDst = dst.width; | |
| const hDst = dst.height; | |
| const bufSrc = src.data; | |
| const bufDst = dst.data; | |
| for (let i = 0; i < hDst; i++) { | |
| for (let j = 0; j < wDst; j++) { | |
| let posDst = (i * wDst + j) * 4; | |
| const iSrc = Math.floor((i * hSrc) / hDst); | |
| const jSrc = Math.floor((j * wSrc) / wDst); | |
| let posSrc = (iSrc * wSrc + jSrc) * 4; | |
| bufDst[posDst++] = bufSrc[posSrc++]; | |
| bufDst[posDst++] = bufSrc[posSrc++]; | |
| bufDst[posDst++] = bufSrc[posSrc++]; | |
| bufDst[posDst++] = bufSrc[posSrc++]; | |
| } | |
| } | |
| }, | |
| bilinearInterpolation(src: any, dst: any) { | |
| const wSrc = src.width; | |
| const hSrc = src.height; | |
| const wDst = dst.width; | |
| const hDst = dst.height; | |
| const bufSrc = src.data; | |
| const bufDst = dst.data; | |
| const interpolate = function ( | |
| k: number, | |
| kMin: number, | |
| vMin: number, | |
| kMax: number, | |
| vMax: number, | |
| ) { | |
| // special case - k is integer | |
| if (kMin === kMax) { | |
| return vMin; | |
| } | |
| return Math.round((k - kMin) * vMax + (kMax - k) * vMin); | |
| }; | |
| const assign = function ( | |
| pos: number, | |
| offset: number, | |
| x: number, | |
| xMin: number, | |
| xMax: number, | |
| y: number, | |
| yMin: number, | |
| yMax: number, | |
| ) { | |
| let posMin = (yMin * wSrc + xMin) * 4 + offset; | |
| let posMax = (yMin * wSrc + xMax) * 4 + offset; | |
| const vMin = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]); | |
| // special case, y is integer | |
| if (yMax === yMin) { | |
| bufDst[pos + offset] = vMin; | |
| } else { | |
| posMin = (yMax * wSrc + xMin) * 4 + offset; | |
| posMax = (yMax * wSrc + xMax) * 4 + offset; | |
| const vMax = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]); | |
| bufDst[pos + offset] = interpolate(y, yMin, vMin, yMax, vMax); | |
| } | |
| }; | |
| for (let i = 0; i < hDst; i++) { | |
| for (let j = 0; j < wDst; j++) { | |
| const posDst = (i * wDst + j) * 4; | |
| // x & y in src coordinates | |
| const x = (j * wSrc) / wDst; | |
| const xMin = Math.floor(x); | |
| const xMax = Math.min(Math.ceil(x), wSrc - 1); | |
| const y = (i * hSrc) / hDst; | |
| const yMin = Math.floor(y); | |
| const yMax = Math.min(Math.ceil(y), hSrc - 1); | |
| assign(posDst, 0, x, xMin, xMax, y, yMin, yMax); | |
| assign(posDst, 1, x, xMin, xMax, y, yMin, yMax); | |
| assign(posDst, 2, x, xMin, xMax, y, yMin, yMax); | |
| assign(posDst, 3, x, xMin, xMax, y, yMin, yMax); | |
| } | |
| } | |
| }, | |
| _interpolate2D(src: any, dst: any, options?: any, interpolate?: any) { | |
| const bufSrc = src.data; | |
| const bufDst = dst.data; | |
| const wSrc = src.width; | |
| const hSrc = src.height; | |
| const wDst = dst.width; | |
| const hDst = dst.height; | |
| // when dst smaller than src/2, interpolate first to a multiple between 0.5 and 1.0 src, then sum squares | |
| const wM = Math.max(1, Math.floor(wSrc / wDst)); | |
| const wDst2 = wDst * wM; | |
| const hM = Math.max(1, Math.floor(hSrc / hDst)); | |
| const hDst2 = hDst * hM; | |
| // =========================================================== | |
| // Pass 1 - interpolate rows | |
| // buf1 has width of dst2 and height of src | |
| const buf1 = Buffer.alloc(wDst2 * hSrc * 4); | |
| for (let i = 0; i < hSrc; i++) { | |
| for (let j = 0; j < wDst2; j++) { | |
| // i in src coords, j in dst coords | |
| // calculate x in src coords | |
| // this interpolation requires 4 sample points and the two inner ones must be real | |
| // the outer points can be fudged for the edges. | |
| // therefore (wSrc-1)/wDst2 | |
| const x = (j * (wSrc - 1)) / wDst2; | |
| const xPos = Math.floor(x); | |
| const t = x - xPos; | |
| const srcPos = (i * wSrc + xPos) * 4; | |
| const buf1Pos = (i * wDst2 + j) * 4; | |
| for (let k = 0; k < 4; k++) { | |
| const kPos = srcPos + k; | |
| const x0 = | |
| xPos > 0 ? bufSrc[kPos - 4] : 2 * bufSrc[kPos] - bufSrc[kPos + 4]; | |
| const x1 = bufSrc[kPos]; | |
| const x2 = bufSrc[kPos + 4]; | |
| const x3 = | |
| xPos < wSrc - 2 | |
| ? bufSrc[kPos + 8] | |
| : 2 * bufSrc[kPos + 4] - bufSrc[kPos]; | |
| buf1[buf1Pos + k] = interpolate(x0, x1, x2, x3, t); | |
| } | |
| } | |
| } | |
| // this._writeFile(wDst2, hSrc, buf1, "out/buf1.jpg"); | |
| // =========================================================== | |
| // Pass 2 - interpolate columns | |
| // buf2 has width and height of dst2 | |
| const buf2 = Buffer.alloc(wDst2 * hDst2 * 4); | |
| for (let i = 0; i < hDst2; i++) { | |
| for (let j = 0; j < wDst2; j++) { | |
| // i&j in dst2 coords | |
| // calculate y in buf1 coords | |
| // this interpolation requires 4 sample points and the two inner ones must be real | |
| // the outer points can be fudged for the edges. | |
| // therefore (hSrc-1)/hDst2 | |
| const y = (i * (hSrc - 1)) / hDst2; | |
| const yPos = Math.floor(y); | |
| const t = y - yPos; | |
| const buf1Pos = (yPos * wDst2 + j) * 4; | |
| const buf2Pos = (i * wDst2 + j) * 4; | |
| for (let k = 0; k < 4; k++) { | |
| const kPos = buf1Pos + k; | |
| const y0 = | |
| yPos > 0 | |
| ? buf1[kPos - wDst2 * 4] | |
| : 2 * buf1[kPos]! - buf1[kPos + wDst2 * 4]!; | |
| const y1 = buf1[kPos]; | |
| const y2 = buf1[kPos + wDst2 * 4]; | |
| const y3 = | |
| yPos < hSrc - 2 | |
| ? buf1[kPos + wDst2 * 8] | |
| : 2 * buf1[kPos + wDst2 * 4]! - buf1[kPos]!; | |
| buf2[buf2Pos + k] = interpolate(y0, y1, y2, y3, t); | |
| } | |
| } | |
| } | |
| // this._writeFile(wDst2, hDst2, buf2, "out/buf2.jpg"); | |
| // =========================================================== | |
| // Pass 3 - scale to dst | |
| const m = wM * hM; | |
| if (m > 1) { | |
| for (let i = 0; i < hDst; i++) { | |
| for (let j = 0; j < wDst; j++) { | |
| // i&j in dst bounded coords | |
| let r = 0; | |
| let g = 0; | |
| let b = 0; | |
| let a = 0; | |
| let realColors = 0; | |
| for (let y = 0; y < hM; y++) { | |
| const yPos = i * hM + y; | |
| for (let x = 0; x < wM; x++) { | |
| const xPos = j * wM + x; | |
| const xyPos = (yPos * wDst2 + xPos) * 4; | |
| const pixelAlpha = buf2[xyPos + 3]!; | |
| if (pixelAlpha) { | |
| r += buf2[xyPos]!; | |
| g += buf2[xyPos + 1]!; | |
| b += buf2[xyPos + 2]!; | |
| realColors++; | |
| } | |
| a += pixelAlpha; | |
| } | |
| } | |
| const pos = (i * wDst + j) * 4; | |
| bufDst[pos] = realColors ? Math.round(r / realColors) : 0; | |
| bufDst[pos + 1] = realColors ? Math.round(g / realColors) : 0; | |
| bufDst[pos + 2] = realColors ? Math.round(b / realColors) : 0; | |
| bufDst[pos + 3] = Math.round(a / m); | |
| } | |
| } | |
| } else { | |
| // replace dst buffer with buf2 | |
| dst.data = buf2; | |
| } | |
| }, | |
| bicubicInterpolation(src: any, dst: any, options?: any) { | |
| const interpolateCubic = function ( | |
| x0: number, | |
| x1: number, | |
| x2: number, | |
| x3: number, | |
| t: number, | |
| ) { | |
| const a0 = x3 - x2 - x0 + x1; | |
| const a1 = x0 - x1 - a0; | |
| const a2 = x2 - x0; | |
| const a3 = x1; | |
| return Math.max( | |
| 0, | |
| Math.min(255, a0 * (t * t * t) + a1 * (t * t) + a2 * t + a3), | |
| ); | |
| }; | |
| return this._interpolate2D(src, dst, options, interpolateCubic); | |
| }, | |
| hermiteInterpolation(src: any, dst: any, options?: any) { | |
| const interpolateHermite = function ( | |
| x0: number, | |
| x1: number, | |
| x2: number, | |
| x3: number, | |
| t: number, | |
| ) { | |
| const c0 = x1; | |
| const c1 = 0.5 * (x2 - x0); | |
| const c2 = x0 - 2.5 * x1 + 2 * x2 - 0.5 * x3; | |
| const c3 = 0.5 * (x3 - x0) + 1.5 * (x1 - x2); | |
| return Math.max( | |
| 0, | |
| Math.min(255, Math.round(((c3 * t + c2) * t + c1) * t + c0)), | |
| ); | |
| }; | |
| return this._interpolate2D(src, dst, options, interpolateHermite); | |
| }, | |
| bezierInterpolation(src: any, dst: any, options?: any) { | |
| // between 2 points y(n), y(n+1), use next points out, y(n-1), y(n+2) | |
| // to predict control points (a & b) to be placed at n+0.5 | |
| // ya(n) = y(n) + (y(n+1)-y(n-1))/4 | |
| // yb(n) = y(n+1) - (y(n+2)-y(n))/4 | |
| // then use std bezier to interpolate [n,n+1) | |
| // y(n+t) = y(n)*(1-t)^3 + 3 * ya(n)*(1-t)^2*t + 3 * yb(n)*(1-t)*t^2 + y(n+1)*t^3 | |
| // note the 3* factor for the two control points | |
| // for edge cases, can choose: | |
| // y(-1) = y(0) - 2*(y(1)-y(0)) | |
| // y(w) = y(w-1) + 2*(y(w-1)-y(w-2)) | |
| // but can go with y(-1) = y(0) and y(w) = y(w-1) | |
| const interpolateBezier = function ( | |
| x0: number, | |
| x1: number, | |
| x2: number, | |
| x3: number, | |
| t: number, | |
| ) { | |
| // x1, x2 are the knots, use x0 and x3 to calculate control points | |
| const cp1 = x1 + (x2 - x0) / 4; | |
| const cp2 = x2 - (x3 - x1) / 4; | |
| const nt = 1 - t; | |
| const c0 = x1 * nt * nt * nt; | |
| const c1 = 3 * cp1 * nt * nt * t; | |
| const c2 = 3 * cp2 * nt * t * t; | |
| const c3 = x2 * t * t * t; | |
| return Math.max(0, Math.min(255, Math.round(c0 + c1 + c2 + c3))); | |
| }; | |
| return this._interpolate2D(src, dst, options, interpolateBezier); | |
| }, | |
| }; | |