loadFromJSON 在不同的屏幕分辨率和纵横比

如何解决loadFromJSON 在不同的屏幕分辨率和纵横比

我需要能够将 loadFromJSON 与画布分辨率/纵横比一起使用,而不是生成 JSON 数据的分辨率/纵横比,同时保持图形元素的关系并使画布内的所有内容居中。

>

我已经尝试了我见过或想出的所有解决方案。在这一点上,我不知道是我的逻辑不好,我的算术还是我的编码。

此处包含功能代码(删除了我失败的“尝试”)。只有第一个函数很重要——其余的是 UI 样板和 JSON 数据。如果更容易,这里有一个 JS Fiddle:https://jsfiddle.net/sunny001/a8thqd0z/24/

详细信息:我使用自定义 widthheight 属性保存 JSON 数据,以便我知道创建数据时的分辨率/纵横比。然后我使用这些属性来确定如何缩放。 canvas 始终设置为窗口的大小,该大小可能会有所不同。我见过一些使用画布“缩放”属性的解决方案,但我不能这样做,因为该应用程序允许用户放大他们正在注释的文档。

背景 这是针对桌面 electron 应用程序,用户可以在其中注释文本文档,因此准确定位很重要。用户可以在窗口模式或全屏模式下创建和呈现注释。

'use strict';


let canvasA
let canvasB

// these are the canvas dimensions
let A = {
  width: 320,height: 190
}
let B = {
  width: 225,height: 150
}

function loadAnnotation(theCanvas,theData,id) {

  theCanvas.clear()

  var containerWidth = theCanvas.getWidth()
  var containerHeight = theCanvas.getHeight()
  var originalWidth = theData.width
  var originalHeight = theData.height
  var scaleFactor

  theCanvas.loadFromJSON(theData,function() {

    /**
     * the canvas seems to change size "on its own" based on the JSON data
     * width & height properties so this hack resets it 
     * 
     * setDimensions() seems buggy – screen redraw artifacts so using setWidth() & setheight()
     * 
     **/
    if (id == "A") {
      // theCanvas.setDimensions(A.width,A.height)
      theCanvas.setWidth(A.width)
      theCanvas.setHeight(A.height)
    } else {
      // theCanvas.setDimensions(B.width,B.height)
      theCanvas.setWidth(B.width)
      theCanvas.setHeight(B.height)
    }

    // just logging code
    if (id == "A") {
      logA(`data w/h:  ${originalWidth} x ${originalHeight}`)
      logA(`canvas w/h:  ${theCanvas.getWidth()} x ${theCanvas.getHeight()}`)
      logA(`canvas zoom:  ${theCanvas.getZoom()}`)
      logA(`scaleFactor: ${scaleFactor}`)
    } else {
      logB(`data w/h:  ${originalWidth} x ${originalHeight}`)
      logB(`canvas w/h:  ${theCanvas.getWidth()} x ${theCanvas.getHeight()}`)
      logB(`canvas zoom:  ${theCanvas.getZoom()}`)
      logB(`scaleFactor: ${scaleFactor}`)
    }

  },function(o,object) {

    var widthRatio = containerWidth / originalWidth
    var heightRatio = containerHeight / originalHeight

    // if (widthRatio <= heightRatio) {
    if (widthRatio > heightRatio) {
      scaleFactor = widthRatio
    } else {
      scaleFactor = heightRatio
    }

    object.scaleX = object.scaleX * scaleFactor
    object.scaleY = object.scaleY * scaleFactor
    object.left = object.left * scaleFactor
    object.top = object.top * scaleFactor

    object.setCoords()
  })

  theCanvas.renderAll();
  theCanvas.calcOffset();
}




// Everything below here is UI code & JSON data

