吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 417|回复: 5
上一主题 下一主题
收起左侧

[资源求助] 求个图像测量工具

[复制链接]
跳转到指定楼层
楼主
李小宝吖 发表于 2026-4-7 22:34 回帖奖励
25吾爱币

求个图像测量工具,具体功能可以看下举例的这个网站:点击跳转

再贴个图,想要电脑版工具,能拖拽图片最好,不喜欢滚轮放大

最佳答案

查看完整内容

离线照片测量 – 图像比例与尺寸工具 https://wwbbn.lanzouw.com/ibXkH3mrm1ne

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
eryuetiankong 发表于 2026-4-7 22:34
eryuetiankong 发表于 2026-4-8 08:36
直接提取网页源代码就能离线使用。
[mw_shl_code=html,true]

离线照片测量 – 图像比例与尺寸工具
https://wwbbn.lanzouw.com/ibXkH3mrm1ne
3#
eryuetiankong 发表于 2026-4-8 08:36
直接提取网页源代码就能离线使用。
[HTML] 纯文本查看 复制代码


<!DOCTYPE html>
<html lang="zh">
<head>
    <meta name="google-adsense-account" content="ca-pub-5360103615621276">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">

    <title>在线照片测量 – 图像比例与尺寸工具</title>
    <meta name="description" content="使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。">
    <meta name="keywords" content="照片测量, 图像比例, 在线尺子, 蓝图测量, 尺寸测量, 技术图纸">
    <link rel="canonical" href="https://measureonimage.com/zh/">

    <link rel="icon" type="image/svg+xml" href="https://measureonimage.com/logo.svg">
    <link rel="apple-touch-icon" href="https://measureonimage.com/logo.svg">
    <link rel="manifest" href="https://measureonimage.com/manifest.json">
    <meta name="theme-color" content="#1e1e1e">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

    <meta property="og:type" content="website">
    <meta property="og:url" content="https://measureonimage.com/zh/">
    <meta property="og:title" content="在线照片测量 – 图像比例与尺寸工具">
    <meta property="og:description" content="使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。">
    <meta property="og:image" content="https://measureonimage.com/social-share.jpg">

    <meta property="twitter:card" content="summary_large_image">
    <meta property="twitter:url" content="https://measureonimage.com/zh/">
    <meta property="twitter:title" content="在线照片测量 – 图像比例与尺寸工具">
    <meta property="twitter:description" content="使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。">
    <meta property="twitter:image" content="https://measureonimage.com/social-share.jpg">

            <link rel="alternate" hreflang="en" href="https://measureonimage.com/" />
        <link rel="alternate" hreflang="x-default" href="https://measureonimage.com/" />
                    <link rel="alternate" hreflang="es" href="https://measureonimage.com/es/" />
                    <link rel="alternate" hreflang="fr" href="https://measureonimage.com/fr/" />
                    <link rel="alternate" hreflang="de" href="https://measureonimage.com/de/" />
                    <link rel="alternate" hreflang="pt" href="https://measureonimage.com/pt/" />
                    <link rel="alternate" hreflang="it" href="https://measureonimage.com/it/" />
                    <link rel="alternate" hreflang="zh" href="https://measureonimage.com/zh/" />
                    <link rel="alternate" hreflang="lt" href="https://measureonimage.com/lt/" />
                    <link rel="alternate" hreflang="pl" href="https://measureonimage.com/pl/" />
                    <link rel="alternate" hreflang="ru" href="https://measureonimage.com/ru/" />
                    <link rel="alternate" hreflang="ja" href="https://measureonimage.com/ja/" />
                    <link rel="alternate" hreflang="ar" href="https://measureonimage.com/ar/" />
                    <link rel="alternate" hreflang="hi" href="https://measureonimage.com/hi/" />
                    <link rel="alternate" hreflang="ko" href="https://measureonimage.com/ko/" />
                    <link rel="alternate" hreflang="vi" href="https://measureonimage.com/vi/" />
                    <link rel="alternate" hreflang="tr" href="https://measureonimage.com/tr/" />
                    <link rel="alternate" hreflang="nl" href="https://measureonimage.com/nl/" />
                    <link rel="alternate" hreflang="sv" href="https://measureonimage.com/sv/" />
                    <link rel="alternate" hreflang="id" href="https://measureonimage.com/id/" />
                    <link rel="alternate" hreflang="th" href="https://measureonimage.com/th/" />
                    <link rel="alternate" hreflang="ro" href="https://measureonimage.com/ro/" />
                    <link rel="alternate" hreflang="hu" href="https://measureonimage.com/hu/" />
                    <link rel="alternate" hreflang="fi" href="https://measureonimage.com/fi/" />
                    <link rel="alternate" hreflang="bn" href="https://measureonimage.com/bn/" />
                    <link rel="alternate" hreflang="ur" href="https://measureonimage.com/ur/" />
                    <link rel="alternate" hreflang="fa" href="https://measureonimage.com/fa/" />
                    <link rel="alternate" hreflang="sw" href="https://measureonimage.com/sw/" />
                    <link rel="alternate" hreflang="uk" href="https://measureonimage.com/uk/" />
                    <link rel="alternate" hreflang="cs" href="https://measureonimage.com/cs/" />
                    <link rel="alternate" hreflang="el" href="https://measureonimage.com/el/" />
                    <link rel="alternate" hreflang="da" href="https://measureonimage.com/da/" />
                    <link rel="alternate" hreflang="no" href="https://measureonimage.com/no/" />
                    <link rel="alternate" hreflang="bg" href="https://measureonimage.com/bg/" />
                    <link rel="alternate" hreflang="mr" href="https://measureonimage.com/mr/" />
                    <link rel="alternate" hreflang="te" href="https://measureonimage.com/te/" />
                    <link rel="alternate" hreflang="ta" href="https://measureonimage.com/ta/" />
                    <link rel="alternate" hreflang="tl" href="https://measureonimage.com/tl/" />
                    <link rel="alternate" hreflang="jv" href="https://measureonimage.com/jv/" />
                    <link rel="alternate" hreflang="pa" href="https://measureonimage.com/pa/" />
                    <link rel="alternate" hreflang="kn" href="https://measureonimage.com/kn/" />
                    <link rel="alternate" hreflang="gu" href="https://measureonimage.com/gu/" />
                    <link rel="alternate" hreflang="my" href="https://measureonimage.com/my/" />
                    <link rel="alternate" hreflang="ha" href="https://measureonimage.com/ha/" />
                    <link rel="alternate" hreflang="eo" href="https://measureonimage.com/eo/" />
            
    <base href="https://measureonimage.com/">

    <script async src="https://www.googletagmanager.com/gtag/js?id=G-ZQP523QG3F" type="4ec5c5409a4e93a82f20310d-text/javascript"></script>
    <script type="4ec5c5409a4e93a82f20310d-text/javascript">
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'G-ZQP523QG3F');
    </script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js" type="4ec5c5409a4e93a82f20310d-text/javascript"></script>

    <style>
        :root {
            --bg-color: #121212;
            --panel-bg: #1e1e1e;
            --item-bg: #2d2d2d;
            --accent: #007aff;
            --success: #28cd41;
            --danger: #ff3b30;
            --text: #ffffff;
            --text-dim: #a0a0a0;
            --warning: #f1c40f;
            --border: #444;
        }
        * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            background: var(--bg-color);
            color: var(--text);
            margin: 0;
        }
        #site-header {
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 60%, #0f3460 100%);
            border-bottom: 1px solid #007aff44;
            padding: 16px 20px;
            margin-bottom: 10px;
        }
        #site-header-inner {
            max-width: 1400px;
            margin: 0 auto;
            display: flex;
            align-items: center;
            gap: 24px;
        }
        #header-text { flex: 1; min-width: 0; text-align: center}
        #site-header h1 {
            font-size: 1.2rem;
            font-weight: 700;
            color: #ffffff;
            margin: 0 0 5px;
            line-height: 1.3;
        }
        #site-header p {
            font-size: 0.82rem;
            color: #a0b8d0;
            margin: 0 0 8px;
            line-height: 1.5;
        }
        #header-badges { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center;}
        #header-badges span {
            background: rgba(0,122,255,0.15);
            border: 1px solid rgba(0,122,255,0.35);
            color: #80b8ff;
            font-size: 11px;
            font-weight: 600;
            padding: 3px 10px;
            border-radius: 20px;
            white-space: nowrap;
        }
        #header-ad {
            flex-shrink: 0;
            width: 728px;
            min-height: 90px;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        [url=home.php?mod=space&uid=945662]@media[/url] (max-width: 900px) {
            #header-ad { width: 100%; min-height: 60px; }
        }
        @media (max-width: 768px) {
            #site-header { padding: 10px 14px; }
            #site-header-inner { flex-direction: column; gap: 8px; align-items: flex-start; }
            #site-header h1 { font-size: 1rem; }
            #site-header p { font-size: 0.78rem; }
        }
        #main-app-container {
            display: flex;
            flex-direction: column;
            height: calc(100vh - 112px);
            height: calc(100dvh - 112px);
        }
        #mobile-hint {
            display: none;
            background: #1a2a3a;
            border-bottom: 1px solid #007aff44;
            color: #a0c8ff;
            font-size: 12px;
            padding: 7px 14px;
            text-align: center;
            line-height: 1.4;
            margin-bottom: 20px;
        }
        @media (max-width: 768px) {
            #mobile-hint { display: block; }
            #main-app-container { height: auto; }
            #canvas-container {
                height: 65vw !important;
                min-height: 260px !important;
                max-height: 60vh !important;
                flex: none !important;
            }
        }
        .seo-h1 { position: absolute; left: -9999px; }
        #toolbar {
            background: var(--panel-bg); padding: 10px; border-bottom: 1px solid #333; align-items: center;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5); z-index: 100; flex-wrap: nowrap; flex-shrink: 0;
            display: grid; grid-auto-flow: column; grid-template-rows: repeat(2, auto); gap: 5px;
            overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch;
        }
        #toolbar::-webkit-scrollbar { height: 4px; }
        #toolbar::-webkit-scrollbar-thumb { background: #555; border-radius: 4px; }
        @media (min-width: 1001px) {
            #toolbar { flex-wrap: wrap; overflow-x: visible; justify-content: center; height: auto; }
        }
        .group {
            display: flex; flex-direction: column; background: var(--item-bg); padding: 8px 12px;
            border-radius: 12px; gap: 6px; border: 1px solid var(--border); flex-shrink: 0;
            justify-content: center; height: 73px;
        }
        label {
            font-size: 10px; text-transform: uppercase; color: var(--text-dim); font-weight: 700;
            letter-spacing: 0.5px; white-space: nowrap;
        }
        .row { display: flex; align-items: center; gap: 8px; }
        .v-separator { width: 1px; height: 24px; background: rgba(255, 255, 255, 0.15); margin: 0 4px; }
        button {
            background: var(--accent); color: white; border: none; padding: 0 14px; height: 36px;
            border-radius: 8px; cursor: pointer; font-weight: 600; font-size: 14px;
            display: flex; align-items: center; justify-content: center; gap: 6px;
            transition: transform 0.1s, filter 0.2s; white-space: nowrap;
        }
        button:active { transform: scale(0.96); }
        button.secondary { background: #444; }
        button.active { background: var(--success); box-shadow: 0 0 10px rgba(40, 205, 65, 0.4); }
        button.delete-btn { background: var(--warning); color: #000; }
        button.danger { background: var(--danger); }
        button:disabled { opacity: 0.3; cursor: not-allowed; transform: none; }
        input[type="number"], input[type="text"], select {
            background: #151515; border: 1px solid #555; color: white; padding: 0 8px;
            height: 36px; border-radius: 8px; font-size: 16px; outline: none;
        }
        #imageLoader { font-size: 12px; color: var(--text-dim); width: 180px; }
        #imageLoader::-webkit-file-upload-button {
            padding: 6px 12px; border-radius: 6px; border: none; background: var(--accent);
            color: white; font-weight: 600; cursor: pointer; margin-right: 10px; font-size: 12px;
        }
        .color-wrapper {
            position: relative; display: flex; align-items: center; justify-content: center;
            width: 36px; height: 36px; background: #000; border: 2px solid #fff;
            border-radius: 8px; overflow: hidden;
        }
        .color-wrapper input[type="color"] {
            position: absolute; top: -50%; left: -50%; width: 200%; height: 200%;
            border: none; padding: 0; margin: 0; cursor: pointer;
        }
        #canvas-container {
            flex: 1; overflow: hidden; position: relative; background: #151515;
            display: flex; align-items: center; justify-content: center;
            background-image: radial-gradient(#333 1px, transparent 1px); background-size: 20px 20px;
        }
        #imageCanvas { position: absolute; top: 0; left: 0; touch-action: none; }
        #drop-zone {
            position: absolute; top: 0; left: 0; width: 100%; height: 100%;
            display: flex; flex-direction: column; align-items: center; justify-content: center;
            z-index: 50; pointer-events: none; transition: opacity 0.2s, background 0.2s;
        }
        #drop-zone.active { background: rgba(0, 122, 255, 0.2); pointer-events: all; }
        .drop-box {
            width: 80%; max-width: 400px; padding: 40px; border: 3px dashed rgba(255, 255, 255, 0.3);
            border-radius: 20px; background: rgba(30, 30, 30, 0.7); text-align: center;
            box-shadow: 0 10px 30px rgba(0,0,0,0.5); pointer-events: auto; cursor: pointer;
            backdrop-filter: blur(2px);
        }
        .drop-box:hover { border-color: var(--accent); background: rgba(30, 30, 30, 0.9); }
        .drop-box .icon { font-size: 40px; margin-bottom: 15px; }
        #tempTextInput {
            position: absolute; background: rgba(0, 0, 0, 0.8); color: #fff; border: 2px solid var(--accent);
            padding: 8px; border-radius: 6px; z-index: 1000; min-width: 100px;
            box-shadow: 0 4px 15px rgba(0,0,0,0.5); transform: translate(-50%, -100%);
        }
        #helpModal {
            display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.8); z-index: 2000; justify-content: center; align-items: center;
            padding: 20px; backdrop-filter: blur(5px);
        }
        .modal-content {
            background: var(--panel-bg); padding: 25px; border-radius: 16px; max-width: 600px;
            width: 100%; border: 1px solid #444; position: relative; box-shadow: 0 10px 40px rgba(0,0,0,0.5);
        }
        .close-modal {
            position: absolute; top: 15px; right: 15px; background: #333; width: 30px; height: 30px;
            border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer;
        }
        #mode-indicator {
            position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
            background: rgba(0,0,0,0.7); color: white; padding: 6px 12px; border-radius: 20px;
            font-size: 13px; pointer-events: none; z-index: 50; opacity: 0; transition: opacity 0.3s;
            white-space: nowrap; border: 1px solid rgba(255,255,255,0.2);
        }
        #mode-indicator.visible { opacity: 1; }
        @media (max-width: 600px) {
            #toolbar { padding: 8px; gap: 8px; }
            .group { padding: 6px 10px; }
            button span { display: none; }
            input[type="number"] { width: 60px !important; }
            input[type="text"] { width: 60px !important; }
            .drop-box {
                width: auto !important; max-width: 200px !important; padding: 15px !important;
                background: rgba(30, 30, 30, 0.6) !important; border: 2px dashed rgba(255,255,255,0.2) !important;
            }
            .drop-box .icon { font-size: 24px !important; margin-bottom: 5px !important; }
            .drop-box h2 { font-size: 13px !important; margin: 0; }
            .drop-box p { display: none; }
        }
        .seo-content {
            padding: 40px 20px; max-width: 900px; margin: 0 auto; line-height: 1.8; font-size: 18px;
        }
        .seo-content h2 {
            font-size: 2.5em; color: var(--accent); border-bottom: 2px solid var(--border);
            padding-bottom: 10px; margin-top: 40px; margin-bottom: 20px;
        }
        .seo-content h3 { font-size: 1.8em; color: var(--text); margin-top: 30px; }
        .seo-content p, .seo-content li { color: var(--text-dim); }
        .seo-content ul { list-style-type: '&#10003; '; padding-left: 20px; }
        .seo-content strong { color: var(--text); font-weight: 600; }
        .ads-placeholder {
            width: 100%; min-height: 100px; background: #222; border: 1px dashed var(--border);
            display: flex; align-items: center; justify-content: center; margin: 40px 0; color: var(--text-dim);
        }
        footer {
            background: var(--panel-bg); text-align: center; padding: 20px; margin-top: 40px;
            border-top: 1px solid var(--border);
        }
        footer a { color: var(--accent); text-decoration: none; margin: 0 15px; font-size: 14px; }
        footer a:hover { text-decoration: underline; }
    </style>
