You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
quaggaJS/src/input/Source.js

321 lines
9.6 KiB
JavaScript

import {clone} from 'lodash';
import {determineOrientation, PORTRAIT, LANDSCAPE, SQUARE} from '../common/device';
import CameraAccess from './camera_access';
import {getViewport} from '../common/utils';
const ConstraintPresets = [
{
width: 720,
height: 1280,
}, {
width: 540,
height: 960,
}, {
width: 600,
height: 800,
}, {
width: 480,
height: 640,
}, {
width: 1280,
height: 720,
}, {
width: 960,
height: 540,
}, {
width: 800,
height: 600,
}, {
width: 640,
height: 480,
}, {
width: 1280,
height: 1280,
}, {
width: 1080,
height: 1080,
}, {
width: 960,
height: 960,
}, {
width: 800,
height: 800,
}, {
width: 640,
height: 640,
},
].map((preset) => Object.assign({}, preset, {aspectRatio: preset.width / preset.height}));
function getFilter(aspectRatio) {
if (aspectRatio === 1) {
return pre => pre.aspectRatio === aspectRatio;
} else if (aspectRatio > 1) {
return pre => pre.aspectRatio > 1;
}
return pre => pre.aspectRatio < 1;
}
function resolveMinWidthToAdvanced({aspectRatio, minPixels}) {
return [...ConstraintPresets]
.filter(getFilter(aspectRatio))
.map((pre) => {
return {
error: Math.abs((pre.width * pre.height) - minPixels),
pre,
};
})
.sort(({error: errorA}, {error: errorB}) => {
if (errorB > errorA) {
return -1;
}
if (errorB < errorA) {
return 1;
}
return 0;
})
.map(({pre}) => pre);
}
function getOrCreateVideo(source, target) {
const $viewport = getViewport(target);
if ($viewport) {
let $video = $viewport.querySelector("video");
if (!$video) {
$video = document.createElement("video");
$viewport.appendChild($video);
}
return $video;
}
return document.createElement("video");
}
function constraintToNumber(constraint) {
if (!constraint) {
return null;
}
if (typeof constraint === 'number') {
return constraint;
}
const {ideal, exact, min, max} = constraint;
if (typeof exact !== 'undefined') {
return exact;
}
if (typeof ideal !== 'undefined') {
return ideal;
}
if (typeof min !== 'undefined') {
return min;
}
if (typeof max !== 'undefined') {
return max;
}
return null;
}
function adjustWithZoom(videoConstraints) {
const constraints = clone(videoConstraints);
const orientation = determineOrientation();
let zoom = constraintToNumber(constraints.zoom) || 1,
width = constraintToNumber(constraints.width),
height = constraintToNumber(constraints.height),
aspectRatio = constraintToNumber(constraints.aspectRatio) || (width / height);
if (constraints[orientation]) {
zoom = constraintToNumber(constraints[orientation].zoom) || zoom;
width = constraintToNumber(constraints[orientation].width) || width;
height = constraintToNumber(constraints[orientation].height) || height;
aspectRatio = constraintToNumber(constraints[orientation].aspectRatio) || (width / height);
}
if (zoom > 1) {
width = Math.floor(width * zoom);
height = Math.floor(height * zoom);
}
delete constraints.zoom;
delete constraints.orientation;
delete constraints.landscape;
delete constraints.portrait;
const advanced = resolveMinWidthToAdvanced({minPixels: (width * height), aspectRatio});
return {
zoom,
video: Object.assign({}, constraints, {
width: {ideal: advanced[0].width},
height: {ideal: advanced[0].height},
aspectRatio: {exact: advanced[0].aspectRatio || aspectRatio},
advanced,
}),
};
}
export function fromCamera(constraints) {
var {video: videoConstraints, zoom} = adjustWithZoom(constraints);
const video = getOrCreateVideo();
return CameraAccess.request(video, videoConstraints)
.then(function(mediastream) {
const track = mediastream.getVideoTracks()[0];
return {
type: "CAMERA",
getDimensions() {
const viewport = {
x: 0,
y: 0,
width: video.videoWidth,
height: video.videoHeight,
};
if (zoom > 1) {
viewport.width = Math.floor(video.videoWidth / zoom);
viewport.height = Math.floor(video.videoHeight / zoom);
viewport.x = Math.floor((video.videoWidth - viewport.width) / 2);
viewport.y = Math.floor((video.videoHeight - viewport.height) / 2);
}
return {
viewport,
canvas: {
width: viewport.width, // AR
height: viewport.height, // AR
},
};
},
getConstraints: function() {
return videoConstraints;
},
getDrawable: function() {
return video;
},
applyConstraints: function(constraints) {
track.stop();
videoConstraints = Object.assign({}, constraints);
if (determineOrientation() === PORTRAIT) {
constraints = Object.assign({}, constraints, {
width: constraints.height,
height: constraints.width,
});
}
console.log(videoConstraints, constraints);
if (constraints.zoom && constraints.zoom.exact > 1) {
constraints.width.ideal = Math.floor(constraints.width.ideal * constraints.zoom.exact);
constraints.height.ideal = Math.floor(constraints.height.ideal * constraints.zoom.exact);
delete constraints.zoom;
}
return CameraAccess.request(video, videoConstraints);
},
getLabel: function() {
return track.label;
}
};
});
}
export function fromCanvas(input) {
var $canvas = null;
if (typeof input === 'string') {
$canvas = document.querySelector(input);
} else if (input instanceof HTMLCanvasElement) {
$canvas = input;
} else {
return Promise.reject("fromCanvas needs a selector or HTMLCanvasElement");
}
return Promise.resolve({
type: "CANVAS",
getWidth: function() {
return $canvas.width;
},
getHeight: function() {
return $canvas.height;
},
getDrawable: function() {
return $canvas;
},
getLabel: function() {
return $canvas.getAttribute('id');
},
getConstraints: function() {
return {};
},
applyConstraints: function() {
console.log('CanvasSource.applyConstraints not implemented');
}
});
}
export function fromImage(input, constraints = {width: 800, height: 800, channels: 3}) {
var $image = null;
var src = null;
if (typeof input === 'string') {
// data or url, or queryString
$image = new Image();
src = input;
} else if (input instanceof HTMLImageElement) {
$image = input;
} else if (input instanceof File) {
$image = new Image();
src = URL.createObjectURL(input);
} else {
return Promise.reject("fromImage needs a src, HTMLImageElement or File");
}
return new Promise(function(resolve, reject) {
if (src || !$image.complete) {
console.log('Adding eventlistener');
$image.addEventListener('load', function() {
resolve();
}, false);
$image.addEventListener('error', function(e) {
reject(e);
}, false);
if (src) {
console.log(`Setting src = ${src}`);
$image.src = src;
}
} else {
return resolve();
}
})
.then(() => {
const width = $image.naturalWidth;
const height = $image.naturalHeight;
const imageAR = width / height;
const calculatedWidth = imageAR > 1 ? constraints.width : Math.floor((imageAR) * constraints.width);
const calculatedHeight = imageAR > 1 ? Math.floor((1 / imageAR) * constraints.width) : constraints.width;
const colorChannels = constraints.channels || 3;
return {
type: "IMAGE",
colorChannels,
getDimensions() {
return {
viewport: {
width: $image.naturalWidth, // AR
height: $image.naturalHeight, // AR
x: 0, // AR
y: 0, // AR
},
canvas: {
width: calculatedWidth, // AR
height: calculatedHeight, // AR
},
};
},
getDrawable: function() {
return $image;
},
getLabel: function() {
return $image.src;
},
getConstraints: function() {
return constraints;
},
applyConstraints: function() {
console.log('ImageSource.applyConstraints not implemented');
},
};
});
}