File: /home/panomity.de/vr.panomity.com/api/equirect-reconstruct.php
<?php
/**
* Equirect Rekonstruktion aus Tiles
*/
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
header('Content-Type: application/json');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { http_response_code(405); echo json_encode(['error' => 'Method not allowed']); exit; }
$input = file_get_contents('php://input');
$data = json_decode($input, true);
$panoId = isset($data['pano_id']) ? (int)$data['pano_id'] : 0;
if ($panoId <= 0) { http_response_code(400); echo json_encode(['error' => 'Invalid pano_id']); exit; }
$rootDir = realpath(__DIR__ . '/..');
if ($rootDir === false) { $rootDir = dirname(__DIR__); }
$tilesDir = $rootDir . '/panos/p' . $panoId . '.tiles';
if (!is_dir($tilesDir)) { @mkdir($tilesDir, 0777, true); }
$equirectPath = $tilesDir . '/equirect.jpg';
$originals = [
$rootDir . '/p' . $panoId . '.jpg',
$rootDir . '/p' . $panoId . '.jpeg',
$rootDir . '/p' . $panoId . '.png',
$rootDir . '/p' . $panoId . '.tif',
$rootDir . '/p' . $panoId . '.tiff',
$rootDir . '/p' . $panoId . '.psb',
];
$ktool = realpath($rootDir . '/admin/data/bin/krpano/krpanotools');
if ($ktool && is_file($ktool)) { @shell_exec('chmod +x ' . escapeshellarg($ktool)); }
function assemble_face_from_tiles($faceDir, $tmpFacePath) {
if (!is_dir($faceDir)) return false;
$yDirs = glob($faceDir . '/*', GLOB_ONLYDIR);
if (!$yDirs) return false;
$yIdxs = [];
foreach ($yDirs as $yd) { $b = basename($yd); if (ctype_digit($b)) $yIdxs[] = (int)$b; }
if (!$yIdxs) return false;
sort($yIdxs);
$tileW = 0; $tileH = 0; $rows = []; $maxX = 0;
foreach ($yIdxs as $y) {
$xFiles = glob($faceDir . '/' . $y . '/*.{jpg,jpeg,png}', GLOB_BRACE);
if (!$xFiles) continue;
$xIdxs = []; $row = [];
foreach ($xFiles as $xf) {
$xb = basename($xf, '.' . pathinfo($xf, PATHINFO_EXTENSION));
if (ctype_digit($xb)) { $xIdxs[] = (int)$xb; $row[(int)$xb] = $xf; }
}
if (!$xIdxs) continue;
sort($xIdxs);
$maxX = max($maxX, max($xIdxs));
$rows[$y] = $row;
if ($tileW === 0 || $tileH === 0) {
$sz = @getimagesize($row[$xIdxs[0]]);
if ($sz) { $tileW = $sz[0]; $tileH = $sz[1]; }
}
}
if ($tileW === 0 || $tileH === 0 || !$rows) return false;
$maxY = max(array_keys($rows));
$canvasW = ($maxX + 1) * $tileW;
$canvasH = ($maxY + 1) * $tileH;
$canvas = imagecreatetruecolor($canvasW, $canvasH);
foreach ($rows as $y => $row) {
foreach ($row as $x => $file) {
$srcImg = @imagecreatefromjpeg($file);
if (!$srcImg) $srcImg = @imagecreatefrompng($file);
if (!$srcImg) continue;
imagecopy($canvas, $srcImg, $x * $tileW, $y * $tileH, 0, 0, $tileW, $tileH);
imagedestroy($srcImg);
}
}
imagejpeg($canvas, $tmpFacePath, 90);
imagedestroy($canvas);
return file_exists($tmpFacePath);
}
function assemble_face_from_flat_tiles($faceDir, $tmpFacePath) {
if (!is_dir($faceDir)) return false;
$files = glob($faceDir . '/*.{jpg,jpeg,png}', GLOB_BRACE);
if (!$files) return false;
$tiles = [];
$xs = []; $ys = [];
foreach ($files as $f) {
$bn = basename($f);
if (preg_match('/(\d+)[._-](\d+)\.(?:jpg|jpeg|png)$/i', $bn, $m)) {
$a = (int)$m[1]; $b = (int)$m[2];
// Versuch 1: a=x, b=y
$tiles[] = ['x'=>$a,'y'=>$b,'f'=>$f,'try'=>1];
$xs[$a] = true; $ys[$b] = true;
// optional: Versuch 2: a=y, b=x wird weiter unten getestet
}
}
if (!$tiles) return false;
ksort($xs); ksort($ys);
$xList = array_keys($xs); $yList = array_keys($ys);
$tileW = 0; $tileH = 0;
$sz = @getimagesize($tiles[0]['f']); if ($sz) { $tileW = $sz[0]; $tileH = $sz[1]; }
if ($tileW === 0 || $tileH === 0) return false;
$canvasW = (count($xList)) * $tileW; $canvasH = (count($yList)) * $tileH;
$canvas = imagecreatetruecolor($canvasW, $canvasH);
foreach ($tiles as $t) {
$srcImg = @imagecreatefromjpeg($t['f']); if (!$srcImg) $srcImg = @imagecreatefrompng($t['f']); if (!$srcImg) continue;
$xi = array_search($t['x'], $xList, true); $yi = array_search($t['y'], $yList, true);
if ($xi === false || $yi === false) { imagedestroy($srcImg); continue; }
imagecopy($canvas, $srcImg, $xi * $tileW, $yi * $tileH, 0, 0, $tileW, $tileH);
imagedestroy($srcImg);
}
imagejpeg($canvas, $tmpFacePath, 90);
imagedestroy($canvas);
if (file_exists($tmpFacePath)) return true;
return false;
}
function split_strip_into_faces_any($imgPath, $outDir, &$facesOut) {
$img = @imagecreatefromjpeg($imgPath);
if (!$img) $img = @imagecreatefrompng($imgPath);
if (!$img) return false;
$w = imagesx($img); $h = imagesy($img);
$orders = [ ['l','f','r','b','u','d'], ['f','r','b','l','u','d'], ['r','b','l','f','u','d'], ['b','l','f','r','u','d'] ];
if ($h % 6 === 0 && abs(($h/6) - $w) < 20) {
$size = (int)round($h/6);
foreach ($orders as $order) {
$tmpFaces = [];
for ($i=0; $i<6; $i++) {
$crop = imagecreatetruecolor($size, $size);
imagecopy($crop, $img, 0, 0, 0, $i*$size, $size, $size);
$p = $outDir . '/' . $order[$i] . '.jpg'; imagejpeg($crop, $p, 90); imagedestroy($crop);
$tmpFaces[$order[$i]] = $p;
}
$facesOut = $tmpFaces; imagedestroy($img); return true;
}
}
if ($w % 6 === 0 && abs(($w/6) - $h) < 20) {
$size = (int)round($w/6);
foreach ($orders as $order) {
$tmpFaces = [];
for ($i=0; $i<6; $i++) {
$crop = imagecreatetruecolor($size, $size);
imagecopy($crop, $img, 0, 0, $i*$size, 0, $size, $size);
$p = $outDir . '/' . $order[$i] . '.jpg'; imagejpeg($crop, $p, 90); imagedestroy($crop);
$tmpFaces[$order[$i]] = $p;
}
$facesOut = $tmpFaces; imagedestroy($img); return true;
}
}
imagedestroy($img); return false;
}
function run_cube2sphere($ktool, $faces, $outPath, &$debugOut = null) {
if (!$ktool || !is_file($ktool)) {
$debugOut = ['error' => 'ktool not found or not a file', 'ktool' => $ktool];
return false;
}
$need = ['l','r','f','b','u','d'];
foreach ($need as $n) {
if (empty($faces[$n])) {
$debugOut = ['error' => 'Missing face: ' . $n, 'faces' => $faces];
return false;
}
}
// krpanotools arbeitet besser mit relativem Arbeitsverzeichnis
// Kopiere alle Faces in ein tmp-Verzeichnis und wechsle dorthin
$workDir = sys_get_temp_dir() . '/krpano_work_' . uniqid();
@mkdir($workDir, 0777, true);
// Bestimme die Cube-Größe aus dem ersten Face
$firstFace = $faces['l'];
$faceSize = null;
$imgInfo = @getimagesize($firstFace);
if ($imgInfo) {
$faceSize = $imgInfo[0]; // Breite = Cube-Größe
}
// krpanotools erwartet Suffixe wie _l, _f, _r, etc. ODER explizite -l= -f= Syntax
// Wir verwenden die explizite Syntax
$localFaces = [];
foreach ($faces as $key => $srcPath) {
$localPath = $workDir . '/face_' . $key . '.jpg';
if (!@copy($srcPath, $localPath)) {
$debugOut = ['error' => 'Failed to copy face to workdir', 'face' => $key, 'src' => $srcPath];
return false;
}
$localFaces[$key] = 'face_' . $key . '.jpg';
}
$localOut = $workDir . '/equirect.jpg';
// Explizite Syntax: -l=file -f=file -r=file -b=file -u=file -d=file
$cmd = 'cd ' . escapeshellarg($workDir) . ' && '
. escapeshellarg($ktool)
. ' cube2sphere'
. ' -l=' . escapeshellarg($localFaces['l'])
. ' -f=' . escapeshellarg($localFaces['f'])
. ' -r=' . escapeshellarg($localFaces['r'])
. ' -b=' . escapeshellarg($localFaces['b'])
. ' -u=' . escapeshellarg($localFaces['u'])
. ' -d=' . escapeshellarg($localFaces['d'])
. ' -o=equirect.jpg';
$output = shell_exec($cmd . ' 2>&1');
$success = false;
if (file_exists($localOut)) {
if (@copy($localOut, $outPath)) {
$success = true;
}
}
// Cleanup
@unlink($localOut);
foreach ($localFaces as $lf) { @unlink($workDir . '/' . $lf); }
@rmdir($workDir);
$debugOut = [
'cmd' => $cmd,
'output' => $output,
'outPath' => $outPath,
'localOut' => $localOut,
'exists' => file_exists($outPath)
];
return $success;
}
function find_single_image_in_dir($dir) {
$files = glob($dir . '/*.{jpg,jpeg,png}', GLOB_BRACE);
if (!$files) return null;
// wähle die größte Datei
usort($files, function($a,$b){ return filesize($b) <=> filesize($a); });
return $files[0];
}
// Original -> equirect
$src = null; foreach ($originals as $cand) { if (file_exists($cand)) { $src = $cand; break; } }
if ($src) {
$ext = strtolower(pathinfo($src, PATHINFO_EXTENSION));
if (in_array($ext, ['jpg','jpeg'])) { @copy($src, $equirectPath); }
else { $img = null; if ($ext === 'png') $img = @imagecreatefrompng($src); if ($img) { @imagejpeg($img, $equirectPath, 90); @imagedestroy($img); } else { @copy($src, $equirectPath); } }
if (file_exists($equirectPath)) { echo json_encode(['success' => true, 'file' => 'panos/p' . $panoId . '.tiles/equirect.jpg', 'mode' => 'original']); exit; }
}
$levelDirs = glob($tilesDir . '/l*', GLOB_ONLYDIR);
$levelNums = [];
if ($levelDirs) { foreach ($levelDirs as $ld) { $bn = basename($ld); if ($bn !== '' && $bn[0] === 'l' && ctype_digit(substr($bn,1))) { $levelNums[] = (int)substr($bn,1); } } }
rsort($levelNums);
$tmpDir = sys_get_temp_dir() . '/p' . $panoId . '_faces_' . uniqid(); @mkdir($tmpDir, 0777, true);
$faces = ['l'=>null,'r'=>null,'f'=>null,'b'=>null,'u'=>null,'d'=>null];
// Level-Grid
foreach ($levelNums as $ln) {
$base = $tilesDir . '/l' . $ln; $okAll = true;
foreach (array_keys($faces) as $face) { $out = $tmpDir . '/' . $face . '.jpg'; if (!assemble_face_from_tiles($base . '/' . $face, $out)) { $okAll = false; break; } $faces[$face] = $out; }
if ($okAll && run_cube2sphere($ktool, $faces, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'grid-level']); exit; }
}
// Flaches Tile-Muster in Face-Ordnern (x_y.jpg)
$facesFlat = [];
$okFlat = true;
foreach (array_keys($faces) as $fc) {
$out = $tmpDir . '/' . $fc . '.jpg';
if (!assemble_face_from_flat_tiles($tilesDir . '/' . $fc, $out)) { $okFlat = false; break; }
$facesFlat[$fc] = $out;
}
if ($okFlat && run_cube2sphere($ktool, $facesFlat, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'flat-faces']); exit; }
// Direkte Face-Dateien im Root (Groß-/Klein-/Synonyme)
$map = [ 'l'=>['l','L','left'], 'r'=>['r','R','right'], 'f'=>['f','F','front'], 'b'=>['b','B','back'], 'u'=>['u','U','up','top'], 'd'=>['d','D','down','bottom'] ];
$df = []; foreach ($map as $key=>$cands) { foreach ($cands as $n) { foreach (glob($tilesDir . '/' . $n . '.{jpg,jpeg,png}', GLOB_BRACE) as $f) { $df[$key]=$f; break 2; } } }
if (count($df) === 6 && run_cube2sphere($ktool, $df, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'root-faces']); exit; }
// Fallback: Unterordner 'vr' durchsuchen
$vr = $tilesDir . '/vr';
$vrDebug = ['exists' => is_dir($vr), 'path' => $vr];
if (is_dir($vr)) {
// pano_*.jpg Dateien im vr-Root (häufigste Variante)
$vrFaces = [];
$vrMap = ['l'=>'pano_l','r'=>'pano_r','f'=>'pano_f','b'=>'pano_b','u'=>'pano_u','d'=>'pano_d'];
foreach ($vrMap as $key => $name) {
$pattern = $vr . '/' . $name . '.{jpg,jpeg,png}';
$matches = glob($pattern, GLOB_BRACE);
$vrDebug['glob_'.$key] = ['pattern' => $pattern, 'matches' => $matches];
if ($matches) {
$vrFaces[$key] = $matches[0];
}
}
$vrDebug['found_faces'] = $vrFaces;
$vrDebug['count'] = count($vrFaces);
$vrDebug['ktool_exists'] = ($ktool && is_file($ktool));
if (count($vrFaces) === 6) {
$c2sDebug = null;
$result = run_cube2sphere($ktool, $vrFaces, $equirectPath, $c2sDebug);
$vrDebug['cube2sphere_result'] = $result;
$vrDebug['cube2sphere_debug'] = $c2sDebug;
$vrDebug['equirect_exists'] = file_exists($equirectPath);
if ($result) {
echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-pano']);
exit;
}
}
// Faces in vr/<face>/ (flach)
$facesV = [];
$okV = true;
foreach (array_keys($faces) as $fc) {
$out = $tmpDir . '/vr_' . $fc . '.jpg';
if (!assemble_face_from_flat_tiles($vr . '/' . $fc, $out)) { $okV = false; break; }
$facesV[$fc] = $out;
}
if ($okV && run_cube2sphere($ktool, $facesV, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-flat']); exit; }
// Direktdateien in vr-Root (generisch)
$df2 = []; foreach ($map as $key=>$cands) { foreach ($cands as $n) { foreach (glob($vr . '/' . $n . '.{jpg,jpeg,png}', GLOB_BRACE) as $f) { $df2[$key]=$f; break 2; } } }
if (count($df2) === 6 && run_cube2sphere($ktool, $df2, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-root']); exit; }
// preview im vr
foreach (['preview.jpg','preview.jpeg','preview.png'] as $prev) {
$preview = $vr . '/' . $prev;
if (file_exists($preview)) {
$faces2 = [];
if (split_strip_into_faces_any($preview, $tmpDir, $faces2)) {
if (run_cube2sphere($ktool, $faces2, $equirectPath)) { echo json_encode(['success'=>true,'file'=>'panos/p'.$panoId.'.tiles/equirect.jpg','mode'=>'vr-strip']); exit; }
}
}
}
}
$ls = @scandir($tilesDir) ?: [];
$debug = ['root' => $ls];
// Face-Ordner inspizieren
foreach (['l','r','f','b','u','d'] as $fc) {
$fdir = $tilesDir . '/' . $fc;
if (is_dir($fdir)) {
$debug[$fc] = @scandir($fdir) ?: [];
}
}
// vr-Ordner inspizieren
if (is_dir($tilesDir . '/vr')) {
$debug['vr'] = @scandir($tilesDir . '/vr') ?: [];
foreach (['l','r','f','b','u','d'] as $fc) {
$fdir = $tilesDir . '/vr/' . $fc;
if (is_dir($fdir)) {
$debug['vr/'.$fc] = @scandir($fdir) ?: [];
}
}
}
$debug['vr_debug'] = $vrDebug ?? null;
http_response_code(404);
echo json_encode(['error'=>'Failed to reconstruct from tiles or preview','debug'=>$debug,'krpano'=>(string)$ktool]);
exit;
?>