Color Thresholding JS, Average Image Color Detect JS

54 views Asked by At

I had watched the following tutorial https://youtu.be/BTWPDboW38o?si=_21TOjCfrXC3VWIL and tried the same. What I expect is, I want to change the background color of document based on the color of video stream. But it is always returning rgb(0,0,0).

'use strict';
try {
    const w = window,
        d = document,
        ng = navigator,
        id = e => {
            return d.getElementById(e)
        },
        cn = id('lh'),
        i = id('i'),
        o = id('o'),
        cx = cn.getContext('2d', {
            willReadFrequently: true
        }),
        face = r => ng.mediaDevices.getUserMedia({
            video: {
                facingMode: {
                    exact: 'user'
                }
            }
        }).then(s => {
            i.srcObject = s;
            i.onloadedmetadata = e => {
                setInterval(() => {
                    let c = 0,
                        k = -4,
                        h = cn.height = i.videoHeight,
                        w = cn.width = i.videoWidth,
                        dt = cx.getImageData(0, 0, w, h),
                        io = dt.data,
                        dl = io.length,
                        R, G, B;
                    R = G = B = 0;
                    cx.drawImage(i, 0, 0, w, h);
                    o.src = cn.toDataURL('image/webp');
                    while ((k += r * 4) < dl) {
                        ++c;
                        R += io[k];
                        G += io[k + 1];
                        B += io[k + 2]
                    };
                    ['R', 'G', 'B'].forEach(e1 => {
                        eval(e1 + '=' + `~~(${e1}/c)`)
                    });
                    let rgb = `rgb(${R},${G},${B})`;
                    d.body.style.background = rgb
                }, -1)
            }
        });
    face(4)
} catch (e) {
    alert(e)
}

I expect it should change the background color of document based on video stream.

1

There are 1 answers

0
matt On BEST ANSWER

ok, heres whats wrong with your code

  1. after assigning the srcObject of a video element you have to call play

  2. in the setInterval function when you assign the values of h and w you also set the width and height of the canvas, doing this clears the canvas

notes:

you could do with some clarification that after invoking face(4) the then function is actually part of the getUserMedia, it confused me for a while

its probably the most elaborate way perform a Math.floor I have ever seen

'use strict';

var timer;
var stream;

document.querySelector('input[value=stop]').onclick = e=>{
  clearInterval(timer);
  if(stream)stream.getTracks().forEach(track=>track.stop());
}

try {
    const w = window,
        d = document,
        ng = navigator,
        id = e => {
            return d.getElementById(e)
        },
        cn = id('lh'),
        i = id('i'),
        o = id('o'),
        cx = cn.getContext('2d', {
            willReadFrequently: true
        }),
        face = r => ng.mediaDevices.getUserMedia({
            video: {width:100,height:100}
        }).then(s => {
            stream=s;
            i.srcObject = s;
            i.play();
            i.onloadedmetadata = e => {
                timer=setInterval(() => {
                    let c = 0,
                        k = -4,
                        h = cn.height = i.videoHeight,
                        w = cn.width = i.videoWidth;

                        cx.drawImage(i, 0, 0, w, h);

                    let dt = cx.getImageData(0, 0, w, h),
                        io = dt.data,
                        dl = io.length,
                        R, G, B;                        
                    R = G = B = 0;
                    o.src = cn.toDataURL('image/webp');
                    while ((k += r*4) < dl) {
                        ++c;
                        R += io[k];
                        G += io[k + 1];
                        B += io[k + 2]
                    };
                    ['R', 'G', 'B'].forEach(e1 => {
                        eval(e1 + '=' + `~~(${e1}/c)`)
                    });
                    let rgb = `rgb(${R},${G},${B})`;
                    d.body.style.background = rgb
                }, -1)
            }
        });
    face(4)
} catch (e) {
    alert(e)
}
canvas {
  border:1px solid lightgray;
}
video {
  border:1px solid lightgray;
}
img {
  border:1px solid lightgray;
}
input {
  font-size:16px;
  padding:5px;
}
<canvas id=lh></canvas>
<video id=i></video>
<img id=o>
<input type=button value=stop>

