2019-08-28 18:28:22 +02:00
2018-12-31 01:03:07 +01:00
import Debug from '../../conf/Debug' ;
import EdgeDetect from './edge-detect/EdgeDetect' ;
import EdgeStatus from './edge-detect/enums/EdgeStatusEnum' ;
import EdgeDetectPrimaryDirection from './edge-detect/enums/EdgeDetectPrimaryDirectionEnum' ;
import EdgeDetectQuality from './edge-detect/enums/EdgeDetectQualityEnum' ;
import GuardLine from './GuardLine' ;
2020-03-08 18:47:01 +01:00
// import DebugCanvas from './DebugCanvas';
2021-02-08 23:04:54 +01:00
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum' ;
import AspectRatioType from '../../../common/enums/AspectRatioType.enum' ;
2021-02-18 22:29:23 +01:00
import { sleep } from '../Util' ;
2020-12-22 23:23:03 +01:00
import BrowserDetect from '../../conf/BrowserDetect' ;
2021-02-18 22:29:23 +01:00
import Logger from '../Logger' ;
import VideoData from '../video-data/VideoData' ;
import Settings from '../Settings' ;
2018-12-31 01:03:07 +01:00
2021-08-25 22:30:06 +02:00
enum VideoPlaybackState {
Playing ,
Paused ,
Ended ,
Error
}
2022-07-29 00:36:18 +02:00
2018-05-08 23:35:16 +02:00
class ArDetector {
2022-07-29 00:36:18 +02:00
//#region helper objects
2021-02-18 22:29:23 +01:00
logger : Logger ;
conf : VideoData ;
video : HTMLVideoElement ;
settings : Settings ;
guardLine : GuardLine ;
edgeDetector : EdgeDetect ;
2022-07-29 00:36:18 +02:00
//#endregion
2021-02-18 22:29:23 +01:00
setupTimer : any ;
sampleCols : any [ ] ;
sampleLines
canFallback : boolean = true ;
fallbackMode : boolean = false ;
blackLevel : number ;
arid : string ;
// ar detector starts in this state. running main() sets both to false
_paused : boolean ;
_halted : boolean = true ;
_exited : boolean = true ;
private manualTickEnabled : boolean ;
_nextTick : boolean ;
canDoFallbackMode : boolean = false ;
// helper objects
2021-08-25 22:30:06 +02:00
private animationFrameHandle : any ;
2021-02-18 22:29:23 +01:00
private attachedCanvas : HTMLCanvasElement ;
canvas : HTMLCanvasElement ;
private blackframeCanvas : HTMLCanvasElement ;
private context : CanvasRenderingContext2D ;
private blackframeContext : CanvasRenderingContext2D ;
private canvasScaleFactor : number ;
private detectionTimeoutEventCount : number ;
canvasImageDataRowLength : number ;
private noLetterboxCanvasReset : boolean ;
private detectedAr : any ;
private canvasDrawWindowHOffset : number ;
private sampleCols_current : number ;
2021-08-25 22:30:06 +02:00
private timers = {
nextFrameCheckTime : Date.now ( )
}
private status = {
lastVideoStatus : VideoPlaybackState.Playing
}
2018-05-09 00:34:22 +02:00
2021-08-25 22:30:06 +02:00
//#region debug variables
private performanceConfig = {
sampleCountForAverages : 32
}
private performance = {
animationFrame : {
lastTime : 0 ,
currentIndex : 0 ,
sampleTime : [ ]
} ,
drawImage : {
currentIndex : 0 ,
sampleTime : [ ] ,
} ,
getImageData : {
currentIndex : 0 ,
sampleTime : [ ] ,
} ,
aard : {
currentIndex : 0 ,
sampleTime : [ ] ,
}
} ;
//#endregion
2021-08-25 20:39:27 +02:00
//#region getters
get defaultAr() {
const ratio = this . video . videoWidth / this . video . videoHeight ;
if ( isNaN ( ratio ) ) {
return undefined ;
}
return ratio ;
}
//#endregion getters
2021-08-25 22:30:06 +02:00
//#region debug getters
//#endregion
2021-08-25 20:39:27 +02:00
//#region lifecycle
2019-09-03 22:42:38 +02:00
constructor ( videoData ) {
this . logger = videoData . logger ;
2018-05-14 20:39:15 +02:00
this . conf = videoData ;
2018-05-08 23:35:16 +02:00
this . video = videoData . video ;
2018-08-05 23:48:56 +02:00
this . settings = videoData . settings ;
2021-09-19 21:22:12 +02:00
2018-05-14 20:39:15 +02:00
this . sampleCols = [ ] ;
2019-02-15 00:00:22 +01:00
this . blackLevel = this . settings . active . arDetect . blackbar . blackLevel ;
2018-09-23 19:46:40 +02:00
this . arid = ( Math . random ( ) * 100 ) . toFixed ( ) ;
2019-02-16 01:19:29 +01:00
// we can tick manually, for debugging
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` [ArDetector::ctor] creating new ArDetector. arid: ${ this . arid } ` ) ;
2018-05-09 00:34:22 +02:00
}
2018-05-08 23:35:16 +02:00
2022-07-29 00:36:18 +02:00
2018-05-08 23:35:16 +02:00
init ( ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` [ArDetect::init] <@ ${ this . arid } > Initializing autodetection. ` ) ;
2019-02-15 00:00:22 +01:00
try {
2019-02-16 01:19:29 +01:00
if ( this . settings . canStartAutoAr ( ) ) {
this . setup ( ) ;
} else {
throw "Settings prevent autoar from starting"
}
2019-02-15 00:00:22 +01:00
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'init' , ` %c[ArDetect::init] <@ ${ this . arid } > Initialization failed. ` , _ard_console_stop , e ) ;
2019-02-15 00:00:22 +01:00
}
2018-05-08 23:35:16 +02:00
}
2021-02-18 22:29:23 +01:00
setup ( cwidth? : number , cheight? : number ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'init' , ` [ArDetect::setup] <@ ${ this . arid } > Starting autodetection setup. ` ) ;
2019-02-15 00:00:22 +01:00
//
// [-1] check for zero-width and zero-height videos. If we detect this, we kick the proverbial
// can some distance down the road. This problem will prolly fix itself soon. We'll also
// not do any other setup until this issue is fixed
//
2021-02-18 22:29:23 +01:00
if ( this . video . videoWidth === 0 || this . video . videoHeight === 0 ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::setup] <@ ${ this . arid } > This video has zero width or zero height. Dimensions: ${ this . video . videoWidth } × ${ this . video . videoHeight } ` ) ;
2019-02-15 00:00:22 +01:00
this . scheduleInitRestart ( ) ;
return ;
2018-07-11 00:01:44 +02:00
}
2019-02-15 00:00:22 +01:00
//
// [0] initiate "dependencies" first
//
this . guardLine = new GuardLine ( this ) ;
this . edgeDetector = new EdgeDetect ( this ) ;
// this.debugCanvas = new DebugCanvas(this);
2018-05-14 20:39:15 +02:00
2021-09-19 21:22:12 +02:00
2019-02-15 00:00:22 +01:00
//
// [1] initiate canvases
//
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
if ( ! cwidth ) {
cwidth = this . settings . active . arDetect . canvasDimensions . sampleCanvas . width ;
cheight = this . settings . active . arDetect . canvasDimensions . sampleCanvas . height ;
}
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
if ( this . canvas ) {
this . canvas . remove ( ) ;
}
2022-07-26 22:15:39 +02:00
// if (this.blackframeCanvas) {
// this.blackframeCanvas.remove();
// }
2018-05-20 23:17:09 +02:00
2021-09-19 21:22:12 +02:00
// things to note: we'll be keeping canvas in memory only.
2019-02-15 00:00:22 +01:00
this . canvas = document . createElement ( "canvas" ) ;
this . canvas . width = cwidth ;
this . canvas . height = cheight ;
2022-07-26 22:15:39 +02:00
// this.blackframeCanvas = document.createElement("canvas");
// this.blackframeCanvas.width = this.settings.active.arDetect.canvasDimensions.blackframeCanvas.width;
// this.blackframeCanvas.height = this.settings.active.arDetect.canvasDimensions.blackframeCanvas.height;
2018-05-09 00:03:22 +02:00
2019-02-15 00:00:22 +01:00
this . context = this . canvas . getContext ( "2d" ) ;
2022-07-26 22:15:39 +02:00
// this.blackframeContext = this.blackframeCanvas.getContext("2d");
2019-02-15 00:00:22 +01:00
// do setup once
// tho we could do it for every frame
this . canvasScaleFactor = cheight / this . video . videoHeight ;
//
// [2] determine places we'll use to sample our main frame
//
2021-02-18 22:38:32 +01:00
let ncol = this . settings . active . arDetect . sampling . staticCols ;
let nrow = this . settings . active . arDetect . sampling . staticRows ;
2021-09-19 21:22:12 +02:00
2021-02-18 22:38:32 +01:00
let colSpacing = this . canvas . width / ncol ;
let rowSpacing = ( this . canvas . height << 2 ) / nrow ;
2021-09-19 21:22:12 +02:00
2019-02-15 00:00:22 +01:00
this . sampleLines = [ ] ;
this . sampleCols = [ ] ;
2018-05-15 20:36:22 +02:00
2021-02-18 22:38:32 +01:00
for ( let i = 0 ; i < ncol ; i ++ ) {
2019-02-15 00:00:22 +01:00
if ( i < ncol - 1 )
this . sampleCols . push ( Math . round ( colSpacing * i ) ) ;
else {
this . sampleCols . push ( Math . round ( colSpacing * i ) - 1 ) ;
2018-05-09 00:03:22 +02:00
}
2019-02-15 00:00:22 +01:00
}
2021-02-18 22:38:32 +01:00
for ( let i = 0 ; i < nrow ; i ++ ) {
2019-02-15 00:00:22 +01:00
if ( i < ncol - 5 )
this . sampleLines . push ( Math . round ( rowSpacing * i ) ) ;
else {
this . sampleLines . push ( Math . round ( rowSpacing * i ) - 4 ) ;
2018-05-09 00:03:22 +02:00
}
2019-02-15 00:00:22 +01:00
}
2018-05-08 23:35:16 +02:00
2019-02-15 00:00:22 +01:00
//
2022-07-29 00:21:34 +02:00
// [3] do other things setup needs to do
2019-02-15 00:00:22 +01:00
//
this . resetBlackLevel ( ) ;
// if we're restarting ArDetect, we need to do this in order to force-recalculate aspect ratio
2021-08-25 20:39:27 +02:00
this . conf . resizer . setLastAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
2019-02-15 00:00:22 +01:00
this . canvasImageDataRowLength = cwidth << 2 ;
2021-09-19 21:22:12 +02:00
2022-07-29 00:21:34 +02:00
this . start ( ) ;
2021-09-19 21:22:12 +02:00
2018-05-09 00:03:22 +02:00
if ( Debug . debugCanvas . enabled ) {
2018-12-31 01:03:07 +01:00
// this.debugCanvas.init({width: cwidth, height: cheight});
2018-05-09 00:03:22 +02:00
// DebugCanvas.draw("test marker","test","rect", {x:5, y:5}, {width: 5, height: 5});
}
2018-05-16 23:26:47 +02:00
this . conf . arSetupComplete = true ;
2018-05-08 23:35:16 +02:00
}
2021-08-26 00:45:12 +02:00
destroy ( ) {
2021-08-25 20:39:27 +02:00
this . logger . log ( 'info' , 'init' , ` %c[ArDetect::destroy] <@ ${ this . arid } > Destroying aard. ` , _ard_console_stop ) ;
// this.debugCanvas.destroy();
2021-08-25 23:13:02 +02:00
this . halt ( ) ;
2021-08-25 20:39:27 +02:00
}
//#endregion lifecycle
//#region AARD control
2019-02-16 01:19:29 +01:00
start() {
2020-06-04 22:44:53 +02:00
if ( this . settings . canStartAutoAr ( ) ) {
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::start] <@ ${ this . arid } > Starting automatic aspect ratio detection ` , _ard_console_start ) ;
} else {
this . logger . log ( 'warn' , 'debug' , ` "%c[ArDetect::start] <@ ${ this . arid } > Wanted to start automatic aspect ratio detection, but settings don't allow that. Aard won't be started. ` , _ard_console_change ) ;
return ;
}
2019-07-16 22:46:16 +02:00
2021-02-08 23:04:54 +01:00
if ( this . conf . resizer . lastAr . type === AspectRatioType . Automatic ) {
2019-02-16 01:19:29 +01:00
// ensure first autodetection will run in any case
2021-08-25 20:39:27 +02:00
this . conf . resizer . setLastAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
2019-02-16 01:19:29 +01:00
}
2019-02-15 00:00:22 +01:00
2021-09-19 21:22:12 +02:00
this . _paused = false ;
2021-08-25 23:13:02 +02:00
this . _halted = false ;
2021-09-19 21:22:05 +02:00
this . _exited = false ;
2021-08-25 23:13:02 +02:00
// start autodetection
2021-08-26 00:37:56 +02:00
this . startLoop ( ) ;
// automatic detection starts halted. If halted=false when main first starts, extension won't run
// this._paused is undefined the first time we run this function, which is effectively the same thing
// as false. Still, we'll explicitly fix this here.
}
startLoop() {
2021-08-25 23:13:02 +02:00
if ( this . animationFrameHandle ) {
window . cancelAnimationFrame ( this . animationFrameHandle ) ;
}
this . animationFrameHandle = window . requestAnimationFrame ( ( ts ) = > this . animationFrameBootstrap ( ts ) ) ;
2021-09-19 21:22:12 +02:00
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::startLoop] <@ ${ this . arid } > AARD loop started. ` , _ard_console_start ) ;
2021-08-25 23:13:02 +02:00
}
2021-08-26 00:37:56 +02:00
2021-08-25 23:13:02 +02:00
stop() {
if ( this . animationFrameHandle ) {
2021-09-19 21:22:12 +02:00
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::stop] <@ ${ this . arid } > Stopping AnimationFrame loop. ` , _ard_console_stop ) ;
2021-08-25 23:13:02 +02:00
window . cancelAnimationFrame ( this . animationFrameHandle ) ;
2021-09-19 21:22:12 +02:00
} else {
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::stop] <@ ${ this . arid } > AnimationFrame loop is already paused (due to an earlier call of this function). ` ) ;
2021-08-25 23:13:02 +02:00
}
2018-05-08 23:35:16 +02:00
}
2018-07-11 23:13:40 +02:00
unpause() {
2021-08-26 00:37:56 +02:00
this . startLoop ( ) ;
2018-07-11 23:13:40 +02:00
}
pause() {
// pause only if we were running before. Don't pause if we aren't running
// (we are running when _halted is neither true nor undefined)
if ( this . _halted === false ) {
this . _paused = true ;
}
2021-08-25 23:13:02 +02:00
this . stop ( ) ;
2018-07-11 23:13:40 +02:00
}
2021-08-25 23:13:02 +02:00
halt ( ) {
this . logger . log ( 'info' , 'debug' , ` "%c[ArDetect::stop] <@ ${ this . arid } > Halting automatic aspect ratio detection ` , _ard_console_stop ) ;
this . stop ( ) ;
2018-05-08 23:35:16 +02:00
this . _halted = true ;
2019-07-04 22:46:18 +02:00
// this.conf.resizer.setArLastAr();
2018-05-08 23:35:16 +02:00
}
2021-08-25 20:39:27 +02:00
setManualTick ( manualTick ) {
this . manualTickEnabled = manualTick ;
}
2019-02-16 01:19:29 +01:00
2021-08-25 20:39:27 +02:00
tick() {
this . _nextTick = true ;
}
//#endregion
2019-02-15 00:00:22 +01:00
2021-08-25 20:39:27 +02:00
//#region helper functions (general)
2019-02-15 00:00:22 +01:00
2021-08-25 20:39:27 +02:00
isRunning ( ) {
return ! ( this . _halted || this . _paused || this . _exited ) ;
2019-02-15 00:00:22 +01:00
}
2021-08-25 22:30:06 +02:00
private getVideoPlaybackState ( ) : VideoPlaybackState {
try {
if ( this . video . ended ) {
return VideoPlaybackState . Ended ;
} else if ( this . video . paused ) {
return VideoPlaybackState . Paused ;
} else if ( this . video . error ) {
return VideoPlaybackState . Error ;
} else {
return VideoPlaybackState . Playing ;
}
} catch ( e ) {
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::getVideoPlaybackState] There was an error while determining video playback state. ` , e ) ;
return VideoPlaybackState . Error ;
}
}
/ * *
* Checks whether conditions for granting a frame check are fulfilled
2021-09-19 21:22:12 +02:00
* @returns
2021-08-25 22:30:06 +02:00
* /
private canTriggerFrameCheck() {
2021-09-15 01:45:51 +02:00
if ( this . _paused || this . _halted || this . _exited ) {
2019-02-15 00:00:22 +01:00
return false ;
}
2021-08-25 22:30:06 +02:00
// if video was paused & we know that we already checked that frame,
// we will not check it again.
const videoState = this . getVideoPlaybackState ( ) ;
2021-09-19 21:22:12 +02:00
2021-08-25 22:30:06 +02:00
if ( videoState !== VideoPlaybackState . Playing ) {
if ( this . status . lastVideoStatus === videoState ) {
return false ;
}
2019-02-15 00:00:22 +01:00
}
2021-08-25 22:30:06 +02:00
this . status . lastVideoStatus = videoState ;
2021-08-25 23:13:02 +02:00
if ( Date . now ( ) < this . timers . nextFrameCheckTime ) {
return false ;
}
2021-08-25 22:30:06 +02:00
this . timers . nextFrameCheckTime = Date . now ( ) + this . settings . active . arDetect . timers . playing ;
2021-08-25 23:13:02 +02:00
return true ;
2019-02-15 00:00:22 +01:00
}
2021-08-25 22:30:06 +02:00
private scheduleInitRestart ( timeout? : number , force_reset? : boolean ) {
2018-05-20 23:17:09 +02:00
if ( ! timeout ) {
timeout = 100 ;
}
// don't allow more than 1 instance
2021-09-19 21:22:12 +02:00
if ( this . setupTimer ) {
2018-05-20 23:17:09 +02:00
clearTimeout ( this . setupTimer ) ;
}
2021-09-19 21:22:12 +02:00
2022-07-29 00:36:18 +02:00
this . setupTimer = setTimeout ( ( ) = > {
this . setupTimer = null ;
try {
this . start ( ) ;
2019-07-16 22:46:16 +02:00
} catch ( e ) {
2022-07-29 00:36:18 +02:00
this . logger . log ( 'error' , 'debug' , ` [ArDetector::scheduleInitRestart] <@ ${ this . arid } > Failed to start main(). Error: ` , e ) ;
2019-07-16 22:46:16 +02:00
}
2018-05-20 23:17:09 +02:00
} ,
timeout
) ;
}
2021-08-25 22:30:06 +02:00
private attachCanvas ( canvas ) {
2018-05-09 00:34:22 +02:00
if ( this . attachedCanvas )
this . attachedCanvas . remove ( ) ;
// todo: place canvas on top of the video instead of random location
canvas . style . position = "absolute" ;
canvas . style . left = "200px" ;
canvas . style . top = "1200px" ;
canvas . style . zIndex = 10000 ;
document . getElementsByTagName ( "body" ) [ 0 ]
. appendChild ( canvas ) ;
}
//#endregion
2021-08-25 22:30:06 +02:00
//#region helper functions (performance measurements)
/ * *
* Returns time ultrawidify spends on certain aspects of autodetection .
2021-09-19 21:22:12 +02:00
*
2021-08-25 22:30:06 +02:00
* The returned object contains the following :
2021-09-19 21:22:12 +02:00
*
2021-08-25 22:30:06 +02:00
* eyeballedTimeBudget — a very inaccurate time budget
* fps — framerate at which we run
2021-09-19 21:22:12 +02:00
* aardTime — time spent on average frameCheck loop .
* It ' s a nearly useless metric , because
2021-08-25 22:30:06 +02:00
* frameCheck can exit very early .
* drawWindowTime — how much time browser spends on executing
* drawWindow ( ) calls .
* getImageData — how much time browser spends on executing
* getImageData ( ) calls .
2021-09-19 21:22:12 +02:00
*
2021-08-25 22:30:06 +02:00
* Most of these are on "per frame" basis and averaged .
* /
getTimings() {
let drawWindowTime = 0 , getImageDataTime = 0 , aardTime = 0 ;
// drawImage and getImageData are of same length and use same everything
for ( let i = 0 ; i < this . performance . drawImage . sampleTime . length ; i ++ ) {
drawWindowTime += this . performance . drawImage . sampleTime [ i ] ? ? 0 ;
getImageDataTime += this . performance . getImageData . sampleTime [ i ] ? ? 0 ;
}
drawWindowTime /= this . performance . drawImage . sampleTime . length ;
getImageDataTime /= this . performance . getImageData . sampleTime . length ;
return {
drawWindowTime ,
getImageDataTime
}
}
//#endregion
2021-08-25 20:39:27 +02:00
2021-08-25 22:30:06 +02:00
/ * *
* This is the "main loop" for aspect ratio autodetection
* /
2021-09-15 00:54:23 +02:00
private async animationFrameBootstrap ( timestamp : number ) {
2021-09-19 21:22:12 +02:00
// this.logger.log('info', 'arDetect_verbose', `[ArDetect::animationFrameBootstrap] <@${this.arid}> New animation frame.\nmanualTickEnabled: ${!this.manualTickEnabled}\ncan trigger frame check? ${this.canTriggerFrameCheck()}\nnext tick? ${this._nextTick}\n => (a&b | c) => Can we do tick? ${ (!this.manualTickEnabled && this.canTriggerFrameCheck()) || this._nextTick}\n\ncan we continue running? ${this && !this._halted && !this._paused}`);
2021-08-25 22:30:06 +02:00
// do timekeeping first
2021-08-25 20:39:27 +02:00
2021-08-25 22:30:06 +02:00
// trigger frame check, if we're allowed to
if ( ( ! this . manualTickEnabled && this . canTriggerFrameCheck ( ) ) || this . _nextTick ) {
2021-09-19 21:22:12 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` [ArDetect::animationFrameBootstrap] <@ ${ this . arid } > Processing next tick. ` ) ;
2021-08-25 22:30:06 +02:00
this . _nextTick = false ;
2021-08-25 20:39:27 +02:00
2021-08-25 22:30:06 +02:00
try {
2021-09-15 00:54:23 +02:00
await this . frameCheck ( ) ;
2021-08-25 22:30:06 +02:00
} catch ( e ) {
2021-09-19 21:22:12 +02:00
this . logger . log ( 'error' , 'debug' , ` %c[ArDetect::animationFrameBootstrap] <@ ${ this . arid } > Frame check failed: ` , "color: #000, background: #f00" , e ) ;
2021-08-25 20:39:27 +02:00
}
}
2022-07-29 00:36:18 +02:00
// if (this && !this._halted && !this._paused) {
2021-08-25 23:13:02 +02:00
this . animationFrameHandle = window . requestAnimationFrame ( ( ts ) = > this . animationFrameBootstrap ( ts ) ) ;
2022-07-29 00:36:18 +02:00
// } else {
// this.logger.log('info', 'debug', `[ArDetect::animationFrameBootstrap] <@${this.arid}> Not renewing animation frame for some reason. Paused? ${this._paused}; Halted?: ${this._halted}, Exited?: ${this._exited}`);
// }
2019-07-04 22:46:18 +02:00
}
2018-07-11 00:01:44 +02:00
calculateArFromEdges ( edges ) {
2018-05-09 00:34:22 +02:00
// if we don't specify these things, they'll have some default values.
if ( edges . top === undefined ) {
edges . top = 0 ;
edges . bottom = 0 ;
2019-05-05 00:09:49 +02:00
edges . left = 0 ; // RESERVED FOR FUTURE — CURRENTLY UNUSED
edges . right = 0 ; // THIS FUNCTION CAN PRESENTLY ONLY HANDLE LETTERBOX
2018-05-09 00:34:22 +02:00
}
2018-07-11 00:01:44 +02:00
2019-05-26 02:54:02 +02:00
let letterbox = edges . top + edges . bottom ;
2021-09-19 21:22:12 +02:00
2022-07-29 00:21:34 +02:00
// Since video is stretched to fit the canvas, we need to take that into account when calculating target
// aspect ratio and correct our calculations to account for that
2019-05-05 00:09:49 +02:00
2022-07-29 00:21:34 +02:00
const fileAr = this . video . videoWidth / this . video . videoHeight ;
const canvasAr = this . canvas . width / this . canvas . height ;
let widthCorrected ;
2018-07-11 23:13:40 +02:00
2022-07-29 00:21:34 +02:00
if ( edges . top && edges . bottom ) {
// in case of letterbox, we take canvas height as canon and assume width got stretched or squished
2018-07-11 00:01:44 +02:00
2022-07-29 00:21:34 +02:00
if ( fileAr != canvasAr ) {
widthCorrected = this . canvas . height * fileAr ;
} else {
widthCorrected = this . canvas . width ;
2018-05-18 23:26:20 +02:00
}
2021-09-19 21:22:12 +02:00
2022-07-29 00:21:34 +02:00
return widthCorrected / ( this . canvas . height - letterbox ) ;
2019-05-05 00:09:49 +02:00
}
2018-07-11 00:01:44 +02:00
}
processAr ( trueAr ) {
2021-09-15 01:45:51 +02:00
if ( ! this . isRunning ( ) ) {
this . logger . log ( 'warn' , 'debug' , ` [ArDetect::processAr] <@ ${ this . arid } > Trying to change aspect ratio while AARD is paused. ` ) ;
return ;
}
2021-09-19 21:22:12 +02:00
2018-05-09 00:34:22 +02:00
// check if aspect ratio is changed:
2021-01-12 23:19:04 +01:00
let lastAr = this . conf . resizer . getLastAr ( ) ;
2021-02-08 23:04:54 +01:00
if ( lastAr . type === AspectRatioType . Automatic && lastAr . ratio !== null && lastAr . ratio !== undefined ) {
2018-05-09 00:34:22 +02:00
// spremembo lahko zavrnemo samo, če uporabljamo avtomatski način delovanja in če smo razmerje stranic
// že nastavili.
//
// we can only deny aspect ratio changes if we use automatic mode and if aspect ratio was set from here.
2021-09-19 21:22:12 +02:00
2021-02-18 22:29:23 +01:00
let arDiff = trueAr - lastAr . ratio ;
2021-09-19 21:22:12 +02:00
2018-05-09 00:34:22 +02:00
if ( arDiff < 0 )
arDiff = - arDiff ;
2021-09-19 21:22:12 +02:00
2021-01-12 23:19:04 +01:00
const arDiff_percent = arDiff / trueAr ;
2021-09-19 21:22:12 +02:00
2018-05-09 00:34:22 +02:00
// is ar variance within acceptable levels? If yes -> we done
2021-02-18 22:29:23 +01:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::processAr] <@ ${ this . arid } > New aspect ratio varies from the old one by this much: \ n ` , "color: #aaf" , "old Ar" , lastAr . ratio , "current ar" , trueAr , "arDiff (absolute):" , arDiff , "ar diff (relative to new ar)" , arDiff_percent ) ;
2019-07-16 22:46:16 +02:00
2018-08-05 23:48:56 +02:00
if ( arDiff < trueAr * this . settings . active . arDetect . allowedArVariance ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::processAr] <@ ${ this . arid } > Aspect ratio change denied — diff %: ${ arDiff_percent } ` , "background: #740; color: #fa2" ) ;
2018-05-09 00:34:22 +02:00
return ;
}
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` %c[ArDetect::processAr] <@ ${ this . arid } > aspect ratio change accepted — diff %: ${ arDiff_percent } ` , "background: #153; color: #4f9" ) ;
2019-02-15 20:40:56 +01:00
}
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'debug' , ` %c[ArDetect::processAr] <@ ${ this . arid } > Triggering aspect ratio change. New aspect ratio: ${ trueAr } ` , _ard_console_change ) ;
2021-09-19 21:22:12 +02:00
2021-02-18 22:29:23 +01:00
this . conf . resizer . updateAr ( { type : AspectRatioType . Automatic , ratio : trueAr } ) ;
2018-05-09 00:34:22 +02:00
}
2019-10-29 18:15:46 +01:00
clearImageData ( id ) {
2021-02-18 22:29:23 +01:00
if ( ( ArrayBuffer as any ) . transfer ) {
( ArrayBuffer as any ) . transfer ( id , 0 ) ;
2019-10-29 18:15:46 +01:00
}
id = undefined ;
}
2022-07-29 00:36:18 +02:00
2021-09-14 19:12:54 +02:00
async frameCheck ( ) {
2021-09-15 00:54:23 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::processAr] <@ ${ this . arid } > Starting frame check. ` ) ;
2018-05-09 00:58:50 +02:00
if ( ! this . video ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'error' , 'debug' , ` %c[ArDetect::frameCheck] <@ ${ this . arid } > Video went missing. Destroying current instance of videoData. ` ) ;
2018-05-16 23:26:47 +02:00
this . conf . destroy ( ) ;
2018-05-09 00:58:50 +02:00
return ;
}
2019-02-16 01:19:29 +01:00
2022-07-29 00:36:18 +02:00
if ( ! this . blackframeContext ) {
this . init ( ) ;
}
2021-09-19 21:22:12 +02:00
2019-02-15 00:00:22 +01:00
let sampleCols = this . sampleCols . slice ( 0 ) ;
2017-12-29 23:34:40 +01:00
2022-07-29 00:36:18 +02:00
2022-07-26 22:15:39 +02:00
await new Promise < void > (
resolve = > {
this . context . drawImage ( this . video , 0 , 0 , this . canvas . width , this . canvas . height ) ;
resolve ( ) ;
2021-09-19 21:22:12 +02:00
}
2022-07-26 22:15:39 +02:00
)
const imageData = this . context . getImageData ( 0 , 0 , this . canvas . width , this . canvas . height ) . data ;
2017-09-27 02:26:47 +02:00
2022-07-26 22:15:39 +02:00
const bfAnalysis = await this . blackframeTest ( imageData ) ;
if ( bfAnalysis . isBlack ) {
2019-02-15 00:00:22 +01:00
// we don't do any corrections on frames confirmed black
2022-07-26 22:15:39 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Black frame analysis suggests this frame is black or too dark. Doing nothing. ` , "color: #fa3" , bfAnalysis ) ;
2019-02-15 00:00:22 +01:00
return ;
2018-05-09 00:58:50 +02:00
}
2018-02-28 23:54:32 +01:00
2022-07-29 00:36:18 +02:00
const fastLetterboxTestRes = this . fastLetterboxPresenceTest ( imageData , sampleCols ) ;
if ( ! fastLetterboxTestRes ) {
2018-05-09 00:58:50 +02:00
// If we don't detect letterbox, we reset aspect ratio to aspect ratio of the video file. The aspect ratio could
// have been corrected manually. It's also possible that letterbox (that was there before) disappeared.
2021-08-25 20:39:27 +02:00
this . conf . resizer . updateAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
2019-02-15 00:00:22 +01:00
this . guardLine . reset ( ) ;
this . noLetterboxCanvasReset = true ;
2018-05-11 00:49:50 +02:00
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Letterbox not detected in fast test. Letterbox is either gone or we manually corrected aspect ratio. Nothing will be done. ` , "color: #fa3" ) ;
2019-05-03 00:49:33 +02:00
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2017-12-31 18:26:59 +01:00
return ;
2018-05-09 00:58:50 +02:00
}
2018-07-15 15:18:40 +02:00
2021-08-26 00:37:56 +02:00
2018-05-09 00:58:50 +02:00
// Če preverjamo naprej, potem moramo postaviti to vrednost nazaj na 'false'. V nasprotnem primeru se bo
// css resetiral enkrat na video/pageload namesto vsakič, ko so za nekaj časa obrobe odstranejene
// if we look further we need to reset this value back to false. Otherwise we'll only get CSS reset once
// per video/pageload instead of every time letterbox goes away (this can happen more than once per vid)
2018-05-16 23:26:47 +02:00
this . noLetterboxCanvasReset = false ;
2021-09-19 21:22:12 +02:00
2018-05-09 00:58:50 +02:00
// poglejmo, če obrežemo preveč.
2019-02-15 00:00:22 +01:00
// let's check if we're cropping too much
2022-07-29 00:21:34 +02:00
const guardLineOut = this . guardLine . check ( imageData , false ) ;
2021-09-19 21:22:12 +02:00
// if both succeed, then aspect ratio hasn't changed.
2022-07-29 00:36:18 +02:00
// otherwise we continue. We add blackbar violations to the list of the cols we'll sample and sort them
2018-05-11 00:49:50 +02:00
if ( ! guardLineOut . imageFail && ! guardLineOut . blackbarFail ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] guardLine tests were successful. (no imagefail and no blackbarfail) \ n ` , "color: #afa" , guardLineOut ) ;
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2018-05-11 00:49:50 +02:00
return ;
2022-07-29 00:36:18 +02:00
} else {
if ( guardLineOut . blackbarFail ) {
sampleCols . concat ( guardLineOut . offenders ) . sort (
( a : number , b : number ) = > a - b
) ;
}
2021-09-19 21:22:12 +02:00
}
2018-05-09 00:58:50 +02:00
// If aspect ratio changes from narrower to wider, we first check for presence of pillarbox. Presence of pillarbox indicates
// a chance of a logo on black background. We could cut easily cut too much. Because there's a somewhat significant chance
// that we will cut too much, we rather avoid doing anything at all. There's gonna be a next chance.
2018-05-16 23:26:47 +02:00
try {
2022-07-29 00:36:18 +02:00
if ( guardLineOut . blackbarFail || guardLineOut . imageFail ) {
const edgeDetectRes = this . edgeDetector . findBars ( imageData , null , EdgeDetectPrimaryDirection . Horizontal ) ;
if ( edgeDetectRes . status === 'ar_known' ) {
2018-07-11 00:01:44 +02:00
if ( guardLineOut . blackbarFail ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` [ArDetect::frameCheck] Detected blackbar violation and pillarbox. Resetting to default aspect ratio. ` ) ;
2021-08-25 20:39:27 +02:00
this . conf . resizer . setAr ( { type : AspectRatioType . Automatic , ratio : this.defaultAr } ) ;
2018-07-11 00:01:44 +02:00
this . guardLine . reset ( ) ;
2021-09-15 00:54:23 +02:00
} else {
this . logger . log ( 'info' , 'arDetect_verbose' , ` [ArDetect::frameCheck] Guardline failed, blackbar didn't, and we got pillarbox. Doing nothing. ` ) ;
2021-09-19 21:22:12 +02:00
}
2018-03-04 23:07:11 +01:00
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2018-07-11 00:01:44 +02:00
return ;
}
2018-05-09 00:58:50 +02:00
}
2021-09-19 21:22:12 +02:00
} catch ( e ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect' , ` [ArDetect::frameCheck] something went wrong while checking for pillarbox. Error: \ n ` , e ) ;
2018-07-15 15:18:40 +02:00
}
2018-03-04 23:07:11 +01:00
2018-05-09 00:58:50 +02:00
// pa poglejmo, kje se končajo črne letvice na vrhu in na dnu videa.
2022-07-26 22:40:45 +02:00
let edgePost = this . edgeDetector . findBars ( imageData , sampleCols , EdgeDetectPrimaryDirection . Vertical , EdgeDetectQuality . Improved , guardLineOut , bfAnalysis ) ;
2021-09-19 21:22:12 +02:00
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] edgeDetector returned this \ n ` , "color: #aaf" , edgePost ) ;
2021-09-19 21:22:12 +02:00
2021-03-06 04:01:47 +01:00
if ( edgePost . status !== EdgeStatus . ARKnown ) {
2019-02-15 00:00:22 +01:00
// no edge was detected. Let's leave things as they were
2021-03-06 04:01:47 +01:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Edge wasn't detected with findBars ` , "color: #fa3" , edgePost , "EdgeStatus.AR_KNOWN:" , EdgeStatus . ARKnown ) ;
2019-10-29 18:15:46 +01:00
this . clearImageData ( imageData ) ;
2019-02-15 00:00:22 +01:00
return ;
}
2018-07-11 00:01:44 +02:00
2021-02-18 22:38:32 +01:00
let newAr = this . calculateArFromEdges ( edgePost ) ;
2021-09-19 21:22:12 +02:00
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , ` %c[ArDetect::frameCheck] Triggering aspect ration change! new ar: ${ newAr } ` , "color: #aaf" ) ;
2022-07-29 00:36:18 +02:00
// we also know edges for guardline, so set them. If edges are okay and not invalid, we also
2019-08-24 22:35:56 +02:00
// allow automatic aspect ratio correction. If edges
2022-07-29 00:21:34 +02:00
// are bogus, we keep aspect ratio unchanged.
2019-08-24 22:35:56 +02:00
try {
2022-07-29 00:21:34 +02:00
// throws error if top/bottom are invalid
this . guardLine . setBlackbar ( { top : edgePost.guardLineTop , bottom : edgePost.guardLineBottom } ) ;
2019-08-24 22:35:56 +02:00
this . processAr ( newAr ) ;
} catch ( e ) {
2021-09-19 21:22:12 +02:00
// edges weren't gucci, so we'll just reset
2019-08-24 22:35:56 +02:00
// the aspect ratio to defaults
2019-10-29 18:15:10 +01:00
this . logger . log ( 'error' , 'arDetect' , ` %c[ArDetect::frameCheck] There was a problem setting blackbar. Doing nothing. Error: ` , e ) ;
2021-09-19 21:22:12 +02:00
2020-06-01 23:52:29 +02:00
try {
2021-02-18 22:29:23 +01:00
this . guardLine . reset ( ) ;
2020-06-01 23:52:29 +02:00
} catch ( e ) {
2022-07-29 00:21:34 +02:00
// no guardline, no biggie
2020-06-01 23:52:29 +02:00
}
2021-09-19 21:22:12 +02:00
// WE DO NOT RESET ASPECT RATIO HERE IN CASE OF PROBLEMS, CAUSES UNWARRANTED RESETS:
2019-10-29 18:15:10 +01:00
// (eg. here: https://www.youtube.com/watch?v=nw5Z93Yt-UQ&t=410)
//
2021-08-25 20:39:27 +02:00
// this.conf.resizer.setAr({type: AspectRatioType.Automatic, ratio: this.defaultAr});
2019-02-15 00:00:22 +01:00
}
2019-10-29 18:15:10 +01:00
this . clearImageData ( imageData ) ;
2019-02-15 00:00:22 +01:00
}
resetBlackLevel ( ) {
2021-09-19 21:22:12 +02:00
this . blackLevel = this . settings . active . arDetect . blackbar . blackLevel ;
2019-02-15 00:00:22 +01:00
}
2022-07-26 22:15:39 +02:00
async blackframeTest ( imageData ) {
2019-05-04 21:33:48 +02:00
if ( this . blackLevel === undefined ) {
2019-07-16 22:46:16 +02:00
this . logger . log ( 'info' , 'arDetect_verbose' , "[ArDetect::blackframeTest] black level undefined, resetting" ) ;
2019-02-15 00:00:22 +01:00
this . resetBlackLevel ( ) ;
}
2018-07-10 21:58:26 +02:00
2022-07-26 22:15:39 +02:00
/ * *
* Performs a quick black frame test
* /
const bfDrawStartTime = performance . now ( ) ;
// await new Promise<void>(
// resolve => {
// this.blackframeContext.drawImage(this.video, 0, 0, this.blackframeCanvas.width, this.blackframeCanvas.height);
// resolve();
// }
// );
// const rows = this.blackframeCanvas.height;
// const cols = this.blackframeCanvas.width;
// const bfImageData = this.blackframeContext.getImageData(0, 0, cols, rows).data;
const rows = this . settings . active . arDetect . canvasDimensions . blackframeCanvas . width ;
const cols = this . settings . active . arDetect . canvasDimensions . blackframeCanvas . height ;
const samples = rows * cols ;
const blackFrameDrawTime = performance . now ( ) - bfDrawStartTime ;
const bfProcessStartTime = performance . now ( ) ;
2019-05-03 00:49:33 +02:00
const pixels = rows * cols ;
let cumulative_r = 0 , cumulative_g = 0 , cumulative_b = 0 ;
let max_r = 0 , max_g = 0 , max_b = 0 ;
let avg_r , avg_g , avg_b ;
let var_r = 0 , var_g = 0 , var_b = 0 ;
2019-02-15 00:00:22 +01:00
let pixelMax = 0 ;
let cumulativeValue = 0 ;
let blackPixelCount = 0 ;
2022-07-26 22:40:45 +02:00
const blackThreshold = this . blackLevel + this . settings . active . arDetect . blackbar . frameThreshold ;
2021-09-19 21:22:12 +02:00
2022-07-26 22:15:39 +02:00
const actualPixels = imageData . length / 4 ;
// generate some random points that we'll use for sampling black frame.
const sampleArray = new Array ( samples ) ;
for ( let i = 0 ; i < samples ; i ++ ) {
sampleArray [ i ] = Math . round ( Math . random ( ) * actualPixels ) ;
}
2018-03-04 23:07:11 +01:00
2019-02-15 00:00:22 +01:00
// we do some recon for letterbox and pillarbox. While this can't determine whether letterbox/pillarbox exists
// with sufficient level of certainty due to small sample resolution, it can still give us some hints for later
let rowMax = new Array ( rows ) . fill ( 0 ) ;
let colMax = new Array ( cols ) . fill ( 0 ) ;
2021-02-18 22:29:23 +01:00
let r : number , c : number ;
2019-02-15 00:00:22 +01:00
2022-07-26 22:15:39 +02:00
for ( const i of sampleArray ) {
pixelMax = Math . max ( imageData [ i ] , imageData [ i + 1 ] , imageData [ i + 2 ] ) ;
imageData [ i + 3 ] = pixelMax ;
2019-05-03 00:49:33 +02:00
2022-07-26 22:15:39 +02:00
if ( pixelMax < blackThreshold ) {
2019-05-04 21:33:48 +02:00
if ( pixelMax < this . blackLevel ) {
this . blackLevel = pixelMax ;
}
2019-02-15 00:00:22 +01:00
blackPixelCount ++ ;
} else {
cumulativeValue += pixelMax ;
2022-07-26 22:15:39 +02:00
cumulative_r += imageData [ i ] ;
cumulative_g += imageData [ i + 1 ] ;
cumulative_b += imageData [ i + 2 ] ;
2019-05-03 00:49:33 +02:00
2022-07-26 22:15:39 +02:00
max_r = max_r > imageData [ i ] ? max_r : imageData [ i ] ;
max_g = max_g > imageData [ i + 1 ] ? max_g : imageData [ i + 1 ] ;
max_b = max_b > imageData [ i + 2 ] ? max_b : imageData [ i + 2 ] ;
2019-02-15 00:00:22 +01:00
}
2021-09-19 21:22:12 +02:00
2019-05-03 00:49:33 +02:00
r = ~ ~ ( i / rows ) ;
2019-02-15 00:00:22 +01:00
c = i % cols ;
if ( pixelMax > rowMax [ r ] ) {
rowMax [ r ] = pixelMax ;
}
if ( pixelMax > colMax [ c ] ) {
colMax [ c ] = colMax ;
}
2018-05-09 00:58:50 +02:00
}
2019-02-15 00:00:22 +01:00
2019-05-03 00:49:33 +02:00
max_r = 1 / ( max_r || 1 ) ;
max_g = 1 / ( max_g || 1 ) ;
max_b = 1 / ( max_b || 1 ) ;
const imagePixels = pixels - blackPixelCount ;
// calculate averages and normalize them
avg_r = ( cumulative_r / imagePixels ) * max_r ;
avg_g = ( cumulative_g / imagePixels ) * max_g ;
avg_b = ( cumulative_b / imagePixels ) * max_b ;
// second pass for color variance
2022-07-26 22:15:39 +02:00
for ( let i = 0 ; i < imageData . length ; i += 4 ) {
if ( imageData [ i + 3 ] >= this . blackLevel ) {
var_r += Math . abs ( avg_r - imageData [ i ] * max_r ) ;
var_g += Math . abs ( avg_g - imageData [ i + 1 ] * max_g ) ;
var_b += Math . abs ( avg_b - imageData [ i + 1 ] * max_b ) ;
2019-05-03 00:49:33 +02:00
}
}
const hasSufficientVariance = Math . abs ( var_r - var_g ) / Math . max ( var_r , var_g , 1 ) > this . settings . active . arDetect . blackframe . sufficientColorVariance
|| Math . abs ( var_r - var_b ) / Math . max ( var_r , var_b , 1 ) > this . settings . active . arDetect . blackframe . sufficientColorVariance
|| Math . abs ( var_b - var_g ) / Math . max ( var_b , var_g , 1 ) > this . settings . active . arDetect . blackframe . sufficientColorVariance
let isBlack = ( blackPixelCount / ( cols * rows ) > this . settings . active . arDetect . blackframe . blackPixelsCondition ) ;
if ( ! isBlack ) {
if ( hasSufficientVariance ) {
isBlack = cumulativeValue < this . settings . active . arDetect . blackframe . cumulativeThresholdLax ;
} else {
isBlack = cumulativeValue < this . settings . active . arDetect . blackframe . cumulativeThresholdStrict ;
}
}
if ( Debug . debug ) {
return {
isBlack : isBlack ,
blackPixelCount : blackPixelCount ,
blackPixelRatio : ( blackPixelCount / ( cols * rows ) ) ,
cumulativeValue : cumulativeValue ,
hasSufficientVariance : hasSufficientVariance ,
2019-05-04 21:33:48 +02:00
blackLevel : this.blackLevel ,
2019-05-03 00:49:33 +02:00
variances : {
raw : {
r : var_r , g : var_g , b : var_b
} ,
relative : {
rg : Math.abs ( var_r - var_g ) / Math . max ( var_r , var_g , 1 ) ,
rb : Math.abs ( var_r - var_b ) / Math . max ( var_r , var_b , 1 ) ,
gb : Math.abs ( var_b - var_g ) / Math . max ( var_b , var_g , 1 ) ,
} ,
relativePercent : {
rg : Math.abs ( var_r - var_g ) / Math . max ( var_r , var_g , 1 ) / this . settings . active . arDetect . blackframe . sufficientColorVariance ,
rb : Math.abs ( var_r - var_b ) / Math . max ( var_r , var_b , 1 ) / this . settings . active . arDetect . blackframe . sufficientColorVariance ,
gb : Math.abs ( var_b - var_g ) / Math . max ( var_b , var_g , 1 ) / this . settings . active . arDetect . blackframe . sufficientColorVariance ,
} ,
varianceLimit : this.settings.active.arDetect.blackframe.sufficientColorVariance ,
} ,
cumulativeValuePercent : cumulativeValue / ( hasSufficientVariance ? this . settings.active.arDetect.blackframe.cumulativeThresholdLax : this.settings.active.arDetect.blackframe.cumulativeThresholdStrict ) ,
rowMax : rowMax ,
colMax : colMax ,
} ;
}
2019-02-15 00:00:22 +01:00
return {
2019-05-03 00:49:33 +02:00
isBlack : isBlack ,
2019-02-15 00:00:22 +01:00
rowMax : rowMax ,
colMax : colMax ,
} ;
2018-05-09 00:58:50 +02:00
}
2018-03-04 23:07:11 +01:00
2021-08-26 00:37:56 +02:00
/ * *
* Does a quick test to see if the aspect ratio is correct
* Returns 'true' if there ' s a chance of letterbox existing , false if not .
2021-09-19 21:22:12 +02:00
* @param imageData
* @param sampleCols
* @returns
2021-08-26 00:37:56 +02:00
* /
2019-02-15 00:00:22 +01:00
fastLetterboxPresenceTest ( imageData , sampleCols ) {
// fast test to see if aspect ratio is correct.
// returns 'true' if presence of letterbox is possible.
2021-09-19 21:22:12 +02:00
// returns 'false' if we found a non-black edge pixel.
2019-02-15 00:00:22 +01:00
// If we detect anything darker than blackLevel, we modify blackLevel to the new lowest value
const rowOffset = this . canvas . width * ( this . canvas . height - 1 ) ;
2019-02-22 23:02:48 +01:00
let currentMin = 255 , currentMax = 0 , colOffset_r , colOffset_g , colOffset_b , colOffset_rb , colOffset_gb , colOffset_bb , blthreshold = this . settings . active . arDetect . blackbar . threshold ;
2021-09-19 21:22:12 +02:00
2019-02-22 23:02:48 +01:00
// detect black level. if currentMax comes above blackbar + blackbar threshold, we know we aren't letterboxed
2019-02-15 00:00:22 +01:00
2021-02-18 22:38:32 +01:00
for ( let i = 0 ; i < sampleCols . length ; ++ i ) {
2019-02-15 00:00:22 +01:00
colOffset_r = sampleCols [ i ] << 2 ;
colOffset_g = colOffset_r + 1 ;
colOffset_b = colOffset_r + 2 ;
colOffset_rb = colOffset_r + rowOffset ;
colOffset_gb = colOffset_g + rowOffset ;
colOffset_bb = colOffset_b + rowOffset ;
currentMax = Math . max (
imageData [ colOffset_r ] , imageData [ colOffset_g ] , imageData [ colOffset_b ] ,
2019-02-15 00:26:54 +01:00
// imageData[colOffset_rb], imageData[colOffset_gb], imageData[colOffset_bb],
2019-02-15 00:00:22 +01:00
currentMax
) ;
2019-02-22 23:02:48 +01:00
if ( currentMax > this . blackLevel + blthreshold ) {
2019-02-15 00:00:22 +01:00
// we search no further
if ( currentMin < this . blackLevel ) {
this . blackLevel = currentMin ;
}
return false ;
}
currentMin = Math . min (
2019-02-15 00:26:54 +01:00
currentMax ,
2019-02-15 00:00:22 +01:00
currentMin
) ;
}
2021-08-26 00:37:56 +02:00
if ( currentMin < this . blackLevel ) {
2019-02-15 00:00:22 +01:00
this . blackLevel = currentMin
2021-08-26 00:37:56 +02:00
}
2019-02-15 00:00:22 +01:00
return true ;
2018-05-14 20:39:15 +02:00
}
2018-05-09 00:58:50 +02:00
}
2018-03-04 23:07:11 +01:00
2021-02-18 22:38:32 +01:00
let _ard_console_stop = "background: #000; color: #f41" ;
let _ard_console_start = "background: #000; color: #00c399" ;
let _ard_console_change = "background: #000; color: #ff8" ;
2018-03-04 23:07:11 +01:00
2018-12-31 01:03:07 +01:00
export default ArDetector ;