将画布视频从灰度渐变为彩色

时间:2016-08-18 09:18:27

标签: javascript html5 canvas

我有2个元素 - 视频和画布。在视频播放事件中,函数在画布上仅绘制灰度相同的视频。然后我有一个按钮,它应该将画布视频从灰度渐变回颜色。到目前为止,我已经设法在按钮点击时恢复颜色,但我需要它来淡化 - 从灰度到颜色,而不仅仅是立即显示颜色。

关于如何实现这一目标的任何想法?或者......它甚至可能吗?

以下是代码:

function grey() {
    if (!stop) {
    bgContext.drawImage(video, 0, 0, w, h);
    var pixelData = bgContext.getImageData(0, 0, w, h);
    for (var i = 0; i < pixelData.data.length; i += 4 ) {
        var r = pixelData.data[i];
        var g = pixelData.data[i+1];
        var b = pixelData.data[i+2];
        var averageColour = (r + g + b) / 3;
        pixelData.data[i] = averageColour;
        pixelData.data[i+1] = averageColour;
        pixelData.data[i+2] = averageColour;
    }
    context.putImageData(pixelData, 0, 0);
    }
}

function color() {
    bgContext.drawImage(video, 0, 0, w, h);
    var pixelData = bgContext.getImageData(0, 0, w, h);
    for (var i = 0; i < pixelData.data.length; i += 4 ) {
        var r = pixelData.data[i];
        var g = pixelData.data[i+1];
        var b = pixelData.data[i+2];
        pixelData.data[i] = r;
        pixelData.data[i+1] = g;
        pixelData.data[i+2] = b;
    }
    context.putImageData(pixelData, 0, 0);
}

video.addEventListener('play', function() {
    setInterval("grey()", 0);
}, false);

button.addEventListener('click', function() {
    stop = true;
    setInterval("color()", 0);
}, false);

3 个答案:

答案 0 :(得分:5)

用于实时视频/动画的画布滤镜。

黑&amp;白色滤镜

要做黑白滤镜很容易。

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

或者您可以将视频渲染到自身以获得其他FX,该演示通过使用上面的代码和一些额外的图层显示黑白滤镜等等。

更多信息

有关显示视频的详情,请参阅Display video inside canvas

演示

该演示展示了如何在我使用时进行黑白和其他一些FX。

如何使用。

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

查看视频标题以了解归因。从左到右依次为FX&#34; Lighten&#34;,&#34; Black&amp; white&#34;,&#34; Sepia&#34;,&#34; Saturate&#34;,&#34; Negative&#34;。

该演示具有以下FX点亮,变暗,黑/白,负,饱和,棕褐色,B&amp; W负片等等。

Javascript

与问题相关的代码全部位于顶部并标记。其余的是UI加载等。

每个FX都是一个函数,可以调用addMixaddOverlay来应用过滤器,如上面的代码段所示。 addMix功能略有不同,因为它会在视频上绘制视频以获取FX而不是填充。

说明在演示中。

请注意并非所有浏览器都支持所有的复合模式(为什么?谁知道!! :()如果浏览器支持某种模式,也没有办法100%确定。安全的赌注是Firefox,Chrome,和Edge对于所有其他浏览器运气好..

&#13;
&#13;
//==========================================================================
// All the mix function are in this section
var FXMix = 1;
var addOverlay = function(type){
    if(FXMix > 0){
        ctx.globalCompositeOperation = type; 
        ctx.globalAlpha = FXMix;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.globalAlpha = 1;
        ctx.globalCompositeOperation = "source-over"; 
    }
}
var addMix = function(type,video){
    if(FXMix > 0){
        ctx.globalCompositeOperation = type; 
        ctx.globalAlpha = FXMix;
        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("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");
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);
&#13;
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;   
}
&#13;
<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>
&#13;
&#13;
&#13;

答案 1 :(得分:2)

最简单的方法是在画布上设置灰度css过滤器。

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>

所以我们在这里做的是为我们的画布添加一个transition类,以便它可以为更改设置动画。然后,当我们向它添加一个grayscale类时,这将改变css过滤器以及过渡,以便淡入。当我们想要再次使它变得丰富多彩时,我们删除了greyscale类。

我用图像制作了示例,但它可以处理所有内容。

这样你就可以在所有参数上进行1s过渡(这里是灰度)。最好使用它,因为你不必计算灰度中的每个像素,它更清晰。

请注意,您可以使用jQuery addClass removeClass来实现更简单,更清晰的灵魂。

另请注意,您应该使用权重对r,g,b求平均值:  https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale

答案 2 :(得分:0)

带转换和-webkit-transition的饱和度版本:-webkit-filter 10s;

这仅适用于Safari和Chrome。 代码用于悬停。

我猜与@godzsa非常相似

我能想到的另一种方法是在视频上创建一个具有更高索引的div,并播放白色饱和度。

对于Youtube视频工作, https://jsfiddle.net/yd215t9p/

对于图像,

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>