一、image-rendering 介绍

CSS 中有一个有趣的特性叫 image-rendering,它可以通过算法来更好地显示被缩放的图片。

假设我们有一张尺寸较小的二维码截图(下方左),将其放大 10 倍后图像会被虚化(下方右):

这时给放大的图片加上 image-rendering: pixelated 的特性,CSS 会通过算法将其像素化展示,使其图像轮廓具有更锐利的边缘:

该特性非常适合应用在色彩单一、轮廓分明、需要被放大的图片上,可以营造出一种伪矢量的既视感(减少放大后的失真)。

对于色彩丰富、细节较多的照片,image-rendering: pixelated 使用后会营造出一种马赛克的外观:

这离本文标题所希望实现的马赛克效果还有段距离 —— 目前图片需要被放大后才能显示出效果,而我们希望能在保有原图尺寸的基础上,给图片覆盖等尺寸马赛克。

然而 image-rendering 特性对尺寸未发生缩放的元素是不会生效的。

二、踩坑等尺寸马赛克的实现

等尺寸马赛克的原理相当于先把一张照片模糊化,然后再经过锐化算法处理得到各种小方格。

image-rendering: pixelated 帮我们实现了“锐化”的步骤,我们得想想怎么实现“模糊”。

首先使用滤镜的模糊方案是行不通的,因为 image-rendering 和图像缩放系数强相关,所以应当思考可以怎样利用图片的缩放能力。

这里得说一句,WEB 上的图片像极了 Photoshop 里的智能对象 —— 你可以任意修改它的尺寸(例如放大很多倍让其变模糊),但最后再把图片改回原本的大小时,图片会变回原来的样子(没有任何失真)。

如何保留图片放大后的“模糊”信息,是优先需要解决的问题。

聪明的小伙伴已经想到了可以尝试使用 canvas 来处理,毕竟 canvas 可以轻松获取、绘制图像,且绘制出来的图像信息是纯数据的,而非图形对象(Image),故经其放大绘制的图片数据再进行缩小绘制(到原尺寸)会失真(这正好是我们所希望发生的)。

但这里也存在一些坑:

  • 外部图像通过 image-rendering: pixelated 算法处理后显示的信息,canvas 是无法拿到的,因为那是显示层的东西。canvas 拿到的依旧是未经锐化的、模糊的原生图像内容;
  • canvas 本身如果没有缩放的话,给 canvas 添加 image-rendering: pixelated 没有任何意义。

这意味着你无法把图片在 canvas 外面放大锐化,然后再写入 canvas 去缩小绘制(并不断迭代处理)来得到锐化后的原尺寸图片。

三、有趣的 canvas 拉伸

在解决上述问题时,我们先来看看 canvas 一个有趣的特性。

如果我们在 canvas 标签里定义了宽高:

<canvas width="100" height="50" ></canvas>

同时又给 canvas 在样式中定义了另一个宽高:

canvas {
width: 200px;
height: 200px;
}

那么 canvas 会以哪个尺寸来显示呢?

答案是以 CSS 的尺寸来显示,但画布的内容尺寸会以画布标签内定义的宽高为准。这意味着虽然我们看到的是 200px * 200px 的画布,但它的内容实际被拉伸了(宽被拉伸了 2 倍,高被拉伸了 4 倍)。



注:左边为画布,右边为原图

这也是 canvas 作为可替换元素的一个特性 —— CSS 无法修改其内容。试想一下,如果 CSS 可以动态地修改 canvas 内容的尺寸,意味着 canvas 的内容会被裁剪掉一部分,或者多出来一部分空白区域,这显然是不可取的。所以 canvas 在保留内容完整的前提下,整体伸缩到样式规定尺寸,是合理的浏览器行为。

利用 canvas 的这个特性,我们可以这样来实现等尺寸马赛克:

  • 创建一个画布,通过样式规定好其宽高,并设置 image-rendering: pixelated 特性;
  • 计算图片最佳展示尺寸(以类似 background-size: contain 的形式展示);
  • 将画布的宽高(非样式)设置为样式宽高的 1/N
  • 绘制图像,绘制的图像宽高为最佳展示尺寸的 1/N

