我有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);
进行黑白滤镜很容易。
// 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
或者您也可以通过自身渲染视频以获取其他效果,演示仅通过使用上面的代码和一些额外的图层即可显示黑白滤镜,以及其他一些滤镜。
有关显示视频的更多信息,请参见Display video inside canvas
该演示演示了在我使用时如何进行黑白和其他FX处理。
请参阅视频标题以了解出处。从左到右的FX分别为“变亮”,“黑白”,“棕褐色”,“饱和”和“负”。
该演示具有以下FX打火机,变暗,黑/白,负片,饱和,棕褐色,黑白负片等。
与该问题相关的代码位于顶部并已标记。其余的是UI加载等。
每个FX是一个函数,它将调用addMix
或addOverlay
以应用过滤器,如上面的代码片段所示。 addMix
功能稍有不同,它在视频上绘制视频以获取FX而不是填充。
说明在演示中。
[请注意,并非所有浏览器都支持所有复合模式(为什么?谁知道!:()也没有办法百分百确定浏览器是否支持某种模式。安全的选择是Firefox,Chrome和Edge对于所有其他浏览器来说都是好运。.
//==========================================================================
// 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>
添加过滤器“运动”,突出显示每帧的变化(运动)。滑块更改突出显示的更改的持久性。
最简单的方法是在画布上设置灰度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
具有过渡和-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>