From d7d51e72043518a6767983fcaf3da7b566fba103 Mon Sep 17 00:00:00 2001 From: GeorgLegato Date: Fri, 7 Apr 2023 10:22:52 +0200 Subject: [PATCH] adding equi script --- javascript/pano_hints.js | 1 + javascript/panoramaviewer-ext.js | 182 +++++++++++++++++++++++++++++++ scripts/panorama-3dviewer.py | 3 + 3 files changed, 186 insertions(+) diff --git a/javascript/pano_hints.js b/javascript/pano_hints.js index 043fdea..b2ee7e1 100644 --- a/javascript/pano_hints.js +++ b/javascript/pano_hints.js @@ -5,6 +5,7 @@ pano_titles = { "🌐": "Switch between selected image and Equirectangular view", "🧊": "Switch between selected image and CubeMap view", "✜": "Convert current spherical map into cubemap (for better outpainting)", + "💫": "Convert current cubemap to equirectangular map (for better upscaling)", "❌": "Close current panorama viewer" } diff --git a/javascript/panoramaviewer-ext.js b/javascript/panoramaviewer-ext.js index c2a963c..271ed51 100644 --- a/javascript/panoramaviewer-ext.js +++ b/javascript/panoramaviewer-ext.js @@ -371,6 +371,188 @@ document.addEventListener("DOMContentLoaded", () => { }); + +/* routine based on disseminate/cube2equi github.com, default Github license */ +function convertto_cubemap() { + const PNG = require( 'pngjs' ).PNG; + + program + .version( '0.1.0' ) + .option( '-i, --input ', 'Input cubemap image' ) + .option( '-o, --output [file]', 'Output cubemap image' ) + .option( '-w, --width ', 'Output cubemap size', parseInt ) + .option( '-h, --height ', 'Output cubemap height', parseInt ) + .parse( process.argv ); + + const W = program.width || 2048; + const H = program.height || 1024; + + const out = program.output || 'out.png'; + + const EquiCoordToPolar = (x, y) => { + const xNorm = ( 2 * x / W ) - 1; + const yNorm = 1 - ( 2 * y / H ); + + const theta = xNorm * Math.PI; + const phi = Math.asin( yNorm ); + + return [theta, phi]; + }; + + const PolarToUnitVector = (theta, phi) => { + const x = Math.cos( phi ) * Math.cos( theta ); + const y = Math.sin( phi ); + const z = Math.cos( phi ) * Math.sin( theta ); + + return [x, y, z]; + }; + + const DotProduct = (a, b) => { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + }; + + const Normalize = (a) => { + const len = Math.sqrt( DotProduct( a, a ) ); + return [a[0] / len, a[1] / len, a[2] / len]; + }; + + const Mul = (a, scalar) => { + return [a[0] * scalar, a[1] * scalar, a[2] * scalar]; + }; + + SIDE_BACK = 1; + SIDE_LEFT = 5; + SIDE_FRONT = 0; + SIDE_RIGHT = 4; + SIDE_TOP = 2; + SIDE_BOTTOM = 3; + + const IntersectRayWithPlane = (side, normal, p0, ray) => { + const denom = DotProduct( normal, ray ); + if( Math.abs( denom ) > 0.0000001 ) { + const t = DotProduct( p0, normal ) / denom; + + if( t >= 0 ) { + const newVec = Mul(ray, t); + if( side === SIDE_LEFT ) { + if( newVec[0] >= -1 && newVec[0] <= 1 && newVec[1] >= -1 && newVec[1] <= 1 ) { + return [(newVec[0] + 1) / 2, (newVec[1] + 1) / 2]; + } + } else if( side === SIDE_RIGHT ) { + if( newVec[0] >= -1 && newVec[0] <= 1 && newVec[1] >= -1 && newVec[1] <= 1 ) { + return [1 - (newVec[0] + 1) / 2, (newVec[1] + 1) / 2]; + } + } else if( side === SIDE_FRONT ) { + if( newVec[1] >= -1 && newVec[1] <= 1 && newVec[2] >= -1 && newVec[2] <= 1 ) { + return [(newVec[2] + 1) / 2, (newVec[1] + 1) / 2]; + } + } else if( side === SIDE_BACK ) { + if( newVec[1] >= -1 && newVec[1] <= 1 && newVec[2] >= -1 && newVec[2] <= 1 ) { + return [1 - (newVec[2] + 1) / 2, 1 - (newVec[1] + 1) / 2]; + } + } else if( side === SIDE_TOP ) { + if( newVec[0] >= -1 && newVec[0] <= 1 && newVec[2] >= -1 && newVec[2] <= 1 ) { + return [1 - (newVec[0] + 1) / 2, 1 - (newVec[2] + 1) / 2]; + } + } else if( side === SIDE_BOTTOM ) { + if( newVec[0] >= -1 && newVec[0] <= 1 && newVec[2] >= -1 && newVec[2] <= 1 ) { + return [(newVec[0] + 1) / 2, (newVec[2] + 1) / 2]; + } + } + } + } + }; + + const MV = (vec) => { + return [-vec[0], -vec[1], -vec[2]]; + }; + + const IntersectRayWithBoxes = (ray) => { + let t; + + const boxes = [ + [1, 0, 0], + [-1, 0, 0], + [0, 1, 0], + [0, -1, 0], + [0, 0, 1], + [0, 0, -1], + ]; + + for( let i = 0; i < boxes.length; i++ ) { + xy = IntersectRayWithPlane(i, MV(boxes[i]), boxes[i], Normalize(ray)); + if( xy !== undefined ) { + return [i, xy[0], xy[1]]; + } + } + }; + + const SideXYToCubemap = (side, x, y) => { + let newY, newX; + switch(side) { + case SIDE_BACK: + newY = (1/3) + y * (1/3); + return [x * 0.25, newY]; + case SIDE_LEFT: + newY = (2/3) - y * (1/3); + return [0.25 + x * 0.25, newY]; + case SIDE_FRONT: + newY = (2/3) - y * (1/3); + return [0.5 + x * 0.25, newY]; + case SIDE_RIGHT: + newY = (2/3) - y * (1/3); + return [0.75 + x * 0.25, newY]; + case SIDE_TOP: + newY = y * ( 1/3 ); + newX = 0.5 - x * 0.25; + return [newX, newY]; + case SIDE_BOTTOM: + newY = (2/3) + y * ( 1/3 ); + newX = 0.25 + x * 0.25; + return [newX, newY]; + } + }; + + fs.createReadStream( program.input ) + .pipe( new PNG({ + filterType: 4 + }) ) + .on( 'parsed', function() { + const png = this; + + const outPNG = new PNG( { + width: W, + height: H, + colorType: 2, + inputHasAlpha: false + }); + + for( let j = 0; j < H; j++ ) { + for( let i = 0; i < W; i++ ) { + const angs = EquiCoordToPolar( i, j ); + const ray = PolarToUnitVector( angs[0], angs[1] ); + const sxc = IntersectRayWithBoxes( ray ); + const xy = SideXYToCubemap( sxc[0], sxc[1], sxc[2] ); + + const sampleX = Math.floor( xy[0] * png.width ); + const sampleY = Math.floor( xy[1] * png.height ); + + const idx = (png.width * sampleY + sampleX) << 2; + const oidx = (W * j + i) * 3; + + outPNG.data[oidx] = png.data[idx]; + outPNG.data[oidx + 1] = png.data[idx + 1]; + outPNG.data[oidx + 2] = png.data[idx + 2]; + } + } + + outPNG.pack().pipe( fs.createWriteStream( program.output ) ); + } ); + + +} + + /* routine based on jerx/github, gpl3 */ function convertto_cubemap() { diff --git a/scripts/panorama-3dviewer.py b/scripts/panorama-3dviewer.py index 416b802..1812d4e 100644 --- a/scripts/panorama-3dviewer.py +++ b/scripts/panorama-3dviewer.py @@ -78,6 +78,9 @@ def after_component(component, **kwargs): conv_cubemap_gallery_button = gr.Button ("\U0000271C", variant="tool", elem_id="convertto_cubemap_button"+suffix) #✜ conv_cubemap_gallery_button.click (None, [],None, _js="convertto_cubemap" ) + conv_equi_gallery_button = gr.Button ("\U0001F4AB", variant="tool", elem_id="convertto_equi_button"+suffix) #💫 + conv_equi_gallery_button.click (None, [],None, _js="convertto_equi" ) + close_panoviewer = gr.Button("\U0000274C", variant="tool") # ❌ close_panoviewer.click(None,[],None,_js="panorama_here(\"""\",\"\",\"""\")" )