document.addEventListener("DOMContentLoaded",(event) => {
  var today = new Date();
  var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
  console.log(time)

  var canvasAloadA = document.querySelector("#canA-loadA")
  canvasAloadA.addEventListener("click",function(event) {
    logA()
    loadAnnotation(canvasA,canvasA_data,"A")
  })

  var canvasAloadB = document.querySelector("#canA-loadB")
  canvasAloadB.addEventListener("click",canvasB_data,"A")
  });

  var clearA = document.querySelector("#canA-clear")
  clearA.addEventListener("click",function(event) {
    canvasA.clear()
    logA()
  });


  var saveA = document.querySelector("#canA-save")
  saveA.addEventListener("click",function(event) {

    canvasA_data = canvasA.toObject(["width","height"])
    // canvasA_data = JSON.stringify(canvasA.toObject(["width","height"]));
    // console.log('Save. A',JSON.stringify(canvasA_data))
  });


  var canvasBloadA = document.querySelector("#canB-loadA")
  canvasBloadA.addEventListener("click",function(event) {
    logB()
    loadAnnotation(canvasB,"B")
  })

  var canvasBloadB = document.querySelector("#canB-loadB")
  canvasBloadB.addEventListener("click","B")
  });

  var clearB = document.querySelector("#canB-clear")
  clearB.addEventListener("click",function(event) {
    canvasB.clear()
    logB()
  });

  var saveB = document.querySelector("#canB-save")
  saveB.addEventListener("click",function(event) {
    canvasB_data = canvasB.toObject(["width","height"])
    // canvasB_data = JSON.stringify(canvasB.toObject(["width","height"]));
    console.log('Save. B',JSON.stringify(canvasB_data))
  })

  setUpFabric()
})


function setUpFabric() {

  canvasA = new fabric.Canvas('canvas-A',{
    backgroundColor: '#FFFFFF',width: 320,height: 190
  })

  loadAnnotation(canvasA,"A")
  // canvasA_LoadData() // only used to generate initial JSON data
  canvasA.renderAll();

  canvasB = new fabric.Canvas('canvas-B',width: 225,height: 150
  });

  loadAnnotation(canvasB,"B")
  // canvasB_LoadData() // only used to generate initial JSON data

  canvasB.renderAll();
}

/**
 * fitInBox
 * Constrains a box (width x height) to fit in a containing box (maxWidth x maxHeight),preserving the aspect ratio
 * @param width      width of the box to be resized
 * @param height     height of the box to be resized
 * @param maxWidth   width of the containing box
 * @param maxHeight  height of the containing box
 * @param expandable (Bool) if output size is bigger than input size,output is left unchanged (false) or expanded (true)
 * @return           {width,height} of the resized box
 */
function fitInBox(width,height,maxWidth,maxHeight,expandable) {
  "use strict";

  var aspect = width / height,initWidth = width,initHeight = height;

  if (width > maxWidth || height < maxHeight) {
    width = maxWidth;
    height = Math.floor(width / aspect);
  }

  if (height > maxHeight || width < maxWidth) {
    height = maxHeight;
    width = Math.floor(height * aspect);
  }

  if (!!expandable === false && (width >= initWidth || height >= initHeight)) {
    width = initWidth;
    height = initHeight;
  }

  return {
    width: width,height: height
  };
}



function logA(txt) {
  if (txt == undefined) {
    document.getElementById('canA').value = ""
  } else {
    document.getElementById('canA').value += `\n${txt}`
  }
}



function logB(txt) {
  if (txt == undefined) {
    document.getElementById('canB').value = ""
  } else {
    document.getElementById('canB').value += `\n${txt}`
  }
}


// this is for initial JSON data setup only - not used in demo
function canvasA_LoadData() {

  var elA = document.getElementById('test-imageA');
  var imgA = new fabric.Image(elA,{
    left: 0,top: 0,selectable: true
  })
  canvasA.add(imgA);

  var containerWidth = canvasA.getWidth()
  var containerHeight = canvasA.getHeight()

  var result = fitInBox(imgA.width,imgA.height,containerWidth,containerHeight,true)

  imgA.scaleToWidth(result.width)

  var xOffset = (containerWidth - imgA.getScaledWidth()) / 2
  var yOffset = (containerHeight - imgA.getScaledHeight()) / 2

  imgA.set({
    left: xOffset,top: yOffset
  })
  imgA.setCoords()

  var rect = new fabric.Rect({
    left: 100,fill: 'red',width: 20,height: 20
  });

  var circle = new fabric.Circle({
    radius: 20,stroke: 'green',strokeWidth: 12,fill: null,left: 200,top: 130
  });

  var triangle = new fabric.Triangle({
    width: 40,height: 40,fill: 'blue',left: 50,top: 140
  });

  var txt = new fabric.Text("Canvas A 320 x 190",{
    fontSize: 24,top: 50,fill: 'white'
  })

  canvasA.add(rect,circle,triangle,txt);

  canvasA.calcOffset();
  canvasA.renderAll();
}

