标定板在线生成器

使用方法
1、标定板参数设置。设置标定板参数,如圆点标定板则设置普通圆点半径大小以及点间距,并控制生成行列数,添加定位点(行、列、半径);棋盘格则只需设置行和列及间距。
2、电子标定板。点击“全屏”,你将通过显示屏获得一个电子标定板,其圆点坐标即为实际物理坐标(mm)。
3、打印标定板。你也可以选择“打印”,将标定板打印在纸张上,长度单位也是实际物理坐标(mm)。
4、标定板文件。你可以选择导入和导出 json 格式的标定板数据,用来保存标定板和快速导入标定板。

实际的显示器dpi可能不一样,请使用下方的标尺结合实际物理长度进行dpi校准

参考资料:https://calib.io/pages/camera-calibration-pattern-generator

<!-- run -->

    <style>
        .calib-board-container {
            width: 100%;
            height: 600px;
            margin: 0 auto;
            /* overflow: hidden; */
        }

        .calib-board {
            margin: 0;
            padding: 0;
            height: 100%;
            width: 100%;
            display: flex;
            gap: 10px;
            font-family: sans-serif;
    align-items: center;
    justify-content: center;
        }

        #dpiCalibration span {
            color: #606266;
            font-size: 14px;
            margin-bottom: 10px;
        }

        #decreaseLength {
            border-radius: 4px 0 0 4px;
            border-right-width: 0;
        }

        #increaseLength {
            border-radius: 0 4px 4px 0;
            border-left-width: 0;
        }

        @media screen and (max-width: 767px) {
            .calib-board {
                flex-direction: column;
            }

            #controls {
                width: 100%;
            }
        }
		#svg-container{
			border: 1px solid lightgray;
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: white;
            cursor: grab;
            border-radius: 4px;
			    display: flex;
    align-items: center;
    justify-content: center;
		}
        #svgContainer {
            width: 100%;
            height: 100%;
            overflow: hidden;
            background: white;
            cursor: grab;
            border-radius: 4px;
			    display: flex;
    align-items: center;
    justify-content: center;
        }
		.full-screen#svgContainer{
		border:none!important;
		}
        #controls {
            /* position: sticky; */
            float: right;
            top: 10px;
            right: 30px;
            max-width: 400px;
            width: 100%;
			height: 100%;
            border-radius: 4px;
            background: rgba(255, 255, 255, 0.95);
            padding: 15px;
            border-radius: 4px;
            border: 1px solid lightgray;
            z-index: 10;
            display: flex;
            flex-direction: column;
            gap: 10px;
            overflow-y: scroll;
        }

        #controls label {
            display: flex;
            gap: 20px;
        }

        #controls label input {
            flex: 1;
        }

        .custom-point {
            display: flex;
            align-items: center;
            gap: 6px;
            margin-bottom: 4px;
        }

        .custom-point input {
            width: 60px;
            padding: 4px;
        }

        .custom-point .rad {
            width: 80px;
        }

        .custom-point button {
            background: none;
            border: none;
            color: #d00;
            font-size: 16px;
            cursor: pointer;
        }

        .my-form {
            display: flex;
            gap: 10px;
        }

        .my-form .el-form-item {
            margin-bottom: 0px;
        }

        .functions button,
        .custom-point input {
            width: 100%;
        }

        @media print {
.postBody{
margin:auto!important;
padding:0!important;
}
            #blog_post_info_block,
            #comment_form_container,
            #footer,
            .float-btn,
            .postDesc,
            .postTitle,
            #header,
            #controls,
            #MySignature,
            #note,
            .post-header {
                display: none !important;
            }

            * {
                margin: 0 !important;
                padding: 0 !important;
                visibility: hidden;
            }

            .forFlow,
            #cnblogs_post_body {
                width: 100% !important;
                max-width: 100% !important;
                height: 100% !important;
                display: flex !important;
                align-items: ceter;
                justify-content: center;
            }

            .print-element,
            .print-element>* {
                visibility: visible !important;
            }

            #svgContainer,.calib-board-container {
width: 100vw;
        height: 100vh;
