Search code examples
javascriptimagecolorsstream

Color Thresholding JS, Average Image Color Detect JS


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.


Solution

  • 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>