Fade canvas video from greyscale to color

后端 未结 3 1911
遇见更好的自我
遇见更好的自我 2020-12-06 14:01

I have 2 elements - video and canvas. On video play event, a functions draws the same video on canvas only greyscale. Then I have a button which is supposed to fade canvas v

相关标签:
3条回答
  • 2020-12-06 14:44

    The simplest is to set a greyscale css filter on the canvas.

    var video = document.getElementById("myCanvas");
    var button = document.getElementById("myButton");
    
    function grey() {
      video.className += " greyscale";
    }
    
    function color() {
      classGreyscale = video.className.indexOf("greyscale");
      if (classGreyscale > 0)
        video.className = video.className.substring(0, video.className.length - 10)
    }
    
    button.addEventListener('click', function() {
      color();
    });
    
    grey();
    .greyscale {
      -webkit-filter: grayscale(100%);
      filter: grayscale(100%);
    }
    
    .transition {
      transition: all 1s;
      -webkit-transition: all 1s;
    }
    <div>
      <img src="https://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png" id="myCanvas" class="transition" />
      <br/>
      <button id="myButton">to Colour</button>
    </div>

    So what we do here is add a transition class for our canvas, so it can animate the changes. Then when we add a grayscale class to it and this changes a css filter along with the transition so it fades in. When we want to make it colorful again we remove the greyscale class.

    I made the example with an image but it will work with everything.

    This way you fade in the class with 1s transition on all parameters (greyscale here). It is better to use this, because you don't have to count every pixel in grayscale, it is cleaner.

    Note that you could use jQuery addClass removeClass for simpler and cleaner soultion.

    Also note that you should average r,g,b with weights: https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale

    0 讨论(0)
  • 2020-12-06 14:53

    Canvas Filters for real-time Video/Animation.

    Black & White filter

    To do a black and white filter is easy.

     // mixAmount is a value from 0 - 1 0 = no mix 1 = full FX
     // video is the video
     ctx.drawImage(video,0,0); // draw the video
     // set up filter
     ctx.fillStyle = "#888"; // gray colour
     ctx.globalAlpha = mixAmount;   // amount of FX
     ctx.globalCompositeOperation = "color";  // The comp setting to do BLACK/WHITE
     ctx.fillRect(0,0,video.width,video.height);  // Draw gray over the video
     ctx.globalAlpha = 1;  // reset alpha
     ctx.globalCompositeOperation = "source-over";  // reset comp
    

    Or you can render the video over itself to get other FX, the demo shows the Black and White filter and several more by just using the above code and a few extra layers.

    More info

    For more on displaying a video see Display video inside canvas


    Demo

    The demo show how to do Black and white and some other FX while I am at it.

    How to use.

    See video title for attribution. FX on left from top to bottom "Lighten", "Black & white", "Sepia", "Saturate", and "Negative".

    The Demo has the following FX Lighter, Darken, Black/White, Negative, Saturate, Sepia, B&W negative, and more.

    The Javascript

    The code relating to the question is all at the top and marked. The rest is UI loading etc..

    Each FX is a function that will call either addMix or addOverlay to apply the filter as shown in the snippet above. The addMix function is slightly different as it draws the video over the video to get the FX rather than a fill.

    Instructions are on the demo.

    Please note not all browser support all comp modes (WHY?? who knows!! :( ) Nor is there a way to be 100% sure if a browser supports a mode or not. The safe bet is Firefox, Chrome, and Edge for all other browsers best of luck..

    //==========================================================================
    // All the mix function are in this section
    var FXMix = 1;
    var addOverlay = function(type, repeat = 1){
        if(FXMix > 0){
            ctx.globalCompositeOperation = type; 
            ctx.globalAlpha = FXMix;
            while (repeat-- > 0) {
                ctx.fillRect(0, 0, canvas.width, canvas.height);
            }
            ctx.globalAlpha = 1;
            ctx.globalCompositeOperation = "source-over"; 
        }
    }
    var addMix = function(type,video, repeat = 1){
        if(FXMix > 0){
            ctx.globalCompositeOperation = type; 
            ctx.globalAlpha = FXMix;
            while (repeat-- > 0) {
                ctx.drawImage(video,0, 0, canvas.width, canvas.height);
            }
            ctx.globalAlpha = 1;
            ctx.globalCompositeOperation = "source-over"; 
        }
    }
    var fill = function(style){
        ctx.globalAlpha = FXMix;
        ctx.fillStyle = style;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalAlpha = 1;
    }
    var FX = {
    }
    var FXList = [];
    var currentFX = "";
    var addFX = function(name,func){
        FXList.push(name);
        FX[name] = func;
        currentFX = name;
    }
    // multiply,screen,overlay,color-dodge,color-burn,hard-light,soft-light,difference,exclusion,hue,saturation,color,luminosity
    addFX("Ligher",(vid)=>{ addMix("lighter",vid);} );
    addFX("BlackWhite",(vid)=>{ ctx.fillStyle = "#888"; addOverlay("color");} );
    addFX("Negative",(vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference");} );
    addFX("Sepia",(vid)=>{ fill("#F94"); addMix("luminosity",vid); ;} );
    addFX("B&W Negative",(vid)=>{ ctx.fillStyle = "#FFF";   addOverlay("difference");ctx.fillStyle = "#888"; addOverlay("color");} );
    addFX("Ligher+",(vid)=>{   addMix("lighter",vid);addMix("lighter",vid);addMix("lighter",vid);} );
    addFX("B&W Lighten",(vid)=>{  addMix("lighter",vid);ctx.fillStyle = "#888"; addOverlay("color");} );
    addFX("Darken+",(vid)=>{  addMix("multiply",vid);addMix("multiply",vid);addMix("multiply",vid);} );
    addFX("Darken",(vid)=>{  addMix("multiply",vid);} );
    addFX("Saturate",()=>{ ctx.fillStyle = "#F00";addOverlay("saturation");});
    addFX("Movement",(vid) => {
      const keepMix = FXMix;
      FXMix = 1;
      addMix("difference",can1);
      addMix("lighter",ctx.canvas,2);
      addMix("multiply",vid,1);
      FXMix = keepMix * 0.95;
      addMix("screen",can2,1);
      can2.ctx.drawImage(ctx.canvas,0,0,canvas.width, canvas.height);
      FXMix = 1;
      addMix("lighter",ctx.canvas,1);
      FXMix = keepMix;
      var scale = videoContainer.scale;
      var vidH = vid.videoHeight;
      var vidW = vid.videoWidth;
      var top = canvas.height / 2 - (vidH /2 ) * scale;
      var left = canvas.width / 2 - (vidW /2 ) * scale;
      if(can1.counting === undefined) { can1.counting = 0 }
      else { can1.counting ++ }
      if(can1.counting % 2 === 0) {
         can1.ctx.drawImage(vid, left, top, vidW * scale, vidH * scale);  
      }
    });
    addFX("None",()=>{});
    
    
    
    // end of FX mixing
    //==========================================================================
    
    var mediaSource = "http://video.webmfiles.org/big-buck-bunny_trailer.webm";
    var mediaSource = "http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv";
    var muted = true;
    var canvas = document.getElementById("myCanvas"); // get the canvas from the page
    var ctx = canvas.getContext("2d");
    const can1 = document.createElement("canvas");
    can1.width = canvas.width;
    can1.height = canvas.height;
    can1.ctx = can1.getContext("2d");
    const can2 = document.createElement("canvas");
    can2.width = canvas.width;
    can2.height = canvas.height;
    can2.ctx = can2.getContext("2d");
    var videoContainer; // object to hold video and associated info
    var video = document.createElement("video"); // create a video element
    video.src = mediaSource;
    // the video will now begin to load.
    // As some additional info is needed we will place the video in a
    // containing object for convenience
    video.autoPlay = false; // ensure that the video does not auto play
    video.loop = true; // set the video to loop.
    video.muted = muted;
    videoContainer = {  // we will add properties as needed
         video : video,
         ready : false,   
    };
    // To handle errors. This is not part of the example at the moment. Just fixing for Edge that did not like the ogv format video
    video.onerror = function(e){
        document.body.removeChild(canvas);
        document.body.innerHTML += "<h2>There is a problem loading the video</h2><br>";
        document.body.innerHTML += "Users of IE9+ , the browser does not support WebM videos used by this demo";
        document.body.innerHTML += "<br><a href='https://tools.google.com/dlpage/webmmf/'> Download IE9+ WebM support</a> from tools.google.com<br> this includes Edge and Windows 10";
        
     }
    video.oncanplay = readyToPlayVideo; // set the event to the play function that 
                                      // can be found below
    function readyToPlayVideo(event){ // this is a referance to the video
        // the video may not match the canvas size so find a scale to fit
        videoContainer.scale = Math.min(
                             canvas.width / this.videoWidth, 
                             canvas.height / this.videoHeight); 
        videoContainer.ready = true;
        // the video can be played so hand it off to the display function
        requestAnimationFrame(updateCanvas);
        // add instruction
        document.getElementById("playPause").textContent = "Click video to play/pause.";
        document.querySelector(".mute").textContent = "Mute";
    }
    var playClick = false;
    function updateCanvas(){
        ctx.clearRect(0,0,canvas.width,canvas.height); 
        // only draw if loaded and ready
        if(videoContainer !== undefined && videoContainer.ready){ 
            // find the top left of the video on the canvas
            video.muted = muted;
            var scale = videoContainer.scale;
            var vidH = videoContainer.video.videoHeight;
            var vidW = videoContainer.video.videoWidth;
            var top = canvas.height / 2 - (vidH /2 ) * scale;
            var left = canvas.width / 2 - (vidW /2 ) * scale;
            // now just draw the video the correct size
            ctx.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
    
            FX[currentFX](videoContainer.video);
            if(videoContainer.video.paused){ // if not playing show the paused screen 
                drawPayIcon();
            }
            overUI = false;
            cursor = "default";
            drawSlider();
            drawList();
            if(mouse.over){
                if(!overUI){
                    if((mouse.button&1)===1){ // bit field
                        playClick = true;
                    }
                    if((mouse.button&1)===0 && playClick){ // bit field
                        playClick = false;
                        playPauseClick();
                    }
                    cursor = "pointer";
                }
            }
            if(showFXName > 0){
                showFXName = Math.max(0,showFXName - 0.05);
                ctx.globalAlpha = Math.min(1,showFXName);
                ctx.font = "32px Arial";
                ctx.textAlign = "center";
                ctx.textbaseLine = "middle";
                ctx.fillStyle = "white";
                ctx.strokeStyle = "black";
                ctx.lineJoin = "round"
                ctx.strokeText(currentFX,canvas.width/2,canvas.height/2);
                ctx.fillText(currentFX,canvas.width/2,canvas.height/2);
                ctx.globalAlpha = 1;
            }
            
            canvas.style.cursor = cursor;
        }
        // all done for display 
        // request the next frame in 1/60th of a second
        requestAnimationFrame(updateCanvas);
    }
    var showFXName = 0;
    var cursor = "default";
    var overUI = false;
    var sliderAlpha = 1;
    var listAlpha = 1;
    var dragging = false;
    var listWidth = null;
    function getMaxListWidth(){
        ctx.font = "12px arial";
        FXList.forEach(text => {listWidth = Math.max(listWidth,ctx.measureText(text).width)})
        
    }
    
    function drawList(){
        if(listWidth === null){
            getMaxListWidth();
            listWidth += 10;
        }
    
        if(!overUI && mouse.over && mouse.x > canvas.width - listWidth){
            listAlpha = 1;
            overUI = true;
            
        }else{
            listAlpha = Math.max(0,listAlpha - 0.05);
        }
        if(listAlpha > 0){
            ctx.font = "12px arial";
            var textH = 14;
            var border = 10;
            ctx.textAlign = "right";
            ctx.textBaseline = "middle";
            ctx.globalAlpha = listAlpha;
             ctx.fillStyle = "black";
             ctx.strokeStyle = "white";        
            var len = FXList.length;
            var h = len * textH;
            var y = canvas.height / 2 - h/2;
            var x = canvas.width - border * 2;
            ctx.fillRect(x - listWidth,y - border, listWidth+border,h + border );
            ctx.strokeRect(x - listWidth,y - border, listWidth + border,h + border );
            ctx.fillStyle = "white"
            for(var i = 0; i < len; i ++){
                var yy = y + i * textH;
                if(FXList[i] === currentFX){
                    ctx.fillStyle = "#0FF";
                    ctx.fillText(FXList[i],x,yy);
                    ctx.fillStyle = "white"
                }else
                if(mouse.x > canvas.width - listWidth && mouse.y > yy - textH/2 && mouse.y < yy + textH /2){
                    ctx.fillStyle = "#0F0";
                    ctx.fillText(FXList[i],x,yy);
                    ctx.fillStyle = "white"
                    cursor = "pointer";
                    if((mouse.button & 1) === 1){
                        currentFX =FXList[i];
                        showFXName = 4;
                    }
                }else{
                    ctx.fillText(FXList[i],x,yy);
                }
                
                
            }
            ctx.globalAlpha = 1;
    
            
            
        }
        
        
    }
    
    function drawSlider(){
        if(currentFX === "None"){
            sliderAlpha = 0;
            return;
        }
    
        var cw = canvas.width;
        var ch = canvas.height;
        var handle = 5;
        var inset = 10
        var x = inset;
        var w = cw - inset*2;
        var h = 20;
        var y = ch - inset - h;
        var pos =  FXMix * w + x;;
        if(mouse.y > y - h* 2){
            cursor = "e-resize";
            overUI = true;
            if((mouse.button&1) && !dragging){  // bit field
                dragging = true;
            }
        }else{
            cursor = "pointer";
    
        }
        if(dragging){
            overUI = true;
            cursor = "e-resize";
            sliderAlpha = 1;
            pos = mouse.x - x;
            FXMix = Math.min(1,Math.max(0,pos / w));
            if( (mouse.button&1) === 0 ){ //bit field
                dragging = false;
    
            }
        }else{
            
        }
        if(!dragging && mouse.y > y-h*2 && mouse.over){
            sliderAlpha = 1;
        }else{
            if(sliderAlpha > 0){
                sliderAlpha = Math.max(0,sliderAlpha- 0.05);
            }
        }
        if(sliderAlpha === 0){
            return;
        }
        ctx.globalAlpha =  sliderAlpha;
        ctx.font = "18px arial";
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        var amount = FXMix;
        ctx.fillStyle = "black";
        ctx.strokeStyle = "white";
        ctx.fillRect(x,y,w,h);
        ctx.strokeRect(x,y,w,h);
        ctx.fillStyle = "white";
        ctx.fillText(currentFX + " "+ (FXMix * 100).toFixed(0)+"%",w/2,y + h / 2);
        pos = amount * w + x;
        ctx.fillStyle = "white";
        ctx.strokeStyle = "black";
        ctx.fillRect(pos-handle*2,y-handle,handle* 4,h + handle * 2);
        ctx.strokeRect(pos-handle*2,y-handle,handle* 4,h + handle * 2);
        ctx.strokeRect(pos-1,y-handle * 0.5,2,h + handle);
        ctx.globalAlpha =  1;
        
        
        
    }
    function drawPayIcon(){
        // ctx.fillStyle = "black";  // darken display
        // ctx.globalAlpha = 0.5;
        // ctx.fillRect(0,0,canvas.width,canvas.height);
         ctx.fillStyle = "#DDD"; // colour of play icon
         ctx.globalAlpha = 0.75; // partly transparent
         ctx.beginPath(); // create the path for the icon
         var size = (canvas.height / 2) * 0.5;  // the size of the icon
         ctx.moveTo(canvas.width/2 + size/2, canvas.height / 2); // start at the pointy end
         ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 + size);
         ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 - size);
         ctx.closePath();
         ctx.fill();
         ctx.globalAlpha = 1; // restore alpha
    }    
    
    
    mouse = (function(){
        var mouse = {
            x : 0, y : 0, w : 0, 
            button : 0,
            over : false,
            bm : [1, 2, 4, 6, 5, 3], 
            active : false,
            bounds : null, 
            border : {top : 10, left : 10},
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,contextmenu".split(",")
        };
        var m = mouse;
        function mouseMove(e) {
            var t = e.type;
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.clientX - m.bounds.left - m.border.left; 
            m.y = e.clientY - m.bounds.top - m.border.top;
            
            if (t === "mousedown") { 
                m.button |= m.bm[e.which-1]; 
            } else if (t === "mouseup") { 
                m.button &= m.bm[e.which + 2]; 
            }else if (t === "mouseout") { 
                m.button = 0; 
                m.over = false; 
            }else if (t === "mouseover") { 
                m.over = true; 
            }
            e.preventDefault();
        }
        m.start = function (element) {
            m.element = element;
            m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
            m.active = true;
            //m.border.top = Number(element.style.borderTopWidth.replace(/[a-zA-Z]/g,""));
            //m.border.left = Number(element.style.borderLeftWidth.replace(/[a-zA-Z]/g,""));
        }
        m.remove = function () {
            if (m.element !== undefined) {
                m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
                m.active = false;
                m.element = undefined;
            }
        }
        return mouse;
    })();
    
    
    
    
    function playPauseClick(){
         if(videoContainer !== undefined && videoContainer.ready){
              if(videoContainer.video.paused){                                 
                    videoContainer.video.play();
              }else{
                    videoContainer.video.pause();
              }
         }
    }
    function videoMute(){
        muted = !muted;
    	if(muted){
             document.querySelector(".mute").textContent = "Mute";
        }else{
             document.querySelector(".mute").textContent= "Sound on";
        }
    
    
    }
    // register the event
    //canvas.addEventListener("click",playPauseClick);
    document.querySelector(".mute").addEventListener("click",videoMute)
    setTimeout(()=>{mouse.start(canvas)},100);
    body {
        font :14px  arial;
        text-align : center;
        background : #36A;
    }
    h2 {
        color : white;
    }
    canvas {
        border : 10px white solid;
        cursor : pointer;
    }
    a {
      color : #F93;
    }
    .mute {
        cursor : pointer;
        display: initial;   
    }
    <h2>Simple video FX via canvas "globalCompositeOperation"</h2>
    <p>This example show how to use the 2d context "globalCompositeOperation" property to create a variety of FX. Video may take a few moment to load.
    </p>
    <p>Play pause video with click. Move to bottom of video to see FX mix slider (Not available if filter None). Move to right to get filter selection and select the filter example. Happy filtering</p>
    <canvas id="myCanvas" width = "532" height ="300" ></canvas><br>
    <h3><div id = "playPause">Loading content.</div></h3>
    <div class="mute"></div><br>

    Update 30 Sep 2019

    Add filter "Movement" that highlight the change per frame (movement). Slider changes the persistence of the highlighted changes.

    0 讨论(0)
  • 2020-12-06 14:56

    Saturation version with transition and -webkit-transition: -webkit-filter 10s;

    This is only for Safari and Chrome. The code is for hovering.

    I guess very similar to @godzsa

    The other method I can think of is to create a div with a higher index on top of your video with a play on white saturation.

    For working Youtube video, https://jsfiddle.net/yd215t9p/

    And for image,

    div {
        -webkit-filter:saturate(0.0);
        -webkit-transition: -webkit-filter 10s; /* Safari */
        transition: -webkit-filter 10s;
    }
    div:hover {
        -webkit-filter:saturate(1.0);
    }
    <div>
    <img src="https://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png" id="myCanvas" class="transition" />
      
    </div>

    0 讨论(0)
提交回复
热议问题