stereo-img
Version:
a web component to display stereographic pictures on web pages, with VR support
107 lines (87 loc) • 3.42 kB
JavaScript
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* Parser for VR Photos that embed left eye in GImage XMP image metadata: Cardboard camera, Pixel Camera panorama, VR180 pictures...
*/
// see official spec at https://developers.google.com/vr/reference/cardboard-camera-vr-photo-format
import exifr from './../../vendor/exifr/full.esm.js';
async function parseVR(url) {
const image = await createImageFromURL(url);
//image.crossOrigin = "Anonymous";
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = image.width;
const height = image.height;
canvas.width = width;
canvas.height = height;
ctx.drawImage(image, 0, 0);
const leftEye = ctx.getImageData(0, 0, width, height);
const result = {leftEye};
const exif = await exifr.parse(url, {
xmp: true,
multiSegment: true,
mergeOutput: false,
ihdr: true, //unclear why we need this, but if not enabled, some VR180 XMP Data are not parsed
});
// VR180 are a half sphere, but are just a special case.
if(exif.GPano?.CroppedAreaImageWidthPixels && exif.GPano?.FullPanoWidthPixels) {
result.phiLength = exif.GPano.CroppedAreaImageWidthPixels / exif.GPano.FullPanoWidthPixels * 2 * Math.PI;
} else {
// assume VR180 (should we assume full sphere instead?)
console.warn('No GPano CroppedAreaImageWidthPixels and FullPanoWidthPixels data found, assuming VR180');
result.phiLength = Math.PI;
}
if(exif.GPano?.CroppedAreaImageHeightPixels && exif.GPano?.FullPanoHeightPixels) {
result.thetaLength = exif.GPano.CroppedAreaImageHeightPixels / exif.GPano.FullPanoHeightPixels * Math.PI;
} else {
result.thetaLength = Math.PI;
}
if(exif.GPano?.CroppedAreaTopPixels) {
result.thetaStart = exif.GPano.CroppedAreaTopPixels / exif.GPano.FullPanoHeightPixels * Math.PI;
} else {
result.thetaStart = 0;
}
if(exif.GPano?.PoseRollDegrees) {
result.roll = exif.GPano.PoseRollDegrees / 180 * Math.PI;
}
if(exif.GPano?.PosePitchDegrees) {
result.pitch = exif.GPano.PosePitchDegrees / 180 * Math.PI;
}
if (!exif.GImage?.Data) {
const err = "No right eye data found in XMP of image";
console.error(err);
result.error = err;
return result;
}
var image2 = await createImageFromURL("data:image/jpg;base64," + exif.GImage.Data);
const canvas2 = document.createElement('canvas');
const ctx2 = canvas2.getContext('2d');
canvas2.width = width;
canvas2.height = height;
ctx2.drawImage(image2, 0, 0);
result.rightEye = ctx2.getImageData(0, 0, width, height);
return result;
}
async function createImageFromURL(url) {
const image = new Image();
image.src = url;
return new Promise((resolve, reject) => {
image.onload = () => {
URL.revokeObjectURL(url);
resolve(image);
};
image.onerror = reject;
});
}
export {parseVR}