i added a stop feature so it can be turned off without reloading the page

im afraid that it wont actually run in the stackoverflow website, i think they must have webcam access turned off and wont allow video to play

ive created a much neater version for anyone who visits this page at a later date, it needs a canvas element and its derived 2d context

  var stream        = await navigator.mediaDevices.getUserMedia({video:true});      
  var video         = document.createElement('video');
  video.srcObject   = stream;
  video.play();
  
  (function update(){
    
        ctx.drawImage(video,0,0,canvas.width,canvas.height);
        
        var img     = ctx.getImageData(0,0,canvas.width,canvas.height);
        var n       = img.data.length;
        var r       = 0;
        var g       = 0;
        var b       = 0;
        
        for(var i=0;i<n;i+=4){
              
              r  += img.data[i]/n*4;
              g  += img.data[i+1]/n*4;
              b  += img.data[i+2]/n*4;
              
        }//for
        
        document.body.style.background    = `rgb(${r},${g},${b})`;
        
        if(abort)return;
        requestAnimationFrame(update);
    
  })();

i thought this was a nice little project so ive added a working example, i dont think a lot of these sites that allow code generation allow video, anyway heres what all the fuss is about

        var ctx         = canvas.getContext('2d');
        var balls       = ballsmod(10);
        var abort       = false;
        var framerate   = 20;
        
        quit.onclick   = e=>abort   = true;
        
        (function update(){
                      
              ctx.clearRect(0,0,canvas.width,canvas.height);
              balls();
              
              var data    = ctx.getImageData(0,0,canvas.width,canvas.height);
              var n       = data.data.length;
              var r       = 0;
              var g       = 0;
              var b       = 0;
              var s       = 4;
              for(var i=0;i<n;i+=4*s){
                    
                    r  += data.data[i]/n*4*s;
                    g  += data.data[i+1]/n*4*s;
                    b  += data.data[i+2]/n*4*s;
                    
              }//for
              
              document.body.style.background    = `rgb(${r},${g},${b})`;
              
              if(abort)return;
              setTimeout(update,1000/framerate);
          
        })();


        function ballsmod(num){
          
                var cols        = ['blue','green','red','yellow','lightblue','lightgray'];
                var balls       = [];
                
                for(var i=0;i<num;i++)balls.push(ball());
                      
                
                function update(){
                 
                      balls.forEach(update_ball);
        
                }//update
                
                function rnd(size,offset){return Math.random()*size+(offset||0)}
                
                function ball(){
                  
                      var col     = cols[parseInt(rnd(cols.length))]
                      var r       = rnd(20,20);
                      var x       = rnd(canvas.width-2*r,r);
                      var y       = rnd(canvas.height-2*r,+r);
                      var dx      = rnd(20,-10);
                      var dy      = rnd(20,-10);
                      var ball    = {x,y,r,col,dx,dy,update};
                      return ball;
                      
                }//ball
                
                function update_ball(ball){
                  
                      ball.x   += ball.dx;
                      ball.y   += ball.dy;
                      if(ball.x-ball.r<0 || ball.x+ball.r>canvas.width){
                            ball.dx  *= -1;
                            ball.x   += ball.dx;
                      }
                      if(ball.y-ball.r<0 || ball.y+ball.r>canvas.height){
                            ball.dy  *= -1
                            ball.y   += ball.dy;
                      }
                      
                      ctx.beginPath();
                      ctx.arc(ball.x,ball.y,ball.r,0,2*Math.PI,false);
                      ctx.fillStyle = ball.col;
                      ctx.fill();
                      
                }//update                
         
                return update;
          
        }//ballsmod
        body {
          text-align:center;
        }
        canvas {
          border:1px solid lightgray;
        }
        input {
          font-size:16px;
          padding:7px;
          margin-left:10px;
          vertical-align:top;
        }
        <input id=quit type=button value=stop>
        <canvas id=canvas></canvas>