【问题标题】:Accounting for Canvas Size Differences when Drawing on Image with Stored Coordinates在存储坐标的图像上绘图时考虑画布大小差异
【发布时间】:2019-03-14 23:43:07
【问题描述】:

我正在努力寻找一种方法/策略来处理存储坐标的绘图以及我的网络应用程序在各种设备和屏幕尺寸上的画布尺寸变化。

基本上我想在画布上显示图像。用户将在图像区域上标记两个点,并且应用程序记录放置这些标记的位置。这个想法是,用户将每隔一天使用该应用程序,能够看到 X 数量的先前点被绘制的位置,并能够将两个新点添加到尚未被先前标记标记的地方所提到的区域。画布当前设置为 height = window.innerHeight 和 width = window.innerWidth/2。

我最初的想法是记录每对点的坐标并根据需要检索它们以便重新绘制它们。但是,如果画布改变大小,这些坐标不匹配,正如我在不同设备上测试网页时发现的那样。无论画布尺寸如何,如何记录以前的坐标并使用它们标记图像的同一区域?

【问题讨论】:

    标签: javascript html canvas screen-size


    【解决方案1】:

    使用百分比!示例:

    假设在设备 1 上,画布大小为 150x200
    用户将标记放在像素 25x30 上。您可以做一些数学运算来获得百分比。
    然后您保存该百分比,而不是位置,
    示例:

    let userX = 25; //where the user placed a marker
    let canvasWidth = 150;
    //Use a calculator to verify :D
    let percent = 100 / (canvasWidth / userX); //16.666%
    

    现在您有了percent,您可以根据该百分比设置标记的位置。
    示例:

    let markerX = (canvasWidth * percent) / 100; //24.999
    canvasWidth = 400; //Lets change the canvas size!
    markerX = (canvasWidth * percent) / 100; //66.664;
    

    瞧 :D 只需抓住画布大小,您就可以每次都确定标记的位置。

    【讨论】:

    • 这很有帮助,但是改变纵横比的问题仍然会导致标记与它们应该绘制的位置不同。
    • @RamsayM 找到任何解决方案?
    • 我的解决方案仍然有效,在任何纵横比和窗口大小上,百分比都可以。如果用户在屏幕中间放置一个标记,计算机就知道它是绘制的 50% Y。因此,当您显示画布时,您只需将该标记放在 50%Y
    【解决方案2】:

    虚拟画布

    您必须定义一个虚拟画布。这是具有预定义大小的理想画布,所有坐标都相对于该画布。这个虚拟画布的中心是坐标 0,0

    输入坐标后,它会转换为虚拟坐标并存储。渲染时,它们会转换为设备屏幕坐标。

    不同的设备具有不同的纵横比,即使是单个设备也可以通过倾斜来改变纵横比。这意味着虚拟画布不会完全适合所有设备。您可以做的最好的事情是确保整个虚拟画布是可见的,而无需在 x 或 y 方向上拉伸它。这称为适合的比例。

    缩放以适应

    要渲染到设备画布,您需要缩放坐标以适应整个虚拟画布。您使用画布变换来应用缩放。

    创建设备比例矩阵

    const vWidth = 1920;  // virtual canvas size
    const vHeight = 1080;
    
    function scaleToFitMatrix(dWidth, dHeight) {
        const scale = Math.min(dWidth / vWidth, dHeight / vHeight);
        return [scale, 0, 0, scale, dWidth / 2, dHeight / 2];
    }
    
    const scaleMatrix = scaleToFitMatrix(innerWidth, innerHeight);
    

    缩放位置不是像素

    点被定义为虚拟画布上的一个位置。然而,转换也会缩放线宽和特征尺寸,这是您在非常低或高分辨率的设备上不想要的。

    要保持相同的像素大小,但仍以像素大小呈现特征,请使用反向比例,并在描边之前重置变换,如下所示(以点为中心的 4 个像素框)

    const point = {x : 0, y : 0}; // center of virtual canvas
    const point1 = {x : -vWidth / 2, y : -vHeight / 2}; // top left of virtual canvas
    const point2 = {x : vWidth / 2, y : vHeight / 2}; // bottom right of virtual canvas
    
    function drawPoint(ctx, matrix, vX, vY, pW, pH) { // vX, vY virtual coordinate
         const invScale = 1 / matrix[0]; // to scale to pixel size
         ctx.setTransform(...matrix); 
         ctx.lineWidth = 1; // width of line
         ctx.beginPath();
         ctx.rect(vX - pW * 0.5 * invScale, vY - pH * 0.5 * invScale, pW * invScale, pH * invScale);
         ctx.setTransform(1,0,0,1,0,0); // reset transform for line width to be correct
         ctx.fill();
         ctx.stroke();
    }
    const ctx = canvas.getContext("2d");
    drawPoint(ctx, scaleMatrix, point.x, point.y, 4, 4);
    

    通过 CPU 转换

    要将点从设备坐标转换为虚拟坐标,您需要将逆矩阵应用于该点。例如,您从鼠标获取 pageX、pageY 坐标,您使用比例矩阵进行转换,如下所示

    function pointToVirtual(matrix, point) {
        point.x = (point.x - matrix[4]) / matrix[0];
        point.y = (point.y - matrix[5]) / matrix[3];
        return point;
    }
    

    从虚拟转换为设备

    function virtualToPoint(matrix, point) {
        point.x = (point.x * matrix[0]) + matrix[4];
        point.y = (point.y * matrix[3]) + matrix[5];
        return point;
    }
    

    检查边界

    画布的上方/下方或左/右可能存在虚拟画布坐标之外的区域。要检查虚拟画布内部是否调用以下代码

    function isInVritual(vPoint) {
         return ! (vPoint.x < -vWidth / 2 || 
             vPoint.y < -vHeight / 2 ||
             vPoint.x >= vWidth / 2 ||
             vPoint.y >= vHeight / 2);
    }
    const dPoint = {x: page.x, y: page.y};  // coordinate in device coords
    if (isInVirtual(pointToVirtual(scaleMatrix,dPoint))) {
         console.log("Point inside");
    } else {
         console.log("Point out of bounds.");
    }
    

    加分

    • 以上假设画布与屏幕对齐。
    • 某些设备将被缩放(捏合缩放)。您需要检查设备像素比例以获得最佳效果。
    • 最好将虚拟画布大小设置为您期望的最大屏幕分辨率。
    • 始终在虚拟坐标中工作,仅在需要渲染时转换为设备坐标。

    【讨论】:

    • 当你提到假设将画布与屏幕对齐时,你的意思是画布占据了整个屏幕,即 window.innerWidth & window.innerHeight?
    • @Ramrod506 align 表示虚拟画布的 x 和 y 轴平行于设备边(您没有旋转虚拟画布)。这是因为我在矩阵计算中采用了一些数学捷径。画布可以是任意大小。为了方便起见,我选择了innerWidthinnerHeight,任何尺寸都可以(当然除了
    • 有道理。关于如何放置圆形标记而不是正方形/矩形的任何提示,考虑到 pixelWidth 和 pixelHeight?
    • @Ramrod506 只需将ctx.rect 替换为ctx.arc 并使用invScale 缩放半径
    猜你喜欢
    • 2014-09-27
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    • 1970-01-01
    • 2021-01-16
    • 1970-01-01
    • 1970-01-01
    • 2018-09-07
    相关资源
    最近更新 更多