const disconnectIcon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAACp0lEQVR4nOSav07CQBzHu6nv4ODgBvgEPoCDzj6DEy/g5CugMJBAKAkmTthAkA02EmYxMWrDIIsxIYQYh9bKNdY0Ta+9f+Xu1xu+JJDe7/f9ftrC0TvD8zxDZ0k3IFvSDchW7IerweAcSbY5UUrKE3vw09GRg5QHCGl5Yg+eFQoeEnQIJHkSB0CGQJqFeaDKosnAXUA10XoXVkgFsXgWXhBS+FQAUCDwePRf1uv1/nA47DSbzfdKpeJFdVcue4/FohdugN6jz+OOz1LII/KKPIs4QX74er3+mdZYJQhIyPNHt3vBe3UaG5q3pE1VgoB6bsK6LOHn8/nJeDy+XiwWx0aj0VjQNpYNIc4DSfjlcnloWdYgqGOa5oshysC2ILCEdxxndzKZXFWr1e9oPSYAsiCwhLdt+7TVar3hajID2DYEXC9c+NVqddDv9+/T6nIB2BaEpB7R4K7r7kyn08tarfZFUpsbQNYQ0mpHAbTb7Wea+kIAZAWBpGYUAG0PYQBEQyCtpRQAURBoaigHgBcC7VglAbBCYBmjLADaQKxXjdIASIPx3DLKA0gLyPulCQIADsIs+p7hFwMMACwEzjkDKAABhLgzj8QyYYIJIO4KYJwxggKg9S2ADc85bQYBQOufQa0nQlpPhbX+M6T132GtH4io8EhM+4ei2j8WDy+M9Ho9K3MAqi2MRGXb9pmWS2NhOY6zh10cpV0elxE+qSfNhoi/5fGHoJ5pmq9og0QHQnhREJDQBonRaHTjb5Ag3SKjQviwh1mp9MMDIZD/krZJSqXwwSYpEfuD/gGQSMXtciI8gQ0vyhvo8CI8gg/P6zUX4Xk85yY8q/dchWfJkLvwtFkSB0ENT5MHOwh6eNI82EF5CE+SR7o52ZJuQLakG5CtXwAAAP//AwAERgZPbFuCNAAAAABJRU5ErkJggg==";
class MaskPicker{
constructor(parentDiv,camNum,maskStr,fillStyle,opacity){
this.parentDiv=parentDiv;
this.camNum=camNum;
this.drawing=false;
this.erasing=false;
this.previewTimer=null;
this.paused=false;
this.cropSource=null;
this.imageValid=false;
this.imgWidth=1;
this.imgHeight=1;
this.mode='pixel';
this.rectangle=null;
this.draggingSide=null;
this.dragStartPos=null;
this.img=AddElement(parentDiv,'img');
this.img.src='img/loading.gif';
this.img.onload=()=>this.imageLoaded();
this.img.className='previewImg';
this.fetchImage();
this.errP=AddElement(parentDiv,'p','display:none;');
this.canvas=AddElement(parentDiv,'canvas','opacity:'+opacity+'%;');
this.canvas.width=32;
this.canvas.height=32;
this.canvas.className='maskCanvas';
this.canvas.onmousedown=(e)=>this.mousedown(e);
this.canvas.onmousemove=(e)=>this.mousemove(e);
this.canvas.onmouseup=(e)=>this.mouseup(e);
this.canvas.oncontextmenu=(e)=>this.rightclick(e);
this.mouseup=this.mouseup.bind(this);
this.upListener=document.addEventListener('mouseup',this.mouseup);
this.ctx=this.canvas.getContext('2d',{willReadFrequently:true});
this.ctx.fillStyle=fillStyle;
this.parseMaskStr(maskStr);
}
dispose(){
if(this.previewTimer){clearTimeout(this.previewTimer);this.previewTimer=null;}
if(this.img){this.img.remove();this.img=null;}
if(this.canvas){this.canvas.remove();this.canvas=null;}
document.removeEventListener('mouseup',this.mouseup);
}
parseMaskStr(maskStr){
const arr=maskStr.split(",");
if(arr.length===4){
this.mode='rectangle';
this.rectangle={
x:parseInt(arr[0]),
y:parseInt(arr[1]),
w:parseInt(arr[2]),
h:parseInt(arr[3])
};
this.drawRectangle();
}else{
this.mode='pixel';
this.rectangle=null;
this.drawMaskStr(maskStr);
}
}
mousedown(e){
const x=Math.round(e.offsetX*32/this.canvas.offsetWidth);
const y=Math.round(e.offsetY*32/this.canvas.offsetHeight);
if(this.mode==='rectangle'){
const side=this.detectSide(x,y);
if(side){
this.draggingSide=side;
this.dragStartPos={x,y};
}else{
this.rectangle={x,y,w:0,h:0};
this.draggingSide='corner';
this.dragStartPos={x,y};
}
}else{
this.drawing=true;
this.drawPixel(e,true);
}
}
mousemove(e){
const x=Math.round(e.offsetX*32/this.canvas.offsetWidth);
const y=Math.round(e.offsetY*32/this.canvas.offsetHeight);
if(this.mode==='rectangle'){
if(this.draggingSide){
if(this.draggingSide==='corner'){
this.rectangle.w=x-this.rectangle.x;
this.rectangle.h=y-this.rectangle.y;
}else if(this.draggingSide==='left'){
const right=this.rectangle.x+this.rectangle.w;
this.rectangle.x=x;
this.rectangle.w=right-x;
}else if(this.draggingSide==='right'){
this.rectangle.w=x-this.rectangle.x;
}else if(this.draggingSide==='top'){
const bottom=this.rectangle.y+this.rectangle.h;
this.rectangle.y=y;
this.rectangle.h=bottom-y;
}else if(this.draggingSide==='bottom'){
this.rectangle.h=y-this.rectangle.y;
}
this.enforceAspect();
this.drawRectangle();
}else{
const side=this.detectSide(x,y);
if(side==='left' || side==='right'){
this.canvas.style.cursor='ew-resize';
}else if(side==='top' || side==='bottom'){
this.canvas.style.cursor='ns-resize';
}else{
this.canvas.style.cursor='default';
}
}
}else{
if(this.drawing)this.drawPixel(e,false);
}
}
enforceAspect(){
if(!this.rectangle || !this.draggingSide)return;
const imgRatio = this.imgHeight / this.imgWidth;
const absW = Math.abs(this.rectangle.w);
const absH = Math.abs(this.rectangle.h);
if(absW > 2 * absH * imgRatio){
if(this.draggingSide === 'corner' || this.draggingSide === 'right'){
this.rectangle.w = Math.sign(this.rectangle.w)* Math.round(2 * absH * imgRatio);
}else if(this.draggingSide === 'left'){
const right = this.rectangle.x + this.rectangle.w;
const targetW = Math.sign(this.rectangle.w)* Math.round(2 * absH * imgRatio);
this.rectangle.x = right - targetW;
this.rectangle.w = targetW;
}else if(this.draggingSide === 'top' || this.draggingSide === 'bottom'){
const centreX = this.rectangle.x + this.rectangle.w / 2;
this.rectangle.w = Math.sign(this.rectangle.w)* Math.round(2 * absH * imgRatio);
this.rectangle.x = Math.round(centreX - this.rectangle.w / 2);
}
}
if(absH > 2 * absW / imgRatio){
if(this.draggingSide === 'corner' || this.draggingSide === 'bottom'){
this.rectangle.h = Math.sign(this.rectangle.h)* Math.round(2 * absW / imgRatio);
}else if(this.draggingSide === 'top'){
const bottom = this.rectangle.y + this.rectangle.h;
const targetH = Math.sign(this.rectangle.h)* Math.round(2 * absW / imgRatio);
this.rectangle.y = bottom - targetH;
this.rectangle.h = targetH;
}else if(this.draggingSide === 'left' || this.draggingSide === 'right'){
const centreY = this.rectangle.y + this.rectangle.h / 2;
this.rectangle.h = Math.sign(this.rectangle.h)* Math.round(2 * absW / imgRatio);
this.rectangle.y = Math.round(centreY - this.rectangle.h / 2);
}
}
}
mouseup(e){
if(this.mode==='rectangle'){
if(this.draggingSide){
if(this.rectangle.w<0){
this.rectangle.x+=this.rectangle.w;
this.rectangle.w=-this.rectangle.w;
}
if(this.rectangle.h<0){
this.rectangle.y+=this.rectangle.h;
this.rectangle.h=-this.rectangle.h;
}
if(this.rectangle.x<0){
this.rectangle.w+=this.rectangle.x;
this.rectangle.x=0;
}
if(this.rectangle.y<0){
this.rectangle.h+=this.rectangle.y;
this.rectangle.y=0;
}
if(this.rectangle.x+this.rectangle.w>32){
this.rectangle.w=32-this.rectangle.x;
}
if(this.rectangle.y+this.rectangle.h>32){
this.rectangle.h=32-this.rectangle.y;
}
this.drawRectangle();
}
this.draggingSide=null;
this.dragStartPos=null;
}else{
this.drawing=false;
this.erasing=false;
}
}
rightclick(e){
e.preventDefault();
return false;
}
detectSide(x,y){
if(!this.rectangle || this.rectangle.w===0 || this.rectangle.h===0)return null;
const tolerance=0;
const r=this.rectangle;
if(Math.abs(x-r.x)<=tolerance && y>=r.y && y<=r.y+r.h)return 'left';
if(Math.abs(x-(r.x+r.w))<=tolerance && y>=r.y && y<=r.y+r.h)return 'right';
if(Math.abs(y-r.y)<=tolerance && x>=r.x && x<=r.x+r.w)return 'top';
if(Math.abs(y-(r.y+r.h))<=tolerance && x>=r.x && x<=r.x+r.w)return 'bottom';
return null;
}
drawRectangle(){
if(this.rectangle &&(this.rectangle.w!==0 || this.rectangle.h!==0)){
this.ctx.fillRect(0,0,32,32);
this.ctx.clearRect(this.rectangle.x,this.rectangle.y,this.rectangle.w,this.rectangle.h);
}else{
this.ctx.clearRect(0,0,32,32);
}
}
drawMaskStr(maskStr){
this.ctx.clearRect(0,0,32,32);
const arr=maskStr.split(",");
for(let y=0;y<32;y++){
const v=parseInt(arr[y]);
for(let x=0;x<32;x++)if(v&(1<<x))this.ctx.fillRect(x,y,1,1);
}
}
maskStr(){
if(this.mode==='rectangle'){
if(this.rectangle &&(this.rectangle.w>0 || this.rectangle.h>0)){
return `${this.rectangle.x},${this.rectangle.y},${this.rectangle.w},${this.rectangle.h}`;
}
return '0,0,0,0';
}else{
let data=this.ctx.getImageData(0,0,32,32).data;
let str='';
for(let p=3,v=0;p<data.length;v=0){
for(let x=0;x<32;x++,p+=4){
if(data[p])v|=(1<<x);
}
str+=(str?',':'')+v;
}
return str;
}
}
isMask(){
if(this.mode==='rectangle'){
return this.rectangle &&(this.rectangle.w>0 || this.rectangle.h>0);
}else{
let data=this.ctx.getImageData(0,0,32,32).data;
for(let i=0;i<data.length;i++)if(data[i])return true;
return false;
}
}
getCropBounds(){
const data=this.ctx.getImageData(0,0,32,32).data;
const masked=(x,y)=>data[(y*32+x)*4+3]>0;
let crop={left:0,top:0,right:32,bottom:32};
for(let y=0;y<32;y++){
let full=true;
for(let x=0;x<32;x++)if(!masked(x,y)){full=false;break;}
if(!full){crop.top=y;break;}
if(y===31)return crop;
}
for(let y=31;y>=crop.top;y--){
let full=true;
for(let x=0;x<32;x++)if(!masked(x,y)){full=false;break;}
if(!full){crop.bottom=y+1;break;}
}
findLeft:
for(let x=0;x<32;x++)
for(let y=crop.top;y<crop.bottom;y++)
if(!masked(x,y)){crop.left=x;break findLeft;}
findRight:
for(let x=31;x>=crop.left;x--)
for(let y=crop.top;y<crop.bottom;y++)
if(!masked(x,y)){crop.right=x+1;break findRight;}
return crop;
}
drawPixel(e,isMouseDown){
const x=Math.floor(e.offsetX*32/this.canvas.offsetWidth);
const y=Math.floor(e.offsetY*32/this.canvas.offsetHeight);
if(isMouseDown && this.isPixel(x,y))this.erasing=true;
if(this.erasing){
if(e.shiftKey)this.ctx.clearRect(x-1,y-1,3,3);
else this.ctx.clearRect(x,y,1,1);
}else{
if(e.shiftKey)this.ctx.fillRect(x-1,y-1,3,3);
else this.ctx.fillRect(x,y,1,1);
}
}
clear(){
if(this.mode==='rectangle'){
this.rectangle={x:0,y:0,w:0,h:0};
this.drawRectangle();
}else{
this.ctx.clearRect(0,0,32,32);
}
}
invert(){
if(this.mode==='pixel'){
const arr=this.maskStr().split(',');
let str='';
for(let i=0;i<32;i++)str+=(0xFFFFFFFF^parseInt(arr[i]))+',';
this.ctx.clearRect(0,0,32,32);
this.drawMaskStr(str);
}
}
isPixel(x,y){
const data=this.ctx.getImageData(x,y,1,1).data;
return data[3]?true:false;
}
fetchImage(){
if(!this.parentDiv.offsetWidth || this.paused){
this.previewTimer=setTimeout(()=>{this.fetchImage()},500);
return;
}
let serverDidReply=false;
let brightness=elementNamed('brightness');
let contrast=elementNamed('contrast');
let transformation=elementNamed('transformation');
let fisheyeType=elementNamed('fisheyeType');
let fisheyeStrength=elementNamed('fisheyeStrength');
let path=`image?settings=1&cameraNum=${this.camNum}&brightness=${brightness?brightness.value:0}&contrast=${contrast?contrast.value:0}&transformation=${transformation?transformation.value:0}&fisheyeType=${fisheyeType?fisheyeType.value:0}&fisheyeStrength=${fisheyeStrength?fisheyeStrength.value:0}`;
fetch(path,{credentials:'same-origin',redirect:'error'})
.then(response=>{
serverDidReply=true;
if(response.ok)return response.blob();
else{
let ssErrDesc=response.headers.get('SS-Err-Desc');
if(!this.imageValid)this.configureImage(null,ssErrDesc?decodeURIComponent(ssErrDesc):null);
throw new Error('Cancelled');
}
})
.then(blob=>{
this.configureImage(URL.createObjectURL(blob));
this.previewTimer=setTimeout(()=>{this.fetchImage()},400);
this.imageValid=true;
})
.catch(error=>{
if(error.message!='Cancelled' && !this.imageValid){
this.configureImage(null,serverDidReply?error:Loc('No connection to host application'));
}
this.previewTimer=setTimeout(()=>{this.fetchImage()},4000);
});
}
configureImage(url,errDesc){
if(url){
this.img.src=url;
this.img.style.display='block';
this.canvas.style.display='block';
this.errP.style.display='none';
setTimeout(URL.revokeObjectURL,2000,url);
}else{
this.img.style.display='none';
this.canvas.style.display='none';
this.errP.style.display='block';
this.errP.innerHTML='<img src="'+disconnectIcon+'"><br>'+(errDesc?errDesc:'');
}
}
imageLoaded(){
this.imgWidth=this.img.naturalWidth;
this.imgHeight=this.img.naturalHeight;
const style=window.getComputedStyle(this.parentDiv);
const container={x:0,y:0,w:parseInt(style.width),h:parseInt(style.height)};
if(this.cropSource && this.imageValid){
const crop=this.cropSource.getCropBounds();
const cw=crop.right-crop.left,ch=crop.bottom-crop.top;
if(cw>0 && ch>0 &&(cw<32 || ch<32)){
const r=this.fit({x:0,y:0,w:this.imgWidth*cw/32,h:this.imgHeight*ch/32},container);
PositionElement(this.canvas,r);
const fullW=r.w*32/cw,fullH=r.h*32/ch;
PositionElement(this.img,{x:r.x-fullW*crop.left/32,y:r.y-fullH*crop.top/32,w:fullW,h:fullH});
this.img.style.clipPath=`inset(${fullH*crop.top/32}px ${fullW*(32-crop.right)/32}px ${fullH*(32-crop.bottom)/32}px ${fullW*crop.left/32}px)`;
return;
}
}
const r=this.fit({x:0,y:0,w:this.imgWidth,h:this.imgHeight},container);
PositionElement(this.canvas,r);
PositionElement(this.img,r);
this.img.style.clipPath='';
}
fit(r1,r2){
let r={x:0,y:0,w:0,h:0};
if(r1.w/r1.h>r2.w/r2.h){
r.w=r2.w;
r.h=32*Math.round((r.w*r1.h/r1.w)/32);
r.y=(r2.h-r.h)/2;
}else{
r.h=r2.h;
r.w=32*Math.round((r.h*r1.w/r1.h)/32);
r.x=(r2.w-r.w)/2;
}
return r;
}
pause(){
this.paused=true;
}
resume(){
this.paused=false;
}
}
