正片叠底效果示例
正片叠底效果示例
正片叠底是一种常见的图像混合模式,能够创造出丰富的视觉效果。本文将带你从零开始,使用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中,我们可以通过操作像素数据来实现正片叠底效果。主要步骤如下:
- 使用
getContext('2d')
获取Canvas的上下文。
绘制两张图片到Canvas上。
通过
getImageData
获取两层的像素数据。
对像素数据进行遍历,根据正片叠底公式计算新像素值。
将计算后的像素数据渲染回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加载一张图片,这张图片将作为底层图片。

/**
* 绘制叠加后的图像到主画布。
* @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。

/**
* 应用正片叠底混合效果。
* @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/