</head>
<body>
<header id="site-header">
    <div id="site-header-inner">

        <div id="header-text">
            <a href="https://measureonimage.com/" title="Home" style="display: inline-flex; align-items: center; gap: 10px; margin-bottom: 15px; text-decoration: none; transition: transform 0.2s;" onmouseover="if (!window.__cfRLUnblockHandlers) return false; this.style.transform='scale(1.02)'" onmouseout="if (!window.__cfRLUnblockHandlers) return false; this.style.transform='scale(1)'" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
                <img src="logo.svg" alt="Measure On Image Logo" style="height: 40px; width: auto;">
                <span style="color: var(--accent, #007aff); font-size: 24px; font-weight: 800; letter-spacing: -0.5px;">MeasureOnImage.com</span>
            </a>

            <div style="height: 1px; background: var(--border, #444); width: 100%; margin-bottom: 15px; opacity: 0.5;"></div>

            <h1>专业在线图像测量工具</h1>
            <p>使用已知大小的物体作为比例,从图像中获取实际测量值。快速、简单且私密 – 您的文件在浏览器中处理。</p>

            <div id="header-badges">
                <span>&#9989; 免费</span><span>&#128274; 无需上传</span><span>&#128241; 支持手机</span>            </div>
        </div>

        <!--<div id="header-ad"></div>-->

    </div>