如此一来,我们实际绘制了一个尺寸仅为最佳尺寸 1/N 的图像,再通过 canvasN 倍放大又变回了视觉上的最佳尺寸。图像因为走的 canvas 绘制,所以放大回最佳尺寸后会保持模糊,从而满足了 image-rendering 的匹配需求。

注:这里提到的“最佳尺寸”,指的是步骤 2 里“确保完整展示图像”所对应的最佳尺寸,而非图片原生尺寸。

四、代码实现

我们按照上方步骤来书写对应代码,当然我们希望灵活一些,例如上述的 N 可以由用户自定义。另外本章的代码可以在 Github 上获取

HTML 部分

主要为选择图片的 <input> 控件、画布、方便画布获取图像的 <img>、供用户自定义缩放倍数的文本框、执行按钮:

  <input id="file" type="file" accept="image/*" />
<canvas id="canvas"></canvas>
<img id="img-raw" />
<label for="compress-times">压缩倍数:</label>
<input id="compress-times" type="number" value="12">
<button>马赛克化</button>

CSS 部分

我们需要通过样式规定好画布的外观尺寸,并配置 image-rendering: pixelated 特性。另外 <img> 标签只是一个传递用户所选图片到画布的中介,可以直接隐藏:

    canvas {
display: block;
border: gray solid 1px;
width: 600px;
height: 600px;
image-rendering: pixelated;
} img {
display: none;
}

JS 部分

    let imgBlobUrl;
const file = document.getElementById('file');
const img = document.getElementById('img-raw');
const compressTimes = document.getElementById('compress-times');
const defaultCompressTimes = compressTimes.value | 0;
const canvas = document.getElementById('canvas');
const button = document.querySelector('button'); const boundingRect = canvas.getBoundingClientRect();
const ctx = canvas.getContext('2d');
const canvas_w = boundingRect.width;
const canvas_h = boundingRect.height; // 以 background-size: contain 形式设置图片尺寸
function matchImgSizeToCanvas(imgElem = img) {
let w = imgElem.width;
let h = imgElem.height;
if (w > canvas_w || h > canvas_h) {
let radio = Math.max(h / canvas_h, w / canvas_w);
radio = Number(radio.toFixed(2));
imgElem.width = parseInt(w / radio);
imgElem.height = parseInt(h / radio);
}
} // 绘制 1/N 大小的图像,画布宽高属性设为样式宽高的 1/N,从而实现画布内容的 N 倍放大
function run() {
let ct = parseInt(compressTimes.value) || defaultCompressTimes;
canvas.width = parseInt(canvas_w / ct);
canvas.height = parseInt(canvas_h / ct);
ctx.drawImage(img, 0, 0, parseInt(img.width / ct), parseInt(img.height / ct));
} function cleanCanvas() {
ctx.clearRect(0, 0, canvas_w, canvas_h);
} function reset() {
img.removeAttribute('width');
img.removeAttribute('height');
cleanCanvas();
matchImgSizeToCanvas(img);
run();
} file.addEventListener('change', function (e) {
window.URL.revokeObjectURL(imgBlobUrl);
const picFile = this.files[0];
imgBlobUrl = window.URL.createObjectURL(picFile);
img.onload = function init() {
reset();
}
img.src = imgBlobUrl;
}, false); button.addEventListener('click', reset, false);

执行效果:

五、马赛克插件封装

通过上方示例我们学习了如何利用 canvas 特性来设计等尺寸的马赛克效果,现在我们尝试把该功能封装为一个简易插件,可以让页面上的图片列表一键马赛克化。

插件的实现方案也很简单 —— 用户点击按钮时,往图片容器上插入一个和容器等尺寸的画布(尺寸通过样式设置),再绘制覆盖画布的图像,并缩小画布的宽高属性来放大画布内容:

插件脚本