// this is for initial JSON data setup only - not used in demo
function canvasB_LoadData() {

  var elB = document.getElementById('test-imageB');

  var imgB = new fabric.Image(elB,selectable: true
  })
  canvasB.add(imgB)

  var containerWidth = canvasB.getWidth()
  var containerHeight = canvasB.getHeight()

  var result = fitInBox(imgB.width,imgB.height,true)

  imgB.scaleToWidth(result.width)

  var xOffset = (containerWidth - imgB.getScaledWidth()) / 2
  var yOffset = (containerHeight - imgB.getScaledHeight()) / 2

  imgB.set({
    left: xOffset,top: yOffset
  })
  imgB.setCoords()

  var rect = new fabric.Rect({
    left: 0,fill: 'orange',width: 60,height: 60
  })

  var circle = new fabric.Circle({
    radius: 40,stroke: 'red',left: 120,top: 40
  })

  var triangle = new fabric.Triangle({
    width: 40,fill: 'black',top: 100
  });

  var txt = new fabric.Text("Canvas B 225 x 150",{
    fontSize: 20,left: 40,top: 40,fill: 'blue'
  })

  canvasB.add(rect,txt);

  canvasB.calcOffset();
  canvasB.renderAll();
}



let canvasB_data = {
  "version": "4.3.0","objects": [{
      "type": "image","version": "4.3.0","originX": "left","originY": "top","left": 62.5,"top": 0,"width": 400,"height": 600,"fill": "rgb(0,0)","stroke": null,"strokeWidth": 0,"strokeDashArray": null,"strokeLineCap": "butt","strokeDashOffset": 0,"strokeLineJoin": "miter","strokeMiterLimit": 4,"scaleX": 0.25,"scaleY": 0.25,"angle": 0,"flipX": false,"flipY": false,"opacity": 1,"shadow": null,"visible": true,"backgroundColor": "","fillRule": "nonzero","paintFirst": "fill","globalCompositeOperation": "source-over","skewX": 0,"skewY": 0,"cropX": 0,"cropY": 0,"src": "https://placekitten.com/400/600","crossOrigin": null,"filters": []
    },{
      "type": "rect","left": 0,"width": 60,"height": 60,"fill": "orange","strokeWidth": 1,"scaleX": 1,"scaleY": 1,"rx": 0,"ry": 0
    },{
      "type": "circle","left": 61.52,"top": 24.21,"width": 80,"height": 80,"fill": null,"stroke": "red","strokeWidth": 12,"scaleX": 1.1,"scaleY": 1.1,"radius": 40,"startAngle": 0,"endAngle": 6.283185307179586
    },{
      "type": "triangle","left": 163,"top": 107.89,"width": 40,"height": 40,"fill": "black","skewY": 0
    },{
      "type": "text","left": 2.33,"top": 39.01,"width": 162.216796875,"height": 22.599999999999998,"fill": "blue","text": "Canvas B 225 x 150","fontSize": 20,"fontWeight": "normal","fontFamily": "Times New Roman","fontStyle": "normal","lineHeight": 1.16,"underline": false,"overline": false,"linethrough": false,"textAlign": "left","textBackgroundColor": "","charSpacing": 0,"styles": {}
    }
  ],"background": "#FFFFFF","width": 225,"height": 150
}

let canvasA_data = {
  "version": "4.3.0","left": 17.5,"width": 600,"height": 400,"scaleX": 0.47,"scaleY": 0.47,"src": "https://placekitten.com/600/400","left": 277.89,"top": 73.23,"width": 20,"height": 20,"fill": "red","left": 92.67,"top": -5.57,"stroke": "green","scaleX": 1.73,"scaleY": 1.73,"radius": 20,"left": 17.2,"top": 146.93,"left": 51.99,"top": 96.51,"width": 195.984375,"height": 27.119999999999994,"fill": "white","text": "Canvas A 320 x 190","fontSize": 24,"width": 320,"height": 190
}
#container {
  display: grid;
  grid-template-columns: 330px 235px;
  gap: 20px;
  grid-template-rows: 300px,20px,100px;
}

