This commit is contained in:
nap.liu 2024-06-05 22:29:11 +08:00
parent 850d7d8277
commit b870413a84
5 changed files with 13156 additions and 18033 deletions

18001
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,8 @@
"@types/node": "^16.18.98",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"antd": "^5.18.0",
"html2canvas": "^1.4.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
@ -22,12 +24,6 @@
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",

12969
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +0,0 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -1,24 +1,192 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import React, { useEffect, useRef, useState } from "react";
import "./App.css";
type ImageWatermarkProps = {
file: File;
text?: string;
fontSize?: number;
fontColor?: string;
opacity?: number;
spacing?: number;
};
const ImageWatermark = ({
file,
text = "水印",
fontSize = 16,
fontColor = "#000",
opacity = 0.2,
spacing = 50,
}: ImageWatermarkProps) => {
// const [src, setSrc] = useState("");
const canvasRef = useRef<HTMLCanvasElement>(null);
const imgRef = useRef(new Image());
useEffect(() => {
const src = URL.createObjectURL(file);
const canvas = canvasRef.current!;
const context = canvas.getContext("2d")!;
const img = imgRef.current;
// 加载图片
img.src = src;
img.onload = () => {
// 获取设备像素比
const devicePixelRatio = window.devicePixelRatio || 1;
// 设置canvas的物理像素尺寸
canvas.width = img.width * devicePixelRatio;
canvas.height = img.height * devicePixelRatio;
// 缩放绘图上下文以适应设备像素比
context.scale(devicePixelRatio, devicePixelRatio);
// 绘制图片
context.drawImage(img, 0, 0, img.width, img.height);
// 设置水印样式
context.font = `${fontSize}px Arial`;
context.fillStyle = fontColor;
context.globalAlpha = opacity;
context.textAlign = "center";
context.textBaseline = "middle";
// 计算水印文本的宽度和高度
const textMetrics = context.measureText(text);
const textWidth = textMetrics.width;
const textHeight = fontSize;
// 计算水印的间距和偏移
const stepX = textWidth + spacing;
const stepY = textHeight + spacing;
const angle = Math.PI / 4; // 45度角
// 保存当前绘图状态
context.save();
// 旋转canvas以绘制斜角水印
context.translate(img.width / 2, img.height / 2);
context.rotate(-angle);
context.translate(-img.width / 2, -img.height / 2);
// 绘制斜角、重复的水印文本
for (let x = -img.width; x < img.width * 2; x += stepX) {
for (let y = -img.height; y < img.height * 2; y += stepY) {
context.fillText(text, x, y);
}
}
// 恢复绘图状态
context.restore();
};
return () => {
URL.revokeObjectURL(src);
};
}, [file, text, fontSize, fontColor, opacity, spacing]);
const onDownload = () => {
const canvas = canvasRef.current!;
const a = document.createElement("a");
a.href = canvas.toDataURL("image/png");
a.download = file.name;
a.click();
};
return (
<canvas
ref={canvasRef}
style={{
width: "100%", // 设置CSS尺寸
height: "auto", // 设置CSS尺寸
}}
onClick={onDownload}
/>
);
};
function App() {
const [files, setFiles] = useState<File[]>([]);
const [keyword, setKeyword] = useState<string>("水印");
const [fontSize, setFontSize] = useState(16);
const [fontColor, setFontColor] = useState("#000");
const [opacity, setOpacity] = useState(0.2);
const [spacing, setSpacing] = useState(50);
const onSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
setFiles(Array.from(e.target.files || []));
};
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<div
style={{ gap: 10, display: "grid", gridTemplate: "'a b' / auto 1fr" }}
>
<label htmlFor="file"></label>
<input
type="file"
id="file"
multiple
onChange={onSelect}
accept="image/*"
/>
<label htmlFor="keyword"></label>
<input
type="text"
id="keyword"
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
/>
<label htmlFor="fontSize"></label>
<input
type="range"
id="fontSize"
value={keyword}
min={12}
max={48}
onChange={(e) => setFontSize(Number(e.target.value))}
/>
<label htmlFor="fontColor"></label>
<input
type="color"
id="fontColor"
value={fontColor}
onChange={(e) => setFontColor(e.target.value)}
/>
<label htmlFor="opacity"></label>
<input
type="range"
id="opacity"
value={opacity}
min={0}
max={1}
step={0.1}
onChange={(e) => setOpacity(Number(e.target.value))}
/>
<label htmlFor="spacing"></label>
<input
type="range"
id="spacing"
value={spacing}
min={10}
max={100}
step={10}
onChange={(e) => setSpacing(Number(e.target.value))}
/>
</div>
{files.map((file) => (
<ImageWatermark
file={file}
fontColor={fontColor}
fontSize={fontSize}
spacing={spacing}
opacity={opacity}
key={file.name}
text={keyword}
/>
))}
</div>
);
}