/** @file mosaic.js **/

class Mosaic {
constructor(url, container, options = {}) {
if (typeof container === 'string') {
container = document.querySelector(container);
} if (!url || !container?.style) {
console.error('参数不正确');
} this.url = url;
this.options = options;
this.container = container; this.init();
}
init() {
const img = new Image();
const canvas = document.createElement('canvas');
canvas.style.position = 'absolute';
canvas.style.zIndex = 999;
canvas.style.imageRendering = 'pixelated';
this.img = img;
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
const containerBoundingRect = this.container.getBoundingClientRect();
const container_w = containerBoundingRect.width;
const container_h = containerBoundingRect.height; // 通过样式初始化画布尺寸为容器尺寸
canvas.style.width = container_w + 'px';
canvas.style.height = container_h + 'px'; img.onload = () => {
this.run(container_w, container_h);
} img.src = this.url;
}
run(w, h) {
// 缩小倍数,可以由参数传入,默认为 12
const compressTimes = parseInt(this.options.compressTimes) || 12;
let compress_w = parseInt(w / compressTimes);
let compress_h = parseInt(h / compressTimes);
// 修改画布尺寸属性为 1/缩小倍数
this.canvas.width = compress_w;
this.canvas.height = compress_h;
// 绘制图片覆盖缩小后的画布
this.ctx.drawImage(this.img, 0, 0, compress_w, compress_h);
this.container.prepend(this.canvas);
this.img = null;
}
remove() {
this.container.removeChild(this.canvas);
this.canvas = null;
}
} export default Mosaic;

插件使用页

/** @file plugin-demo.html **/
<head>
<style>
ul {
list-style: none;
margin: 0;
padding: 0;
} li {
float: left;
line-height: 0;
margin: 0 20px 20px 0;
} li>img {
max-height: 180px;
} div {
display: block;
clear: both;
}
</style>
</head> <body>
<ul>
<li><img src="./assert/0.png" /></li>
<li><img src="./assert/1.png" /></li>
<li><img src="./assert/2.png" /></li>
<li><img src="./assert/3.png" /></li>
</ul>
<div>
<button id="generate">铺上马赛克</button>
<button id="remove">移除马赛克</button>
</div> <script type="module">
import Mosaic from './mosaic.js'; let liElems = document.querySelectorAll('li');
let mosaicList = []; document.querySelector('#generate').onclick = () => {
remove();
for (let i = 0; i < liElems.length; i++) {
let liElem = liElems[i];
let url = liElem.querySelector('img').src;
let mosaic = new Mosaic(url, liElem);
mosaicList.push(mosaic);
}
} function remove() {
mosaicList.forEach((mosaic) => {
mosaic.remove();
});
mosaicList.length = 0;
} document.querySelector('#remove').onclick = remove; </script>
</body>

执行效果:

以上便是本文全部内容,相关代码可以在 Github 上获取

希望能令你有所收获,共勉~

