问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

正片叠底效果示例

创作时间:
作者:
@小白创作中心

正片叠底效果示例

引用
1
来源
1.
https://juejin.cn/post/7462269806743928841

正片叠底是一种常见的图像混合模式,能够创造出丰富的视觉效果。本文将带你从零开始,使用Canvas实现正片叠底效果,并且会分别对图片和视频进行实现。

前言

年前在知乎和抖音上刷到了一些关于“正片叠底”的讲解,感觉挺有意思的,于是趁现在有时间决定用Canvas来实现一下这个效果。

在图像和视频编辑中,“正片叠底”是一种常见的混合模式,能够创造出丰富的视觉效果。如果你用过Photoshop或其他设计工具,应该对这个功能不陌生。本文将带你从零开始,使用Canvas实现正片叠底效果,并且会分别对图片和视频进行实现。

什么是正片叠底?

简单来说,正片叠底是一种混合模式,通过将两张图片或两个视频的像素逐一叠加,计算它们的颜色值。最终的效果通常会让画面变得更暗,颜色更加浓郁。

它的核心公式是:

R=\frac{A \times B}{255}

其中:

  • A:底层像素的颜色值。
  • B:上层像素的颜色值。
  • R:输出像素的颜色值。

这个公式模拟了摄影中的光学效果,将两层画面的明暗部分巧妙地结合在一起。

抖音上有一个短视频讲解得非常清楚,链接放在文末,感兴趣的话可以看看。

正片叠底的原理

从数学角度来看,正片叠底通过对每个像素的RGB值进行相乘并归一化,减少了高亮区域的影响,因此整体效果会偏暗。

举个例子,假设有两个像素:

  • 底层像素:A=(100,150,200)
  • 上层像素:B=(50,100,150)

通过公式计算:

R=(100×50255,150×100255,200×150255)≈(20,59,118)R = \left( \frac{100 \times 50}{255}, \frac{150 \times 100}{255}, \frac{200 \times 150}{255} \right) \approx (20, 59, 118)R=(,,)≈

最终输出的像素值R是一个较暗的颜色。

正片叠底的实现

在Canvas中,我们可以通过操作像素数据来实现正片叠底效果。主要步骤如下:

  1. 使用

getContext('2d')

获取Canvas的上下文。

  1. 绘制两张图片到Canvas上。

  2. 通过

getImageData

获取两层的像素数据。

  1. 对像素数据进行遍历,根据正片叠底公式计算新像素值。

  2. 将计算后的像素数据渲染回Canvas。

接下来,我们用代码来实现!

实现图片的正片叠底效果

创建一个黑底白字的图片

首先,我们需要创建一个黑底白字的图片,作为上层的图片。


/**  
 * 创建一个带有文本内容的画布。  
 * @param {number} width - 画布的宽度。  
 * @param {number} height - 画布的高度。  
 * @returns {Object} - 包含画布和上下文的对象。  
 */  
function createTextImageCanvas(width, height) {  
  const textCanvas = document.createElement('canvas');  
  textCanvas.width = width;  
  textCanvas.height = height;  
  const textContext = textCanvas.getContext('2d');  
  textContext.fillStyle = 'black';  
  textContext.fillRect(0, 0, width, height);  
  textContext.font = '130px Arial Black';  
  textContext.fillStyle = 'white';  
  textContext.textAlign = 'center';  
  textContext.textBaseline = 'middle';  
  textContext.fillText('Hello, World', width / 2, height / 2);  
  return { canvas: textCanvas, context: textContext };  
}  
const textImageCanvas = createTextImageCanvas(1000, 600);  
document.body.appendChild(textImageCanvas.canvas);  

实现效果:

加载图片

接下来,我们使用Canvas加载一张图片,这张图片将作为底层图片。


