前言
学习目标
- 创建Img对象
知识点
- drawImage
- 矩阵
前情回顾
之前我们创建了Object2D对象,接下来我们接着往上写Img对象。
1-Img对象的功能分析
Img对象是对canvas的drawImage() 方法的封装,若大家对此方法不了解,可以去看一下我的canvas基础课程。
除了绘图,Img还具备以下功能:
- 图片样式设置,比如投影、透明度、全局合成和裁剪。
- 边框路径的绘制。
- 提供与图片偏移相关的矩阵。
2-Img对象的代码实现
其整体代码如下:
- /src/lmm/objects/Img.ts
import { Matrix3 } from '../math/Matrix3'
import { Vector2 } from '../math/Vector2'
import { BasicStyle, BasicStyleType } from '../style/BasicStyle'
import { Object2D, Object2DType } from './Object2D'
import { crtPathByMatrix } from './ObjectUtils'
type ImgType = Object2DType & {
image?: CanvasImageSource
offset?: Vector2
size?: Vector2
view?: View | undefined
src?: string
style?: BasicStyleType
}
type View = {
x: number
y: number
width: number
height: number
}
class Img extends Object2D {
image: CanvasImageSource = new Image()
offset: Vector2 = new Vector2()
size: Vector2 = new Vector2(300, 150)
view: View | undefined
style: BasicStyle = new BasicStyle()
// 类型
readonly isImg = true
constructor(attr: ImgType = {}) {
super()
this.setOption(attr)
}
/* 属性设置 */
setOption(attr: ImgType) {
for (let [key, val] of Object.entries(attr)) {
switch (key) {
case 'src':
if (this.image instanceof Image) {
this.image.src = val as 'string'
}
break
case 'style':
this.style.setOption(val as BasicStyleType)
break
default:
this[key] = val
}
}
}
/* 世界模型矩阵*偏移矩阵 */
get moMatrix(): Matrix3 {
const {
offset: { x, y },
} = this
return this.worldMatrix.multiply(new Matrix3().makeTranslation(x, y))
}
/* 视图投影矩阵*世界模型矩阵*偏移矩阵 */
get pvmoMatrix(): Matrix3 {
const {
offset: { x, y },
} = this
return this.pvmMatrix.multiply(new Matrix3().makeTranslation(x, y))
}
/* 绘图 */
drawShape(ctx: CanvasRenderingContext2D) {
const { image, offset, size, view, style } = this
//样式
style.apply(ctx)
// 绘制图像
if (view) {
ctx.drawImage(
image,
view.x,
view.y,
view.width,
view.height,
offset.x,
offset.y,
size.x,
size.y
)
} else {
ctx.drawImage(image, offset.x, offset.y, size.x, size.y)
}
}
/* 绘制图像边界 */
crtPath(ctx: CanvasRenderingContext2D, matrix = this.pvmoMatrix) {
const {
size: { x: imgW, y: imgH },
} = this
crtPathByMatrix(ctx, [0, 0, imgW, 0, imgW, imgH, 0, imgH], matrix)
}
}
export { Img }
解释一下上面的代码。
type类型
- ImgType是Img构造参数的类型,它基本上是与Img对象的属性相对应的。
type ImgType = Object2DType & {
image?: CanvasImageSource
offset?: Vector2
size?: Vector2
view?: View | undefined
src?: string
style?: BasicStyleType
}
- View 是drawImg() 时的视图参数。
type View = {
x: number
y: number
width: number
height: number
}
Img对象的属性
- image、offset、size、view对应drawImage() 方法的参数。
/* 绘图 */
drawShape(ctx: CanvasRenderingContext2D) {
const { image, offset, size, view, style } = this
//样式
style.apply(ctx)
// 绘制图像
if (view) {
ctx.drawImage(
image,
view.x,
view.y,
view.width,
view.height,
offset.x,
offset.y,
size.x,
size.y
)
} else {
ctx.drawImage(image, offset.x, offset.y, size.x, size.y)
}
}
- style:图片样式,通过BasicStyle 对象来定义。
/src/lmm/style/BasicStyle .ts
export type BasicStyleType = {
// 投影相关
shadowColor?: string | undefined
shadowBlur?: number
shadowOffsetX?: number
shadowOffsetY?: number
// 全局透明度
globalAlpha?: number | undefined
//合成相关
globalCompositeOperation?: GlobalCompositeOperation | undefined
// 裁剪
clip?: boolean
}
class BasicStyle {
// 投影相关
shadowColor: string | undefined
shadowBlur = 0
shadowOffsetX = 0
shadowOffsetY = 0
// 全局透明度
globalAlpha: number | undefined
//合成相关
globalCompositeOperation: GlobalCompositeOperation | undefined
// 裁剪
clip = false
/* 设置样式 */
setOption(attr: BasicStyleType = {}) {
Object.assign(this, attr)
}
/* 应用样式 */
apply(ctx: CanvasRenderingContext2D) {
const {
globalAlpha,
globalCompositeOperation,
shadowColor,
shadowBlur,
shadowOffsetX,
shadowOffsetY,
clip,
} = this
/* 投影 */
if (shadowColor) {
ctx.shadowColor = shadowColor
ctx.shadowBlur = shadowBlur
ctx.shadowOffsetX = shadowOffsetX
ctx.shadowOffsetY = shadowOffsetY
}
/* 全局合成 */
globalCompositeOperation &&
(ctx.globalCompositeOperation = globalCompositeOperation)
/*透明度合成*/
globalAlpha !== undefined && (ctx.globalAlpha = globalAlpha)
/* 裁剪 */
clip && ctx.clip()
}
}
export { BasicStyle }
构造函数
构造函数会通过setOption()方法设置Img对象的属性。
constructor(attr: ImgType = {}) {
super()
this.setOption(attr)
}
/* 属性设置 */
setOption(attr: ImgType) {
for (let [key, val] of Object.entries(attr)) {
switch (key) {
case 'src':
if (this.image instanceof Image) {
this.image.src = val as 'string'
}
break
case 'style':
this.style.setOption(val as BasicStyleType)
break
default:
this[key] = val
}
}
}
setOption()方法中,src 会赋值给Img对象的image属性;style 则是用于设置样式的。其余属性正常赋值。
get 属性
- moMatrix:世界模型矩阵*偏移矩阵。这里的偏移就是图像的偏移值,之后在改变图像的变换基点时会有用到。
get moMatrix(): Matrix3 {
const {
offset: { x, y },
} = this
return this.worldMatrix.multiply(new Matrix3().makeTranslation(x, y))
}
- pvmoMatrix:视图投影矩阵世界模型矩阵偏移矩阵。用它可以把图像的本地顶点提升到裁剪坐标系里,以此来绘制图案控制器。
get pvmoMatrix(): Matrix3 {
const {
offset: { x, y },
} = this
return this.pvmMatrix.multiply(new Matrix3().makeTranslation(x, y))
}
Img 对象的方法
- drawShape(ctx: CanvasRenderingContext2D) :绘图方法。
drawShape(ctx: CanvasRenderingContext2D) {
const { image, offset, size, view, style } = this
//样式
style.apply(ctx)
// 绘制图像
if (view) {
ctx.drawImage(
image,
view.x,
view.y,
view.width,
view.height,
offset.x,
offset.y,
size.x,
size.y
)
} else {
ctx.drawImage(image, offset.x, offset.y, size.x, size.y)
}
}
此方法会根据view属性提供两套绘图方案,view 会对图像进行裁剪,其原理我在canvas 基础课程里说过。
- crtPath(ctx,matrix) :绘制图像边界,可用于图像的选择。
crtPath(ctx: CanvasRenderingContext2D, matrix = this.pvmoMatrix) {
const {
size: { x: imgW, y: imgH },
} = this
crtPathByMatrix(ctx, [0, 0, imgW, 0, imgW, imgH, 0, imgH], matrix)
}
crtPathByMatrix() 是从ObjectUtils 中导入的方法,可以基于矩阵建立路径。
- /src/lmm/objects/ObjectUtils .ts
import { Matrix3 } from '../math/Matrix3'
import { Vector2 } from '../math/Vector2'
function crtPathByMatrix(
ctx: CanvasRenderingContext2D,
vertices: number[],
matrix: Matrix3
) {
const p0 = new Vector2(vertices[0], vertices[1]).applyMatrix3(matrix)
ctx.moveTo(p0.x, p0.y)
for (let i = 2, len = vertices.length; i < len; i += 2) {
const pn = new Vector2(vertices[i], vertices[i + 1]).applyMatrix3(matrix)
ctx.lineTo(pn.x, pn.y)
}
ctx.closePath()
}
export { crtPathByMatrix }
在crtPathByMatrix() 方法中,matrix会变换vertices中的顶点,然后绘图。
我把crtPathByMatrix() 方法放到ObjectUtils 中的原因是它在其它地方也会用到。
3-Img图像测试
在examples中建立一个Img.vue文件。
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { Vector2 } from '../lmm/math/Vector2'
import { Img } from '../lmm/objects/Img'
// 获取父级属性
const props = defineProps({
size: { type: Object, default: { width: 0, height: 0 } },
})
// 对应canvas 画布的Ref对象
const canvasRef = ref<HTMLCanvasElement>()
/* 图案 */
const image = new Image()
image.src =
'https://yxyy-pandora.oss-cn-beijing.aliyuncs.com/stamp-images/1.png'
const pattern = new Img({ image })
/* 渲染 */
function render(ctx: CanvasRenderingContext2D) {
const imgSize = new Vector2(image.width, image.height).multiplyScalar(0.6)
pattern.setOption({
/* 模型矩阵 */
rotate:0.4,
position:new Vector2(0, -50),
scale:new Vector2(0.5),
/* Img属性 */
size: imgSize.clone(),
offset: imgSize.clone().multiplyScalar(-0.5),
view: {
x: 0,
y: 0,
width: image.width / 2,
height: image.height / 2,
},
/* 样式 */
style: {
globalAlpha: 0.8,
shadowColor: 'rgba(0,0,0,0.5)',
shadowBlur: 5,
shadowOffsetY: 20,
},
})
/* 把canvas坐标系的原点移动到画布中心,以此为裁剪空间 */
ctx.translate(props.size.width / 2, props.size.height / 2)
/* 绘图 */
pattern.draw(ctx)
/* 绘制图案边界 */
ctx.save()
pattern.crtPath(ctx)
ctx.stroke()
ctx.restore()
}
onMounted(() => {
const canvas = canvasRef.value
const ctx = canvas?.getContext('2d')
if (!ctx) {
return
}
image.onload = function () {
render(ctx)
}
})
</script>
<template>
<canvas ref="canvasRef" :width="size.width" :height="size.height"></canvas>
</template>
<style scoped></style>
路由的配置我就省略了。
效果如下:
总结
这一章的要点是canvas 原生的drawImage方法、矩阵运算和图形架构,这些都不算难,惟手熟尔。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END