我想在一段时间之后淡出电视噪音,然后转换更顺畅。是否有可能消除掉噪音。 我发布的代码已经出现在stakcoverflow上 https://stackoverflow.com/a/22085531/7935298
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
// closer to analouge appearance
canvas.width = canvas.height = 256;
function resize() {
canvas.style.width = window.innerWidth + 'px';
canvas.style.height = window.innerHeight + 'px';
}
resize();
window.onresize = resize;
function noise(ctx) {
var w = ctx.canvas.width,
h = ctx.canvas.height,
idata = ctx.createImageData(w, h),
buffer32 = new Uint32Array(idata.data.buffer),
len = buffer32.length,
run = 0,
color = 10,
m = Math.random() * 6 + 4,
band = Math.random() * 256 * 256,
p = 0,
i = 0;
for (; i < len;) {
if (run < 0) {
run = m * Math.random();
p = Math.pow(Math.random(), 0.1);
if (i > band && i < band + 48 * 256) {
p = Math.random();
}
color = (255 * p) << 25;
}
run -= 1;
buffer32[i++] = color;
}
ctx.putImageData(idata, 0, 0);
}
var frame = 0;
// added toggle to get 30 FPS instead of 60 FPS
(function loop() {
frame += 1;
if (frame % 3) {
requestAnimationFrame(loop);
return;
}
noise(ctx);
requestAnimationFrame(loop);
})();
<canvas id="canvas"></canvas>
答案 0 :(得分:0)
是的,可以消除一些噪音。
使用ctx.globalAlpha混合噪音。由于噪声影响帧内容,您应该将噪声作为双通道进行。使用ctx.globalCompositeOperation="multiply"
后,会为现有像素添加噪点,并使用ctx.globalCompositeOperation="source-over"
对噪点进行去饱和处理。对于良好的高速噪声FX,我预先用一点色度噪声计算噪声,然后从噪声阵列中截取随机部分以填充像素数据阵列。由于旧的噪声通常沿x方向稍微拉出(并减少CPU负载),因此我将噪声创建为画布分辨率的1/3,并将噪声拉伸以适应。这给噪音FX提供了更好的感觉。
以下内容会为画布添加噪点(假设视频已经渲染到画布上。第一个参数是你想要的噪音量.0没有噪音和1个完整的噪音。
// following variable are set when noise function first called.
var canvasNoise; // canvas to hold the noise
var ctxNoise; // context of above canvas
var rawPixels; // pixel image data
var rawPixel32Arr; // pixel image data as 32bit array
var randomPix; // array of precalculated noise.
// FXMix the amount of noise to add 0 no noise 1 full noise
function noise(FXMix) {
var i,d;
// if noise canvas not created create it now
if(canvasNoise === undefined){
canvasNoise = document.createElement("canvas");
canvasNoise.width = Math.floor(canvas.width / 3);
canvasNoise.height = canvas.height;
ctxNoise = canvasNoise.getContext("2d");
rawPixels = ctxNoise.createImageData(canvasNoise.width, canvasNoise.height);
rawPixel32Arr = new Uint32Array(rawPixels.data.buffer);
// create array of random noise
randomPix = new Uint32Array(canvasNoise.width * 10);
i = canvasNoise.width * 10;
d = randomPix; // alias to make code readable
while(i--){
var r = Math.floor(Math.random() * 255);
// create slight croma noise
var g = Math.min(255, Math.max(0,Math.floor(r + (Math.random()-0.5) * 32)));
var b = Math.min(255, Math.max(0,Math.floor(r + (Math.random()-0.5) * 32)));
d[i] = 0xFF000000 + (r<<16) + (g<<8) + b;
}
}
// fill the pixel data with random noise from the randomPix array
i = rawPixel32Arr.length;
d = rawPixel32Arr; // alias to make code readable
while(i){
var len = Math.floor(Math.random() * 100) + 20;
var start = Math.floor(Math.random() * randomPix.length - len);
i -= len;
i = i < 0 ? 0 : i;
d.set(randomPix.subarray(start,start + len), i);
}
// put noise onto canvas
ctxNoise.putImageData(rawPixels, 0, 0);
// now mix noise over video
ctx.globalAlpha = FXMix;
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalAlpha = 1;
}
以下演示是我在Fade from black & white to color答案中使用的现有演示的更新。如果您将鼠标悬停在视频右侧,我已将噪音FX添加到FX列表中如果您将鼠标悬停在视频的底部,则可以通过混合滑块控制噪音。
FX只是对上述代码段功能噪音的轻微修改。
更新我添加了第二层使用第三层的噪音(使用ctx.globalCompositeOperation = "lighter"
绘制噪点画布,通过添加乘法操作移除的内容来保持总亮度级别一致。请参阅噪声函数更多信息。并修复过滤器选择错误,因为FX列表太长而FX混合滑块妨碍了底部过滤器。
视频归属可以在视频的开头和结尾标题/信用中找到(由作者指定)
//==========================================================================
// start canvas animation after all the code below has been parsed and executed
setTimeout(()=>requestAnimationFrame(updateCanvas),0);
//==========================================================================
// UI variables
var showFXName = 0;
var cursor = "default";
var overUI = false;
var sliderAlpha = 1;
var listAlpha = 1;
var dragging = false;
var listWidth = null;
var playClick = false;
//==========================================================================
// Media setup
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
//==========================================================================
var FXMix = 1;
var FX = {}
var FXList = [];
var currentFX = "";
var animateFXMix = false; // if true thr FXMix is animated over time. This flag is cleared to false once a frame
//==========================================================================
// Mix noise variables. Created when filter first used.
var canvasNoise;
var ctxNoise;
var rawPixels;
var rawPixel32Arr;
//==========================================================================
// Mix function are in this section
function addOverlay(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";
}
}
function addMix(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";
}
}
function fill(style){ // draws a rectangle over the canvas using style as the colour
ctx.globalAlpha = FXMix;
ctx.fillStyle = style;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
}
function mask(direction){
ctx.globalCompositeOperation = "destination-in";
ctx.fillStyle = "black";
ctx.globalAlpha = 1;
if(direction === "Vertical"){
ctx.fillRect(0, 0, canvas.width, canvas.height * FXMix);
}else if(direction === "Horizontal"){
ctx.fillRect(0, 0, canvas.width * FXMix, canvas.height);
}
ctx.globalCompositeOperation = "destination-atop";
// assuming bacground is black. But you could use anything including a second video
// using "destination-atop" only draws on transparent pixels.
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
}
function glowBar(RGB, direction){ // RGB is array of channel values 0-255
var grad;
var col = "rgba("+RGB[0]+","+RGB[1]+","+RGB[2]+",";
if(direction === "Vertical"){
grad = ctx.createLinearGradient(0,0,0,canvas.height * 0.1);
}else if(direction === "Horizontal"){
grad = ctx.createLinearGradient(0,0,canvas.width * 0.1,0);
}
grad.addColorStop(0.0, col + "0.0)");
grad.addColorStop(0.3, col + "0.8)");
grad.addColorStop(0.4, col + "1.0)");
grad.addColorStop(0.6, col + "1.0)");
grad.addColorStop(0.7, col + "0.8)");
grad.addColorStop(1.0, col + "0.0)");
ctx.fillStyle = grad;
if(direction === "Vertical"){
ctx.setTransform(1,0,0,1,0,canvas.height * FXMix - canvas.height * 0.05);
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.1);
ctx.setTransform(1,0,0,1.1,0,canvas.height * FXMix -canvas.height * 0.05 * 1.1 );
ctx.globalCompositeOperation = "lighter";
ctx.fillRect(0, 0, canvas.width, canvas.height * 0.1);
}else if(direction === "Horizontal"){
ctx.setTransform(1,0,0,1,canvas.width * FXMix -canvas.width * 0.05, 0);
ctx.fillRect(0, 0, canvas.width * 0.1, canvas.height);
ctx.setTransform(1.1,0,0,1,canvas.width * FXMix -canvas.width * 0.05 * 1.1, 0);
ctx.globalCompositeOperation = "lighter";
ctx.fillRect(0, 0, canvas.width * 0.1, canvas.height);
}
ctx.globalCompositeOperation = "source-over";
ctx.setTransform(1,0,0,1,0,0);
}
function noise(type) {
var i,d;
if(canvasNoise === undefined){
canvasNoise = document.createElement("canvas");
canvasNoise.width = Math.floor(canvas.width / 3);
canvasNoise.height = canvas.height;
ctxNoise = canvasNoise.getContext("2d");
rawPixels = ctxNoise.createImageData(canvasNoise.width, canvasNoise.height);
rawPixel32Arr = new Uint32Array(rawPixels.data.buffer);
randomPix = new Uint32Array(canvasNoise.width * 10);
i = canvasNoise.width * 10;
d = randomPix; // alias to make code readable
while(i--){
var r = Math.floor(Math.random() * 255);
// create slight croma noise
var g = Math.min(255, Math.max(0,Math.floor(r + (Math.random()-0.5) * 32)));
var b = Math.min(255, Math.max(0,Math.floor(r + (Math.random()-0.5) * 32)));
d[i] = 0xFF000000 + (r<<16) + (g<<8) + b;
}
}
i = rawPixel32Arr.length;
d = rawPixel32Arr; // alias to make code readable
while(i){
var len = Math.floor(Math.random() * 100) + 20;
var start = Math.floor(Math.random() * randomPix.length - len);
i -= len;
i = i < 0 ? 0 : i;
d.set(randomPix.subarray(start,start + len), i);
}
ctxNoise.putImageData(rawPixels, 0, 0);
if(type === "Addative"){
ctx.globalAlpha = FXMix;
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalAlpha = 1;
}else{
ctx.globalAlpha = FXMix;
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(canvasNoise,0,0,canvas.width,canvas.height)
ctx.globalAlpha = 1;
}
}
function addFX(name, func) {
FXList.push(name);
FX[name] = func;
currentFX = name;
}
//==========================================================================
// avialable composite operations
// multiply,screen,overlay,color-dodge,color-burn,hard-light,soft-light,difference,
// exclusion,hue,saturation,color,luminosity
addFX("Noise", (vid)=>{ noise() })
addFX("Noise 2", (vid)=>{ noise("Addative") })
addFX("V scanner", (vid)=>{ animateFXMix = true; mask("Vertical"); glowBar([100,200,255], "Vertical") })
addFX("H scanner", (vid)=>{ animateFXMix = true; mask("Horizontal"); glowBar([100,255,200], "Horizontal") })
addFX("Ligher", (vid)=>{ addMix("lighter",vid) } );
addFX("Ligher+", (vid)=>{ addMix("lighter", vid); addMix("lighter",vid); addMix("lighter", vid) } );
addFX("Darken", (vid)=>{ addMix("multiply", vid) } );
addFX("Darken+", (vid)=>{ addMix("multiply", vid); addMix("multiply",vid); addMix("multiply", vid) } );
addFX("Saturate", () =>{ ctx.fillStyle = "#F00"; addOverlay("saturation") });
addFX("Negative", (vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference") } );
addFX("Sepia", (vid)=>{ fill("#F94"); addMix("luminosity", vid) } );
addFX("BlackWhite", (vid)=>{ ctx.fillStyle = "#888"; addOverlay("color") } );
addFX("B&W Negative",(vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference"); ctx.fillStyle = "#888"; addOverlay("color") } );
addFX("B&W Lighten", (vid)=>{ addMix("lighter", vid); ctx.fillStyle = "#888"; addOverlay("color") } );
addFX("None", () =>{});
// end of FX mixing
//==========================================================================
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
// add instruction
document.getElementById("playPause").textContent = "Click video to play/pause.";
document.querySelector(".mute").textContent = "Mute";
}
function updateCanvas(time){
ctx.fillStyle = "#222";
ctx.fillRect(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;
if(animateFXMix){
FXMix = Math.sin(time / 400) * 0.5 + 0.5;
FXMix = FXMix < 0 ? 0 : FXMix > 1 ? 1 : FXMix; // Clamp to 0-1 or may get flicker on some FX
animateFXMix = false;
}
}else{
drawLoadingAnim(time);
}
// all done for display
// request the next frame in 1/60th of a second
requestAnimationFrame(updateCanvas);
}
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;
}
if(dragging){
animateFXMix = false; // dont animate if slider being dragged
}
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* 1.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;
}
}
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 = "#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
}
function drawLoadingAnim(time){
ctx.strokeStyle = "#8DF"; // colour of play icon
ctx.lineCap = "round";
ctx.globalAlpha = 0.75; // partly transparent
ctx.lineWidth = 6;
ctx.beginPath(); // create the path for the icon
var size = (canvas.height / 2) * 0.5; // the size of the icon
ctx.arc(canvas.width / 2 , canvas.height / 2, size, time / 100, time / 100 + 2);
ctx.stroke();
ctx.lineWidth = 1;
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;