</header>

<div id="main-app-container">

    <h1 class="seo-h1">专业在线图像测量工具</h1>

    <div id="mobile-hint">
        &#128208; 在已知距离上画一条红色比例线进行校准。 &nbsp;|&nbsp; 手机: 单指绘制。双指 移动和缩放。    </div>

    <div id="toolbar">
        <div class="group">
            <label>&#128193; 上传图像</label>
            <input type="file" id="imageLoader" accept="image/*">
        </div>

        <div class="group">
            <label>&#128208; 比例与单位</label>
            <div class="row">
                <input type="number" id="knownValue" value="1.0" step="0.1" style="width: 70px;">
                <input type="text" id="unitName" value="m" style="width: 50px;">
            </div>
        </div>

        <div class="group">
            <label>&#8596;&#65039; 线条样式/宽度 | 文字颜色/大小 | 类型</label>
            <div class="row">
                <div class="color-wrapper" title="线条颜色"><input type="color" id="lineColor" value="#000000"></div>
                <select id="lineWidth" style="width: 50px;" title="线条宽度">
                    <option value="1">1</option>
                    <option value="2" selected>2</option>
                    <option value="4">4</option>
                    <option value="8">8</option>
                    <option value="12">12</option>
                </select>

                <button id="customLineBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleCustomLineMode()" title="绘制一条没有测量文本的线" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
                    简单线条                </button>

                <div class="v-separator"></div>
                <div class="color-wrapper" title="线条文字颜色"><input type="color" id="lineTextColor" value="#ffffff"></div>
                <select id="lineTextSize" style="width: 55px;" title="线条上方文字大小">
                    <option value="8">XS</option>
                    <option value="16">S</option>
                    <option value="24" selected>M</option>
                    <option value="36">L</option>
                    <option value="48">XL</option>
                </select>
                <div class="v-separator"></div>
                <select id="capType" style="width: 50px;">
                    <option value="none">─</option>
                    <option value="arrow">&#8596;</option>
                    <option value="tshape" selected>┫┣</option>
                    <option value="circle">●</option>
                </select>
            </div>
        </div>

        <div class="group">
            <label>&#9997;&#65039; 添加自定义文本</label>
            <div class="row">
                <div class="color-wrapper" title="自定义文本颜色"><input type="color" id="textColor" value="#ffff00"></div>
                <select id="textSize" style="width: 60px;" title="自定义文本大小">
                    <option value="10">XS</option>
                    <option value="20">S</option>
                    <option value="32" selected>M</option>
                    <option value="48">L</option>
                    <option value="72">XL</option>
                </select>
                <button id="addTextBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleTextMode()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">添加文本</button>
            </div>
        </div>

        <div class="group">
            <label>&#128465;&#65039; 删除</label>
            <button id="deleteBtn" class="delete-btn" onclick="if (!window.__cfRLUnblockHandlers) return false; deleteSelected()" title="选择一个元素并按此处或 DEL 键" disabled data-cf-modified-4ec5c5409a4e93a82f20310d-="">&#128465;&#65039; 删除元素</button>
        </div>

        <div class="group">
            <label>&#128269; 视图</label>
            <div class="row">
                <button class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; changeZoom(1.2)" data-cf-modified-4ec5c5409a4e93a82f20310d-="">+</button>
                <button class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; changeZoom(0.8)" data-cf-modified-4ec5c5409a4e93a82f20310d-="">-</button>
                <button class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; setZoomOriginal()" title="重置图像为原始大小 (1:1)" data-cf-modified-4ec5c5409a4e93a82f20310d-="">1:1</button>
                <button onclick="if (!window.__cfRLUnblockHandlers) return false; fitToScreen()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">Fit</button>
            </div>
        </div>

        <div class="group">
            <label>&#8617;&#65039; 历史</label>
            <div class="row">
                <button id="undoBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; performUndo()" disabled data-cf-modified-4ec5c5409a4e93a82f20310d-="">&#8617;</button>
                <button id="redoBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; performRedo()" disabled data-cf-modified-4ec5c5409a4e93a82f20310d-="">&#8618;</button>
                <button class="danger" onclick="if (!window.__cfRLUnblockHandlers) return false; resetAll()" data-cf-modified-4ec5c5409a4e93a82f20310d-=""> 重置 </button>
            </div>
        </div>

        <div class="group">
            <label>&#128190; 导出</label>
            <div class="row">
                <button class="success" onclick="if (!window.__cfRLUnblockHandlers) return false; exportFile('pdf')" data-cf-modified-4ec5c5409a4e93a82f20310d-="">PDF</button>
                <button class="success" onclick="if (!window.__cfRLUnblockHandlers) return false; exportFile('png')" data-cf-modified-4ec5c5409a4e93a82f20310d-="">PNG</button>
                <button class="success" onclick="if (!window.__cfRLUnblockHandlers) return false; exportFile('jpg')" data-cf-modified-4ec5c5409a4e93a82f20310d-="">JPG</button>
            </div>
        </div>

        <div class="group">
            <label>语言</label>
            <select onchange="if (!window.__cfRLUnblockHandlers) return false; location.href='https://measureonimage.com/' + (this.value === 'en' ? '' : this.value + '/')" style="min-width: 80px; padding: 5px;" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
                                    <option value="en" >English</option>
                                    <option value="es" >Espa&#241;ol</option>
                                    <option value="fr" >Fran&#231;ais</option>
                                    <option value="de" >Deutsch</option>
                                    <option value="pt" >Português</option>
                                    <option value="it" >Italiano</option>
                                    <option value="zh" selected>中文 (Chinese)</option>
                                    <option value="lt" >Lietuvi&#371;</option>
                                    <option value="pl" >Polski</option>
                                    <option value="ru" >Русский</option>
                                    <option value="ja" >日本語</option>
                                    <option value="ar" >&#1575;&#1604;&#1593;&#1585;&#1576;&#1610;&#1577;</option>
                                    <option value="hi" >&#2361;&#2367;&#2344;&#2381;&#2342;&#2368;</option>
                                    <option value="ko" >&#54620;&#44397;&#50612;</option>
                                    <option value="vi" >Ti&#7871;ng Vi&#7879;t</option>
                                    <option value="tr" >Türk&#231;e</option>
                                    <option value="nl" >Nederlands</option>
                                    <option value="sv" >Svenska</option>
                                    <option value="id" >Bahasa Indonesia</option>
                                    <option value="th" >&#3652;&#3607;&#3618;</option>
                                    <option value="ro" >Rom&#226;n&#259;</option>
                                    <option value="hu" >Magyar</option>
                                    <option value="fi" >Suomi</option>
                                    <option value="bn" >&#2476;&#2494;&#2434;&#2482;&#2494;</option>
                                    <option value="ur" >&#1575;&#1585;&#1583;&#1608;</option>
                                    <option value="fa" >&#1601;&#1575;&#1585;&#1587;&#1740;</option>
                                    <option value="sw" >Kiswahili</option>
                                    <option value="uk" >Укра&#1111;нська</option>
                                    <option value="cs" >&#268;e&#353;tina</option>
                                    <option value="el" >Ελληνικ&#940;</option>
                                    <option value="da" >Dansk</option>
                                    <option value="no" >Norsk</option>
                                    <option value="bg" >Български</option>
                                    <option value="mr" >&#2350;&#2352;&#2366;&#2336;&#2368;</option>
                                    <option value="te" >&#3108;&#3142;&#3122;&#3137;&#3095;&#3137;</option>
                                    <option value="ta" >&#2980;&#2990;&#3007;&#2996;&#3021;</option>
                                    <option value="tl" >Filipino</option>
                                    <option value="jv" >Basa Jawa</option>
                                    <option value="pa" >&#2602;&#2672;&#2588;&#2622;&#2604;&#2624;</option>
                                    <option value="kn" >&#3221;&#3240;&#3277;&#3240;&#3233;</option>
                                    <option value="gu" >&#2711;&#2753;&#2716;&#2736;&#2750;&#2724;&#2752;</option>
                                    <option value="my" >&#4121;&#4156;&#4116;&#4154;&#4121;&#4140;</option>
                                    <option value="ha" >Hausa</option>
                                    <option value="eo" >Esperanto</option>
                            </select>
        </div>

        <div class="group">
            <label>帮助</label>
            <button id="helpBtn" class="secondary" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleHelp()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">?</button>
        </div>
    </div>

    <div id="canvas-container">
        <div id="mode-indicator">1 Finger: Draw &#8226; 2 Fingers: Pan/Zoom</div>
        <div id="drop-zone">
            <div class="drop-box" onclick="if (!window.__cfRLUnblockHandlers) return false; document.getElementById('imageLoader').click()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">
                <div class="icon">&#128193;</div>
                <h2>上传您的图像</h2>
                <p>将文件拖到此处或使用上面的按钮</p>
            </div>
        </div>
        <canvas id="imageCanvas"></canvas>
    </div>

    <div id="helpModal">
        <div class="modal-content">
            <div class="close-modal" onclick="if (!window.__cfRLUnblockHandlers) return false; toggleHelp()" data-cf-modified-4ec5c5409a4e93a82f20310d-="">&#10005;</div>
            <h2>&#128208; 使用说明</h2>
            <ul style="line-height: 1.6; padding-left: 20px;">
                <li>在已知距离上画一条红色比例线进行校准。</li>
                <li>在物体上画其他线条 - 系统将计算长度。</li>
                <li>双击任何标签以编辑其值。</li>
                <li>选择一个元素并按 DEL 键将其删除。</li>
                <div style="text-align: center">- - - - - - - - - -</div>
                <li>桌面: 左键绘制。空格 + 拖动 (或右键) 移动。</li>
                <li>手机: 单指绘制。双指 移动和缩放。</li>

            </ul>
        </div>
    </div>
