如何解决loadFromJSON 在不同的屏幕分辨率和纵横比
我需要能够将 loadFromJSON
与画布分辨率/纵横比一起使用,而不是生成 JSON 数据的分辨率/纵横比,同时保持图形元素的关系并使画布内的所有内容居中。
我已经尝试了我见过或想出的所有解决方案。在这一点上,我不知道是我的逻辑不好,我的算术还是我的编码。
此处包含功能代码(删除了我失败的“尝试”)。只有第一个函数很重要——其余的是 UI 样板和 JSON 数据。如果更容易,这里有一个 JS Fiddle:https://jsfiddle.net/sunny001/a8thqd0z/24/
详细信息:我使用自定义 width
和 height
属性保存 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 举报,一经查实,本站将立刻删除。