├── .npmignore ├── .gitignore ├── doc ├── preview.png └── preview.eq.png ├── runConfigurations └── test.run.xml ├── src ├── dev.js ├── stopwatch.js ├── marzipano.js ├── randomaccessfile.js ├── scale.js ├── pannellum.js ├── cli.js ├── renderer.js ├── img.js ├── psd.js └── converter.js ├── LICENSE ├── package.json ├── README.md └── yarn.lock /.npmignore: -------------------------------------------------------------------------------- 1 | runConfigurations 2 | .idea 3 | yarn-error.log 4 | doc 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | bck 4 | yarn-error.log 5 | package-lock.json 6 | -------------------------------------------------------------------------------- /doc/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/sphere2cube-js/HEAD/doc/preview.png -------------------------------------------------------------------------------- /doc/preview.eq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zebrajaeger/sphere2cube-js/HEAD/doc/preview.eq.png -------------------------------------------------------------------------------- /runConfigurations/test.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 16 | 17 | 18 | 19 |
20 | 32 | 33 | 34 | `} 35 | -------------------------------------------------------------------------------- /src/randomaccessfile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | async function read(fd, position, length) { 4 | return new Promise((resolve, reject) => { 5 | const b = new Buffer.alloc(length); 6 | fs.read(fd, b, 0, length, position, (err, bytesRead, buffer) => { 7 | if (err) { 8 | reject(err); 9 | } else { 10 | if (length !== bytesRead) { 11 | reject('File to short') 12 | } else { 13 | resolve(buffer); 14 | } 15 | } 16 | }) 17 | }); 18 | } 19 | 20 | exports.RandomAccessFile = { 21 | open: async (path) => { 22 | return new Promise((resolve, reject) => { 23 | fs.open(path, 'r', (err, fd) => { 24 | if (err) { 25 | reject(err); 26 | } else { 27 | resolve(fd); 28 | } 29 | }) 30 | }) 31 | }, 32 | 33 | close: async (fd) => { 34 | return new Promise((resolve, reject) => { 35 | fs.close(fd, (err) => { 36 | if (err) { 37 | reject(err); 38 | } else { 39 | resolve(); 40 | } 41 | }) 42 | }) 43 | }, 44 | 45 | read: read, 46 | 47 | fileSize: (filename) => { 48 | const stats = fs.statSync(filename) 49 | return stats["size"] 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/scale.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | class Bilinear extends EventEmitter { 4 | scale(srcParams, targetParams, getPixel, setPixel) { 5 | 6 | const wSrc = srcParams.w; 7 | const hSrc = srcParams.h; 8 | 9 | const wDst = targetParams.w; 10 | const hDst = targetParams.h; 11 | 12 | const assign = function (dX, dY, 13 | x, xMin, xMax, 14 | y, yMin, yMax, 15 | getPixel, setPixel) { 16 | const vMin = interpolate(x, xMin, getPixel(xMin, yMin), xMax, getPixel(xMax, yMin)); 17 | 18 | // special case, y is integer 19 | if (yMax === yMin) { 20 | setPixel(dX, dY, vMin); 21 | } else { 22 | const vMax = interpolate(x, xMin, getPixel(xMin, yMax), xMax, getPixel(xMax, yMax)); 23 | setPixel(dX, dY, interpolate(y, yMin, vMin, yMax, vMax)); 24 | } 25 | } 26 | 27 | const interpolate = function (k, kMin, vMin, kMax, vMax) { 28 | // special case - k is integer 29 | if (kMin === kMax) { 30 | return vMin; 31 | } 32 | 33 | return { 34 | r: Math.round((k - kMin) * vMax.r + (kMax - k) * vMin.r), 35 | g: Math.round((k - kMin) * vMax.g + (kMax - k) * vMin.g), 36 | b: Math.round((k - kMin) * vMax.b + (kMax - k) * vMin.b), 37 | a: Math.round((k - kMin) * vMax.a + (kMax - k) * vMin.a) 38 | } 39 | } 40 | 41 | this.emit('begin', hDst - 1); 42 | for (let i = 0; i < hDst; i++) { 43 | for (let j = 0; j < wDst; j++) { 44 | // x & y in src coordinates 45 | const x = (j * wSrc) / wDst; 46 | const xMin = Math.floor(x); 47 | const xMax = Math.min(Math.ceil(x), wSrc - 1); 48 | 49 | const y = (i * hSrc) / hDst; 50 | const yMin = Math.floor(y); 51 | const yMax = Math.min(Math.ceil(y), hSrc - 1); 52 | 53 | assign(j, i, x, xMin, xMax, y, yMin, yMax, getPixel, setPixel); 54 | } 55 | this.emit('progress', i) 56 | } 57 | this.emit('end') 58 | } 59 | 60 | } 61 | 62 | module.exports.Bilinear = Bilinear; 63 | 64 | 65 | module.exports.nearestNeighbour = (src, dst, getPixel, setPixel) => { 66 | const wSrc = src.w; 67 | const hSrc = src.h; 68 | 69 | const wDst = dst.w; 70 | const hDst = dst.h; 71 | 72 | console.log('nearestNeighbour', {src, dst}, {wSrc, hSrc, wDst, hDst}) 73 | for (let i = 0; i < hDst; i++) { 74 | for (let j = 0; j < wDst; j++) { 75 | const iSrc = Math.floor((i * hSrc) / hDst); 76 | const jSrc = Math.floor((j * wSrc) / wDst); 77 | setPixel(j, i, getPixel(jSrc, iSrc)); 78 | } 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /src/pannellum.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | createHtml: createHtml 3 | } 4 | 5 | function createHtml(config,data) { 6 | return ` 7 | 8 | 9 | 10 | 11 | ${config.htmlTitle} 12 | 13 | 14 | 25 | 26 | 27 | 28 |
29 | 30 | 84 | 85 | 86 | `; 87 | } 88 | 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @zebrajaeger/createpano 2 | 3 | [![NPM Version](https://img.shields.io/npm/v/@zebrajaeger/createpano.svg?style=flat)](https://www.npmjs.org/package/@zebrajaeger/createpano) 4 | [![Install Size](https://packagephobia.now.sh/badge?p=@zebrajaeger/createpano)](https://packagephobia.now.sh/result?p=@zebrajaeger/createpano) 5 | [![License](https://img.shields.io/github/license/zebrajaeger/sphere2cube-js)](https://img.shields.io/github/license/zebrajaeger/sphere2cube-js) 6 | 7 | Convert 8 | - full spheric panorama image to viewer (equirectangular) 9 | - 360° panorama image to viewer (y to small for equirectangular) 10 | - partial panorama image to viewer 11 | 12 | Reads 13 | - PSD and PSB with RAW or RLE Encoding 14 | - jpg 15 | - png 16 | 17 | Writes 18 | - preview (cubic) 19 | - preview (downscaled) 20 | - tiles (pyramide levels) 21 | - html (pannellum implementation) 22 | - all above as zip file 23 | 24 | ## Installation 25 | 26 | ```bash 27 | $ npm install -g @zebrajaeger/createpano 28 | ``` 29 | 30 | ## Usage 31 | 32 | ### Minimum 33 | 34 | ```bash 35 | $ createpano -i sourceimage.psd 36 | ``` 37 | 38 | ## Options 39 | ```bash 40 | Usage: cli [options] 41 | 42 | Options: 43 | -V, --version output the version number 44 | -i, --source Source image (mandatory) 45 | -ipa, --panoAngle Angle of pano (default: "360") 46 | -ipy, --panoYOffset Y-Offset in degree [-90.0...90.0] (default: "0") 47 | -o, --output Output folder (default: "_dist") 48 | -te, --targetSize Image edge length of a face @ max resolution (default: inputImage.x / 4) 49 | -fr, --facesToRender Faces To render (default: "flrbud") 50 | -ti, --tilesIgnore Dont render tiles 51 | -ts, --tileSize Tile size (default: "512") 52 | -tq, --tileJpgQuality Jpg Image quality of tiles in percent (default: "85") 53 | -tp, --tilePathTemplate