ultrawidify/js/lib/EdgeDetect.js

400 lines
14 KiB
JavaScript

class EdgeDetect{
constructor(ardConf){
this.conf = ardConf;
this.sampleWidthBase = ExtensionConf.arDetect.edgeDetection.sampleWidth << 2; // corrected so we can work on imageData
this.halfSample = this.sampleWidthBase >> 1;
this.detectionTreshold = ExtensionConf.arDetect.edgeDetection.detectionTreshold;
this.init(); // initiate things that can change
}
// initiates things that we may have to change later down the line
init() {
}
findEdges(image, sampleCols, direction = EdgeDetectPrimaryDirection.VERTICAL, quality = EdgeDetectQuality.IMPROVED, guardLineOut){
var fastCandidates, edgeCandidates, edges,
if (direction == EdgeDetectPrimaryDirection.VERTICAL) {
fastCandidates = this.findCandidates(image, sampleCols, guardLine);
if(quality == EdgeDetectQuality.FAST){
edges = fastCandidates;
} else {
edgeCandidates = this.edgeDetect(image, edges);
}
}
}
findCandidates(image, sampleCols, guardLineOut){
var upper_top, upper_bottom, lower_top, lower_bottom;
var blackbarTreshold;
var cols_a = sampleCols;
var cols_b = []
// todo: cloning can be done better. check array.splice or whatever
for(var i in cols){
cols_b[i] = cols_a[i] + 0;
}
var res_top = [];
var res_bottom = [];
this.colsTreshold = cols.length * ExtensionConf.arDetect.edgeDetection.minColsForSearch;
if(colsTreshold == 0)
colsTreshold = 1;
this.blackbarTreshold = this.conf.blackLevel + ExtensionConf.arDetect.blackbarTreshold;
// if guardline didn't fail and imageDetect did, we don't have to check the upper few pixels
// but only if upper and lower edge are defined. If they're not, we need to check full height
if(guardLineOut){
if(guardLineOut.imageFail && !guardLineOut.blackbarFail && this.conf.guardLine.blackbar.top) {
upper_top = this.conf.guardLine.blackbar.top;
upper_bottom = this.conf.canvas.height >> 1;
lower_top = upper_bottom;
lower_bottom = this.conf.guardLine.blackbar.bottom;
} else if (! guardLineOut.imageFail && !guardLineOut.blackbarFail && this.conf.guardLine.blackbar.top) {
// ta primer se lahko zgodi tudi zaradi kakšnega logotipa. Ker nočemo, da nam en jeben
// logotip vsili reset razmerja stranic, se naredimo hrvata in vzamemo nekaj varnostnega
// pasu preko točke, ki jo označuje guardLine.blackbar. Recimo 1/8 višine platna na vsaki strani.
// a logo could falsely trigger this case, so we need to add some extra margins past
// the point marked by guardLine.blackbar. Let's say 1/8 of canvas height on either side.
upper_top = 0;
upper_bottom = this.conf.guardLine.blackbar.top + (this.conf.canvas.height >> 3);
lower_top = this.conf.guardLine.blackbar.bottom - (this.conf.canvas.height >> 3);
lower_bottom = this.conf.canvas.height - 1;
} else {
upper_top = 0;
upper_bottom = (this.canvas.height >> 1) /*- parseInt(this.canvas.height * ExtensionConf.arDetect.edgeDetection.middleIgnoredArea);*/
lower_top = (this.canvas.height >> 1) /*+ parseInt(this.canvas.height * ExtensionConf.arDetect.edgeDetection.middleIgnoredArea);*/
lower_bottom = this.canvas.height - 1;
}
} else{
upper_top = 0;
upper_bottom = (this.canvas.height >> 1) /*- parseInt(this.canvas.height * ExtensionConf.arDetect.edgeDetection.middleIgnoredArea);*/
lower_top = (this.canvas.height >> 1) /*+ parseInt(this.canvas.height * ExtensionConf.arDetect.edgeDetection.middleIgnoredArea);*/
lower_bottom = this.canvas.height - 1;
}
if(Debug.debug){
console.log("[EdgeDetect::findCandidates] searching for candidates on ranges", upper_top, "<->", upper_bottom, ";", lower_top, "<->", lower_bottom)
}
var upper_top_corrected = upper_top * this.conf.canvasImageDataRowLength;
var upper_bottom_corrected = upper_bottom * this.conf.canvasImageDataRowLength;
var lower_top_corrected = lower_top * this.conf.canvasImageDataRowLength;
var lower_bottom_corrected = lower_bottom * this.conf.canvasImageDataRowLength;
if(Debug.debugCanvas.enabled){
this._columnTest_dbgc(image, upper_top_corrected, upper_bottom_corrected, cols_a, res_top, false);
this._columnTest_dbgc(image, lower_top_corrected, lower_bottom_corrected, cols_b, res_bottom, true);
} else {
this._columnTest(image, upper_top_corrected, upper_bottom_corrected, cols_a, res_top, false);
this._columnTest(image, lower_top_corrected, lower_bottom_corrected, cols_b, res_bottom, true);
}
return {res_top: res_top, res_bottom: res_bottom};
}
// dont call the following outside of this class
edgeDetect(image, samples){
var edgeCandidatesTop = {count: 0};
var edgeCandidatesBottom = {count: 0};
var detections;
var canvasWidth = this.conf.canvas.width;
var canvasHeight = this.conf.canvas.height;
var sampleStart, sampleEnd, loopEnd;
var sampleRow_black, sampleRow_color;
var blackEdgeViolation = false;
var topEdgeCount = 0;
var bottomEdgeCount = 0;
for(sample of samples.res_top){
blackEdgeViolation = false; // reset this
// determine our bounds. Note that sample.col is _not_ corrected for imageData, but halfSample is
sampleStart = (sample.col << 2) - this.halfSample;
if(sampleStart < 0)
sampleStart = 0;
sampleEnd = sampleStart + this.sampleWidthBase;
if(sampleEnd > this.conf.canvas.imageDataRowLength)
sampleEnd = this.conf.canvas.imageDataRowLength;
// calculate row offsets for imageData array
sampleRow_black = (sample.top - ExtensionConf.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvas.imageDataRowLength;
sampleRow_color = (sample.top + 1 + ExtensionConf.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvas.imageDataRowLength;
// že ena kršitev črnega roba pomeni, da kandidat ni primeren
// even a single black edge violation means the candidate is not an edge
loopEnd = sampleRow_black + sampleEnd;
if(Debug.debugCanvas.enabled){
blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
} else {
blackEdgeViolation = this._blackbarTest(image, sampleRow_black + sampleStart, loopEnd);
}
// če je bila črna črta skrunjena, preverimo naslednjega kandidata
// if we failed, we continue our search with the next candidate
if(blackEdgeViolation)
continue;
detections = 0;
loopEnd = sampleRow_color + sampleEnd;
if(Debug.debugCanvas.enabled) {
this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample, edgeCandidatesTop)
} else {
this._imageTest(image, start, end, sample, edgeCandidatesBottom)
}
}
for(sample of samples.res_bottom){
blackEdgeViolation = false; // reset this
// determine our bounds. Note that sample.col is _not_ corrected for imageData, but this.halfSample is
sampleStart = (sample.col << 2) - this.halfSample;
if(sampleStart < 0)
sampleStart = 0;
sampleEnd = sampleStart + this.sampleWidthBase;
if(sampleEnd > this.conf.canvas.imageDataRowLength)
sampleEnd = this.conf.canvas.imageDataRowLength;
// calculate row offsets for imageData array
sampleRow_black = (sample.bottom + ExtensionConf.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvas.imageDataRowLength;
sampleRow_color = (sample.bottom - 1 - ExtensionConf.arDetect.edgeDetection.edgeTolerancePx) * this.conf.canvas.imageDataRowLength;
// že ena kršitev črnega roba pomeni, da kandidat ni primeren
// even a single black edge violation means the candidate is not an edge
loopEnd = sampleRow_black + sampleEnd;
if(Debug.debugCanvas.enabled){
blackEdgeViolation = this._blackbarTest_dbg(image, sampleRow_black + sampleStart, loopEnd);
} else {
blackEdgeViolation = this._blackbarTest(image, sampleRow_black + sampleStart, loopEnd);
}
// če je bila črna črta skrunjena, preverimo naslednjega kandidata
// if we failed, we continue our search with the next candidate
if(blackEdgeViolation)
continue;
detections = 0;
loopEnd = sampleRow_color + sampleEnd;
if(Debug.debugCanvas.enabled) {
this._imageTest_dbg(image, sampleRow_color + sampleStart, loopEnd, sample, edgeCandidatesTop);
} else {
this._imageTest(image, start, end, sample, edgeCandidatesBottom);
}
}
return {
edgeCandidatesTop: edgeCandidatesTop,
edgeCandidatesTopCount: edgeCandidatesTop.count,
edgeCandidatesBottom: edgeCandidatesBottom,
edgeCandidatesBottomCount: edgeCandidatesBottom.count
};
}
// pomožne funkcije
// helper functions
_columnTest(image, top, bottom, colsIn, colsOut, reverseSearchDirection){
var tmpI;
if(reverseSearchDirection){
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
for(var col of colsIn){
tmpI = i + (col << 2);
if( image[tmpI] > this.blackbarTreshold ||
image[tmpI + 1] > this.blackbarTreshold ||
image[tmpI + 2] > this.blackbarTreshold ){
var bottom = (i / this.conf.canvasImageDataRowLength) + 1;
colsOut.push({
col: col,
bottom: bottom,
bottomRelative: this.canvas.height - bottom
});
colsIn.splice(colsIn.indexOf(col), 1);
}
}
if(colsIn.length < this.colsTreshold)
break;
}
} else {
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
for(var col of colsIn){
tmpI = i + (col << 2);
if( image[tmpI] > this.blackbarTreshold ||
image[tmpI + 1] > this.blackbarTreshold ||
image[tmpI + 2] > this.blackbarTreshold ){
colsOut.push({
col: col,
top: (i / this.conf.canvasImageDataRowLength) - 1
});
colsIn.splice(cols_a.indexOf(col), 1);
}
}
if(colsIn.length < this.colsTreshold)
break;
}
}
}
_columnTest_dbgc(image, top, bottom, colsIn, colsOut, reverseSearchDirection){
var tmpI;
if(reverseSearchDirection){
for(var i = bottom - this.conf.canvasImageDataRowLength; i >= top; i-= this.conf.canvasImageDataRowLength){
for(var col of colsIn){
tmpI = i + (col << 2);
if( image[tmpI] > this.blackbarTreshold ||
image[tmpI + 1] > this.blackbarTreshold ||
image[tmpI + 2] > this.blackbarTreshold ){
var bottom = (i / this.conf.canvasImageDataRowLength) + 1;
colsOut.push({
col: col,
bottom: bottom,
bottomRelative: this.canvas.height - bottom
});
colsIn.splice(colsIn.indexOf(col), 1);
this.conf.debugCanvas.trace(tmpI,DebugCanvasClasses.EDGEDETECT_CANDIDATE);
}
else{
this.conf.debugCanvas.trace(tmpI, DebugCanvasClasses.EDGEDETECT_ONBLACK);
}
}
if(colsIn.length < this.colsTreshold)
break;
}
} else {
for(var i = top; i < bottom; i+= this.conf.canvasImageDataRowLength){
for(var col of colsIn){
tmpI = i + (col << 2);
if( image[tmpI] > this.blackbarTreshold ||
image[tmpI + 1] > this.blackbarTreshold ||
image[tmpI + 2] > this.blackbarTreshold ){
colsOut.push({
col: col,
top: (i / this.conf.canvasImageDataRowLength) - 1
});
colsIn.splice(cols_a.indexOf(col), 1);
this.conf.debugCanvas.trace(tmpI, DebugCanvasClasses.EDGEDETECT_CANDIDATE);
} else {
this.conf.debugCanvas.trace(tmpI, DebugCanvasClasses.EDGEDETECT_ONBLACK);
}
}
if(colsIn.length < this.colsTreshold)
break;
}
}
}
_blackbarTest(image, start, end){
for(var i = sampleRow_black + sampleStart; i < loopEnd; i += 4){
if( image[i ] > this.blackbarTreshold ||
image[i+1] > this.blackbarTreshold ||
image[i+2] > this.blackbarTreshold ){
return true;
}
}
return false; // no violation
}
_blackbarTest_dbg(image, start, end){
for(var i = start; i < end; i += 4){
if( image[i ] > this.blackbarTreshold ||
image[i+1] > this.blackbarTreshold ||
image[i+2] > this.blackbarTreshold ){
this.conf.debugCanvas.trace(i, DebugCanvasClasses.VIOLATION)
return true;
} else {
this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_BLACKBAR)
}
}
return false; // no violation
}
_imageTest(image, start, end, sample, edgeCandidates){
var detections = 0;
for(var i = start; i < end; i += 4){
if( image[i ] > this.blackbarTreshold ||
image[i+1] > this.blackbarTreshold ||
image[i+2] > this.blackbarTreshold ){
++detections;
}
}
if(detections >= detectionTreshold){
if(edgeCandidates[sample.top] != undefined)
edgeCandidates[sample.top].count++;
else{
edgeCandidates[sample.top] = {top: sample.top, count: 1};
edgeCandidates.count++;
}
}
}
_imageTest_dbg(image, start, end, sample, edgeCandidates){
var detections = 0;
for(var i = start; i < end; i += 4){
if( image[i ] > this.blackbarTreshold ||
image[i+1] > this.blackbarTreshold ||
image[i+2] > this.blackbarTreshold ){
++detections;
this.conf.debugCanvas.trace(i, DebugCanvasClasses.EDGEDETECT_IMAGE);
} else {
this.conf.debugCanvas.trace(i, DebugCanvasClasses.WARN);
}
}
if(detections >= this.detectionTreshold){
if(edgeCandidates[sample.top] != undefined)
edgeCandidates[sample.top].count++;
else{
edgeCandidates[sample.top] = {top: sample.top, count: 1};
edgeCandidates.count++;
}
}
}
}
var EdgeDetectPrimaryDirection = {
VERTICAL: 0,
HORIZONTAL: 1
}
var EdgeDetectQuality = {
FAST: 0,
IMPROVED: 1
}