import argparse
from PIL import Image
import os
import sys
import math
Image.MAX_IMAGE_PIXELS = None
ANTIALIAS = Image.Resampling.LANCZOS if hasattr(Image, "Resampling") else Image.ANTIALIAS
parser = argparse.ArgumentParser(description='Generate a Pannellum multires tile set from a full equirectangular panorama.',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('inputFile', metavar='INPUT',
help='directory containing cube faces')
parser.add_argument('-o', '--output', dest='output', default='./output',
help='output directory, optionally to be used as basePath (defaults to "./output")')
parser.add_argument('-s', '--tilesize', dest='tileSize', default=512, type=int,
help='tile size in pixels')
parser.add_argument('-f', '--fallbacksize', dest='fallbackSize', default=1024, type=int,
help='fallback tile size in pixels (defaults to 1024, set to 0 to skip)')
parser.add_argument('-a', '--autoload', action='store_true',
help='automatically load panorama in viewer')
parser.add_argument('-q', '--quality', dest='quality', default=75, type=int,
help='output JPEG quality 0-100')
parser.add_argument('--png', action='store_true',
help='output PNG tiles instead of JPEG tiles')
parser.add_argument('-d', '--debug', action='store_true',
help='debug mode (print status info and keep intermediate files)')
args = parser.parse_args()
if os.path.exists(args.output):
print('Output directory "' + args.output + '" already exists')
if not args.debug:
sys.exit(1)
else:
os.makedirs(args.output)
faceLetters = ['f', 'b', 'u', 'd', 'l', 'r']
print('Processing input image information...')
inputDir = args.inputFile
faces = [f for f in os.listdir(inputDir) if os.path.isfile(os.path.join(args.inputFile, f))]
if len(faces) != 6:
print('Error: the number of faces is different than 6.')
sys.exit(1)
cubeSize = -1
for f in faces:
f = os.path.join(args.inputFile, f)
with Image.open(f) as image:
w, h = image.size
if w != h:
print('Error: the face %s is not a square.' % f)
sys.exit(1)
if cubeSize == -1:
cubeSize = w
elif w != cubeSize:
print("Error: all faces don't have the same size.")
sys.exit(1)
faces = sorted(faces, key=lambda f: faceLetters.index(f[0]))
tileSize = min(args.tileSize, cubeSize)
levels = int(math.ceil(math.log(float(cubeSize) / tileSize, 2))) + 1
if round(cubeSize / 2**(levels - 2)) == tileSize:
levels -= 1 extension = '.jpg'
if args.png:
extension = '.png'
if args.debug:
print('maxLevel: ' + str(levels))
print('tileResolution: ' + str(tileSize))
print('cubeResolution: ' + str(cubeSize))
print('Generating tiles...')
for f in range(0, 6):
size = cubeSize
faceExists = os.path.exists(os.path.join(args.inputFile, faces[f]))
if faceExists:
face = Image.open(os.path.join(args.inputFile, faces[f]))
for level in range(levels, 0, -1):
if not os.path.exists(os.path.join(args.output, str(level))):
os.makedirs(os.path.join(args.output, str(level)))
tiles = int(math.ceil(float(size) / tileSize))
if (level < levels):
face = face.resize([size, size], ANTIALIAS)
for i in range(0, tiles):
for j in range(0, tiles):
left = j * tileSize
upper = i * tileSize
right = min(j * args.tileSize + args.tileSize, size) lower = min(i * args.tileSize + args.tileSize, size) tile = face.crop([left, upper, right, lower])
if extension == ".jpg":
tile = tile.convert("RGB")
tile.save(os.path.join(args.output, str(level), faceLetters[f] + str(i) + '_' + str(j) + extension), quality=args.quality)
if args.debug:
print('level: ' + str(level) + ' tiles: ' + str(tiles) + ' tileSize: ' + str(tileSize) + ' size: ' + str(size))
print('left: ' + str(left) + ' upper: ' + str(upper) + ' right: ' + str(right) + ' lower: ' + str(lower))
size = int(size / 2)
if args.fallbackSize > 0:
print('Generating fallback tiles...')
for f in range(0, 6):
if not os.path.exists(os.path.join(args.output, 'fallback')):
os.makedirs(os.path.join(args.output, 'fallback'))
if os.path.exists(os.path.join(args.inputFile, faces[f])):
face = Image.open(os.path.join(args.inputFile, faces[f]))
if extension == ".jpg":
face = face.convert("RGB")
face = face.resize([args.fallbackSize, args.fallbackSize], ANTIALIAS)
face.save(os.path.join(args.output, 'fallback', faceLetters[f] + extension), quality = args.quality)
text = []
text.append('{')
text.append(' "hfov": 100' + ',')
if args.autoload:
text.append(' "autoLoad": true,')
text.append(' "type": "multires",')
text.append(' "multiRes": {')
text.append(' "path": "/%l/%s%y_%x",')
if args.fallbackSize > 0:
text.append(' "fallbackPath": "/fallback/%s",')
text.append(' "extension": "' + extension[1:] + '",')
text.append(' "tileResolution": ' + str(tileSize) + ',')
text.append(' "maxLevel": ' + str(levels) + ',')
text.append(' "cubeResolution": ' + str(cubeSize))
text.append(' }')
text.append('}')
text = '\n'.join(text)
with open(os.path.join(args.output, 'config.json'), 'w') as f:
f.write(text)