border:none!important;
            }

            @page {
                margin: 0;
            }
        }
    </style>

    <div id="app">
        <div class="calib-board-container">
            <div class="calib-board">
				<div id="svg-container">
                <div id="svgContainer" class="print-element">

                    <svg class="print-element" id="board" xmlns="http://www.w3.org/2000/svg"></svg>
 </div>
                </div>

                <div id="controls">
                    <!-- 下拉选择器 -->
                    <el-form class="my-form">
                        <el-form-item label="选择图案">
                            <el-select v-model="patternType" @change="draw">
                                <el-option label="圆点点阵" value="dots"></el-option>
                                <el-option label="棋盘格" value="checkerboard"></el-option>
                            </el-select>
                        </el-form-item>
						<el-form-item label="背景着色">
                            <el-select v-model="backgroundType" @change="color">
                                <el-option label="黑色" value="black"></el-option>
                                <el-option label="白色" value="white"></el-option>
                            </el-select>
						</el-form-item>
                    </el-form>

                    <!-- 使用 Element UI 的 Input 组件 -->
                    <el-form class="my-form">
                        <el-form-item label="普通点半径(mm)" v-if="patternType === 'dots'">
                            <el-input v-model="settings.radius" type="number" step="0.1" @input="draw"></el-input>
                        </el-form-item>
                        <el-form-item label="网格宽度(mm)" v-if="patternType === 'checkerboard'">
                            <el-input v-model="settings.gridWidth" type="number" step="1" @input="draw"></el-input>
                        </el-form-item>
                        <el-form-item label="点间距(mm)" v-if="patternType === 'dots'">
                            <el-input v-model="settings.spacing" type="number" step="1" @input="draw"></el-input>
                        </el-form-item>
                    </el-form>


                    <el-form class="my-form">
                        <el-form-item label="行数">
                            <el-input v-model="settings.rows" type="number" step="1" @input="draw"></el-input>
                        </el-form-item>
                        <el-form-item label="列数">
                            <el-input v-model="settings.cols" type="number" step="1" @input="draw"></el-input>
                        </el-form-item>
                    </el-form>

                    <!-- 使用 Element UI 的 Button 组件 -->
                    <el-button v-if="patternType === 'dots'" @click="addCustomPoint">添加定位点</el-button>
                    <div id="customPointList" v-if="patternType === 'dots'">
                        <div v-for="(point, index) in settings.customPoints" :key="index" class="custom-point">
                            <el-input v-model="point.row" type="number" min="1" placeholder="行"
                                @input="draw"></el-input>
                            <el-input v-model="point.col" type="number" min="1" placeholder="列"
                                @input="draw"></el-input>
                            <el-input v-model="point.radius" type="number" step="0.1" placeholder="半径"
                                @input="draw"></el-input>
                            <el-button @click="removeCustomPoint(index)" type="danger"
                                icon="el-icon-delete"></el-button>
                        </div>
						
                    </div>
                    <el-row :gutter="10" class="functions">
                        <el-col :span="8"><el-button icon="el-icon-refresh" @click="resetView">重置</el-button></el-col>
                        <el-col :span="8"><el-button icon="el-icon-full-screen"
                                @click="toggleFullScreen">全屏</el-button></el-col>
                        <el-col :span="8"><el-button icon="el-icon-printer" @click="printPDF">打印</el-button></el-col>
                    </el-row>
                    <el-row :gutter="10" class="functions">
                        <el-col :span="12"><el-button icon="el-icon-upload2"
                                @click="exportPoints">导出</el-button></el-col>
                        <el-col :span="12"><el-button icon="el-icon-download"
                                @click="importPoints">导入</el-button></el-col>
                        <input type="file" id="fileInput" style="display: none" @change="handleFileImport">
                    </el-row>

                    <div id="dpiCalibration" style="margin-top: 10px;">
                        <span>拖动调整标尺线长度使其与真实 10mm 一致:</span>
                        <el-row :gutter="10" style="display: flex;">
                            <el-button id="decreaseLength">-</el-button>
                            <div
                                style="position: relative; width: 100%; height: 50px; background: #eee; border: 1px solid #ccc;">

                                <div id="rulerLine"
                                    style="position: absolute; height: 100%; background: #000; width: 50px; cursor: ew-resize;">
                                </div>
                            </div>
                            <el-button id="increaseLength">+</el-button>
                        </el-row>
                        <span>当前长度:<span id="rulerLength">50</span> px; DPI为 <span id="dpiVal">96.000</span></span>
                    </div>

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


    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    patternType: 'dots',
                    settings: {
                        radius: 1,
                        spacing: 10,
                        rows: 20,
                        cols: 30,
                        customPoints: [],
                        gridWidth: 10
                    },
                    offsetX: 0,
                    offsetY: 0,
                    dragging: false,
                    lastX: 0,
                    lastY: 0,
                    DPI: 96,
                    rulerPx: 300,
                    rulerMm: 100,
                    calibratedDPI: 96,
                    MM_PER_INCH: 25.4,
                    file: null,
					backgroundType:"white",
					backgroundColor:"#fff",
					targetColor:"#000",
                    fileContent: ''
                };
            },
            mounted() {
                this.draw();
				this.color();
                const container = document.getElementById("svgContainer");
                const controls = document.getElementById("controls");
                const btnIncrease = document.getElementById("increaseLength");
                const btnDecrease = document.getElementById("decreaseLength");

                container.addEventListener("mousedown", (e) => {
                    this.dragging = true;
                    this.lastX = e.clientX;
                    this.lastY = e.clientY;
                    container.style.cursor = "grabbing";
                });

                container.addEventListener("mousemove", (e) => {
                    if (this.dragging) {
                        this.offsetX += e.clientX - this.lastX;
                        this.offsetY += e.clientY - this.lastY;
                        this.lastX = e.clientX;
                        this.lastY = e.clientY;
                        this.draw();
                    }
                });

                container.addEventListener("mouseup", () => {
                    this.dragging = false;
                    container.style.cursor = "grab";
                });
                container.addEventListener("mouseleave", () => {
                    this.dragging = false;
                    container.style.cursor = "grab";
                });

                window.addEventListener("resize", () => this.draw());

                document.addEventListener("fullscreenchange", () => {
                    if (document.fullscreenElement) {
                        controls.style.display = "none";
                    } else {
                        controls.style.display = "flex";
                    }
                });


                window.addEventListener("beforeprint", () => {
                    this.resetView();
					document.getElementById("svgContainer").style.backgroundColor = this.backgroundColor;
                    if (this.patternType === 'dots') {
                        const { radius, spacing, rows, cols, customPoints } = this.settings;
                        const spacingPx = this.mmToPx(spacing);
                        const defaultRadiusPx = this.mmToPx(radius);
                        const customMap = new Map();
                        customPoints.forEach((pt) => {
                            customMap.set(`${pt.row}-${pt.col}`, this.mmToPx(pt.radius));
                        });

                        /* 计算矩形区域 A 的尺寸*/
                        const areaAWidth = cols * spacingPx;
                        const areaAHeight = rows * spacingPx;

                        /* 计算扩展距离(2 倍点间距)*/
                        const expandDistance = 2 * spacingPx;

                        /*计算矩形区域 B 的尺寸*/
                        const areaBWidth = areaAWidth + 2 * expandDistance;
                        const areaBHeight = areaAHeight + 2 * expandDistance;

                        /* 设置 SVG 的尺寸为矩形区域 B 的尺寸*/
                        const svg = document.getElementById("board");
                        svg.setAttribute("width", areaBWidth);
                        svg.setAttribute("height", areaBHeight);
						svg.setAttribute("fill",this.backgroundColor);
						
                        /*计算点阵在矩形区域 B 中的偏移量*/
                        const newOffsetX = expandDistance;
                        const newOffsetY = expandDistance;

                        svg.innerHTML = "";

                        for (let row = 1; row <= rows; row++) {
                            for (let col = 1; col <= cols; col++) {
                                const x = (col - 0.5) * spacingPx + newOffsetX;
                                const y = (row - 0.5) * spacingPx + newOffsetY;
                                const key = `${row}-${col}`;
                                const r = customMap.has(key) ? customMap.get(key) : defaultRadiusPx;

                                const circle = document.createElementNS(
                                    "http://www.w3.org/2000/svg",
                                    "circle"
                                );
                                circle.setAttribute("cx", x);
                                circle.setAttribute("cy", y);
                                circle.setAttribute("r", r);
                                circle.setAttribute("fill", this.targetColor);
                                svg.appendChild(circle);
                            }
                        }
                    }
                    else if (this.patternType === 'checkerboard'){
                        const { rows, cols, gridWidth } = this.settings;
                        const gridWidthPx = this.mmToPx(gridWidth);


                        /* 计算矩形区域 A 的尺寸*/
                        const areaAWidth = cols * gridWidthPx;
                        const areaAHeight = rows * gridWidthPx;

                        /* 计算扩展距离(2 倍点间距)*/
                        const expandDistance = 2 * gridWidthPx;

                        /*计算矩形区域 B 的尺寸*/
                        const areaBWidth = areaAWidth + 2 * expandDistance;
                        const areaBHeight = areaAHeight + 2 * expandDistance;

                        /* 设置 SVG 的尺寸为矩形区域 B 的尺寸*/
                        const svg = document.getElementById("board");
                        svg.setAttribute("width", areaBWidth);
                        svg.setAttribute("height", areaBHeight);
						svg.setAttribute("fill",this.backgroundColor);
						
                        /*计算点阵在矩形区域 B 中的偏移量*/
                        const newOffsetX = expandDistance;
                        const newOffsetY = expandDistance;

                        svg.innerHTML = "";

                        for (let row = 1; row <= rows; row++) {
                            for (let col = 1; col <= cols; col++) {
                                const x = (col - 0.5) * gridWidthPx + newOffsetX;
                                const y = (row - 0.5) * gridWidthPx + newOffsetY;

                                const rect = document.createElementNS(
                                    "http://www.w3.org/2000/svg",
                                    "rect"
                                );
                                rect.setAttribute("x", x);
                                rect.setAttribute("y", y);
                                rect.setAttribute("width", gridWidthPx);
                                rect.setAttribute("height", gridWidthPx);
                                rect.setAttribute("fill", (row + col) % 2 === 0 ? this.targetColor : this.backgroundColor);
                                svg.appendChild(rect);
                            }
                        } 
                    }

                    /* 隐藏控件*/
                    controls.style.display = "none";
                });


                window.addEventListener("afterprint", () => {
                    this.draw();
                    /* 显示控件*/
                    controls.style.display = "flex";
                });

                const ruler = document.getElementById("rulerLine");
                const lengthDisplay = document.getElementById("rulerLength");
                let draggingRuler = false;

                ruler.addEventListener("mousedown", (e) => {
                    draggingRuler = true;
                    e.preventDefault();
                });

                document.addEventListener("mousemove", (e) => {
                    if (draggingRuler) {
                        const parent = ruler.parentElement;
                        const rect = parent.getBoundingClientRect();
                        let newWidth = e.clientX - rect.left;
                        console.log("newWidth", newWidth);
                        newWidth = Math.max(10, Math.min(parent.offsetWidth, newWidth));
                        ruler.style.width = newWidth + "px";
                        lengthDisplay.textContent = newWidth.toFixed(0);
                        this.applyDpiCalibration();
                    }
                });
                btnIncrease.addEventListener("click", () => {
                    const parent = ruler.parentElement;
                    let currentWidth = ruler.offsetWidth;
                    let newWidth = Math.min(parent.offsetWidth, currentWidth + 1);
                    ruler.style.width = newWidth + "px";
                    lengthDisplay.textContent = newWidth.toFixed(0);
                    this.applyDpiCalibration();
                });

                btnDecrease.addEventListener("click", () => {
                    let currentWidth = ruler.offsetWidth;
                    let newWidth = Math.max(10, currentWidth - 1);
                    ruler.style.width = newWidth + "px";
                    lengthDisplay.textContent = newWidth.toFixed(0);
                    this.applyDpiCalibration();
                });
                document.addEventListener("mouseup", () => {
                    draggingRuler = false;
                });

            },
            methods: {
                mmToPx(mm) {
                    return mm * this.DPI / this.MM_PER_INCH;
                },
                pxToMm(px) {
                    return px * this.MM_PER_INCH / this.DPI;
                },

                /** 更新 DPI(从 rulerPx 和 rulerMm 得出) */
                updateDPIFromRuler() {
                    if (this.rulerPx > 0 && this.rulerMm > 0) {
                        this.DPI = this.rulerPx * this.MM_PER_INCH / this.rulerMm;
                        this.draw();
                    }
                },

                /** 当用户拖拽时设置新 rulerPx 并刷新 DPI */
                setRulerPx(newPx) {
                    this.rulerPx = newPx;
                    this.updateDPIFromRuler();
                },

                /** 当用户点击按钮修改毫米时 */
                changeRulerMm(delta) {
                    this.rulerMm = Math.max(1, this.rulerMm + delta);
                    this.updateDPIFromRuler();
                },
				color(){
					if(this.backgroundType === 'white')
					{
						this.backgroundColor = "#fff";
						this.targetColor = "#000";
					}
					if(this.backgroundType === 'black')
					{
						this.backgroundColor = "#000";
						this.targetColor = "#fff";
					}
					this.draw();
				},
                draw() {
										document.getElementById("svgContainer").style.backgroundColor = this.backgroundColor;
                    if (this.patternType === 'dots') {

                        const { radius, spacing, rows, cols, customPoints } = this.settings;
                        const spacingPx = this.mmToPx(spacing);
                        const defaultRadiusPx = this.mmToPx(radius);
                        const customMap = new Map();
                        customPoints.forEach((pt) => {
                            customMap.set(`${pt.row}-${pt.col}`, this.mmToPx(pt.radius));
                        });

                        const svg = document.getElementById("board");
                        svg.innerHTML = "";

                        const svgWidth = cols * spacingPx;
                        const svgHeight = rows * spacingPx;

                        const post = document.querySelector('#svgContainer');
                        const postWidth = post.offsetWidth;
                        const postHeight = post.offsetHeight;

                        const viewBoxOffsetX = this.offsetX + (postWidth - svgWidth) / 2;
                        const viewBoxOffsetY = this.offsetY + (postHeight - svgHeight) / 2;

                        svg.setAttribute("width", postWidth);
                        svg.setAttribute("height", postHeight);
						svg.setAttribute("fill",this.backgroundColor);
						
                        for (let row = 1; row <= rows; row++) {
                            for (let col = 1; col <= cols; col++) {
                                const x = (col - 0.5) * spacingPx + viewBoxOffsetX;
                                const y = (row - 0.5) * spacingPx + viewBoxOffsetY;
                                const key = `${row}-${col}`;
                                const r = customMap.has(key) ? customMap.get(key) : defaultRadiusPx;

                                const circle = document.createElementNS(
                                    "http://www.w3.org/2000/svg",
                                    "circle"
                                );
                                circle.setAttribute("cx", x);
                                circle.setAttribute("cy", y);
                                circle.setAttribute("r", r);
                                circle.setAttribute("fill", this.targetColor);
                                svg.appendChild(circle);
                            }
                        }
                    }
                    else if (this.patternType === 'checkerboard') {
                        const { rows, cols, gridWidth } = this.settings;
                        const gridWidthPx = this.mmToPx(gridWidth);
                        const svg = document.getElementById("board");
                        svg.innerHTML = "";

                        const svgWidth = cols * gridWidthPx;
                        const svgHeight = rows * gridWidthPx;

                        const post = document.querySelector('#svgContainer');
                        const postWidth = post.offsetWidth;
                        const postHeight = post.offsetHeight;

                        const viewBoxOffsetX = this.offsetX + (postWidth - svgWidth) / 2;
                        const viewBoxOffsetY = this.offsetY + (postHeight - svgHeight) / 2;

                        svg.setAttribute("width", postWidth);
                        svg.setAttribute("height", postHeight);
						svg.setAttribute("fill",this.backgroundColor);


                        for (let row = 0; row < rows; row++) {
                            for (let col = 0; col < cols; col++) {
                                const x = col * gridWidthPx + viewBoxOffsetX;
                                const y = row * gridWidthPx + viewBoxOffsetY;
                                const rect = document.createElementNS(
                                    "http://www.w3.org/2000/svg",
                                    "rect"
                                );
                                rect.setAttribute("x", x);
                                rect.setAttribute("y", y);
                                rect.setAttribute("width", gridWidthPx);
                                rect.setAttribute("height", gridWidthPx);
                                /*rect.setAttribute("fill", (row + col) % 2 === 0 ? "#000" : "#fff");*/
                                rect.setAttribute("fill", (row + col) % 2 === 0 ? this.backgroundColor : this.targetColor);
                                svg.appendChild(rect);
                            }
                        }
                    }
                },
                addCustomPoint() {
                    this.settings.customPoints.push({ row: null, col: null, radius: null });
                    this.draw();
                },
                removeCustomPoint(index) {
                    this.settings.customPoints.splice(index, 1);
                    this.draw();
                },
                resetView() {
                    this.offsetX = 0;
                    this.offsetY = 0;
                    this.draw();
                },
                toggleFullScreen() {
                    const container = document.querySelector('#svgContainer');
                    if (document.fullscreenElement) {
                        document.exitFullscreen();
                    } else {
						container.requestFullscreen();
                    }
                },
                printPDF() {
                    window.print();
                },
                exportPoints() {
                    let data = {};
                    let fileName = "";
                    if (this.patternType === 'dots') {

                        const { radius, spacing, rows, cols, customPoints } = this.settings;
                        const boardInfo = {
                            radius,
                            spacing,
                            rows,
                            cols,
                            customPoints
                        };
                        const points = [];
                        let id = 0;
                        for (let row = 1; row <= rows; row++) {
                            for (let col = 1; col <= cols; col++) {
                                const isCustom = customPoints.some(point => point.row === row && point.col === col);
                                const pointRadius = isCustom ? customPoints.find(point => point.row === row && point.col === col).radius : radius;
                                points.push({
                                    id: id,
                                    x: row * spacing,
                                    y: col * spacing,
                                    radius: pointRadius
                                });
                                id++;
                            }
                        }
                        data = {
                            boardInfo,
                            points
                        };
                        fileName = `CircleBoard ${rows}×${cols}-${radius}mm.json`;
                    }

                    else if (this.patternType === 'checkerboard') {
                        const { rows, cols, gridWidth, customPoints } = this.settings;
                        const boardInfo = {
                            gridWidth,
                            rows,
                            cols,
                            customPoints
                        };
                        const points = [];
                        let id = 0;
                        for (let row = 1; row <= rows; row++) {
                            for (let col = 1; col <= cols; col++) {
                                points.push({
                                    id: id,
                                    x: row * gridWidth,
                                    y: col * gridWidth
                                });
                                id++;
                            }
                        }
                        data = {
                            boardInfo,
                            points
                        };
                        fileName = `CheckerBoard ${rows}×${cols}-${gridWidth}mm.json`;
                    }

                    const jsonData = JSON.stringify(data, null, 2);
                    const blob = new Blob([jsonData], { type: 'application/json' });
                    const url = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = url;

                    a.download = fileName;
                    a.click();
                    URL.revokeObjectURL(url);
                },
                importPoints() {
                    document.getElementById('fileInput').click();
                },
                handleFileImport(event) {
                    const file = event.target.files[0];
                    if (file) {
                        const reader = new FileReader();
                        if (this.patternType === 'dots') {
                            reader.onload = (e) => {
                                const data = JSON.parse(e.target.result);
                                const { boardInfo } = data;
                                this.settings.radius = boardInfo.radius;
                                this.settings.spacing = boardInfo.spacing;
                                this.settings.rows = boardInfo.rows;
                                this.settings.cols = boardInfo.cols;
                                this.settings.customPoints = boardInfo.customPoints;
                                this.draw();
                            };
                            reader.readAsText(file);
                        } else if (this.patternType === 'checkerboard') {
                            reader.onload = (e) => {
                                const data = JSON.parse(e.target.result);
                                const { boardInfo } = data;
                                this.settings.gridWidth = boardInfo.gridWidth;
                                this.settings.rows = boardInfo.rows;
                                this.settings.cols = boardInfo.cols;
                                this.settings.customPoints = boardInfo.customPoints;
                                this.draw();
                            };
                            reader.readAsText(file);
                        }
                    }
                },
                getDPI() {
                    var dpiDiv = document.createElement('div');
                    dpiDiv.style.cssText = 'width:1in;height:1in;position:absolute;left:-100%;top:-100%;';
                    document.body.appendChild(dpiDiv);
                    var dpi = dpiDiv.offsetWidth;
                    document.body.removeChild(dpiDiv);
                    console.log('dpi', dpi);
                    return dpi;
                },
                applyDpiCalibration() {
                    const pxLength = parseFloat(document.getElementById("rulerLength").textContent);
                    const mm = 10;
                    const newDPI = pxLength * this.MM_PER_INCH / mm;
                    this.calibratedDPI = newDPI;
                    this.DPI = newDPI;
                    const dpiVal = document.getElementById("dpiVal");
                    dpiVal.textContent = this.DPI.toFixed(3);
                    this.draw();
                }

            }
        });
    </script>


posted @ 2025-04-10 23:18  GShang  阅读(2751)  评论(0)    收藏  举报