</div>


<script type="4ec5c5409a4e93a82f20310d-text/javascript">
    const translations = {
        svgTitle: "图像测量",
        svgSub: "(上传蓝图、平面图或照片)",
        svgInstr: "开始使用:",
        instr1: "在已知距离上画一条红色比例线进行校准。",
        instr2: "在物体上画其他线条 - 系统将计算长度。",
        instr3: "双击任何标签以编辑其值。",
        instr4: "选择一个元素并按 DEL 键将其删除。",
        instr5: "桌面: 左键绘制。空格 + 拖动 (或右键) 移动。",
        instr6: "手机: 单指绘制。双指 移动和缩放。",
    };

    const canvas = document.getElementById('imageCanvas');
    const ctx = canvas.getContext('2d', { alpha: false });
    const container = document.getElementById('canvas-container');
    const dropZone = document.getElementById('drop-zone');
    const modeIndicator = document.getElementById('mode-indicator');

    let img = new Image();
    let lines = [];
    let customTexts = [];
    let selectedElement = null;
    let undoStack = [], redoStack = [];
    let scale = 1, offsetX = 0, offsetY = 0;
    let isDrawing = false, isPanning = false, textMode = false, spacePressed = false;
    let customLineMode = false;
    let pointers = [];
    let prevDiff = -1;
    let lastCenter = null;
    let draggedPoint = null, draggedText = null, draggedWholeLine = null;
    let pointerStartX, pointerStartY, originalElementState = null;

    const SNAP_THRESHOLD = 20;
    const HIT_RADIUS = 20;

    document.addEventListener('DOMContentLoaded', () => {
        resizeCanvas();
        loadInitialSVG();
        initHistory();
        window.addEventListener('resize', () => { resizeCanvas(); requestRedraw(); });
    });

    function loadInitialSVG() {
        const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="800" viewBox="0 0 1000 800"><rect width="100%" height="100%" fill="#f8f9fa"/><defs><pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"><path d="M 40 0 L 0 0 0 40" fill="none" stroke="#e0e4e8" stroke-width="1"/></pattern></defs><rect width="100%" height="100%" fill="url(#grid)" /><rect x="50" y="50" width="900" height="700" fill="none" stroke="#cfd8dc" stroke-width="2" rx="10"/><path d="M200 200 h600 v300 h-600 z M200 350 h250 M450 200 v300" fill="none" stroke="#546e7a" stroke-width="3" stroke-linejoin="round"/><text x="500" y="100" font-family="Segoe UI, Arial" font-size="28" font-weight="600" text-anchor="middle" fill="#2c3e50">${translations.svgTitle}</text><path d="M205 205 l15 15 M795 205 l-15 15" stroke="#d32f2f" stroke-width="2" stroke-linecap="round"/><text x="500" y="130" font-family="Arial" font-size="14" text-anchor="middle" fill="#90a4ae">${translations.svgSub}</text><g transform="translate(100, 540)"><rect width="800" height="180" fill="white" fill-opacity="0.6" rx="8" stroke="#eceff1"/><text x="400" y="30" font-family="Segoe UI, Arial" font-size="18" font-weight="bold" text-anchor="middle" fill="#455a64">${translations.svgInstr}</text><text x="400" y="60" font-family="Segoe UI, Arial" font-size="15" text-anchor="middle" fill="#d32f2f">1. ${translations.instr1}</text><text x="400" y="80" font-family="Segoe UI, Arial" font-size="15" text-anchor="middle" fill="#546e7a">2. ${translations.instr2}</text><text x="400" y="110" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">3. ${translations.instr3}</text><text x="400" y="125" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">4. ${translations.instr4}</text><text x="400" y="140" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">5. ${translations.instr5}</text><text x="400" y="155" font-family="Segoe UI, Arial" font-size="12" text-anchor="middle" fill="#78909c" font-style="italic">6. ${translations.instr6}</text></g></svg>`;
        loadSource("data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgString))), true, false);
    }

    canvas.addEventListener('pointerdown', handlePointerDown);
    canvas.addEventListener('pointermove', handlePointerMove);
    canvas.addEventListener('pointerup', handlePointerUp);
    canvas.addEventListener('pointercancel', handlePointerUp);
    canvas.addEventListener('pointerout', handlePointerUp);

    window.addEventListener('keydown', (e) => {
        if (e.code === 'Space') {
            spacePressed = true;
            container.style.cursor = 'grab';
            e.preventDefault();
        }
        if (e.key === 'Delete' || e.key === 'Backspace') deleteSelected();
        if ((e.ctrlKey || e.metaKey) && e.key === 'z') performUndo();
        if ((e.ctrlKey || e.metaKey) && e.key === 'y') performRedo();
    });
    window.addEventListener('keyup', (e) => {
        if (e.code === 'Space') {
            spacePressed = false;
            container.style.cursor = textMode ? 'text' : 'crosshair';
        }
    });

    container.addEventListener('wheel', (e) => {
        e.preventDefault();
        const zoomIntensity = 0.1;
        const delta = e.deltaY > 0 ? (1 - zoomIntensity) : (1 + zoomIntensity);
        zoomToPoint(e.clientX, e.clientY, delta);
    }, { passive: false });

    document.getElementById('imageLoader').onchange = (e) => handleFile(e.target.files[0]);
    window.addEventListener('dragover', (e) => {
        e.preventDefault();
        dropZone.classList.add('active');
    });
    window.addEventListener('dragleave', (e) => {
        e.preventDefault();
        if (e.relatedTarget === null) dropZone.classList.remove('active');
    });
    window.addEventListener('drop', (e) => {
        e.preventDefault();
        dropZone.classList.remove('active');
        if (e.dataTransfer.files[0]) handleFile(e.dataTransfer.files[0]);
    });

    function handlePointerDown(e) {
        canvas.setPointerCapture(e.pointerId);
        pointers.push(e);

        if (pointers.length === 2) {
            showModeIndicator(true);
            if (isDrawing) { lines.pop(); isDrawing = false; }
            draggedPoint = null; draggedText = null; draggedWholeLine = null;
            const p1 = pointers[0]; const p2 = pointers[1];
            prevDiff = Math.hypot(p1.clientX - p2.clientX, p1.clientY - p2.clientY);
            lastCenter = { x: (p1.clientX + p2.clientX)/2, y: (p1.clientY + p2.clientY)/2 };
            return;
        }

        const pt = getCanvasCoordinates(e.clientX, e.clientY);

        if (pointers.length === 1) {
            if (spacePressed || e.button === 2) {
                isPanning = true;
                container.style.cursor = 'grabbing';
                return;
            }
            if (textMode) { createTextInput(e.clientX, e.clientY, pt.x, pt.y); return; }

            for (let i = customTexts.length - 1; i >= 0; i--) {
                if (isHitText(customTexts[i], pt.x, pt.y)) {
                    selectedElement = { type: 'text', index: i }; draggedText = i;
                    pointerStartX = pt.x; pointerStartY = pt.y;
                    requestRedraw(); updateUI(); return;
                }
            }
            for (let i = 0; i < lines.length; i++) {
                if (dist(lines[i].x1, lines[i].y1, pt.x, pt.y) < HIT_RADIUS / scale) { draggedPoint = { idx: i, type: 'p1' }; return; }
                if (dist(lines[i].x2, lines[i].y2, pt.x, pt.y) < HIT_RADIUS / scale) { draggedPoint = { idx: i, type: 'p2' }; return; }
            }
            for (let i = 0; i < lines.length; i++) {
                const center = { x: (lines[i].x1 + lines[i].x2)/2, y: (lines[i].y1 + lines[i].y2)/2 };
                if (dist(center.x, center.y, pt.x, pt.y) < HIT_RADIUS / scale) {
                    selectedElement = { type: 'line', index: i }; draggedWholeLine = i;
                    pointerStartX = pt.x; pointerStartY = pt.y; originalElementState = { ...lines[i] };
                    requestRedraw(); updateUI(); return;
                }
            }

            selectedElement = null; updateUI();
            const snap = getSnappedPos(pt, -1);
            isDrawing = true;
            lines.push({
                x1: snap.x, y1: snap.y, x2: snap.x, y2: snap.y,
                lColor: document.getElementById('lineColor').value,
                lWidth: parseInt(document.getElementById('lineWidth').value),
                tColor: document.getElementById('lineTextColor').value,
                tSize: parseInt(document.getElementById('lineTextSize').value),
                cap: document.getElementById('capType').value,
                isCustom: customLineMode
            });
            requestRedraw();
        }
    }

    function handlePointerMove(e) {
        const index = pointers.findIndex(p => p.pointerId === e.pointerId);
        if (index !== -1) pointers[index] = e;

        if (pointers.length === 2) {
            const p1 = pointers[0]; const p2 = pointers[1];
            const curCenter = { x: (p1.clientX + p2.clientX)/2, y: (p1.clientY + p2.clientY)/2 };
            const curDiff = Math.hypot(p1.clientX - p2.clientX, p1.clientY - p2.clientY);
            if (prevDiff > 0 && lastCenter) {
                const dx = curCenter.x - lastCenter.x;
                const dy = curCenter.y - lastCenter.y;
                offsetX += dx; offsetY += dy;
                const zoomFactor = curDiff / prevDiff;
                const oldScale = scale;
                scale *= zoomFactor;
                if (scale < 0.05) scale = 0.05; if (scale > 50) scale = 50;
                offsetX = curCenter.x - (curCenter.x - offsetX) * (scale / oldScale);
                offsetY = curCenter.y - (curCenter.y - offsetY) * (scale / oldScale);
                requestRedraw();
            }
            prevDiff = curDiff; lastCenter = curCenter;
            return;
        }

        if (pointers.length === 1) {
            const pt = getCanvasCoordinates(e.clientX, e.clientY);
            if (isPanning) { offsetX += e.movementX; offsetY += e.movementY; requestRedraw(); return; }
            if (isDrawing) {
                const snap = getSnappedPos(pt, lines.length - 1);
                lines[lines.length - 1].x2 = snap.x; lines[lines.length - 1].y2 = snap.y;
                requestRedraw();
            } else if (draggedPoint) {
                const snap = getSnappedPos(pt, draggedPoint.idx);
                if (draggedPoint.type === 'p1') { lines[draggedPoint.idx].x1 = snap.x; lines[draggedPoint.idx].y1 = snap.y; }
                else { lines[draggedPoint.idx].x2 = snap.x; lines[draggedPoint.idx].y2 = snap.y; }
                requestRedraw();
            } else if (draggedText !== null) {
                customTexts[draggedText].x = pt.x; customTexts[draggedText].y = pt.y;
                requestRedraw();
            } else if (draggedWholeLine !== null) {
                const baseDx = pt.x - pointerStartX; const baseDy = pt.y - pointerStartY;
                lines[draggedWholeLine].x1 = originalElementState.x1 + baseDx; lines[draggedWholeLine].y1 = originalElementState.y1 + baseDy;
                lines[draggedWholeLine].x2 = originalElementState.x2 + baseDx; lines[draggedWholeLine].y2 = originalElementState.y2 + baseDy;
                requestRedraw();
            }
        }
    }

    function handlePointerUp(e) {
        pointers = pointers.filter(p => p.pointerId !== e.pointerId);
        if (pointers.length < 2) { prevDiff = -1; lastCenter = null; showModeIndicator(false); }
        if (isPanning && pointers.length === 0) {
            isPanning = false;
            container.style.cursor = spacePressed ? 'grab' : (textMode ? 'text' : 'crosshair');
        }
        if (isDrawing) {
            const l = lines[lines.length - 1];
            if (Math.hypot(l.x2 - l.x1, l.y2 - l.y1) < 5 / scale) { lines.pop(); } else { saveState(); }
        } else if (draggedPoint || draggedText !== null || draggedWholeLine !== null) {
            saveState();
        }
        isDrawing = false; draggedPoint = null; draggedText = null; draggedWholeLine = null; originalElementState = null;
        requestRedraw();
    }

    function getCanvasCoordinates(clientX, clientY) {
        const rect = canvas.getBoundingClientRect();
        return { x: (clientX - rect.left - offsetX) / scale, y: (clientY - rect.top - offsetY) / scale };
    }
    function zoomToPoint(cx, cy, factor) {
        const oldScale = scale;
        scale *= factor;
        if (scale < 0.05) scale = 0.05; if (scale > 50) scale = 50;
        offsetX = cx - (cx - offsetX) * (scale / oldScale);
        offsetY = cy - (cy - offsetY) * (scale / oldScale);
        requestRedraw();
    }
    function changeZoom(factor) {
        const rect = container.getBoundingClientRect();
        zoomToPoint(rect.left + rect.width/2, rect.top + rect.height/2, factor);
    }
    function fitToScreen() {
        if (!img.complete || img.naturalWidth === 0) return;
        const padding = 20;
        const scaleX = (canvas.width - padding*2) / img.naturalWidth;
        const scaleY = (canvas.height - padding*2) / img.naturalHeight;
        scale = Math.min(scaleX, scaleY);
        offsetX = (canvas.width - img.naturalWidth * scale) / 2;
        offsetY = (canvas.height - img.naturalHeight * scale) / 2;
        requestRedraw();
    }
    function setZoomOriginal() {
        scale = 1;
        offsetX = (canvas.width - img.naturalWidth) / 2;
        offsetY = (canvas.height - img.naturalHeight) / 2;
        requestRedraw();
    }

    function getSnappedPos(pos, currentLineIdx) {
        let bestDist = SNAP_THRESHOLD / scale;
        let bestPos = pos;
        lines.forEach((l, i) => {
            if (i === currentLineIdx) return;
            const d1 = dist(l.x1, l.y1, pos.x, pos.y); const d2 = dist(l.x2, l.y2, pos.x, pos.y);
            if (d1 < bestDist) { bestDist = d1; bestPos = {x: l.x1, y: l.y1}; }
            else if (d2 < bestDist) { bestDist = d2; bestPos = {x: l.x2, y: l.y2}; }
        });
        return bestPos;
    }
    function isHitText(t, x, y) {
        const w = t.text.length * (t.size * 0.6); const h = t.size;
        return (x > t.x - w/2 && x < t.x + w/2 && y > t.y - h && y < t.y + h/2);
    }
    function dist(x1, y1, x2, y2) { return Math.hypot(x2 - x1, y2 - y1); }

    let animationFrameId;
    function requestRedraw() { if (!animationFrameId) { animationFrameId = requestAnimationFrame(() => { draw(); animationFrameId = null; }); } }

    function draw() {
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        if (!img.complete || img.naturalWidth === 0) return;

        ctx.translate(offsetX, offsetY); ctx.scale(scale, scale); ctx.drawImage(img, 0, 0);

        let pixelsPerUnit = 0;
        if (lines[0]) {
            const pxLen = dist(lines[0].x1, lines[0].y1, lines[0].x2, lines[0].y2);
            if (pxLen > 0) pixelsPerUnit = pxLen / parseFloat(document.getElementById('knownValue').value);
        }
        const unitName = document.getElementById('unitName').value;

        lines.forEach((l, i) => {
            const isRef = (i === 0);
            const isSel = (selectedElement && selectedElement.type === 'line' && selectedElement.index === i);
            ctx.beginPath(); ctx.lineWidth = l.lWidth;
            ctx.strokeStyle = isRef ? "#ff3b30" : (isSel ? "#f1c40f" : l.lColor);
            ctx.moveTo(l.x1, l.y1); ctx.lineTo(l.x2, l.y2); ctx.stroke();
            const angle = Math.atan2(l.y2 - l.y1, l.x2 - l.x1);
            drawCap(ctx, l.x1, l.y1, angle + Math.PI, l.cap, ctx.strokeStyle, l.lWidth);
            drawCap(ctx, l.x2, l.y2, angle, l.cap, ctx.strokeStyle, l.lWidth);

            if (!l.isCustom || isRef) {
                let valText = isRef ? parseFloat(document.getElementById('knownValue').value).toString() : (dist(l.x1,l.y1,l.x2,l.y2)/pixelsPerUnit).toFixed(2);
                drawTextWithBg(valText + " " + unitName, (l.x1 + l.x2)/2, (l.y1 + l.y2)/2 - 10/scale, l.tSize, l.tColor, isSel);
            }
        });

        customTexts.forEach((t, i) => {
            const isSel = (selectedElement && selectedElement.type === 'text' && selectedElement.index === i);
            drawTextWithBg(t.text, t.x, t.y, t.size, isSel ? "#f1c40f" : t.color, isSel);
        });
    }

    function drawCap(ctx, x, y, angle, type, color, w) {
        if (type === 'none') return;
        ctx.save(); ctx.translate(x, y); ctx.rotate(angle);
        ctx.fillStyle = color; ctx.strokeStyle = color; ctx.lineWidth = w;
        ctx.beginPath();
        if (type === 'arrow') { const s = w * 3; ctx.moveTo(0, 0); ctx.lineTo(-s, -s/2); ctx.lineTo(-s, s/2); ctx.fill(); }
        else if (type === 'tshape') { const s = w * 4; ctx.moveTo(0, -s/2); ctx.lineTo(0, s/2); ctx.stroke(); }
        else if (type === 'circle') { ctx.arc(0, 0, w * 1.5, 0, Math.PI * 2); ctx.fill(); }
        ctx.restore();
    }
    function drawTextWithBg(txt, x, y, size, color, isSel) {
        ctx.save();
        ctx.font = `bold ${size}px sans-serif`; ctx.textAlign = "center"; ctx.textBaseline = "middle";
        ctx.strokeStyle = "rgba(0,0,0,0.8)"; ctx.lineWidth = size / 8; ctx.strokeText(txt, x, y);
        ctx.fillStyle = color; ctx.fillText(txt, x, y);
        ctx.restore();
    }

    function handleFile(file) {
        const reader = new FileReader();
        reader.onload = (ev) => loadSource(ev.target.result, true, true);
        reader.readAsDataURL(file);
    }

    function loadSource(src, resetZoom, hideDropZone) {
        img = new Image();
        img.onload = () => {
            if (hideDropZone) dropZone.style.display = 'none';
            if (resetZoom) {
                lines = [];
                customTexts = [];
                fitToScreen();
                initHistory();
            }
            requestRedraw();
        };
        img.src = src;
    }
    function resizeCanvas() { canvas.width = container.clientWidth; canvas.height = container.clientHeight; requestRedraw(); }

    function toggleTextMode() { textMode = !textMode; customLineMode = false; document.getElementById('addTextBtn').classList.toggle('active', textMode); document.getElementById('customLineBtn').classList.remove('active'); }
    function toggleCustomLineMode() { customLineMode = !customLineMode; textMode = false; document.getElementById('customLineBtn').classList.toggle('active', customLineMode); document.getElementById('addTextBtn').classList.remove('active'); }
    function deleteSelected() { if (!selectedElement) return; if (selectedElement.type === 'line') lines.splice(selectedElement.index, 1); else customTexts.splice(selectedElement.index, 1); selectedElement = null; saveState(); updateUI(); requestRedraw(); }

    function resetAll() {
        if (confirm("Clear all?")) {
            lines = [];
            customTexts = [];
            selectedElement = null;
            saveState();
            requestRedraw();
        }
    }

    function createTextInput(screenX, screenY, canvasX, canvasY) {
        spawnInput(screenX, screenY, "", (val) => {
            if (val) { customTexts.push({ x: canvasX, y: canvasY, text: val, color: document.getElementById('textColor').value, size: parseInt(document.getElementById('textSize').value) }); saveState(); }
        });
    }
    function spawnInput(x, y, initialVal, callback) {
        const input = document.createElement('input');
        input.id = 'tempTextInput'; input.type = 'text'; input.value = initialVal;
        input.style.left = x + 'px'; input.style.top = y + 'px';
        document.body.appendChild(input);
        setTimeout(() => { input.focus(); }, 50);
        const finish = () => { callback(input.value.trim()); if (input.parentNode) input.parentNode.removeChild(input); textMode = false; document.getElementById('addTextBtn').classList.remove('active'); requestRedraw(); };
        input.addEventListener('keydown', (e) => { if (e.key === 'Enter') finish(); });
        input.addEventListener('blur', finish);
    }

    // --- UNDO / REDO  ---
    function initHistory() {
        undoStack = [JSON.stringify({ l: [], t: [] })];
        redoStack = [];
        updateUI();
    }

    function saveState() {
        redoStack = [];
        undoStack.push(JSON.stringify({ l: lines, t: customTexts }));
        updateUI();
    }

    function performUndo() {
        if (undoStack.length <= 1) return;

        const currentState = undoStack.pop();
        redoStack.push(currentState);

        const prevState = JSON.parse(undoStack[undoStack.length - 1]);

        lines = prevState.l.map(obj => ({...obj}));
        customTexts = prevState.t.map(obj => ({...obj}));
        selectedElement = null;
        requestRedraw();
        updateUI();
    }

    function performRedo() {
        if (redoStack.length === 0) return;

        const nextStateStr = redoStack.pop();
        const nextState = JSON.parse(nextStateStr);

        undoStack.push(nextStateStr);

        lines = nextState.l.map(obj => ({...obj}));
        customTexts = nextState.t.map(obj => ({...obj}));
        selectedElement = null;
        requestRedraw();
        updateUI();
    }

    function updateUI() {
        document.getElementById('deleteBtn').disabled = !selectedElement;
        document.getElementById('undoBtn').disabled = undoStack.length <= 1;
        document.getElementById('redoBtn').disabled = redoStack.length === 0;
    }
    // --- UNDO / REDO  ---

    function showModeIndicator(show) { modeIndicator.classList.toggle('visible', show); }
    function toggleHelp() { document.getElementById('helpModal').style.display = (document.getElementById('helpModal').style.display === 'flex') ? 'none' : 'flex'; }

    function exportFile(type) {
        if (!img.complete || img.naturalWidth === 0) return;
        const tempCanvas = document.createElement('canvas'); tempCanvas.width = img.naturalWidth; tempCanvas.height = img.naturalHeight; const tCtx = tempCanvas.getContext('2d'); tCtx.drawImage(img, 0, 0);
        let pixelsPerUnit = 0; if (lines[0]) { pixelsPerUnit = dist(lines[0].x1, lines[0].y1, lines[0].x2, lines[0].y2) / parseFloat(document.getElementById('knownValue').value); }
        const unitName = document.getElementById('unitName').value;
        lines.forEach((l, i) => {
            tCtx.lineWidth = l.lWidth; tCtx.strokeStyle = (i===0) ? "#ff3b30" : l.lColor;
            tCtx.beginPath(); tCtx.moveTo(l.x1, l.y1); tCtx.lineTo(l.x2, l.y2); tCtx.stroke();
            if (!l.isCustom || i===0) {
                let txt = (i===0) ? document.getElementById('knownValue').value : (dist(l.x1,l.y1,l.x2,l.y2)/pixelsPerUnit).toFixed(2);
                tCtx.font = `bold ${l.tSize}px sans-serif`; tCtx.textAlign = "center"; tCtx.fillStyle = l.tColor;
                tCtx.strokeText(txt+" "+unitName, (l.x1+l.x2)/2, (l.y1+l.y2)/2-10); tCtx.fillText(txt+" "+unitName, (l.x1+l.x2)/2, (l.y1+l.y2)/2-10);
            }
        });
        customTexts.forEach(t => { tCtx.font = `bold ${t.size}px sans-serif`; tCtx.textAlign = "center"; tCtx.fillStyle = t.color; tCtx.strokeText(t.text, t.x, t.y); tCtx.fillText(t.text, t.x, t.y); });
        const link = document.createElement('a'); link.download = 'measurement.'+type; link.href = tempCanvas.toDataURL('image/'+(type==='png'?'png':'jpeg')); link.click();
    }
</script>

<style>
    .blog-teasers {
        max-width: 900px;
        margin: 40px auto 0;
        padding: 0 20px;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    }
    .blog-teasers h2 {
        font-size: 1.6rem;
        color: #ffffff;
        border-bottom: 2px solid var(--accent, #007aff);
        padding-bottom: 10px;
        margin-bottom: 20px;
    }
    .teaser-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
        gap: 20px;
    }
    .teaser-card {
        background: var(--item-bg, #2d2d2d);
        border: 1px solid var(--border, #444);
        border-radius: 12px;
        padding: 20px;
        text-decoration: none;
        transition: transform 0.2s, border-color 0.2s;
        display: flex;
        flex-direction: column;
    }
    .teaser-card:hover {
        transform: translateY(-5px);
        border-color: var(--accent, #007aff);
    }
    .teaser-category {
        font-size: 0.8rem;
        color: var(--accent, #007aff);
        text-transform: uppercase;
        font-weight: 700;
        margin-bottom: 8px;
    }
    .teaser-title {
        color: #ffffff;
        font-size: 1.15rem;
        font-weight: 600;
        margin: 0 0 10px 0;
        line-height: 1.4;
    }
    .teaser-desc {
        color: #a0a0a0;
        font-size: 0.9rem;
        line-height: 1.5;
        margin: 0;
        flex-grow: 1;
    }
    .read-more {
        margin-top: 15px;
        font-size: 0.9rem;
        color: #fff;
        font-weight: 600;
    }
</style>

<div class="blog-teasers">
    <h2>Guides & Tutorials</h2>
    <div class="teaser-grid">
        <a href="https://measureonimage.com/blog#photo-measurement" class="teaser-card">
            <span class="teaser-category">Tutorial</span>
            <h3 class="teaser-title">Measuring from Photos</h3>
            <p class="teaser-desc">Learn how to accurately extract real-world dimensions and room sizes using just a single digital photograph.</p>
            <span class="read-more">Read article →</span>
        </a>
        <a href="https://measureonimage.com/blog#blueprint-guide" class="teaser-card">
            <span class="teaser-category">Guide</span>
            <h3 class="teaser-title">Reading Blueprints</h3>
            <p class="teaser-desc">A beginner's guide to understanding architect floor plans, symbols, and how to scale them digitally.</p>
            <span class="read-more">Read article →</span>
        </a>
        <a href="https://measureonimage.com/blog#diy-renovation" class="teaser-card">
            <span class="teaser-category">DIY & Planning</span>
            <h3 class="teaser-title">Home Renovation Tips</h3>
            <p class="teaser-desc">Top planning strategies to save money and time during your next DIY home improvement project.</p>
            <span class="read-more">Read article →</span>
        </a>
    </div>
</div>


<style>
    .content-sections {
        max-width: 900px;
        margin: 0 auto;
        padding: 40px 20px 20px;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        color: #e0e0e0;
        direction: ltr;
    }

    .content-sections h2 {
        font-size: 1.6rem;
        color: #ffffff;
        border-bottom: 2px solid #007aff;
        padding-bottom: 10px;
        margin-top: 50px;
        margin-bottom: 20px;
    }

    .content-sections h3 {
        font-size: 1.1rem;
        color: #ffffff;
        margin-bottom: 6px;
    }

    .content-sections p {
        line-height: 1.7;
        color: #c0c0c0;
        margin-bottom: 14px;
    }

    .how-steps {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
        gap: 16px;
        margin-bottom: 20px;
    }

    .how-step {
        background: #1e1e1e;
        border: 1px solid #333;
        border-radius: 10px;
        padding: 16px;
    }

    .how-step h3 {
        color: #007aff;
        font-size: 0.95rem;
        margin-bottom: 8px;
    }

    .how-step p {
        font-size: 0.9rem;
        margin: 0;
    }

    .faq-item {
        background: #1e1e1e;
        border: 1px solid #333;
        border-radius: 10px;
        padding: 16px 20px;
        margin-bottom: 12px;
    }

    .faq-item h3 {
        font-size: 1rem;
        color: #ffffff;
        margin-bottom: 8px;
    }

    .faq-item p {
        font-size: 0.9rem;
        margin: 0;
        color: #aaa;
    }

    .usecases-list {
        list-style: none;
        padding: 0;
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
        gap: 10px;
        margin-bottom: 20px;
    }

    .usecases-list li {
        background: #1e1e1e;
        border: 1px solid #333;
        border-radius: 8px;
        padding: 12px 16px;
        font-size: 0.9rem;
        color: #c0c0c0;
        line-height: 1.5;
    }

    .usecases-list li::before {
        content: "&#10003; ";
        color: #28cd41;
        font-weight: bold;
    }

    @media (max-width: 600px) {
        .how-steps, .usecases-list {
            grid-template-columns: 1fr;
        }

        .content-sections h2 {
            font-size: 1.3rem;
        }
    }
</style>

<div class="content-sections">

    <h2>关于 MeasureOnImage.com</h2>
    <p>MeasureOnImage.com 是一款免费的、基于浏览器的工具,可直接在任何图像上测量距离和尺寸——平面图、蓝图、地图、技术图纸或照片。无需安装,无需上传至服务器。</p>

    <h2>使用方法</h2>
    <div class="how-steps">
                    <div class="how-step">
                <h3>1. 上传图像</h3>
                <p>点击&quot;上传图像&quot;或将照片、平面图或地图拖放到画布上。支持 JPG、PNG 格式。</p>
            </div>
                    <div class="how-step">
                <h3>2. 绘制比例线</h3>
                <p>在已知尺寸的对象上绘制一条红色比例线——例如门(2米)、桌子(1.5米)或尺子。输入已知值和单位。</p>
            </div>
                    <div class="how-step">
                <h3>3. 测量任何对象</h3>
                <p>在需要测量的对象上绘制其他线条,系统将自动根据比例计算实际长度。</p>
            </div>
                    <div class="how-step">
                <h3>4. 导出结果</h3>
                <p>将标注图像下载为 PNG、JPG 或 PDF。</p>
            </div>
            </div>

    <h2>常见使用场景</h2>
    <ul class="usecases-list">
                    <li>平面图测量 — 从房产或建筑图纸估算房间尺寸。</li>
                    <li>蓝图缩放 — 验证建筑或工程图纸上的尺寸。</li>
                    <li>地图测距 — 根据已知比例计算地图上的实际距离。</li>
                    <li>家具规划 — 从照片测量家具和空间。</li>
                    <li>产品尺寸估计 — 从目录图像确定大致的产品尺寸。</li>
                    <li>土地分析 — 从卫星或地籍地图图像测量地块面积。</li>
            </ul>

    <h2>常见问题</h2>
            <div class="faq-item">
            <h3>MeasureOnImage.com 是免费的吗?</h3>
            <p>是的,该工具完全免费,无需注册。</p>
        </div>
            <div class="faq-item">
            <h3>我的图像会上传到服务器吗?</h3>
            <p>不会。图像完全在您的浏览器中处理,永远不会离开您的设备。</p>
        </div>
            <div class="faq-item">
            <h3>可以测量哪些类型的图像?</h3>
            <p>平面图、蓝图、地图、建筑图纸、产品照片等。</p>
        </div>
            <div class="faq-item">
            <h3>测量有多准确?</h3>
            <p>准确度取决于您设置的比例参考。如果您使用已知对象设置比例,测量结果将按比例准确。</p>
        </div>
            <div class="faq-item">
            <h3>该工具支持手机使用吗?</h3>
            <p>支持。单指绘制线条,双指移动和缩放。</p>
        </div>
            <div class="faq-item">
            <h3>可以使用哪些单位?</h3>
            <p>米、厘米、毫米、英寸、英尺或任何自定义单位。</p>
        </div>
            <div class="faq-item">
            <h3>我可以保存测量结果吗?</h3>
            <p>您可以将标注后的图像导出为 PNG、JPG 或 PDF 以保存您的工作。</p>
        </div>
    
    <!-- FAQ JSON-LD Schema for Google -->
    <script type="application/ld+json">
        {
            "@context": "https://schema.org",
            "@type": "FAQPage",
            "mainEntity": [
                    {
                "@type": "Question",
                "name": "MeasureOnImage.com \u662f\u514d\u8d39\u7684\u5417\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u662f\u7684\uff0c\u8be5\u5de5\u5177\u5b8c\u5168\u514d\u8d39\uff0c\u65e0\u9700\u6ce8\u518c\u3002"                }
            },                    {
                "@type": "Question",
                "name": "\u6211\u7684\u56fe\u50cf\u4f1a\u4e0a\u4f20\u5230\u670d\u52a1\u5668\u5417\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u4e0d\u4f1a\u3002\u56fe\u50cf\u5b8c\u5168\u5728\u60a8\u7684\u6d4f\u89c8\u5668\u4e2d\u5904\u7406\uff0c\u6c38\u8fdc\u4e0d\u4f1a\u79bb\u5f00\u60a8\u7684\u8bbe\u5907\u3002"                }
            },                    {
                "@type": "Question",
                "name": "\u53ef\u4ee5\u6d4b\u91cf\u54ea\u4e9b\u7c7b\u578b\u7684\u56fe\u50cf\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u5e73\u9762\u56fe\u3001\u84dd\u56fe\u3001\u5730\u56fe\u3001\u5efa\u7b51\u56fe\u7eb8\u3001\u4ea7\u54c1\u7167\u7247\u7b49\u3002"                }
            },                    {
                "@type": "Question",
                "name": "\u6d4b\u91cf\u6709\u591a\u51c6\u786e\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u51c6\u786e\u5ea6\u53d6\u51b3\u4e8e\u60a8\u8bbe\u7f6e\u7684\u6bd4\u4f8b\u53c2\u8003\u3002\u5982\u679c\u60a8\u4f7f\u7528\u5df2\u77e5\u5bf9\u8c61\u8bbe\u7f6e\u6bd4\u4f8b\uff0c\u6d4b\u91cf\u7ed3\u679c\u5c06\u6309\u6bd4\u4f8b\u51c6\u786e\u3002"                }
            },                    {
                "@type": "Question",
                "name": "\u8be5\u5de5\u5177\u652f\u6301\u624b\u673a\u4f7f\u7528\u5417\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u652f\u6301\u3002\u5355\u6307\u7ed8\u5236\u7ebf\u6761\uff0c\u53cc\u6307\u79fb\u52a8\u548c\u7f29\u653e\u3002"                }
            },                    {
                "@type": "Question",
                "name": "\u53ef\u4ee5\u4f7f\u7528\u54ea\u4e9b\u5355\u4f4d\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u7c73\u3001\u5398\u7c73\u3001\u6beb\u7c73\u3001\u82f1\u5bf8\u3001\u82f1\u5c3a\u6216\u4efb\u4f55\u81ea\u5b9a\u4e49\u5355\u4f4d\u3002"                }
            },                    {
                "@type": "Question",
                "name": "\u6211\u53ef\u4ee5\u4fdd\u5b58\u6d4b\u91cf\u7ed3\u679c\u5417\uff1f",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "\u60a8\u53ef\u4ee5\u5c06\u6807\u6ce8\u540e\u7684\u56fe\u50cf\u5bfc\u51fa\u4e3a PNG\u3001JPG \u6216 PDF \u4ee5\u4fdd\u5b58\u60a8\u7684\u5de5\u4f5c\u3002"                }
            }                ]
    }
    </script>