.canvas-wrapper {
  margin-left: 10px;
  grid-column: 1;
  justify-self: center;
}

.panel {
  grid-column: 2;
  margin-top: 20px;
}

.buttons {
  font-family: sans-serif;
  font-size: 10pt;
  width: 100px;
  margin: 3px 0;
}

.labels {
  font-family: sans-serif;
  font-size: 12pt;
}
<!DOCTYPE html>
<html>

<head>
  <title></title>

  <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/fabric@4.3.0/dist/fabric.js"></script>
  <script type="text/javascript" src="js/fabric-resizing.js" defer></script>

  <link rel="stylesheet" href="css/fabric-resize.css">
</head>

<body>
  <div id="container" width="100%" height="100%">
    <div class="canvas-wrapper">
      <div class="labels">Canvas A - 320 x 190 px</div>
      <div>
        <canvas id="canvas-A" width="320" height="200" style="border:1px solid #000000;"></canvas>
      </div>
    </div>
    <div class="panel">
      <textarea name="canA" id="canA" cols="23" rows="8"></textarea>
      <div>
        <button id="canA-loadB" class="buttons">Load B Json</button>
        <button id="canA-loadA" class="buttons">Load A Json</button>
      </div>
      <div>
        <button id="canA-clear" class="buttons">Clear A</button>
        <button id="canA-save" class="buttons">Save A Json</button>
      </div>
    </div>
    <div class="canvas-wrapper">
      <div class="labels">Canvas B - 225 x 150 px</div>
      <canvas id="canvas-B" width="225" height="150" style="border:1px solid #000000;"></canvas>
    </div>
    <div class="panel">
      <textarea name="canB" id="canB" cols="23" rows="8"></textarea>
      <div>
        <button id="canB-loadA" class="buttons">Load A Json</button>
        <button id="canB-loadB" class="buttons">Load B Json</button>
      </div>
      <div>
        <button id="canB-clear" class="buttons">Clear B</button>
        <button id="canB-save" class="buttons">Save B Json</button>
      </div>
    </div>
    <div class="labels" style="position:fixed; left:50px; top:455px;">
      600 x 400 px
    </div>
    <div>
      <img id="test-imageA" src="https://placekitten.com/600/400" style="position:fixed; left:50px; top:475px; width:25%; border:none;" />
    </div>
    <div class="labels" style="position:fixed; left:250px; top:455px;">
      400 x 600 px
    </div>
    <div>
      <img id="test-imageB" src="https://placekitten.com/400/600" style="position:fixed; left:250px; top:475px; height:25%; border:none;" />
    </div>
  </div>
</body>

</html>

解决方法

在尝试了许多不同的方法(和很多痛苦)后终于解决了这个问题。

要点是选择所有对象,然后缩放和居中。一个棘手的事情是我的数据中的第一个对象是一个图像,所有绘制的元素都需要与它保持一致。因此,我首先更正图像路径,然后在缩放选区后,将选区移动到图像在屏幕上保持居中。

现在看代码,我可以看到可以优化它的地方(例如,由于我的数据中只有一张图片,因此在找到图片后无需遍历所有剩余数据)


