Recently, I need to synthesize poster pictures in wechat mini program for a project. I have encountered many problems in the process. Please record them for future reference
Results show
!
Description of drawing Process
- Request poster data
- Loading pictures
- Initialize the canvas
- Rendering text
- Draw the block
- Wechat small program code acquisition
- Draw pictures
- Exporting image Content
- Applets code parsing
Draw key points
Canvas initialization
Normally, 1px shows 1px on the screen, but on a hd screen, this is not the case. Take the iPhone4S for example, its devicePixelRatio is 2, and you will see that the logical value of 100px is equal to the device value of 200px. Images are created at the logical pixel width of the element, and when they are drawn, they are scaled up by device pixels and become blurred. The way to solve this problem is to create images scaled devicePixelRatio and then use CSS to scale them down to the same scale.
Canvas width and height control the bitmap properties of elements. If not set, the default values are width=300 and height=150
/ / get the canvas
function getCanvas() {
return new Promise((resolve, reject) = > {
Taro.createSelectorQuery()
.select('#myCanvas')
.fields({
node: true.size: true
})
.exec(res= > {
res ? resolve(res[0]) : reject(null);
});
});
}
/** * initializes canvas and returns CTX **@param {Object} res
* @returns {*}* /
function setContext(res: Object) :any {
let {canvas, width, height} = res;
const ctx = canvas.getContext('2d');
const dpr = Taro.getSystemInfoSync().pixelRatio;
// Prevent repeated amplification
if(canvas.width ! == width * dpr) {// Set canvas painting size = CSS size * device pixel ratio.
canvas.width = width * dpr;
canvas.height = height * dpr;
// Scale all drawing operations through DPR
ctx.scale(dpr, dpr);
}
return ctx;
}
let {node: canvas, width, height} = await getCanvas();
let ctx = setContext({canvas, width, height});
// Clear it before drawing to prevent it from being affected by the original pattern
ctx.clearRect(0.0, width, height);
ctx.fillStyle = '#ffffff';
Copy the code
Canvas tag, wechat has optimized canvas rendering for the same layer, especially pay attention not to put canvas tag into sub-components, it must be put into page page, otherwise it cannot get canvas object
<Canvas type='2d' id='myCanvas' style='width: 414px; height: 736px; ' />
Copy the code
Wechat small program code acquisition
- Interface A: Applies to service scenarios that require A small number of codes
- Generate small program code. The path parameter is long and the number of generated programs is limited. For the number of generated programs, see precautions.
- Interface B: Applicable to service scenarios where a large number of codes are required
- Generate small code, can accept page parameters short, the number of generation is not limited.
- Interface C: Applicable to service scenarios that require a small number of codes
- Generate a TWO-DIMENSIONAL code, the acceptable path parameter is long, the number of generated is limited, the number of limits see precautions.
So we’re using interface B, so what we need to pay attention to,
- The scene parameter accepts only the length of 32
- When the small program is not published, the page parameter must be empty, scene= XXXX, and the TWO-DIMENSIONAL code can be obtained
- The data returned by the interface must be in base64 format for canvas drawing
Image acquisition before drawing
Taro.getImageInfo({
src: imgUrl
}).than(res= > {
let { path } = res;
let imgtag = canvas.createImage();
imgtag.src = path;
imgtag.onload = res= > {
ctx.save();
ctx.drawImage(imgtag, 26.615.2 * 27.2 * 27);
ctx.restore();
};
});
Copy the code
Image cropping
/** * draw circles **@export
* @param {IDrawRound} options
* @example* drawRound({ * ctx, * r: 19, * x: 22, * y: 15, * next() { * ctx.fillStyle = '#F5F5F5'; * ctx.fill(); * *}}); * /
export function drawRound(options: IDrawRound) {
let { ctx, r, x, y, next } = options;
ctx.save(); // Save the previous one
let cx = x + r; // Arc coordinates x
let cy = y + r; // Arc coordinates y
ctx.arc(cx, cy, r, 0.2 * Math.PI);
if (typeof next === 'function') {
next();
}
ctx.restore(); // Return to the previous state
}
// Draw a circular avatar
drawRound({
ctx,
r: 27.x: 26.y: 615.next() {
ctx.clip();
ctx.drawImage(imgtag, 26.615.2 * 27.2 * 27); / / draw picture}});Copy the code
Draw rounded rectangles
/** * Draw a rounded rectangle, fill it with a color **@export
* @param {IDrawRound} options
*
* @example* drawRoundRect({ * ctx, * r: 13, * x: 26, * y: 80, * w: 361, * h: 361, * next() { * ctx.clip(); * ctx.drawImage(imgtag, 26, 80, 361, 361); * *}}); * /
export function drawRoundRect(options: IDrawRoundRect) {
let {ctx, x, y, w, h, r, next} = options;
ctx.save();
if (w < 2 * r) {
r = w / 2;
}
if (h < 2 * r) {
r = h / 2;
}
ctx.beginPath();
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 3 / 2);
ctx.lineTo(w - r + x, y);
ctx.arc(w - r + x, r + y, r, Math.PI * 3 / 2.Math.PI * 2);
ctx.lineTo(w + x, h + y - r);
ctx.arc(w - r + x, h - r + y, r, 0.Math.PI * 1 / 2);
ctx.lineTo(r + x, h + y);
ctx.arc(r + x, h - r + y, r, Math.PI * 1 / 2.Math.PI);
ctx.closePath();
if (typeof next === 'function') {
next();
}
ctx.restore(); // Return to the previous state
}
// Rectangle with pink rounded corners at bottom
drawRoundRect({
ctx,
r: 15.x: 26.y: 687.w: 147.h: 23.next() {
// Gradient fill
var grd = ctx.createLinearGradient(0.0.200.0);
grd.addColorStop(0.'#E828FF');
grd.addColorStop(0.7.'#FF1D74');
grd.addColorStop(1.'#FFB050'); ctx.fillStyle = grd; ctx.fill(); ctx.strokeStyle = grd; ctx.stroke(); }});Copy the code
Rendering text
/** * draw text **@export
* @param {*} CTX Canvas 2D object *@param {string} T Drawn text *@param {number} x
* @param {number} y
* @param {number} W Text width *@param {number} L = 30 line height *@param {number} Limit = 2 rows * /
export function drawText(ctx: any, t: string, x: number, y: number, w: number, l:number = 30, limit:number = 2) {
// Parameter description
// CTX: canvas 2D object, t: drawn text, x,y: text coordinates, w: maximum text width
let chr = t.split(' ');
let temp = ' ';
let row = [];
let limitIndex = 0;
let wordsNum = 0;
for (let a = 0; a < chr.length; a++) {
wordsNum++;
if (ctx.measureText(temp).width < w && ctx.measureText(temp + (chr[a])).width <= w) {
temp += chr[a];
}
else {
/ / line number + 1
limitIndex++;
if (limitIndex < limit) {
row.push(temp);
temp = chr[a];
}
else {
break; }}}// The last line exceeds the maximum number of words
if (limitIndex === limit && chr.length > wordsNum) {
temp = temp.substring(0, temp.length - 1) + '... ';
}
row.push(temp);
for (let b = 0; b < row.length; b++) {
ctx.fillText(row[b], x, y + (b + 1) * l);// The y coordinates of each line of font are spaced 30 times apart}};Copy the code
After drawing, export the image
export async function draw() {
let { node: canvas, width, height } = await getCanvas();
let ctx = setContext({ canvas, width, height });
// Clear it before drawing to prevent the original pattern from being affected
ctx.clearRect(0.0, width, height);
ctx.fillStyle = '#ffffff';
ctx.fillRect(0.0.414.736);
// Bottom gray block
ctx.fillStyle = '#f5f5f5';
ctx.fillRect(0.596.414.140);
// Rectangle with pink rounded corners at bottom
drawRoundRect({
ctx,
r: 15.x: 26.y: 687.w: 147.h: 23.next() {
// Gradient fill
var grd = ctx.createLinearGradient(0.0.200.0);
grd.addColorStop(0.'#E828FF');
grd.addColorStop(0.7.'#FF1D74');
grd.addColorStop(1.'#FFB050'); ctx.fillStyle = grd; ctx.fill(); ctx.strokeStyle = grd; ctx.stroke(); }});/ / reference
ctx.fillStyle = '# 666666';
ctx.font = '13 px / 1.2 PingFangSC - Regular';
ctx.fillText(`${nickName}`.93.635);
/ / recommendation
ctx.fillStyle = '# 222222';
ctx.font = '15 px / 1.2 PingFangSC - Regular';
ctx.fillText(`${recommendation || 'Recommend you a value beautification project'}`.93.660);
// Bottom text
ctx.fillStyle = '#ffffff';
ctx.font = '13 px / 1.2 PingFangSC - Regular';
ctx.fillText('Long click recognition applet shopping'.43.703);
// Omit some image drawing code
// Get the image temporary path
let { tempFilePath } = await Taro.canvasToTempFilePath({ canvas } as any);
return Promise.resolve(tempFilePath);
};
Copy the code
The exported image addresses can be assigned directly to the IMG tag
Save pictures to albums
const res = await Taro.saveImageToPhotosAlbum({
filePath: tempFilePath
});
if (res.errMsg === 'saveImageToPhotosAlbum:ok') {
Taro.showToast({
title: 'Image saved successfully'.icon: 'success'.duration: 2000
});
}
Copy the code
Applets code parsing
After parsing the small program code, the parameters received in page are in scene
componentWillMount() {
let params = this.$router.params;
// Enter the scene through the small program code
if (params.scene) {
let scene = decodeURIComponent(params.scene);
let {id} = query2parmas(scene);
this.setState({
goodsId: id
});
}
// Enter parameters directly in params by sharing cards
if (params.id) {
this.setState({
goodsId: params.id }); }}Copy the code