巧用 CSS 把图片马赛克化的更多相关文章

  1. three.js 将图片马赛克化

    这篇郭先生来说说BufferGeometry,类型化数组和粒子系统的使用,并且让图片有马赛克效果(同理可以让不清晰的图片清晰化),如图所示.在线案例点击博客原文 1. 解析图片 解析图片和上一篇一样 ...

  2. java处理图片--图片的缩放,旋转和马赛克化

    这是我自己结合网上的一些资料封装的java图片处理类,支持图片的缩放,旋转,马赛克化.(转载请注明出处:http://blog.csdn.net/u012116457) 不多说,上代码: packag ...

  3. selenium模块无头化浏览器 设置不加载页面css、图片、js

    下面代码基于火狐浏览器,谷歌浏览器代码类似 from selenium import webdriver from selenium.webdriver.firefox.options import ...

  4. 基于HTML5 Canvas实现的图片马赛克模糊特效

    效果请点击下面网址: http://hovertree.com/texiao/html5/1.htm 一.开门见山受美国肖像画家Chuck Close的启发,此脚本通过使用HTML5 canvas元素 ...

  5. HTML5_canvas_像素操作_图片马赛克_图片反相

    canvas 像素操作 像素,即像素点,一个像素只有一个颜色 100*100 的 px 的屏幕区域有 100*100*4 个像素点,即 width*height*4 rgba(0, 0, 0, 1); ...

  6. 巧用 CSS 构建渐变彩色二维码

    今日,群里有个很有意思的问题,问我如何实现一个彩色的,带渐变的二维码,像是这样: 很有意思的问题,我们在百度谷歌,搜索 qrcode,能搜到非常多在线制作二维码的工具,它们其中一些也会带有制作渐变二维 ...

  7. Django调用JS、CSS、图片等静态文件

    zz 在下面的例子中,我们将media作为静态(CSS\JS\图片文件)文件的目录 方法一. 1.首先在settings.py文件中自定义参数 STATIC_PATH=’./media’ .(意为当前 ...

  8. Atitit 图像处理 灰度图片 灰度化的原理与实现

    Atitit 图像处理 灰度图片 灰度化的原理与实现 24位彩色图与8位灰度图 首先要先介绍一下24位彩色图像,在一个24位彩色图像中,每个像素由三个字节表示,通常表示为RGB.通常,许多24位彩色图 ...

  9. Bootstrap css背景图片的设置

    一. 网页中添加图片的方式有两种 一种是:通过<img>标签直接插入到html中 另一种是:通过css背景属性添加 居中方法:水平居中的text-align:center 和 margin ...

随机推荐

  1. JVM组成详解

    一.JVM 整体组成 JVM 整体组成可分为以下四个部分: 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库 ...

  2. Winfrom统一单例窗口

    //调用方式 var frm = new MyForm().Instance(); public static class ExFrm { static Dictionary<string, F ...

  3. 【C++】类-多态

    类-多态 目录 类-多态 1. 基本概念 2. 运算符重载 2.1 重载为类的成员函数 2.2 重载为非成员函数 3. 虚函数 4. 抽象类 5. override与final 1. 基本概念 多态性 ...

  4. 乡亲们,我们创建了 Dapr 中文交流频道

    我们创建了 Dapr 中文交流 QQ 频道,欢迎大家加入!加入方式在文章最后一节. 为什么要创建频道? 解决什么问题 专业性,"你可以在我们群里面钓鱼,因为都是水" 你肯定加过非常 ...

  5. vue文档1-VSCode介绍

    开发工具VSCode: Vue的开发工具用的是VSCode(Visual Studio Code),这款开发工具是微软官方出品,开源,免费,并且功能相当强大,使用者很多,插件相当丰富,是Vue开发的不 ...

  6. Python 安装MySQL 错误处理

    正常情况下如果使用python 连接数据库需要安装 python-MySQL 类库 #pip install python-MySQL 等待安装完成即可 使用时 import MySQLdb ==== ...

  7. 学习JAVAWEB第二天

    DML:C:添加数据INSERT INTO 表名(列名1,2...) Values(值1,2,3..)查询表select * from表名D:删除数据delete from 表名 where 条件(i ...

  8. 010 Linux 文本统计与去重 (wc 和 uniq)

    wc 命令一般是作为组合命令的一员与其他命令一同起到统计的作用.而一般情况下使用wc -l 命令较多. uniq 可检查文本文件中重复出现的行,一般与 sort 命令结合使用.一起组合搭配使用完成统计 ...

  9. vi TOhtml:复制保持格式和高亮

    1. 文本编辑:在vim中编辑好,复制到opera mail中就会格式错乱,比如:行前空格.缩进消失:2. 代码复制到其他地方,无法显示彩色高亮:找到了一个变通方案:使用TOhtml把vim内容转换为 ...

  10. ittun.com的使用方法

    [如果这篇文章对你有所作用,请加关注哦!] 步骤一: 进入官网http://ittun.com/ Windows 64位下载http://ittun.com/upload/17.2/ittun_win ...