-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5b014e4
commit d86602a
Showing
5 changed files
with
960 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,328 @@ | ||
/* | ||
This helper can help for: | ||
* adjusting the canvas resolution to the good size -> this is crucial to | ||
optimize the code because if the canvas is too large, | ||
there are too much pixels to compute => it will be slow | ||
* to mirror horizontally or not the canvas -> if the front camera is used we | ||
need it flipped (mirror effect), while if the rear camera is used we need it not flipped | ||
* to get the best camera resolution (either above the canvas resolution or closer) | ||
to balance between performance and quality | ||
*/ | ||
"use strict"; | ||
|
||
const JeelizResizer = (function(){ | ||
// private vars: | ||
let _domCanvas = null, | ||
_whCanvasPx = null, | ||
_isApplyCSS = false, | ||
_resizeAttemptsCounter = 0, | ||
_overSamplingFactor = 1, | ||
_isFullScreen = false, | ||
_timerFullScreen = null, | ||
_callbackResize = null, | ||
_isInvFullscreenWH = false; | ||
|
||
const _cameraResolutions = [ // all resolutions should be in landscape mode | ||
[640,480], | ||
[768,480], | ||
[800,600], | ||
[960,640], | ||
[960,720], | ||
[1024,768], | ||
[1280,720], | ||
[1920, 1080] | ||
]; | ||
|
||
//private functions | ||
function add_CSStransform(domElement, CSS){ | ||
const CSStransform = domElement.style.transform; | ||
if (CSStransform.indexOf(CSS) !== -1) return; | ||
domElement.style.transform = CSS + ' ' + CSStransform; | ||
} | ||
|
||
// Compute overlap between 2 rectangles A and B | ||
// characterized by their width and their height in pixels | ||
// the rectangles are centered | ||
// return the ratio (pixels overlaped)/(total pixels) | ||
function compute_overlap(whA, whB){ | ||
const aspectRatioA = whA[0] / whA[1]; | ||
const aspectRatioB = whB[0] / whB[1]; //higher aspectRatio -> more landscape | ||
|
||
var whLandscape, whPortrait; | ||
if (aspectRatioA > aspectRatioB){ | ||
whLandscape = whA, whPortrait = whB; | ||
} else { | ||
whLandscape = whB, whPortrait = whA; | ||
} | ||
|
||
// The overlapped area will be always a rectangle | ||
const areaOverlap = Math.min(whLandscape[0], whPortrait[0]) * Math.min(whLandscape[1], whPortrait[1]); | ||
|
||
var areaTotal; | ||
if (whLandscape[0]>=whPortrait[0] && whLandscape[1]>=whPortrait[1]){ //union is a rectangle | ||
areaTotal = whLandscape[0]*whLandscape[1]; | ||
} else if (whPortrait[0]>whLandscape[0] && whPortrait[1]>whLandscape[1]){ //union is a rectangle | ||
areaTotal = whPortrait[0]*whPortrait[1]; | ||
} else { //union is a cross | ||
areaTotal = whLandscape[0]*whLandscape[1]; | ||
areaTotal += (whPortrait[1]-whLandscape[1])*whPortrait[0]; | ||
} | ||
|
||
return areaOverlap / areaTotal; | ||
} //end compute_overlap() | ||
|
||
function update_sizeCanvas(){ | ||
const domRect = _domCanvas.getBoundingClientRect(); | ||
apply_sizeCanvas(domRect.width, domRect.height); | ||
} | ||
|
||
function apply_sizeCanvas(width, height){ | ||
_whCanvasPx = [ | ||
Math.round(_overSamplingFactor * width), | ||
Math.round(_overSamplingFactor * height) | ||
]; | ||
|
||
// set canvas resolution: | ||
_domCanvas.setAttribute('width', _whCanvasPx[0]); | ||
_domCanvas.setAttribute('height', _whCanvasPx[1]); | ||
|
||
// canvas display size: | ||
if (_isApplyCSS){ | ||
_domCanvas.style.width = width.toString() + 'px'; | ||
_domCanvas.style.height = height.toString() + 'px'; | ||
} | ||
} | ||
|
||
function on_windowResize(){ | ||
// avoid to resize too often using a timer | ||
// (it can create weird bug with some browsers) | ||
if (_timerFullScreen){ | ||
clearTimeout(_timerFullScreen); | ||
} | ||
_timerFullScreen = setTimeout(resize_fullScreen, 50); | ||
} | ||
|
||
function resize_canvasToFullScreen(){ | ||
const wh = [window['innerWidth'], window['innerHeight']]; | ||
if (_isInvFullscreenWH){ | ||
wh.reverse(); | ||
} | ||
apply_sizeCanvas(wh[0], wh[1]); | ||
} | ||
|
||
function resize_fullScreen(){ | ||
resize_canvasToFullScreen(); | ||
JEELIZFACEFILTER.resize(); | ||
_timerFullScreen = null; | ||
if (_callbackResize) { | ||
_callbackResize(); | ||
} | ||
} | ||
|
||
// public methods: | ||
const that = { | ||
// return true or false if the device is in portrait or landscape mode | ||
// see https://stackoverflow.com/questions/4917664/detect-viewport-orientation-if-orientation-is-portrait-display-alert-message-ad | ||
is_portrait: function(){ | ||
try{ | ||
if (window['matchMedia']("(orientation: portrait)")['matches']){ | ||
return true; | ||
} else { | ||
return false; | ||
} | ||
} catch(e){ | ||
return (window['innerHeight'] > window['innerWidth']); | ||
} | ||
}, | ||
|
||
// check whether the user is using IOS or not | ||
// see https://stackoverflow.com/questions/9038625/detect-if-device-is-ios | ||
check_isIOS: function(){ | ||
const isIOS = /iPad|iPhone|iPod/.test(navigator['userAgent']) && !window['MSStream']; | ||
return isIOS; | ||
}, | ||
|
||
// Should be called only if IOS was detected | ||
// see https://stackoverflow.com/questions/8348139/detect-ios-version-less-than-5-with-javascript | ||
get_IOSVersion: function(){ | ||
const v = (navigator['appVersion']).match(/OS (\d+)_(\d+)_?(\d+)?/); | ||
return (v.length > 2) ? [parseInt(v[1], 10), parseInt(v[2], 10), parseInt(v[3] || 0, 10)] : [0, 0, 0]; | ||
}, | ||
|
||
// Check whether the user is using Android or not | ||
// see https://stackoverflow.com/questions/6031412/detect-android-phone-via-javascript-jquery | ||
check_isAndroid: function(){ | ||
const ua = navigator['userAgent'].toLowerCase(); | ||
return (ua.indexOf('android') !== -1); | ||
}, | ||
|
||
// Should be called only if Android was detected | ||
// see https://stackoverflow.com/questions/7184573/pick-up-the-android-version-in-the-browser-by-javascript | ||
get_androidVersion: function(){ | ||
const ua = navigator['userAgent'].toLowerCase(); | ||
const match = ua.match(/android\s([0-9\.]*)/i); | ||
if (!match || match.length<2){ | ||
return [0,0,0]; | ||
} | ||
const v = match[1].split('.'); | ||
return [ | ||
parseInt(v[0], 10), | ||
parseInt(v[1], 10), | ||
parseInt(v[2] || 0, 10) | ||
]; | ||
}, | ||
|
||
// to get a video of 480x640 (480 width and 640 height) | ||
// with a mobile phone in portrait mode, the default implementation | ||
// should require a 480x640 video (Chrome, Firefox) | ||
// but bad implementations needs to always request landscape resolutions (so 640x480) | ||
// see https://github.com/jeeliz/jeelizFaceFilter/issues/144 | ||
require_flipVideoWHIfPortrait: function(){ | ||
// disabled because of https://github.com/jeeliz/jeelizFaceFilter/issues/144 | ||
// seems quite a mess though... | ||
|
||
/* if (that.check_isIOS()){ | ||
//the user is using IOS | ||
const version = that.get_IOSVersion(); | ||
if (version[0] >= 13){ | ||
if (version[1] <= 1 // IOS 13.0.X | ||
|| (version[1] === 1 && version[2] < 3)){ // IOS 13.1.X with X<3 | ||
return false; | ||
} | ||
} | ||
} | ||
if (that.check_isAndroid()){ | ||
const version = that.get_androidVersion(); | ||
if (version[0] >= 9){ // Android 9+ | ||
return false; | ||
} | ||
} */ | ||
|
||
// normal implementation | ||
return false; | ||
}, | ||
|
||
// size canvas to the right resolution | ||
// should be called after the page loading | ||
// when the canvas has already the right size | ||
// options: | ||
// - <string> canvasId: id of the canvas | ||
// - <HTMLCanvasElement> canvas: if canvasId is not provided | ||
// - <function> callback: function to launch if there was an error or not | ||
// - <float> overSamplingFactor: facultative. If 1, same resolution than displayed size (default). | ||
// If 2, resolution twice higher than real size | ||
// - <boolean> CSSFlipX: if we should flip the canvas or not. Default: false | ||
// - <boolean> isFullScreen: if we should set the canvas fullscreen. Default: false | ||
// - <function> onResize: function called when the window is resized. Only enabled if isFullScreen = true | ||
// - <boolean> isInvWH: if we should invert width and height for fullscreen mode only. default = false | ||
// - <boolean> isApplyCSS: if we should also apply canvas dimensions as CSS. default = false | ||
size_canvas: function(optionsArg){ | ||
const options = Object.assign({ | ||
canvasId: 'undefinedCanvasId', | ||
canvas: null, | ||
overSamplingFactor: window.devicePixelRatio || 1, | ||
|
||
isFullScreen: false, | ||
isInvWH: false, | ||
CSSFlipX: false, | ||
isApplyCSS: false, | ||
|
||
onResize: null, | ||
callback: function(){} | ||
}, optionsArg); | ||
|
||
_domCanvas = (options.canvas) ? options.canvas : document.getElementById(options.canvasId); | ||
_isFullScreen = options.isFullScreen; | ||
_isInvFullscreenWH = options.isInvWH; | ||
_isApplyCSS = options.isApplyCSS; | ||
_overSamplingFactor = options.overSamplingFactor; | ||
|
||
if (_isFullScreen){ | ||
// we are in fullscreen mode | ||
_callbackResize = options.onResize; | ||
|
||
resize_canvasToFullScreen(); | ||
window.addEventListener('resize', on_windowResize, false); | ||
window.addEventListener('orientationchange', on_windowResize, false); | ||
|
||
} else { // not fullscreen mode | ||
|
||
// get display size of the canvas: | ||
const domRect = _domCanvas.getBoundingClientRect(); | ||
if (domRect.width===0 || domRect.height===0){ | ||
console.log('WARNING in JeelizResize.size_canvas(): the canvas has its width or its height null, Retry a bit later...'); | ||
if (++_resizeAttemptsCounter > 20){ | ||
options.callback('CANNOT_RESIZECANVAS'); | ||
return; | ||
} | ||
setTimeout(that.size_canvas.bind(null, options), 50); | ||
return; | ||
} | ||
|
||
// do resize canvas: | ||
_resizeAttemptsCounter = 0; | ||
update_sizeCanvas(); | ||
} | ||
|
||
// flip horizontally if required: | ||
if (options.CSSFlipX){ | ||
add_CSStransform(_domCanvas, 'rotateY(180deg)'); | ||
} | ||
|
||
// compute the best camera resolutions: | ||
const allResolutions = _cameraResolutions.map(function(x){ | ||
return x.slice(0) | ||
}); | ||
|
||
// if we are in portrait mode, the camera is also in portrait mode | ||
// so we need to set all resolutions to portrait mode | ||
if (that.is_portrait() && that.require_flipVideoWHIfPortrait()){ | ||
allResolutions.forEach(function(wh){ | ||
wh.reverse(); | ||
}); | ||
} | ||
|
||
// sort camera resolutions from the best to the worst: | ||
allResolutions.sort(function(resA, resB){ | ||
return compute_overlap(resB, _whCanvasPx) - compute_overlap(resA, _whCanvasPx); | ||
}); | ||
|
||
// pick the best camera resolution: | ||
const bestCameraResolution = { | ||
'idealWidth': allResolutions[0][0], | ||
'idealHeight': allResolutions[0][1] | ||
}; | ||
|
||
console.log('INFO in JeelizResizer: bestCameraResolution =', bestCameraResolution); | ||
|
||
// launch the callback function after a small interval to let it | ||
// some time to size: | ||
setTimeout(options.callback.bind(null, false, bestCameraResolution), 1); | ||
}, //end size_canvas() | ||
|
||
// Should be called if the canvas is resized to update the canvas resolution: | ||
resize_canvas: function(){ | ||
if (_isFullScreen){ | ||
resize_canvasToFullScreen() | ||
} else { | ||
update_sizeCanvas(); | ||
} | ||
}, | ||
|
||
get_canvasSize: function(){ | ||
return _whCanvasPx; | ||
} | ||
}; //end that | ||
return that; | ||
})(); | ||
|
||
// Export ES6 module: | ||
try { | ||
module.exports = JeelizResizer; | ||
} catch(e){ | ||
console.log('JeelizResizer ES6 Module not exported'); | ||
window.JeelizResizer = JeelizResizer; | ||
} |
Oops, something went wrong.