Path: blob/master/node_modules/@jimp/plugin-resize/src/modules/resize2.js
1126 views
/**1* Copyright (c) 2015 Guyon Roche2*3* Permission is hereby granted, free of charge, to any person obtaining a copy4* of this software and associated documentation files (the "Software"), to deal5* in the Software without restriction, including without limitation the rights6* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell7* copies of the Software, and to permit persons to whom the Software is8* furnished to do so, subject to the following conditions:</p>9*10* The above copyright notice and this permission notice shall be included in11* all copies or substantial portions of the Software.12*13* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR14* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,15* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE16* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER17* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,18* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN19* THE SOFTWARE.20*/2122module.exports = {23nearestNeighbor(src, dst) {24const wSrc = src.width;25const hSrc = src.height;2627const wDst = dst.width;28const hDst = dst.height;2930const bufSrc = src.data;31const bufDst = dst.data;3233for (let i = 0; i < hDst; i++) {34for (let j = 0; j < wDst; j++) {35let posDst = (i * wDst + j) * 4;3637const iSrc = Math.floor((i * hSrc) / hDst);38const jSrc = Math.floor((j * wSrc) / wDst);39let posSrc = (iSrc * wSrc + jSrc) * 4;4041bufDst[posDst++] = bufSrc[posSrc++];42bufDst[posDst++] = bufSrc[posSrc++];43bufDst[posDst++] = bufSrc[posSrc++];44bufDst[posDst++] = bufSrc[posSrc++];45}46}47},4849bilinearInterpolation(src, dst) {50const wSrc = src.width;51const hSrc = src.height;5253const wDst = dst.width;54const hDst = dst.height;5556const bufSrc = src.data;57const bufDst = dst.data;5859const interpolate = function(k, kMin, vMin, kMax, vMax) {60// special case - k is integer61if (kMin === kMax) {62return vMin;63}6465return Math.round((k - kMin) * vMax + (kMax - k) * vMin);66};6768const assign = function(pos, offset, x, xMin, xMax, y, yMin, yMax) {69let posMin = (yMin * wSrc + xMin) * 4 + offset;70let posMax = (yMin * wSrc + xMax) * 4 + offset;71const vMin = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]);7273// special case, y is integer74if (yMax === yMin) {75bufDst[pos + offset] = vMin;76} else {77posMin = (yMax * wSrc + xMin) * 4 + offset;78posMax = (yMax * wSrc + xMax) * 4 + offset;79const vMax = interpolate(x, xMin, bufSrc[posMin], xMax, bufSrc[posMax]);8081bufDst[pos + offset] = interpolate(y, yMin, vMin, yMax, vMax);82}83};8485for (let i = 0; i < hDst; i++) {86for (let j = 0; j < wDst; j++) {87const posDst = (i * wDst + j) * 4;88// x & y in src coordinates89const x = (j * wSrc) / wDst;90const xMin = Math.floor(x);91const xMax = Math.min(Math.ceil(x), wSrc - 1);9293const y = (i * hSrc) / hDst;94const yMin = Math.floor(y);95const yMax = Math.min(Math.ceil(y), hSrc - 1);9697assign(posDst, 0, x, xMin, xMax, y, yMin, yMax);98assign(posDst, 1, x, xMin, xMax, y, yMin, yMax);99assign(posDst, 2, x, xMin, xMax, y, yMin, yMax);100assign(posDst, 3, x, xMin, xMax, y, yMin, yMax);101}102}103},104105_interpolate2D(src, dst, options, interpolate) {106const bufSrc = src.data;107const bufDst = dst.data;108109const wSrc = src.width;110const hSrc = src.height;111112const wDst = dst.width;113const hDst = dst.height;114115// when dst smaller than src/2, interpolate first to a multiple between 0.5 and 1.0 src, then sum squares116const wM = Math.max(1, Math.floor(wSrc / wDst));117const wDst2 = wDst * wM;118const hM = Math.max(1, Math.floor(hSrc / hDst));119const hDst2 = hDst * hM;120121// ===========================================================122// Pass 1 - interpolate rows123// buf1 has width of dst2 and height of src124const buf1 = Buffer.alloc(wDst2 * hSrc * 4);125for (let i = 0; i < hSrc; i++) {126for (let j = 0; j < wDst2; j++) {127// i in src coords, j in dst coords128129// calculate x in src coords130// this interpolation requires 4 sample points and the two inner ones must be real131// the outer points can be fudged for the edges.132// therefore (wSrc-1)/wDst2133const x = (j * (wSrc - 1)) / wDst2;134const xPos = Math.floor(x);135const t = x - xPos;136const srcPos = (i * wSrc + xPos) * 4;137const buf1Pos = (i * wDst2 + j) * 4;138139for (let k = 0; k < 4; k++) {140const kPos = srcPos + k;141const x0 =142xPos > 0 ? bufSrc[kPos - 4] : 2 * bufSrc[kPos] - bufSrc[kPos + 4];143const x1 = bufSrc[kPos];144const x2 = bufSrc[kPos + 4];145const x3 =146xPos < wSrc - 2147? bufSrc[kPos + 8]148: 2 * bufSrc[kPos + 4] - bufSrc[kPos];149buf1[buf1Pos + k] = interpolate(x0, x1, x2, x3, t);150}151}152}153// this._writeFile(wDst2, hSrc, buf1, "out/buf1.jpg");154155// ===========================================================156// Pass 2 - interpolate columns157// buf2 has width and height of dst2158const buf2 = Buffer.alloc(wDst2 * hDst2 * 4);159for (let i = 0; i < hDst2; i++) {160for (let j = 0; j < wDst2; j++) {161// i&j in dst2 coords162163// calculate y in buf1 coords164// this interpolation requires 4 sample points and the two inner ones must be real165// the outer points can be fudged for the edges.166// therefore (hSrc-1)/hDst2167const y = (i * (hSrc - 1)) / hDst2;168const yPos = Math.floor(y);169const t = y - yPos;170const buf1Pos = (yPos * wDst2 + j) * 4;171const buf2Pos = (i * wDst2 + j) * 4;172for (let k = 0; k < 4; k++) {173const kPos = buf1Pos + k;174const y0 =175yPos > 0176? buf1[kPos - wDst2 * 4]177: 2 * buf1[kPos] - buf1[kPos + wDst2 * 4];178const y1 = buf1[kPos];179const y2 = buf1[kPos + wDst2 * 4];180const y3 =181yPos < hSrc - 2182? buf1[kPos + wDst2 * 8]183: 2 * buf1[kPos + wDst2 * 4] - buf1[kPos];184185buf2[buf2Pos + k] = interpolate(y0, y1, y2, y3, t);186}187}188}189// this._writeFile(wDst2, hDst2, buf2, "out/buf2.jpg");190191// ===========================================================192// Pass 3 - scale to dst193const m = wM * hM;194if (m > 1) {195for (let i = 0; i < hDst; i++) {196for (let j = 0; j < wDst; j++) {197// i&j in dst bounded coords198let r = 0;199let g = 0;200let b = 0;201let a = 0;202let realColors = 0;203204for (let y = 0; y < hM; y++) {205const yPos = i * hM + y;206207for (let x = 0; x < wM; x++) {208const xPos = j * wM + x;209const xyPos = (yPos * wDst2 + xPos) * 4;210const pixelAlpha = buf2[xyPos + 3];211212if (pixelAlpha) {213r += buf2[xyPos];214g += buf2[xyPos + 1];215b += buf2[xyPos + 2];216realColors++;217}218219a += pixelAlpha;220}221}222223const pos = (i * wDst + j) * 4;224bufDst[pos] = realColors ? Math.round(r / realColors) : 0;225bufDst[pos + 1] = realColors ? Math.round(g / realColors) : 0;226bufDst[pos + 2] = realColors ? Math.round(b / realColors) : 0;227bufDst[pos + 3] = Math.round(a / m);228}229}230} else {231// replace dst buffer with buf2232dst.data = buf2;233}234},235236bicubicInterpolation(src, dst, options) {237const interpolateCubic = function(x0, x1, x2, x3, t) {238const a0 = x3 - x2 - x0 + x1;239const a1 = x0 - x1 - a0;240const a2 = x2 - x0;241const a3 = x1;242return Math.max(2430,244Math.min(255, a0 * (t * t * t) + a1 * (t * t) + a2 * t + a3)245);246};247248return this._interpolate2D(src, dst, options, interpolateCubic);249},250251hermiteInterpolation(src, dst, options) {252const interpolateHermite = function(x0, x1, x2, x3, t) {253const c0 = x1;254const c1 = 0.5 * (x2 - x0);255const c2 = x0 - 2.5 * x1 + 2 * x2 - 0.5 * x3;256const c3 = 0.5 * (x3 - x0) + 1.5 * (x1 - x2);257return Math.max(2580,259Math.min(255, Math.round(((c3 * t + c2) * t + c1) * t + c0))260);261};262263return this._interpolate2D(src, dst, options, interpolateHermite);264},265266bezierInterpolation(src, dst, options) {267// between 2 points y(n), y(n+1), use next points out, y(n-1), y(n+2)268// to predict control points (a & b) to be placed at n+0.5269// ya(n) = y(n) + (y(n+1)-y(n-1))/4270// yb(n) = y(n+1) - (y(n+2)-y(n))/4271// then use std bezier to interpolate [n,n+1)272// 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^3273// note the 3* factor for the two control points274// for edge cases, can choose:275// y(-1) = y(0) - 2*(y(1)-y(0))276// y(w) = y(w-1) + 2*(y(w-1)-y(w-2))277// but can go with y(-1) = y(0) and y(w) = y(w-1)278const interpolateBezier = function(x0, x1, x2, x3, t) {279// x1, x2 are the knots, use x0 and x3 to calculate control points280const cp1 = x1 + (x2 - x0) / 4;281const cp2 = x2 - (x3 - x1) / 4;282const nt = 1 - t;283const c0 = x1 * nt * nt * nt;284const c1 = 3 * cp1 * nt * nt * t;285const c2 = 3 * cp2 * nt * t * t;286const c3 = x2 * t * t * t;287return Math.max(0, Math.min(255, Math.round(c0 + c1 + c2 + c3)));288};289290return this._interpolate2D(src, dst, options, interpolateBezier);291}292};293294295