![](https://wy-static.wenxiaobai.com/chat-rag-image/7630266892770872220)  
/**  
  * 绘制叠加后的图像到主画布。  
  * @param {string} imagePath - 底层图片路径。  
  */  
function drawBlendedImage(imagePath) {  
  const canvas = document.getElementById('canvas');  
  const context = canvas.getContext('2d');  
  canvas.width = 1000; // 设置画布宽度  
  canvas.height = 700; // 设置画布高度  
  // 创建带有文本的画布  
  const textCanvas = createTextImageCanvas(canvas.width, canvas.height);  
  const baseImage = new Image();  
  baseImage.src = imagePath;  
  // 当图片加载完成时,开始处理图像  
  baseImage.onload = () => {  
    context.drawImage(baseImage, 0, 0, canvas.width, canvas.height); // 绘制底层图片  
  };  
}  
drawBlendedImage('./img/img.jpg')  

实现效果:

正片叠底算法

现在,我们已经有了两张图片,接下来需要将文字图片和背景图片融合到一起,使文字部分镂空显示背景图片。我们需要实现一个正片叠底混合算法。

通过

ctx.getImageData

获取两张图片的像素数据,然后对每个像素点进行A×B255\frac{A \times B}{255}的计算,最后将结果通过

ctx.putImageData

渲染回Canvas。

![](https://wy-static.wenxiaobai.com/chat-rag-image/12597221828496800224)  

/**  
 * 应用正片叠底混合效果。  
 * @param {ImageData} baseImage - 底层图像的像素数据。  
 * @param {ImageData} overlayImage - 叠加图像的像素数据。  
 * @returns {ImageData} - 混合后的像素数据。  
 */  
function applyMultiplyBlend(baseImage, overlayImage) {  
  const baseData = baseImage.data;  
  const overlayData = overlayImage.data;  
  const resultImage = new ImageData(baseImage.width, baseImage.height);  
  const resultData = resultImage.data;  
  for (let i = 0; i < baseData.length; i += 4) {  
    resultData[i] = (baseData[i] * overlayData[i]) / 255; // 混合红色通道  
    resultData[i + 1] = (baseData[i + 1] * overlayData[i + 1]) / 255; // 混合绿色通道  
    resultData[i + 2] = (baseData[i + 2] * overlayData[i + 2]) / 255; // 混合蓝色通道  
    resultData[i + 3] = baseData[i + 3]; // 保留透明度  
  }  
  return resultImage;  
}  

实现效果:

完整代码

以下是两张图片进行正片叠底混合的完整代码示例:


<!DOCTYPE html>  
<head>  
  <meta charset="UTF-8">  
  <meta name="viewport" content="width=device-width, initial-scale=1.0">  
  <title>正片叠底效果示例</title>  
</head>  
<body>  
  </canvas>  
  <script>  
    /**  
      * 创建一个带有文本内容的画布。  
      * @param {number} width - 画布的宽度。  
      * @param {number} height - 画布的高度。  
      * @returns {Object} - 包含画布和上下文的对象。  
      */  
    function createTextImageCanvas(width, height) {  
      const textCanvas = document.createElement("canvas");  
      textCanvas.width = width;  
      textCanvas.height = height;  
      const textContext = textCanvas.getContext("2d");  
      textContext.fillStyle = "black";  
      textContext.fillRect(0, 0, width, height);  
      textContext.font = "130px Arial Black";  
      textContext.fillStyle = "white";  
      textContext.textAlign = "center";  
      textContext.textBaseline = "middle";  
      textContext.fillText("Hello, World", width / 2, height / 2);  
      return { canvas: textCanvas, context: textContext };  
    }  
    /**  
     * 应用正片叠底混合效果。  
     * @param {ImageData} baseImage - 底层图像的像素数据。  
     * @param {ImageData} overlayImage - 叠加图像的像素数据。  
     * @returns {ImageData} - 混合后的像素数据。  
     */  
    function applyMultiplyBlend(baseImage, overlayImage) {  
      const baseData = baseImage.data;  
      const overlayData = overlayImage.data;  
      const resultImage = new ImageData(baseImage.width, baseImage.height);  
      const resultData = resultImage.data;  
      for (let i = 0; i < baseData.length; i += 4) {  
        resultData[i] = (baseData[i] * overlayData[i]) / 255; // 混合红色通道  
        resultData[i + 1] = (baseData[i + 1] * overlayData[i + 1]) / 255; // 混合绿色通道  
        resultData[i + 2] = (baseData[i + 2] * overlayData[i + 2]) / 255; // 混合蓝色通道  
        resultData[i + 3] = baseData[i + 3]; // 保留透明度  
      }  
      return resultImage;  
    }  
    /**  
     * 绘制叠加后的图像到主画布。  
     * @param {string} imagePath - 底层图片路径。  
     */  
    function drawBlendedImage(imagePath) {  
      const canvas = document.getElementById('canvas');  
      const context = canvas.getContext('2d');  
      canvas.width = 1000; // 设置画布宽度  
      canvas.height = 700; // 设置画布高度  
      // 创建带有文本的画布  
      const textCanvas = createTextImageCanvas(canvas.width, canvas.height);  
      const baseImage = new Image();  
      baseImage.src = imagePath;  
      // 当图片加载完成时,开始处理图像  
      baseImage.onload = () => {  
        context.drawImage(baseImage, 0, 0, canvas.width, canvas.height); // 绘制底层图片  
        // 获取底层图像和叠加文本的像素数据  
        const baseImageData = context.getImageData(0, 0, canvas.width, canvas.height);  
        const overlayImageData = textCanvas.context.getImageData(0, 0, textCanvas.canvas.width, textCanvas.canvas.height);  
        // 应用正片叠底混合效果  
        const blendedImageData = applyMultiplyBlend(baseImageData, overlayImageData);  
        // 将混合后的图像数据绘制回画布  
        context.putImageData(blendedImageData, 0, 0);  
      };  
    }  
    // 调用函数绘制图像(替换为实际图片路径)  
    drawBlendedImage('./img/tk.jpg');  
  </script>  
</body>  
</html>  

实现视频的正片叠底效果

刚才我们实现了图片的正片叠底效果,接下来我们结合视频来实现一个炫酷一些的效果。

具体实现

处理视频的正片叠底与处理图片的基本思路相同,唯一的区别是,我们需要通过

requestAnimationFrame

获取视频的每一帧,然后结合上层图片进行正片叠底计算。


<!DOCTYPE html>  
<head>  
  <meta charset="UTF-8">  
  <meta name="viewport" content="width=device-width, initial-scale=1.0">  
  <title>视频与图片正片叠底效果</title>  
  <style>  
    body {  
      margin: 0;  
      padding: 0;  
      overflow: hidden;  
      display: flex;  
      justify-content: center;  
      align-items: center;  
      height: 100vh;  
    }  
    #blend-container {  
      position: relative;  
      width: 100%;  
      height: 100%;  
    }  
    video,  
    canvas {  
      position: absolute;  
      top: 0;  
      left: 0;  
    }  
    video {  
      visibility: hidden;  
    }  
  </style>  
</head>  
<body>  
  <div id="blend-container">  
    <video id="source-video" autoplay loop muted playsinline></video>  
    <canvas id="blend-canvas"></canvas>  
  </div>  
  <script>  
    const videoElement = document.getElementById("source-video");  
    const canvasElement = document.getElementById("blend-canvas");  
    const canvasContext = canvasElement.getContext("2d");  
    /**  
     * 创建一个带有文本内容的画布。  
     * @param {number} width - 画布的宽度。  
     * @param {number} height - 画布的高度。  
     * @returns {Object} - 包含画布和上下文的对象。  
     */  
    function createTextImageCanvas(width, height) {  
      const textCanvas = document.createElement("canvas");  
      textCanvas.width = width;  
      textCanvas.height = height;  
      const textContext = textCanvas.getContext("2d");  
      textContext.fillStyle = "black";  
      textContext.fillRect(0, 0, width, height);  
      textContext.font = "150px Arial Black";  
      textContext.fillStyle = "white";  
      textContext.textAlign = "center";  
      textContext.textBaseline = "middle";  
      textContext.fillText("Hello, World", width / 2, height / 2);  
      return { canvas: textCanvas, context: textContext };  
    }  
    /**  
     * 对两个图像帧应用正片叠底混合模式。  
     * @param {ImageData} videoFrame - 视频帧的图像数据。  
     * @param {ImageData} imageFrame - 图像帧的图像数据。  
     * @returns {ImageData} - 混合后的图像数据。  
     */  
    function applyMultiplyBlend(videoFrame, imageFrame) {  
      const videoData = videoFrame.data;  
      const imageData = imageFrame.data;  
      const blendedFrame = new ImageData(videoFrame.width, videoFrame.height);  
      for (let i = 0; i < videoData.length; i += 4) {  
        blendedFrame.data[i] = (videoData[i] * imageData[i]) / 255; // 红色通道  
        blendedFrame.data[i + 1] = (videoData[i + 1] * imageData[i + 1]) / 255; // 绿色通道  
        blendedFrame.data[i + 2] = (videoData[i + 2] * imageData[i + 2]) / 255; // 蓝色通道  
        blendedFrame.data[i + 3] = videoData[i + 3]; // 透明通道  
      }  
      return blendedFrame;  
    }  
    /**  
     * 在画布上绘制混合效果。  
     */  
    function drawBlendEffect() {  
      if (videoElement.readyState >= 2) {  
        // 绘制当前视频帧  
        canvasContext.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);  
        const videoFrame = canvasContext.getImageData(0, 0, canvasElement.width, canvasElement.height);  
        // 创建并绘制文本图像帧  
        const textImage = createTextImageCanvas(canvasElement.width, canvasElement.height);  
        const imageFrame = textImage.context.getImageData(0, 0, textImage.canvas.width, textImage.canvas.height);  
        // 应用正片叠底混合模式并渲染  
        const blendedFrame = applyMultiplyBlend(videoFrame, imageFrame);  
        canvasContext.putImageData(blendedFrame, 0, 0);  
      }  
      requestAnimationFrame(drawBlendEffect);  
    }  
    // 初始化视频和画布设置  
    videoElement.src = "./img/video.mp4";  
    videoElement.addEventListener("loadeddata", () => {  
      canvasElement.width = videoElement.videoWidth;  
      canvasElement.height = videoElement.videoHeight;  
    });  
    videoElement.addEventListener("play", drawBlendEffect);  
  </script>  
</body>  
</html>  

实现效果

结语

通过本文,我们了解了正片叠底的原理,并使用Canvas实现了图片和视频的正片叠底效果。这种技术不仅可以用于学习图像处理的基础知识,还可以帮助你在前端开发中实现一些酷炫的效果。

相关链接

  • 正片叠底视频:v.douyin.com/ifkWY1YF/
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号