| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8" />
- <title>OCR</title>
- <style>
- * {
- box-sizing: border-box;
- margin: 0;
- padding: 0;
- }
- body, html {
- height: 100%;
- font-family: sans-serif;
- background-color: #ffffff;
- }
- .container {
- display: flex;
- flex-direction: column;
- align-items: center;
- min-height: 100vh;
- padding: 5px 20px 20px 20px;
- }
- .drop-zone {
- border: 2px dashed #888;
- border-radius: 10px;
- width: 400px;
- height: 150px;
- display: flex;
- align-items: center;
- justify-content: center;
- text-align: center;
- color: #555;
- font-size: 16px;
- cursor: pointer;
- transition: background-color 0.3s;
- margin-bottom: 20px;
- }
- .drop-zone.dragover {
- background-color: #f0f0f0;
- }
- .canvas-container {
- display: none;
- gap: 20px;
- flex-wrap: wrap;
- justify-content: center;
- margin-top: 20px;
- }
- canvas {
- border: 1px solid #ccc;
- max-width: 600px;
- height: auto;
- }
- /* 加载动画 */
- .loading-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background: rgba(255, 255, 255, 0.7);
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 9999;
- display: none;
- }
- .spinner {
- width: 50px;
- height: 50px;
- border: 5px solid #ccc;
- border-top: 5px solid #3498db;
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- </style>
- </head>
- <body>
- <div class="container">
- <h2>OCR</h2>
- <div id="dropZone" class="drop-zone">
- 点击或拖动图片到这里上传
- </div>
- <!-- 加载动画 -->
- <div id="loadingOverlay" class="loading-overlay">
- <div class="spinner"></div>
- </div>
- <!-- 结果区域 -->
- <div id="canvasContainer" class="canvas-container">
- <canvas id="canvasOriginal"></canvas>
- <canvas id="canvasTextOnly"></canvas>
- </div>
- </div>
- <script>
- const dropZone = document.getElementById('dropZone');
- const fileInput = document.createElement('input');
- fileInput.type = 'file';
- fileInput.accept = 'image/*';
- const MAX_DISPLAY_WIDTH = 500;
- const canvasOriginal = document.getElementById('canvasOriginal');
- const ctxOriginal = canvasOriginal.getContext('2d');
- const canvasTextOnly = document.getElementById('canvasTextOnly');
- const ctxTextOnly = canvasTextOnly.getContext('2d');
- const canvasContainer = document.getElementById('canvasContainer');
- const loadingOverlay = document.getElementById('loadingOverlay');
- // 点击上传
- dropZone.addEventListener('click', () => {
- fileInput.click();
- });
- // 拖拽上传
- dropZone.addEventListener('dragover', (e) => {
- e.preventDefault();
- dropZone.classList.add('dragover');
- });
- dropZone.addEventListener('dragleave', () => {
- dropZone.classList.remove('dragover');
- });
- dropZone.addEventListener('drop', (e) => {
- e.preventDefault();
- dropZone.classList.remove('dragover');
- const file = e.dataTransfer.files[0];
- if (file && file.type.startsWith('image/')) {
- handleImage(file);
- } else {
- alert("请上传图片文件");
- }
- });
- fileInput.addEventListener('change', () => {
- const file = fileInput.files[0];
- if (file) {
- handleImage(file);
- }
- });
- function handleImage(file) {
- const reader = new FileReader();
- reader.onload = function (e) {
- const img = new Image();
- img.onload = function () {
- const scale = MAX_DISPLAY_WIDTH / img.width;
- const displayWidth = img.width * scale;
- const displayHeight = img.height * scale;
- // 设置 canvas 尺寸
- canvasOriginal.width = displayWidth;
- canvasOriginal.height = displayHeight;
- canvasTextOnly.width = displayWidth;
- canvasTextOnly.height = displayHeight;
- // 清除之前的绘图
- ctxOriginal.clearRect(0, 0, displayWidth, displayHeight);
- ctxTextOnly.clearRect(0, 0, displayWidth, displayHeight);
- // 绘制原始图像
- ctxOriginal.drawImage(img, 0, 0, displayWidth, displayHeight);
- // 显示 canvas 容器
- canvasContainer.style.display = "flex";
- // 显示加载动画
- loadingOverlay.style.display = "flex";
- const base64Image = e.target.result.split(',')[1];
- sendToOCR(base64Image, img.width, img.height, displayWidth, displayHeight);
- };
- img.src = e.target.result;
- };
- reader.readAsDataURL(file);
- }
- function sendToOCR(base64Image, originalWidth, originalHeight, displayWidth, displayHeight) {
- fetch('/ocr', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({ image: base64Image })
- })
- .then(res => res.json())
- .then(data => {
- drawBoxesAndTextWithOrientation(data.results, originalWidth, originalHeight, displayWidth, displayHeight);
- // 隐藏加载动画
- loadingOverlay.style.display = "none";
- })
- .catch(err => {
- console.error("OCR 调用失败", err);
- alert("OCR 识别失败,请查看控制台日志");
- loadingOverlay.style.display = "none";
- });
- }
- function drawBoxesAndTextWithOrientation(results, originalWidth, originalHeight, displayWidth, displayHeight) {
- ctxTextOnly.clearRect(0, 0, canvasTextOnly.width, canvasTextOnly.height);
- ctxTextOnly.font = "12px sans-serif";
- ctxTextOnly.fillStyle = "black";
- const scaleX = displayWidth / originalWidth;
- const scaleY = displayHeight / originalHeight;
- results.forEach(result => {
- const box = result.bounding_box.map(([x, y]) => [
- x * scaleX,
- y * scaleY
- ]);
- const [[x1, y1], [x2, y2], [x3, y3]] = box;
- const width = Math.abs(x2 - x1);
- const height = Math.abs(y3 - y1);
- const decodedText = decodeUnicode(result.text);
- // 判断方向:宽 < 高 → 竖排
- if (width < height && height > 10) {
- drawVerticalText(ctxTextOnly, decodedText, x1, y1, height);
- } else {
- ctxTextOnly.fillText(decodedText, x1, y1 + height / 2);
- }
- // 绘制左侧红色框
- ctxOriginal.strokeStyle = "red";
- ctxOriginal.lineWidth = 1;
- ctxOriginal.strokeRect(x1, y1, width, height);
- });
- }
- // 竖排文字绘制函数
- function drawVerticalText(ctx, text, x, y, height) {
- for (let i = 0; i < text.length; i++) {
- ctx.fillText(text[i], x, y + i * 14);
- }
- }
- // Unicode 解码函数
- function decodeUnicode(str) {
- return str.replace(/\\u([0-9a-fA-F]{4})/g, function (_, hex) {
- return String.fromCharCode(parseInt(hex, 16));
- });
- }
- </script>
- </body>
- </html>
|