add
This commit is contained in:
parent
850d7d8277
commit
b870413a84
18001
package-lock.json
generated
18001
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
12969
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
});
|
||||
202
src/App.tsx
202
src/App.tsx
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user