loadData(data) {

    var jsonObj = JSON.parse(data);

    jsonObj.objects.forEach(element => {
      if (element.type == "image") {
        var imgPath = element.src.split("assets")

        if (imgPath.length > 1) {
          element.src = upath.joinSafe(projectDirectory,"assets",imgPath[1])
        }
      }
    });

    var self = this;
    this.canvas.loadFromJSON(jsonObj,function () {

      var selection = new fabric.ActiveSelection(self.canvas.getObjects(),{ canvas: self.canvas });


      // var selectionWidth = (selection.width >  self.canvas.getWidth()) ?  self.canvas.getWidth() : selection.width
      // var selectionHeight = (selection.height > self.canvas.getHeight()) ? self.canvas.getHeight() : selection.height

      var sizeObj = self.resizer(
        { width: self.canvas.getWidth(),height: self.canvas.getHeight() },{ width: selection.width,height: selection.height });

      // console.log('sizeObj',sizeObj);

      // selection.scaleToWidth(sizeObj.width,false)
      // selection.scaleToWidth(sizeObj.width,true)
      selection.scaleToHeight(sizeObj.height)
      // selection.scaleToHeight(sizeObj.height,true)

      selection.center();


      /**
       * ------------------------------------------
       * This keeps the IMAGE centered on the canvas instead of 
       * just centering  the selection – otherwise the image will shift 
       */
      var selectionObjs = selection.getObjects();
      var imgObj = selectionObjs[0]
      var matrix = selection.calcTransformMatrix();
      var finalPosition = fabric.util.transformPoint({ x: imgObj.left,y: imgObj.top },matrix);

      sizeObj = self.resizer(
        { width: self.canvas.getWidth(),{ width: imgObj.getScaledWidth(),height: imgObj.getScaledHeight() });

      selection.left += sizeObj.x - finalPosition.x;
      selection.top += sizeObj.y - finalPosition.y;
      // ------------------------------------------

      selection.setCoords()
      selection.destroy()

      self.canvas.renderAll();
      self.canvas.calcOffset()

    },function (o,object) {

    })

    self.setObjectsSelectable(self.toolbarIsVisible)
    self.toolActive = false
  }

resizer(canvas,imageObj) {
    var imageAspectRatio = imageObj.width / imageObj.height;
    var canvasAspectRatio = canvas.width / canvas.height;
    var renderableHeight,renderableWidth,xStart,yStart;

    // If image's aspect ratio is less than canvas's we fit on height
    // and place the image centrally along width
    if (imageAspectRatio < canvasAspectRatio) {
      renderableHeight = canvas.height;
      renderableWidth = imageObj.width * (renderableHeight / imageObj.height);
      xStart = (canvas.width - renderableWidth) / 2;
      yStart = 0;
    }

    // If image's aspect ratio is greater than canvas's we fit on width
    // and place the image centrally along height
    else if (imageAspectRatio > canvasAspectRatio) {
      renderableWidth = canvas.width
      renderableHeight = imageObj.height * (renderableWidth / imageObj.width);
      xStart = 0;
      yStart = (canvas.height - renderableHeight) / 2;
    }

    // Happy path - keep aspect ratio
    else {
      renderableHeight = canvas.height;
      renderableWidth = canvas.width;
      xStart = 0;
      yStart = 0;
    }
    return { x: xStart,y: yStart,width: renderableWidth,height: renderableHeight }
  }

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-
参考1 参考2 解决方案 # 点击安装源 协议选择 http:// 路径填写 mirrors.aliyun.com/centos/8.3.2011/BaseOS/x86_64/os URL类型 软件库URL 其他路径 # 版本 7 mirrors.aliyun.com/centos/7/os/x86
报错1 [root@slave1 data_mocker]# kafka-console-consumer.sh --bootstrap-server slave1:9092 --topic topic_db [2023-12-19 18:31:12,770] WARN [Consumer clie
错误1 # 重写数据 hive (edu)&gt; insert overwrite table dwd_trade_cart_add_inc &gt; select data.id, &gt; data.user_id, &gt; data.course_id, &gt; date_format(
错误1 hive (edu)&gt; insert into huanhuan values(1,&#39;haoge&#39;); Query ID = root_20240110071417_fe1517ad-3607-41f4-bdcf-d00b98ac443e Total jobs = 1
报错1:执行到如下就不执行了,没有显示Successfully registered new MBean. [root@slave1 bin]# /usr/local/software/flume-1.9.0/bin/flume-ng agent -n a1 -c /usr/local/softwa
虚拟及没有启动任何服务器查看jps会显示jps,如果没有显示任何东西 [root@slave2 ~]# jps 9647 Jps 解决方案 # 进入/tmp查看 [root@slave1 dfs]# cd /tmp [root@slave1 tmp]# ll 总用量 48 drwxr-xr-x. 2
报错1 hive&gt; show databases; OK Failed with exception java.io.IOException:java.lang.RuntimeException: Error in configuring object Time taken: 0.474 se
报错1 [root@localhost ~]# vim -bash: vim: 未找到命令 安装vim yum -y install vim* # 查看是否安装成功 [root@hadoop01 hadoop]# rpm -qa |grep vim vim-X11-7.4.629-8.el7_9.x
修改hadoop配置 vi /usr/local/software/hadoop-2.9.2/etc/hadoop/yarn-site.xml # 添加如下 &lt;configuration&gt; &lt;property&gt; &lt;name&gt;yarn.nodemanager.res