class Equirect { clamp(v, lo, hi) { return Math.min(hi, Math.max(lo, v)); } srgbToLinear(v) { const component = +v * (1.0 / 255.0); return component * component; } linearToSRGB(v) { return (Math.sqrt(v) * 255.0) | 0; } transform(inPixels, face, facePixels) { const opts = { flipTheta: false, interpolation: 'bilinear', }; let thetaFlip = opts.flipTheta ? -1 : 1; let edge = facePixels.width | 0; let inWidth = inPixels.width | 0; let inHeight = inPixels.height | 0; let inData = inPixels.data; let smoothNearest = opts.interpolation === 'nearest'; let faceData = facePixels.data; let faceWidth = facePixels.width | 0; let faceHeight = facePixels.height | 0; let iFaceWidth2 = 2.0 / faceWidth; let iFaceHeight2 = 2.0 / faceHeight; for (let j = 0; j < faceHeight; ++j) { for (let i = 0; i < faceWidth; ++i) { let a = iFaceWidth2 * i; let b = iFaceHeight2 * j; let outPos = (i + j * edge) << 2; let x = 0.0, y = 0.0, z = 0.0; // @@NOTE: Tried using explicit matrices for this and didn't see any switch (face) { case 0: x = 1.0 - a; y = 1.0; z = 1.0 - b; break; // right (+x) case 1: x = a - 1.0; y = -1.0; z = 1.0 - b; break; // left (-x) case 2: x = b - 1.0; y = a - 1.0; z = 1.0; break; // top (+y) case 3: x = 1.0 - b; y = a - 1.0; z = -1.0; break; // bottom (-y) case 4: x = 1.0; y = a - 1.0; z = 1.0 - b; break; // front (+z) case 5: x = -1.0; y = 1.0 - a; z = 1.0 - b; break; // back (-z) } let theta = thetaFlip * Math.atan2(y, x); let rad = Math.sqrt(x * x + y * y); let phi = Math.atan2(z, rad); let uf = (2.0 * (inWidth / 4) * (theta + Math.PI)) / Math.PI; let vf = (2.0 * (inWidth / 4) * (Math.PI / 2 - phi)) / Math.PI; let ui = Math.floor(uf) | 0, vi = Math.floor(vf) | 0; if (smoothNearest) { let inPos = ((ui % inWidth) + inWidth * clamp(vi, 0, inHeight - 1)) << 2; faceData[outPos + 0] = inData[inPos + 0] | 0; faceData[outPos + 1] = inData[inPos + 1] | 0; faceData[outPos + 2] = inData[inPos + 2] | 0; faceData[outPos + 3] = inData[inPos + 3] | 0; } else { // bilinear blend let u2 = ui + 1, v2 = vi + 1; let mu = uf - ui, nu = vf - vi; let pA = ((ui % inWidth) + inWidth * this.clamp(vi, 0, inHeight - 1)) << 2; let pB = ((u2 % inWidth) + inWidth * this.clamp(vi, 0, inHeight - 1)) << 2; let pC = ((ui % inWidth) + inWidth * this.clamp(v2, 0, inHeight - 1)) << 2; let pD = ((u2 % inWidth) + inWidth * this.clamp(v2, 0, inHeight - 1)) << 2; let aA = (inData[pA + 3] | 0) * (1.0 / 255.0); let aB = (inData[pB + 3] | 0) * (1.0 / 255.0); let aC = (inData[pC + 3] | 0) * (1.0 / 255.0); let aD = (inData[pD + 3] | 0) * (1.0 / 255.0); // Do the bilinear blend in linear space. let rA = this.srgbToLinear(inData[pA + 0] | 0) * aA, gA = this.srgbToLinear(inData[pA + 1] | 0) * aA, bA = this.srgbToLinear(inData[pA + 2] | 0) * aA; let rB = this.srgbToLinear(inData[pB + 0] | 0) * aB, gB = this.srgbToLinear(inData[pB + 1] | 0) * aB, bB = this.srgbToLinear(inData[pB + 2] | 0) * aB; let rC = this.srgbToLinear(inData[pC + 0] | 0) * aC, gC = this.srgbToLinear(inData[pC + 1] | 0) * aC, bC = this.srgbToLinear(inData[pC + 2] | 0) * aC; let rD = this.srgbToLinear(inData[pD + 0] | 0) * aD, gD = this.srgbToLinear(inData[pD + 1] | 0) * aD, bD = this.srgbToLinear(inData[pD + 2] | 0) * aD; let _r = rA * (1.0 - mu) * (1.0 - nu) + rB * mu * (1.0 - nu) + rC * (1.0 - mu) * nu + rD * mu * nu; let _g = gA * (1.0 - mu) * (1.0 - nu) + gB * mu * (1.0 - nu) + gC * (1.0 - mu) * nu + gD * mu * nu; let _b = bA * (1.0 - mu) * (1.0 - nu) + bB * mu * (1.0 - nu) + bC * (1.0 - mu) * nu + bD * mu * nu; let _a = aA * (1.0 - mu) * (1.0 - nu) + aB * mu * (1.0 - nu) + aC * (1.0 - mu) * nu + aD * mu * nu; let _ia = 1.0 / _a; faceData[outPos + 0] = this.linearToSRGB(_r * _ia) | 0; faceData[outPos + 1] = this.linearToSRGB(_g * _ia) | 0; faceData[outPos + 2] = this.linearToSRGB(_b * _ia) | 0; faceData[outPos + 3] = (_a * 255.0) | 0; } } } return facePixels; } draw(image) { const size = 1 << Math.round(Math.log(image.width / 4) / Math.log(2)); let canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; let face = canvas.getContext('2d').createImageData(size, size); let res = this.transform(image, this.direction, face); canvas.getContext('2d').putImageData(res, 0, 0); const src = canvas.toDataURL(); canvas = null; return src; } run(image) { let canvas = document.createElement('canvas'); canvas.width = image.naturalWidth; canvas.height = image.naturalHeight; const ctx = canvas.getContext('2d'); ctx.drawImage(image, 0, 0); const src = this.draw(ctx.getImageData(0, 0, canvas.width, canvas.height)); canvas = null; return src; } constructor() { this.directions = ['right', 'left', 'top', 'bottom', 'front', 'back']; this.direction = 0; this.load = (source, callback) => { let image = new Image(); image.crossOrigin = ''; image.onload = function() { callback(null, this); image = null; }; image.onerror = function(err) { callback(err, this); image = null; }; image.src = source; } this.single = (source, direction, callback) => { const idx = this.directions.indexOf(direction); this.direction = idx > -1 ? idx : 4; callback(null, this.run(source)); }; this.cube = (source, callback) => { const task = Array(6) .fill() .map((v, i) => next => { this.single(source, this.directions[i], (err, ans) => { next(err, ans); }); }); async.series(task, (err, ans) => { callback(err, ans); }); }; } } const equirect = new Equirect(); new Vue({ el: "#app", data: { source: "https://images.unsplash.com/photo-1557971370-e7298ee473fb?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2500&q=80g", images: [], }, computed: { output() { return window.Base64[this.encode ? 'encode' : 'decode'](this.source); } }, methods: { convert(){ console.log(this.source); equirect.load( this.source, (err, image)=>{ if(err){ alert('image load error'); } else { equirect.cube(image, (err, ans)=>{ this.images = ans; }) } } ) } }, mounted() { this.convert(); /*equirect.single(this.source, 'back', (err, ans) => { this.images = [ans]; });*/ } })