失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > React + Canvas 像素风格取色器

React + Canvas 像素风格取色器

时间:2021-10-19 07:08:31

相关推荐

React + Canvas 像素风格取色器

前言

有时候我们需要通过图片去获得具体像素的颜色。而强大的 Canvas 为我们提供了现成的接口。 这个功能其实并不难,只不过我们需要正确的理解 Canvas 并学会利用它的 API 。 如果你急于看到效果,可以直接访问

演示地

源码地址

我不会详细得写下每一个步骤,但是你可以一边参照源码,一边配合这篇教程进行阅读。

绘制图片(-)

首先,我们需要基于图片去绘制 Canvas。 操作步骤

创建一个与图片等宽高的canvas获得canvas的 context将图片绘制到 canvas

我们在React中用最小化模型展示出来 我们在 React 的 DidMount 里拿到 image 实例。当然,你也可以直接创建一个 image 对象。

import React, { PureComponent } from 'react'import PropTypes from 'prop-types'export class TestPicker extends PureComponent {static propTypes = {src: PropTypes.string.isRequired,width: PropTypes.number.isRequired,height: PropTypes.number.isRequired,}static defaultProps = {width: 1300,height: 769,src: '/sec3.png'}// 在初始化阶段注册 ref 回调函数去获得 DOM 的实例constructor (props) {super(props)this.imageCanvasRef = ref => this.imageCanvas = refthis.image = new Image()this.image.src = props.src}// 请注意,一定要在图片加载完全之后才开始绘制 CanvascomponentDidMount () {this.image.onload = () => this.renderImageCanvas()}renderImageCanvas = () => {const { width, height } = this.propsthis.imageCtx = this.imageCanvas.getContext('2d')this.imageCtx.drawImage(this.image, 0, 0, width, height)}render () {const { width, height, src } = this.propsreturn <div><canvaswidth={width}height={height}style={{ width, height }}ref={this.imageCanvasRef}></canvas></div>}}复制代码

只要将它挂载到相应的节点下,你可以看到有一个和图片一样大小的 Canvas 并且绘制了图片。

但是我们需要注意图片应该是同源的,如果不是同源,Canvas 绘制图片时会报错。具体如何设置可以参考 使用图像 Using images

Canvas 画布与实际宽高

本质上canvas的宽高设定包含两个层面,一个是画布的大小,另外一个则是Canvas 在文档对象所占据的宽高。由于Canvas内部的绘制区域画布大小默认是(width: 300px, height: 150px) ,比如当你 通过 css 设定 (width: 3000px;height: 1500px)的时候,内部的绘制区域大小会被强制与整体宽高保持统一,即内部的绘制区域会被放大十倍。像素级别的放大会导致实际的渲染效果变得更加模糊。因为要注意有时候你的绘制区域出现缩放现象。

实现放大镜位移(二)

我们需要让放大镜的位置在鼠标正中心,并且跟随鼠标移动。 实现方式也比较简单,通过 onmousemove 时获得当前 clientX 和 clientY, 并且减去当前 Canvas 视窗所占据的 left 和 top 即可。

首先,我们在构造函数加了初始化的 state用来表示当前鼠标位移。 在鼠标移动时触发 onmousemove 时去修改 state,通过改变 state 触发 re-render,修改 left 和 top。

constructor() {this.glassCanvasRef = ref => this.glassCanvas = refthis.state = {left: 0,top: 0}}handleMouseMove = (e) => {// 计算当前鼠标相对 canvas 中的位置this.calculateCenterPoint({ clientX: e.clientX, clientY: e.clientY })const { centerX, centerY } = this.centerPointthis.setState({ left: centerX, top: centerY })}calculateCenterPoint = ({ clientX, clientY }) => {const { left, top } = this.imageCanvas.getBoundingClientRect()this.centerPoint = {centerX: Math.floor(clientX - left),centerY: Math.floor(clientY - top)}}render () {const { width, height, src } = this.propsconst { left, top } = this.statereturn <div style={{ position: 'relative' }}><canvaswidth={width}height={height}style={{ width, height }}onMouseMove={this.handleMouseMove}ref={this.imageCanvasRef}></canvas><canvas ref={this.glassCanvasRef}className="glass" style={{ left: left - glassWidth/2, top: top - glassHeight/2, width: glassWidth, height: glassHeight }}></canvas></div>}const glassWidth = 100const glassHeight = 100复制代码

绘制放大区域内容(三)

好了,其实我们完成快一半了。接下来就是把放大区域部分的图像放置到我们的放大镜中。 在绘制之前,我们先清除一次画布

handleMouseMove = (e) => {this.glassCtx.clearRect(0, 0, glassWidth, glassWidth)}复制代码

我们希望将放大镜部分的元素放大, 我默认取了10倍放大效果。这种情况呈现的样式比较友好,如果你还需要对元素再放大,你只需要修改 scale 即可。

const INIT_NUMBER = 10const finallyScale = INIT_NUMBER * (scale < 1 ? 1 : scale)复制代码

接下来我们使用 canvas 提供的 drawImage 的复杂版本进行截取部分图像。 CanvasRenderingContext2D.drawImage() 根据 MDN 中的演示图片,我们知道

sx 和 sy 是原图到我们需要绘制的放大镜放大的左侧以及顶部距离sWidth 和 sHeight 是我们要选择放大的部分dx 和 dy 是当前绘制内容在放大镜中的偏移量dWidth 和 dHeight 即为放大镜大小

drawImageSmoothingEnable(this.glassCtx, false)this.glassCtx.drawImage(this.image,Math.floor(centerX - (glassWidth / 2) / finallyScale), Math.floor(centerY - (glassHeight / 2) / finallyScale),Math.floor(glassWidth / finallyScale), Math.floor(glassHeight / finallyScale),-INIT_NUMBER, -INIT_NUMBER,glassWidth, glassHeight)const drawImageSmoothingEnable = (context, enable) => {context.mozImageSmoothingEnabled = enablecontext.webkitImageSmoothingEnabled = enablecontext.msImageSmoothingEnabled = enablecontext.imageSmoothingEnabled = enable}复制代码

我们需要计算放大后的因素。此外,由于在计算鼠标当前位置时,可能会有1像素偏差,但被放大了10倍。所以我增加了10个像素的偏移量。你可以根据实际情况来决定偏移。

通过drawImageSmoothingEnable函数让我们最终绘制的图像产生锯齿效果。这样就会有真实的像素风格了。

绘制网格线(四)

关于绘制网格线,依然可以参考 MDN 上的文档。

const GRID_COLOR = 'lightgray'drawGrid(this.glassCtx, GRID_COLOR, INIT_NUMBER, INIT_NUMBER)const drawGrid = (context, color, stepx, stepy) => {context.strokeStyle = colorcontext.lineWidth = 0.5for (let i = stepx + 0.5; i < context.canvas.width; i += stepx) {context.beginPath()context.moveTo(i, 0)context.lineTo(i, context.canvas.height)context.stroke()}for (let i = stepy + 0.5; i < context.canvas.height; i += stepy) {context.beginPath()context.moveTo(0, i)context.lineTo(context.canvas.width, i)context.stroke()}}复制代码

实现取色(五)

我们通过 getImageData 获得具体的像素点的数据,不过还需要转换一下才能变成可用的数据。

getColor = () => {const { centerX, centerY } = this.centerPointconst { data } = this.imageCtx.getImageData(centerX, centerY, 1, 1)const color = transform2rgba(data)}const transform2rgba = (arr) => {arr[3] = parseFloat(arr[3] / 255)return `rgba(${arr.join(', ')})`}复制代码

结语

原本我实现了一个在 Canvas 里又绘制一个放大镜去放大图像。但这样的问题就是放大镜只能在 Canvas 内部活动,添加样式之类的需要通过 Canvas 绘制,失去了 CSS 的能力。 现在这种分离的方式可以支持自定义 CSS 样式,而且减少了 Canvas 中继续绘制 Canvas 放大倍数的复杂度。

当然,这只是一个 启发性的demo,依然有许多粗糙的地方。希望能对你有用~

如果觉得《React + Canvas 像素风格取色器》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。