</div>

<style>
    .site-footer {
        background-color: var(--panel-bg, #1e1e1e);
        border-top: 1px solid var(--border, #444);
        padding: 30px 20px;
        text-align: center;
        margin-top: auto; /* Prispaud&#382;ia footer&#303; prie apa&#269;ios, jei puslapis trumpas */
        width: 100%;
        box-sizing: border-box;
    }
    .site-footer .footer-links {
        display: flex;
        flex-wrap: wrap; /* Leid&#382;ia nuorodoms gra&#382;iai persikelti &#303; kit&#261; eilut&#281; mobiliuosiuose */
        justify-content: center;
        align-items: center;
        gap: 15px 25px; /* Tarpai: 15px vertikaliai, 25px horizontaliai */
        margin-bottom: 15px;
    }
    .site-footer a {
        color: var(--text-dim, #a0a0a0);
        text-decoration: none;
        font-size: 14px;
        font-weight: 500;
        transition: color 0.2s ease;
        white-space: nowrap; /* Neleid&#382;ia vienos nuorodos &#382;od&#382;iams lū&#382;ti per dvi eilutes */
    }
    .site-footer a:hover {
        color: var(--accent, #007aff);
    }
    .site-footer p {
        color: var(--text-dim, #a0a0a0);
        font-size: 12px;
        margin: 0;
        opacity: 0.7;
    }
</style>

<footer class="site-footer">
    <div class="footer-links">
        <a href="/zh/privacy-policy">隐私政策</a>
        <a href="/zh/terms-of-service">服务条款</a>
        <a href="/zh/about-us">关于我们</a>
        <a href="/zh/contact">联系我们</a>
        <a href="/zh/blog">Blog & Guides</a>
    </div>
    <p>&#169; 2026 MeasureOnImage.com. All rights reserved.</p>
</footer>

<script src="/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js" data-cf-settings="4ec5c5409a4e93a82f20310d-|49" defer></script><script defer src="https://static.cloudflareinsights.com/beacon.min.js/v8c78df7c7c0f484497ecbca7046644da1771523124516" integrity="sha512-8DS7rgIrAmghBFwoOTujcf6D9rXvH8xm8JQ1Ja01h9QX8EzXldiszufYa4IFfKdLUKTTrnSFXLDkUEOTrZQ8Qg==" data-cf-beacon='{"version":"2024.11.0","token":"2e3197faebd747898c47de4a68adef7a","r":1,"server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>
4#
gaoxiaoao 发表于 2026-4-8 12:48
eryuetiankong 发表于 2026-4-8 08:36
直接提取网页源代码就能离线使用。
[mw_shl_code=html,true]

你真是个天才
5#
flyLoveforever 发表于 2026-4-8 13:00
eryuetiankong 发表于 2026-4-8 08:36
直接提取网页源代码就能离线使用。
[mw_shl_code=html,true]

大佬 这个怎么使用啊
6#
 楼主| 李小宝吖 发表于 2026-4-8 21:47 |楼主
eryuetiankong 发表于 2026-4-8 15:19
离线照片测量 – 图像比例与尺寸工具
https://wwbbn.lanzouw.com/ibXkH3mrm1ne

这波脑洞可以的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-5-24 14:04

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表