first commit

This commit is contained in:
lingxiao865
2026-02-10 08:05:03 +08:00
commit c5af079d8c
1094 changed files with 97530 additions and 0 deletions

View File

@@ -0,0 +1,360 @@
import { loadImage } from '../../common/canvas/index';
import { getWindowInfo } from '../../common/utils';
const ratio = getWindowInfo().pixelRatio || 1;
// 元素中心为旋转点执行旋转
const drawRotate = (
ctx,
x,
y,
rotate,
) => {
ctx.translate(x, y);
ctx.rotate((Math.PI / 180) * Number(rotate));
ctx.translate(-x, -y);
};
// 绘制文字
const drawText = (
ctx,
x,
y,
markHeight,
text,
fontWeight,
fontSize,
fontFamily,
fillStyle,
) => {
ctx.font = `normal normal ${fontWeight} ${
fontSize * ratio
}px/${markHeight}px ${fontFamily}`;
ctx.fillStyle = fillStyle;
ctx.textAlign = 'start';
ctx.textBaseline = 'top';
ctx.fillText(text, x, y);
};
export default async function generateBase64Url(
canvas,
canvasId,
{
width,
height,
gapX,
gapY,
offsetLeft,
offsetTop,
rotate,
alpha,
watermarkContent,
lineSpace,
watermarkColor,
layout,
},
onFinish,
onFinally,
) {
const isHexagonal = layout === 'hexagonal';
let ctx;
// #ifdef MP || H5
ctx = canvas.getContext('2d', { willReadFrequently: true });
// #endif
if (!ctx) {
ctx = uni.createCanvasContext(canvasId, this);
}
if (!ctx) {
console.warn('当前环境不支持Canvas, 无法绘制水印');
onFinish('');
return;
}
let actualBackgroundSize = {
width: gapX + width,
};
const canvasWidth = (gapX + width) * ratio;
const canvasHeight = (gapY + height) * ratio;
const markWidth = width * ratio;
const markHeight = height * ratio;
const dislocationRotateX = canvasWidth;
const dislocationRotateY = canvasHeight;
const dislocationDrawX = (gapX + width) * ratio;
const dislocationDrawY = (gapY + height) * ratio;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
if (isHexagonal) {
canvas.width = canvasWidth * 2;
canvas.height = canvasHeight * 2;
// 两倍宽度+间距
actualBackgroundSize = {
width: gapX + width * 2 + width / 2,
};
}
ctx.globalAlpha = alpha;
// h5需要全局缩放
// #ifdef H5 || APP-PLUS
ctx.scale(1 / ratio, 1 / ratio);
// #endif
ctx.fillStyle = 'transparent';
ctx.fillRect(0, 0, markWidth, markHeight);
ctx.translate(offsetLeft * ratio, offsetTop * ratio);
const contents = Array.isArray(watermarkContent)
? watermarkContent
: [{ ...watermarkContent }];
let top = 0;
// 预处理
contents.forEach((item) => {
item.top = top;
if (item.url) {
top += height;
} else if (item.text) {
top += lineSpace;
}
});
// 绘制水印内容
const renderWatermarkItem = async (
item,
offsetX = 0,
offsetY = 0,
rotateX = 0,
rotateY = 0,
) => {
if (item.url) {
const { url, isGrayscale = false } = item;
const img = await loadImage({
canvas,
src: url,
});
ctx.save?.();
drawRotate(ctx, rotateX, rotateY, rotate);
// TODO其他技术栈修复了「灰度效果只影响图片不影响文字」的bug因为小程序不能创建临时canvas暂时没有想到比较优雅的解决方案
if (isGrayscale) {
// #ifdef APP-PLUS
ctx.drawImage(
img,
offsetX,
offsetY + item.top * ratio,
width * ratio / 2,
height * ratio / 2,
);
// #endif
// #ifndef APP-PLUS
ctx.drawImage(
img,
offsetX,
offsetY + item.top * ratio,
width * ratio,
height * ratio,
);
// #endif
const imgData = await getImageData(ctx, {
x: 0,
y: 0,
width,
height,
});
const pixels = imgData.data;
for (let i = 0; i < pixels.length; i += 4) {
const lightness = (pixels[i] + pixels[i + 1] + pixels[i + 2]) / 3;
pixels[i] = lightness;
pixels[i + 1] = lightness;
pixels[i + 2] = lightness;
}
await putImageData(ctx, imgData, {
x: 0,
y: 0,
});
} else {
// #ifdef APP-PLUS
ctx.drawImage(
img,
offsetX,
(offsetY + item.top * ratio),
width * ratio / 2,
height * ratio / 2,
);
// #endif
// #ifndef APP-PLUS
ctx.drawImage(
img,
offsetX,
offsetY + item.top * ratio,
width * ratio,
height * ratio,
);
// #endif
}
ctx.restore?.();
return;
}
if (item.text) {
const {
text,
fontSize = 16,
fontFamily = 'normal',
fontWeight = 'normal',
} = item;
const fillStyle = item?.fontColor || watermarkColor;
ctx.save?.();
drawRotate(ctx, rotateX, rotateY, rotate);
drawText(
ctx,
offsetX,
offsetY + item.top * ratio,
markHeight,
text,
fontWeight,
fontSize,
fontFamily,
fillStyle,
);
ctx.restore?.();
}
};
// 矩形水印
for (const item of contents) {
await renderWatermarkItem(item, 0, 0, 0, 0);
}
// 六边形水印
if (isHexagonal) {
for (const item of contents) {
await renderWatermarkItem(
item,
dislocationDrawX,
dislocationDrawY,
dislocationRotateX,
dislocationRotateY,
);
}
}
// #ifdef APP-PLUS
ctx.draw();
// #endif
// 没有图片
const canvasUrl = await exportCanvasImage.call(this, canvas, canvasId);
onFinish(canvasUrl, actualBackgroundSize, ratio);
onFinally?.();
}
// 跨平台 Canvas 导出方法
export function exportCanvasImage(canvas, canvasId) {
return new Promise((resolve, reject) => {
let parsed = false;
// #ifdef H5 || MP
resolve(canvas.toDataURL('image/png'));
parsed = true;
// #endif
if (parsed) return;
const query = uni.createSelectorQuery().in(this);
query
.select(`#${canvasId}`)
.fields({ node: true, size: true })
.exec(async (res) => {
if (!res[0]?.node) {
console.error('Canvas node not found');
return;
}
uni.canvasToTempFilePath({
// #ifdef MP
canvas: res[0].node,
// #endif
// #ifndef MP
canvasId,
// #endif
success: res => resolve(res.tempFilePath),
fail: reject,
});
});
});
}
async function getImageData(ctx, options) {
let result;
// #ifdef H5 || MP
result = ctx.getImageData(options.x, options.y, options.width, options.height);
// #endif
if (!result) {
result = new Promise((resolve) => {
uni.canvasGetImageData({
canvasId: options.canvasId,
x: options.x,
y: options.y,
width: options.width,
height: options.height,
success: (res) => {
// 小程序/App 返回的数据结构需要转换
resolve({
data: new Uint8ClampedArray(res.data),
width: res.width,
height: res.height,
});
},
}, this);
});
}
return result;
}
async function putImageData(ctx, imageData, options) {
let result;
// #ifdef H5 || MP
ctx.putImageData(imageData, options.x, options.y);
result = Promise.resolve();
// #endif
if (!result) {
result = new Promise((resolve, reject) => {
uni.canvasPutImageData({
canvasId: options.canvasId,
x: options.x,
y: options.y,
width: imageData.width,
height: imageData.height,
data: imageData.data,
success: resolve,
fail: reject,
}, this);
});
}
return result;
}