Skip to content

Instantly share code, notes, and snippets.

@mutoe
Last active July 23, 2019 02:57
Show Gist options
  • Save mutoe/dea1bc18609c1164a30887d3d3f5a7d0 to your computer and use it in GitHub Desktop.
Save mutoe/dea1bc18609c1164a30887d3d3f5a7d0 to your computer and use it in GitHub Desktop.
canvas 工具类 (可绘制圆角图片、含有缩放模式的图片绘制)
/**
* Canvas 助手方法
*/
export class CanvasUtil {
constructor (ctx, canvasWidth, canvasHeight) {
this.ctx = ctx
this.canvasWidth = canvasWidth
this.canvasHeight = canvasHeight
return this
}
/** 绘制圆角裁切区域 */
clipRadius (
/** 绘制区域左上角 x 坐标 */
x,
/** 绘制区域左上角 y 坐标 */
y,
/** 绘制区域宽 */
width,
/** 绘制区域高 */
h,
/** 圆角半径 */
radius,
/** 在圆角裁切区域内绘制的回调方法 */
callback) {
this.ctx.save()
this.ctx.beginPath()
this.ctx.moveTo(x + radius, y)
this.ctx.lineTo(x + width - radius, y)
this.ctx.arcTo(x + width, y, x + width, y + radius, radius)
this.ctx.lineTo(x + width, y + h - radius)
this.ctx.arcTo(x + width, y + h, x + width - radius, y + h, radius)
this.ctx.lineTo(x + radius, y + h)
this.ctx.arcTo(x, y + h, x, y + h - radius, radius)
this.ctx.lineTo(x, y + radius)
this.ctx.arcTo(x, y, x + radius, y, radius)
this.ctx.closePath()
this.ctx.clip()
callback()
this.ctx.restore()
}
/**
* 绘制图片
*
* 可自动按比例缩放图片
*
* @param imagePath 图片路径
* @param imageWidth 图片原始宽度
* @param imageHeight 图片原始高度
* @param dx 绘制区域左上角 x 坐标 (相对于 canvas)
* @param dy 绘制区域左上角 y 坐标 (相对于 canvas)
* @param dw 绘制区域宽度
* @param dh 绘制区域高度
* @param [mode=cover] 填充模式
* - `cover`: 短边优先 (默认)
* - `contain`: 长边优先
* - `fill`: 铺满
* @param [align=center] 对齐方式 (只在 `cover` 和 `contain` 模式下有效)
* - `center`: 居中
* - `start`: 左/上
* - `end`: 右/下
*/
drawImage (imagePath, imageWidth, imageHeight, dx, dy, dw, dh, mode = 'cover', align = 'center') {
/** 裁切区域左上角 x 坐标 */
let sx = 0
/** 裁切区域左上角 y 坐标 */
let sy = 0
/** 裁切区域的宽 */
let sw = imageWidth
/** 裁切区域的高 */
let sh = imageHeight
/** 原始图片宽高比 */
const imageRatio = imageWidth / imageHeight
/** 绘制区域宽高比 */
const drawRatio = dw / dh
// 拉伸铺满模式
if (mode === 'fill') {
this.ctx.drawImage(imagePath, sx, sy, sw, sh, dx, dy, dw, dh)
}
// 覆盖模式
else if (mode === 'cover') {
if (imageRatio > drawRatio) {
const originSWidth = sw
sw = sh * drawRatio
if (align === 'center') {
sx -= (sw - originSWidth) / 2
} else if (align === 'end') {
sx -= sw - originSWidth
}
} else {
const originSHeight = sh
sh = sw / drawRatio
if (align === 'center') {
sy -= (sh - originSHeight) / 2
} else if (align === 'end') {
sy -= sh - originSHeight
}
}
this.ctx.drawImage(imagePath, sx, sy, sw, sh, dx, dy, dw, dh)
}
// 包含模式
else if (mode === 'contain') {
if (imageRatio > drawRatio) {
const originDHeight = dh
dh = dw / imageRatio
if (align === 'center') {
dy += (originDHeight - dh) / 2
} else if (align === 'end') {
dy += originDHeight - dh
}
} else {
const originDWidth = dw
dw = dh * imageRatio
if (align === 'center') {
dx += (originDWidth - dw) / 2
} else if (align === 'end') {
dx += originDWidth - dw
}
}
this.ctx.drawImage(imagePath, sx, sy, sw, sh, dx, dy, dw, dh)
}
}
}
/**
* Canvas 助手方法
*/
export class CanvasUtil {
/** 原生 Canvas 上下文对象 */
public ctx: wx.CanvasContext
public canvasWidth: number
public canvasHeight: number
public constructor(ctx: wx.CanvasContext, canvasWidth: number, canvasHeight: number) {
this.ctx = ctx
this.canvasWidth = canvasWidth
this.canvasHeight = canvasHeight
return this
}
/** 绘制圆角裁切区域 */
public clipRadius(
/** 绘制区域左上角 x 坐标 */
x: number,
/** 绘制区域左上角 y 坐标 */
y: number,
/** 绘制区域宽 */
width: number,
/** 绘制区域高 */
h: number,
/** 圆角半径 */
radius: number,
/** 在圆角裁切区域内绘制的回调方法 */
callback: () => void,
) {
this.ctx.save()
this.ctx.beginPath()
this.ctx.moveTo(x + radius, y)
this.ctx.lineTo(x + width - radius, y)
this.ctx.arcTo(x + width, y, x + width, y + radius, radius)
this.ctx.lineTo(x + width, y + h - radius)
this.ctx.arcTo(x + width, y + h, x + width - radius, y + h, radius)
this.ctx.lineTo(x + radius, y + h)
this.ctx.arcTo(x, y + h, x, y + h - radius, radius)
this.ctx.lineTo(x, y + radius)
this.ctx.arcTo(x, y, x + radius, y, radius)
this.ctx.closePath()
this.ctx.clip()
callback()
this.ctx.restore()
}
/**
* 绘制图片
*
* 可自动按比例缩放图片
*
* @param imagePath 图片路径
* @param imageWidth 图片原始宽度
* @param imageHeight 图片原始高度
* @param dx 绘制区域左上角 x 坐标 (相对于 canvas)
* @param dy 绘制区域左上角 y 坐标 (相对于 canvas)
* @param dw 绘制区域宽度
* @param dh 绘制区域高度
* @param [mode=cover] 填充模式
* - `cover`: 短边优先 (默认)
* - `contain`: 长边优先
* - `fill`: 铺满
* @param [align=center] 对齐方式 (只在 `cover` 和 `contain` 模式下有效)
* - `center`: 居中
* - `start`: 左/上
* - `end`: 右/下
*/
public drawImage(
imagePath: string,
imageWidth: number,
imageHeight: number,
dx: number,
dy: number,
dw: number,
dh: number,
mode: 'cover' | 'contain' | 'fill' = 'cover',
align: 'start' | 'center' | 'end' = 'center',
) {
/** 裁切区域左上角 x 坐标 */
let sx = 0
/** 裁切区域左上角 y 坐标 */
let sy = 0
/** 裁切区域的宽 */
let sw = imageWidth
/** 裁切区域的高 */
let sh = imageHeight
/** 原始图片宽高比 */
const imageRatio = imageWidth / imageHeight
/** 绘制区域宽高比 */
const drawRatio = dw / dh
// 拉伸铺满模式
if (mode === 'fill') {
this.ctx.drawImage(imagePath, sx, sy, sw, sh, dx, dy, dw, dh)
}
// 覆盖模式
else if (mode === 'cover') {
if (imageRatio > drawRatio) {
const originSWidth = sw
sw = sh * drawRatio
if (align === 'center') {
sx -= (sw - originSWidth) / 2
} else if (align === 'end') {
sx -= sw - originSWidth
}
} else {
const originSHeight = sh
sh = sw / drawRatio
if (align === 'center') {
sy -= (sh - originSHeight) / 2
} else if (align === 'end') {
sy -= sh - originSHeight
}
}
this.ctx.drawImage(imagePath, sx, sy, sw, sh, dx, dy, dw, dh)
}
// 包含模式
else if (mode === 'contain') {
if (imageRatio > drawRatio) {
const originDHeight = dh
dh = dw / imageRatio
if (align === 'center') {
dy += (originDHeight - dh) / 2
} else if (align === 'end') {
dy += originDHeight - dh
}
} else {
const originDWidth = dw
dw = dh * imageRatio
if (align === 'center') {
dx += (originDWidth - dw) / 2
} else if (align === 'end') {
dx += originDWidth - dw
}
}
this.ctx.drawImage(imagePath, sx, sy, sw, sh, dx, dy, dw, dh)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment