<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Canvas 图片平铺马赛克特效</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
text-align: center;
}
.container {
margin-bottom: 20px;
}
canvas {
border: 1px solid #ccc;
max-width: 100%;
}
.controls {
margin: 20px 0;
}
input, button {
padding: 8px 12px;
margin: 5px;
}
.info {
margin-top: 20px;
font-size: 14px;
color: #666;
}
</style>
</head>
<body>
<h1>Canvas 图片平铺马赛克特效</h1>
<div class="controls">
<input type="file" id="imageInput" accept="image/*">
<button id="applyEffect">应用马赛克效果</button>
<div>
<label for="tileSize">马赛克块大小:</label>
<input type="range" id="tileSize" min="5" max="50" value="15">
<span id="tileSizeValue">15</span>
</div>
</div>
<div class="container">
<canvas id="originalCanvas"></canvas>
<p>原始图片</p>
</div>
<div class="container">
<canvas id="mosaicCanvas"></canvas>
<p>马赛克效果</p>
</div>
<div class="info">
<p>上传图片后,点击"应用马赛克效果"按钮生成马赛克特效</p>
<p>调整滑块可以改变马赛克块的大小</p>
</div>
<script>
// 获取DOM元素
const imageInput = document.getElementById('imageInput');
const applyEffectBtn = document.getElementById('applyEffect');
const tileSizeSlider = document.getElementById('tileSize');
const tileSizeValue = document.getElementById('tileSizeValue');
const originalCanvas = document.getElementById('originalCanvas');
const mosaicCanvas = document.getElementById('mosaicCanvas');
let originalCtx = originalCanvas.getContext('2d');
let mosaicCtx = mosaicCanvas.getContext('2d');
let originalImage = null;
// 更新滑块值显示
tileSizeSlider.addEventListener('input', function() {
tileSizeValue.textContent = this.value;
});
// 图片上传处理
imageInput.addEventListener('change', function(e) {
if (e.target.files && e.target.files[0]) {
const reader = new FileReader();
reader.onload = function(event) {
originalImage = new Image();
originalImage.onload = function() {
// 设置画布尺寸
const maxWidth = 600;
const maxHeight = 400;
let width = originalImage.width;
let height = originalImage.height;
if (width > maxWidth) {
height = (maxWidth / width) * height;
width = maxWidth;
}
if (height > maxHeight) {
width = (maxHeight / height) * width;
height = maxHeight;
}
originalCanvas.width = width;
originalCanvas.height = height;
mosaicCanvas.width = width;
mosaicCanvas.height = height;
// 绘制原始图片
originalCtx.drawImage(originalImage, 0, 0, width, height);
};
originalImage.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
});
// 应用马赛克效果
applyEffectBtn.addEventListener('click', function() {
if (!originalImage) {
alert('请先上传图片');
return;
}
const tileSize = parseInt(tileSizeSlider.value);
applyMosaicEffect(originalImage, mosaicCanvas, mosaicCtx, tileSize);
});
// 马赛克效果函数
function applyMosaicEffect(image, canvas, ctx, tileSize) {
const width = canvas.width;
const height = canvas.height;
// 先绘制原始图片
ctx.drawImage(image, 0, 0, width, height);
// 获取图像数据
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// 创建临时canvas用于处理
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.putImageData(imageData, 0, 0);
// 清空主画布
ctx.clearRect(0, 0, width, height);
// 计算水平和垂直方向的平铺数量
const cols = Math.ceil(width / tileSize);
const rows = Math.ceil(height / tileSize);
// 遍历每个马赛克块
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
// 计算当前块的坐标和尺寸
const tileX = x * tileSize;
const tileY = y * tileSize;
const tileWidth = Math.min(tileSize, width - tileX);
const tileHeight = Math.min(tileSize, height - tileY);
// 获取块中心点的颜色
const centerX = tileX + Math.floor(tileWidth / 2);
const centerY = tileY + Math.floor(tileHeight / 2);
const pixel = tempCtx.getImageData(centerX, centerY, 1, 1).data;
// 用中心点颜色填充整个块
ctx.fillStyle = `rgba(${pixel[0]}, ${pixel[1]}, ${pixel[2]}, ${pixel[3] / 255})`;
ctx.fillRect(tileX, tileY, tileWidth, tileHeight);
}
}
}
</script>
</body>
</html>