import { GenerateFrameResult, XQrcoderProps } from "./interface.uts" import { generateFrame } from "./qrcode.uts" // 绘制圆形定位点模式的函数 function drawRoundedPositionPattern(ctx: CanvasRenderingContext2D, x: number, y: number, px: number, pdColor: string, bgColor: string) { // 外层黑色圆形 (7x7) ctx.fillStyle = pdColor ctx.beginPath() ctx.arc(x + px * 3.5, y + px * 3.5, px * 3.5, 0, 2 * Math.PI) ctx.fill() ctx.closePath() // 中层白色圆形 (5x5) ctx.fillStyle = bgColor ctx.beginPath() ctx.arc(x + px * 3.5, y + px * 3.5, px * 2.5, 0, 2 * Math.PI) ctx.fill() ctx.closePath() // 内层黑色圆形 (3x3) ctx.fillStyle = pdColor ctx.beginPath() ctx.arc(x + px * 3.5, y + px * 3.5, px * 1.5, 0, 2 * Math.PI) ctx.fill() ctx.closePath() } // 绘制星形图案的函数 function drawStar(ctx: CanvasRenderingContext2D, x: number, y: number, px: number) { const centerX = x + px / 2 const centerY = y + px / 2 const outerRadius = px * 0.4 // 外圆半径 const innerRadius = px * 0.15 // 内圆半径 const spikes = 4 // 四个尖角 ctx.beginPath() for (let i = 0; i < spikes * 2; i++) { const angle = (i * Math.PI) / spikes const radius = i % 2 === 0 ? outerRadius : innerRadius const starX = centerX + Math.cos(angle) * radius const starY = centerY + Math.sin(angle) * radius if (i === 0) { ctx.moveTo(starX, starY) } else { ctx.lineTo(starX, starY) } } ctx.closePath() ctx.fill() } // 兼容的圆角矩形绘制函数 function drawRoundedRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius: number) { ctx.beginPath() // 如果圆角半径太大,限制为宽度或高度的一半 const maxRadius = Math.min(width, height) / 2 radius = Math.min(radius, maxRadius) // 绘制圆角矩形路径 ctx.moveTo(x + radius, y) ctx.lineTo(x + width - radius, y) ctx.arc(x + width - radius, y + radius, radius, -Math.PI / 2, 0) ctx.lineTo(x + width, y + height - radius) ctx.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2) ctx.lineTo(x + radius, y + height) ctx.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI) ctx.lineTo(x, y + radius) ctx.arc(x + radius, y + radius, radius, Math.PI, Math.PI * 3 / 2) ctx.closePath() } export function drawQrcode(opts : XQrcoderProps, img : any | null = null) { let fo = generateFrame(opts.text, opts.ecc) let points = fo.frameBuffer let width = fo.width let px = opts.size / width let borderWidth = 0 const ctx = opts.ctx; // 绘制背景 ctx.fillStyle = opts.background ctx.fillRect(0, 0, opts.size, opts.size) // 判断是否是定位点的函数 function isPositionDetectionPattern(i : number, j : number, width : number) : boolean { // 左上角 if (i < 7 && j < 7) return true; // 右上角 if (i > width - 8 && j < 7) return true; // 左下角 if (i < 7 && j > width - 8) return true; return false; } let dot = px * 0.1 let pdColor = opts?.pdColor??opts.foreground // 先绘制定位点区域(如果需要圆角) if ((opts?.pdRounded??false)) { // 绘制三个定位点的同心圆角矩形结构 // 左上角 drawRoundedPositionPattern(ctx, borderWidth, borderWidth, px, pdColor, opts.background) // 右上角 drawRoundedPositionPattern(ctx, borderWidth + px * (width - 7), borderWidth, px, pdColor, opts.background) // 左下角 drawRoundedPositionPattern(ctx, borderWidth, borderWidth + px * (width - 7), px, pdColor, opts.background) } for (let i = 0; i < width; i++) { for (let j = 0; j < width; j++) { if (points[j * width + i] > 0) { // 设置颜色,如果是定位点则使用pdColor,否则使用foreground const isPostion = isPositionDetectionPattern(i, j, width); ctx.fillStyle = opts.foreground; if (isPostion) { // 如果启用了圆角,定位点区域已经整体绘制过了,跳过单个方块的绘制 if (!(opts?.pdRounded??false)) { ctx.fillStyle = pdColor ctx.fillRect(borderWidth + px * i, borderWidth + px * j, px, px) } } else { if (opts.mode == 'line') { ctx.fillRect(borderWidth + px * i, borderWidth + px * j, px, px / 2) } else if (opts.mode == 'circular') { ctx.beginPath() let rx = borderWidth + px * i + px / 2 - dot let ry = borderWidth + px * j + px / 2 - dot ctx.arc(rx, ry, px / 2 - dot, 0, 2 * Math.PI) ctx.fill() ctx.closePath() } else if (opts.mode == 'rectSmall') { ctx.fillRect(borderWidth + px * i + dot, borderWidth + px * j + dot, px - dot*2, px - dot*2) } else if (opts.mode == 'xing') { // 绘制星形 drawStar(ctx, borderWidth + px * i, borderWidth + px * j, px) } else if (opts.mode == 'vertical') { continue } else { ctx.fillRect(borderWidth + px * i, borderWidth + px * j, px, px) } } } } } // 对于vertical模式,需要特殊处理垂直方向的连续点 if (opts.mode == 'vertical') { // 创建已处理标记数组 let processed:boolean[] = [] as boolean[] for(let km=0;km 0 && !processed[index]) { const isPostion = isPositionDetectionPattern(i, j, width); if (isPostion) { // 定位点跳过,已经处理过了 continue } // 分析垂直方向的连续点 let verticalLength = 1 let startJ = j // 向上查找连续点 while (startJ > 0 && points[(startJ - 1) * width + i] > 0 && !isPositionDetectionPattern(i, startJ - 1, width)) { startJ-- verticalLength++ } // 向下查找连续点 let endJ = j while (endJ < width - 1 && points[(endJ + 1) * width + i] > 0 && !isPositionDetectionPattern(i, endJ + 1, width)) { endJ++ verticalLength++ } // 标记这些点为已处理 for (let k = startJ; k <= endJ; k++) { processed[k * width + i] = true } // 根据长度决定绘制方式 ctx.fillStyle = opts.foreground if (verticalLength == 1) { // 单个点:绘制圆点 ctx.beginPath() let centerX = borderWidth + px * i + px / 2 let centerY = borderWidth + px * j + px / 2 ctx.arc(centerX, centerY, px * 0.25, 0, 2 * Math.PI) // 稍微减小圆点半径 ctx.fill() ctx.closePath() } else if (verticalLength <= 3) { // 2-3个点:绘制连续线条 let x = borderWidth + px * i + px / 2 let y = borderWidth + px * startJ + px * 0.2 // 减少上下间距 let lineWidth = px * 0.5 // 减少线条宽度 let lineHeight = px * (endJ - startJ + 1) * 0.8 // 增加线条高度 // 圆角半径适中,不完全半圆 let radius = px * 0.5 drawRoundedRect(ctx, x - lineWidth / 2, y, lineWidth, lineHeight, radius) ctx.fill() } else { // 超过3个点:分段绘制,每3个点一段 let segments = Math.ceil(verticalLength / 3) for (let seg = 0; seg < segments; seg++) { let segStart = startJ + seg * 3 let segEnd = Math.min(startJ + (seg + 1) * 3 - 1, endJ) let segLength = segEnd - segStart + 1 let x = borderWidth + px * i + px / 2 let y = borderWidth + px * segStart + px * 0.2 // 减少上下间距 let lineWidth = px * 0.5 // 减少线条宽度 let lineHeight = px * segLength * 0.8 // 增加线条高度 // 圆角半径适中,不完全半圆 let radius = px * 0.5 drawRoundedRect(ctx, x - lineWidth / 2, y, lineWidth, lineHeight, radius) ctx.fill() } } } } } } if (opts.logo != '') { // #ifdef APP-ANDROID||APP-IOS||WEB let images = new Image(opts.logoSize, opts.logoSize) images.src = opts.logo console.log(opts.logo) images.onload = () => { uni.getImageInfo({ src: opts.logo, success: (res) => { ctx.save() ctx.fillStyle = opts.logoBgColor let centerx = (opts.size - opts.logoSize) / 2; ctx.fillRect(centerx, centerx, opts.logoSize, opts.logoSize) ctx.drawImage(images, 0, 0, res.width, res.height, centerx + 3, centerx + 3, opts.logoSize - 6, opts.logoSize - 6) ctx.restore() } }) } // #endif // #ifdef MP-WEIXIN||APP-HARMONY img.src = opts.logo img.onload = () => { uni.getImageInfo({ src: opts.logo, success: (res) => { ctx.save() ctx.fillStyle = opts.logoBgColor let centerx = (opts.size - opts.logoSize) / 2; ctx.fillRect(centerx, centerx, opts.logoSize, opts.logoSize) // #ifdef MP ctx.drawImage( img, 0, 0, res.width, res.height, centerx + 3, centerx + 3, opts.logoSize - 6, opts.logoSize - 6 ) // #endif // #ifdef APP-HARMONY let dpr = uni.getWindowInfo().pixelRatio ctx.drawImage( img, centerx + 3, centerx + 3, opts.logoSize - 6, opts.logoSize - 6 ) // #endif ctx.restore() } }) } // #endif } }