Paper.js背景光栅化 [英] Paper.js Background Rasterization Glitches

查看:677
本文介绍了Paper.js背景光栅化的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在使用Paper.js构建一个图像编辑器。我们在Paper.js画布的一侧有一个队列,允许在图像之间切换。每次我们在图像之间切换时,我们要将所有注释(光栅化)平整到正在编辑的图像上。



每次切换图像时,其将当前图像和注释光栅化到数据URL。 (如果我们再次访问此图片,将显示此数据网址的栅格。)

  var flattenToDataUrl = function b $ b layerAsRaster = paper.project.layers [0] .rasterize(); // Layer to Paper.js Raster object 
layerAsRaster.visible = false; //尝试设置不可见
var dataString = layerAsRaster.toDataURL();
return dataString;
};

然后我们最终调用这个方法,改变了我们正在编辑的图像:

  var setCanvasImage = function(imageObject){
if(imageObject!= null)
{
imageHeight = imageObject.height;
var imageWidth = imageObject.width;

//设置HTMLImage
var imageElement = new Image(imageObject.width,imageObject.height);
if(_。has(imageObject,'imageData')){//成为64位数据
imageElement.src ='data:image / png; base64,'+ imageObject.imageData;
} else if(_。has(imageObject,'imageUrl')){//成为URL
imageElement.src = imageObject.imageUrl;
}

//将图像添加到Paper.js画布
imageElement.onload = function(event){

//初始化Paper.js画布
paper.setup(canvas);

raster = new paper.Raster(imageElement,new paper.Point(canvas.width / 2,canvas.height / 2));

setUpNotes();

selectedItems = new paper.Group(); //因为Paper.js已经设置好了,我们现在可以初始化这个组
registerCanvasEvents(); //平移,缩放,移动所选项目,取消选择所有选定项目

fitToPage();
};
}
};

所以,这改变了图像,但是当我将鼠标移动到画布上,图像在队列中,它毛刺到我们刚刚(直到它的注释)的图像,直到我做一些像平移,缩放等的东西。然后我看到我选择的图像,并真正使用。



删除 flattenToDataUrl()功能使队列无缝工作。所以在我看来,一些是鱼腥的。我们在该方法中生成一个Paper.js Raster对象。 Rasters似乎自动添加自己。我尝试通过调用

  layerAsRaster.visible = false来遏制此问题; 

但无效。



更新




$ b 为了清楚(希望)和完整性,我决定将我们使用的整个PaperFunctions类与承载我们的< canvas> 元素的React一起发布。有很多代码,很多清理,特别是在 registerCanvasEvents()。熊与这个学习初学者。还有几百行,所以它可能是有帮助的,将其粘贴到您最喜欢的编辑器。入口点包括具有< canvas>的React类的 componentDidMount 中调用的 setCanvas ; 元素和 canvasSetImage ,从队列中调用。我同意bmacnaughton的答案,每当我们加载一个新的图像,调用 paper.setup(canvas)是奇怪的。我目前正在调查这个正确的解决方案,正确的地方放它。 setCanvas 似乎是合乎逻辑的,但是当我拖动图像在该设置中移动它时,它留下一串图像在它的尾迹。无论如何,这里是PaperFunctions.js:

  var JQueryMousewheel = require('jquery-mousewheel')($); 

var SimplePanAndZoom = require('./ SimplePanAndZoom.js');
var panAndZoom = new SimplePanAndZoom();

var selectedItems;

//我们以两种不同的方式使用选择。
//一个项目可能选择了Paper.js,但不在选择组中。
//这是因为我们想要显示它是可选择的。
//蓝色边界框表示它是可选择的。
//绿色边框表示它实际上已被选中并添加到selectedItems中。
//只有在selectedItems中的东西实际上被操作。
//因此,这个方法中的事件处理程序基本上设置项目是否在selectedItems中(因此将被操作用于移动,调整大小,删除等)。
//也就是说,这里的事件处理程序涉及向用户显示项目的选择的状态 - 当事件实际发生在selectedItems组上时是否将被操作。
var registerItemEvents = function(item){
// mouseup的布尔标志,用于知道是拖动还是单击
var dragged;

//用于跟踪是否发生拖动或点击
item.on('mousedown',function(e){
dragged = false;
}

//单击切换选择
item.on('mouseup',function(event){
event.stopPropagation(); //仅适用于
event.preventDefault();

if(!dragged){
var justAdded = addIfNotInSelectedItems(item);
if(!justAdded){// group,remove it
item.remove();
paper.project.activeLayer.addChild(item);

this.selectedColor = paper.project.activeLayer.selectedColor;
//item.selected = false;
}
}
});

//即使在取消选择之后仍显示为可选
item.on('mousemove',function(event){
this.selected = true;
} )

//如果没有选择,在鼠标进入时显示它是可选的
item.on('mouseenter',function(event){
if(!this.selected ){
this.selected = true;
}
});

//如果没有选择,鼠标离开可选择的删除指示器
item.on('mouseleave',function(event){
var isInSelectedItems = selectedItems.getItem item;
if(this.selected&& isInSelectedItems == null){
this.selected = false;
}
});

//拖动,移动项
item.on('mousedrag',function(event){
dragged = true;

如果用户开始拖动,自动选择项目
addIfNotInSelectedItems(item);
});
}

var addIfNotInSelectedItems = function(item){
var isInSelectedItems = selectedItems.getItem(item);
if(isInSelectedItems == null){//项目当前不在选择组中,添加它
selectedItems.addChild(item);
item.selectedColor ='green';
item.selected = true;
return true; //被添加,返回true
} else {
return false; //已经在,返回false
}
}

var registerCanvasEvents = function(){
if(paper.view!= null& = null){
//放大鼠标滚轮
$(canvas).mousewheel(function(event){
event.preventDefault();
var mousePosition = new paper.Point (event.offsetX,event.offsetY);
var viewPosition = paper.view.viewToProject(mousePosition);
var returnedValues = panAndZoom.changeZoom(paper.view.zoom,(event.deltaY * -1 ),paper.view.center,viewPosition,1.1);
var newZoom = returnedValues [0];
var offset = returnedValues [1];
paper.view.zoom = newZoom;
paper.view.center = paper.view.center.add(offset);
});

//用于跟踪是否发生拖动或点击
var dragged;
paper.project.layers [0] .on('mousedown',function(e){// TODO应该是第0层的长期运行
dragged = false;
}


//鼠标拖动
/*paper.project.layers[0].on('mousedrag',function(event){// TODO应该是layer 0在长期运行?
if(!event.event.ctrlKey&!event.event.altKey&!amp;!event.event.shiftKey){//没有键(我们使用)推送
dragged = true; //我们正在平移,我们不希望取消选择所有项目,因为我们可以点击
paper.view.center = panAndZoom.changeCenter(paper.view。 center,event.delta.x,event.delta.y,0.7);
//event.preventDefault();
}
}); * /

//在鼠标拖动时移动所选项
selectedItems.on('mousedrag',function(event){
event.stopPropagation(); //不传播或者它将注册为pan event
event.preventDefault();

dragged = true; //我们正在平移,我们不希望取消选择所有项目,因为我们可以单击

this.translate(new paper.Point(event.delta.x,event.delta.y));
});

//如果是点击而不是拖动,取消选择所选项目
paper.project.layers [0] .on('mouseup',function(event){
if(!dragged){
var removedItems = selectedItems.removeChildren(); //从选择组中移除,也会从显示中移除
paper.project.activeLayer.addChildren(removedItems); //返回显示

//重置选择颜色以显示可选
for(var i = 0; i removedItems [i] .selectedColor = paper .project.activeLayer.selectedColor;
removedItems [i] .selected = false;
}
}
});

//初始路径对象,在释放Alt之后将重置为新路径
var path = newPath();
var paths = [];
paths.push(path);

//在mousedown上添加点从
开始paper.project.layers [0] .on('mousedown',function(event){
if .altKey&&!event.event.ctrlKey){// Alt键添加路径,但不允许尝试同时添加文本
if(paths [paths.length-1] .lastSegment = = null){
//path.add(event.point,event.point);
paths [paths.length-1] .add(event.point,event.point);
} else {
//path.add(path.lastSegment.point,path.lastSegment.point);
paths [paths.length-1] .add(paths [paths.length-1] .lastSegment.point,paths [paths.length-1] .lastSegment.point);
}
}
});

// on mousedrag添加点到路径
paper.project.layers [0] .on('mousedrag',function(event){
if(event.event。 altKey&&!event.event.ctrlKey){// Alt键添加路径,但不允许尝试在同一时间添加文本
if(event.event.shiftKey){//使用shift键for freeform
//path.add(event.point);
paths [paths.length-1] .add(event.point);
} else {//默认直线添加到路径
//path.lastSegment.point = event.point;
路径[paths.length-1] .lastSegment.point = event.point;
}
}
} .bind(this));

var tool = new paper.Tool();

var startDragPoint;

//捕获开始拖动选择
paper.tool.onMouseDown = function(event){
if((event.event.ctrlKey&& event.event。 shiftKey)||(event.event.ctrlKey&& event.event.altKey)){
startDragPoint = new paper.Point(event.point);
}
};

paper.tool.onMouseDrag = function(event){
// Panning
if(!event.event.ctrlKey&&!event.event.altKey& &!event.event.shiftKey){//没有键(我们使用)可以推送
dragged = true; //我们正在平移,我们不希望取消选择所有项目,因为我们会单击
paper.view.center = panAndZoom.changeCenter(paper.view.center,event.delta.x,event 。0.7);
//event.preventDefault();
}

//显示指示所选区域的框
//用于移动区域和空白区域
if((event.event.ctrlKey& ;& event.event.shiftKey)||(event.event.ctrlKey&&& event.event.altKey)){
dragged = true;
var showSelection = new paper.Path.Rectangle({
from:startDragPoint,
to:event.point,
strokeColor:'red',
strokeWidth:1
});

//停止显示所选区域拖动(新的一个被创建)和向上,因为我们完成
showSelection.removeOn({
drag:true,
up:true
});
}
};

//捕获开始拖动选择
paper.tool.onMouseUp = function(event){
if((event.event.ctrlKey&& event.event。 shiftKey)||(event.event.ctrlKey&& event.event.altKey)){
var endDragPoint = new paper.Point(event.point);
if(event.event.ctrlKey&& event.event.shiftKey){// Whiteout area
whiteoutArea(startDragPoint,endDragPoint);
} else if(event.event.ctrlKey&& event.event.altKey){//移动所选区域
selectArea(startDragPoint,endDragPoint);
}
}
};

//关键事件
paper.tool.onKeyUp = function(event){
//删除删除键上的所选项
if(event.key == 'delete'){
selectedItems.removeChildren();
} else if(event.key =='option'){
registerItemEvents(paths [paths.length-1]);

//开始一个新路径
paths.push(newPath());
}
}
}
}

//这些变量的范围使得PaperFunctions中的所有方法都可以访问它们
var canvas; // Set by setCanvas
var imageHeight; // Set by setCanvasImage

var raster;

var toolsSetup = false;

var setCanvas = function(canvasElement){
canvas = canvasElement;
paper.setup(canvas);
};

var setCanvasImage = function(imageObject){
if(imageObject!= null)
{
imageHeight = imageObject.height;
var imageWidth = imageObject.width;

//设置HTMLImage
var imageElement = new Image(imageObject.width,imageObject.height);
if(_。has(imageObject,'imageData')){//成为64位数据
imageElement.src ='data:image / png; base64,'+ imageObject.imageData;
} else if(_。has(imageObject,'imageUrl')){//成为URL
imageElement.src = imageObject.imageUrl;
}

//将图像添加到Paper.js画布
imageElement.onload = function(event){
//canvas.height = $(document)。 height() - 3; //设置画布高度。为什么这里这里,而不是在React组件?因为我们在这里设置宽度,所以我们把它们保持在一起。也许在将来,当我们响应窗口调整大小时,这将会改变。
// scalingFactor = canvas.height / imageObject.height; //确定比率
//canvas.width = imageElement.width * scalingFactor; //根据高度缩放宽度;画布高度已设置为文档的高度

//在画布上初始化Paper.js
paper.setup(canvas);

raster = new paper.Raster(imageElement,new paper.Point(canvas.width / 2,canvas.height / 2));

// setUpLineAndFreeFormDrawing(); // TODO一旦我们循环图像,我们需要为每个新的图像重置这个或者我们可以只做一次?

setUpNotes(); // TODO一旦我们循环图像,我们需要为每个新的图像重置这个或者我们可以只做一次?

selectedItems = new paper.Group(); //因为Paper.js已经设置好了,我们现在可以初始化这个组
registerCanvasEvents(); //平移,缩放,移动所选项目,取消选择所有选定项目

fitToPage();
};
}
};

var fitToPage = function(){
if(paper.view!= null&& canvas!= null){
//将图像整合到页面显示
var scalingFactor = canvas.height / imageHeight; //画布大小与图像大小之比的常量表示
var zoomFactor = scalingFactor / paper.view.zoom; //动画表示需要返回到在画布中查看整个图像的缩放

//将中心点重置为画布的中心
var canvasCenter = new paper.Point canvas.height / 2,canvas.height / 2);
paper.view.center = canvasCenter;

//缩放以适应画布中的整个图像
var returnedValues = panAndZoom.changeZoom(paper.view.zoom,-1,canvasCenter,canvasCenter,zoomFactor); //总是传递-1作为delta,不完全确定为什么
var newZoom = returnedValues [0];
var offset = returnedValues [1];
paper.view.zoom = newZoom;
paper.view.center = paper.view.center.add(offset);
}
};

var addImage = function(imageDataUrl){
if(paper.view!= null){
var img = document.createElement(img);
img.src = imageDataUrl;
var presentMomentForId = new Date()。getTime()+-image; //为了具有唯一的ID
img.id = presentMomentForId;
img.hidden = true;
document.body.appendChild(img);

var raster = new paper.Raster(presentMomentForId);

registerItemEvents(raster);
}
};

var setUpLineAndFreeFormDrawing = function(){
if(paper.project!= null){
//初始路径对象,将在Alt发布后重置为新路径
var path = newPath();
var paths = [];
paths.push(path);

//在mousedown上添加点从
开始paper.project.layers [0] .on('mousedown',function(event){
if .altKey&&!event.event.ctrlKey){// Alt键添加路径,但不允许尝试同时添加文本
if(paths [paths.length-1] .lastSegment = = null){
//path.add(event.point,event.point);
paths [paths.length-1] .add(event.point,event.point);
} else {
//path.add(path.lastSegment.point,path.lastSegment.point);
paths [paths.length-1] .add(paths [paths.length-1] .lastSegment.point,paths [paths.length-1] .lastSegment.point);
}
}
});

// on mousedrag添加点到路径
paper.project.layers [0] .on('mousedrag',function(event){
if(event.event。 altKey&&!event.event.ctrlKey){// Alt键添加路径,但不允许尝试在同一时间添加文本
if(event.event.shiftKey){//使用shift键for freeform
//path.add(event.point);
paths [paths.length-1] .add(event.point);
} else {//默认直线添加到路径
//path.lastSegment.point = event.point;
路径[paths.length-1] .lastSegment.point = event.point;
}
}
} .bind(this));

//每次Alt启动一个新路径
paper.tool.onKeyUp = function(event){
if(event.key ==option) {
registerItemEvents(paths [paths.length-1]);

//开始一个新路径
paths.push(newPath());
}
};
}
};

//建立默认线型
var newPath = function(){
var path = new paper.Path();
path.strokeColor ='black';
path.strokeWidth = 10;
return path;
};

var note =;
var setNote = function(newNote){
note = newNote;
};

var setUpNotes = function(){
if(paper.project!= null){
paper.project.layers [0] .on('mousedown',function event){// TODO应该是长期的第0层?
if(event.event.ctrlKey&!amp;!event.event.altKey&!amp;!event.event.shiftKey){//只有Ctrl键添加文本

//添加文本框
var textBox = new paper.PointText(event.point);
textBox.justification ='left';
textBox.fillColor ='black';
textBox.fontSize = 60;
textBox.content = note;

registerItemEvents(textBox);
}
});
}
};

var selectArea = function(startDragPoint,endDragPoint){
var rasterTopLeftCorner = new paper.Point(raster.bounds.topLeft);
var adjustedStartDragPoint = new paper.Point(startDragPoint.x - rasterTopLeftCorner.x,startDragPoint.y - rasterTopLeftCorner.y);
var adjustedEndDragPoint = new paper.Point(endDragPoint.x - rasterTopLeftCorner.x,endDragPoint.y - rasterTopLeftCorner.y);
var boundingRectangleRasterCoordinates = new paper.Rectangle(adjustedStartDragPoint,adjustedEndDragPoint);
var boundingRectangleCanvasCoordinates = new paper.Rectangle(startDragPoint,endDragPoint);

var selectedArea = raster.getSubRaster(boundingRectangleRasterCoordinates);

var whitedOutSelection = new paper.Shape.Rectangle(boundingRectangleCanvasCoordinates);
whitedOutSelection.fillColor ='white';
whitedOutSelection.insertAbove(raster); // Whiteout正上方我们使用的图像

registerItemEvents(selectedArea);
}

var whiteoutArea = function(startDragPoint,endDragPoint){
var whitedOutSelection = new paper.Shape.Rectangle(startDragPoint,endDragPoint);
whitedOutSelection.fillColor ='white';
whitedOutSelection.insertAbove(raster); // Whiteout上面的图像我们使用
}

var flattenToDataUrl = function(){
layerAsRaster = paper.project.layers [0] .rasterize ; // TODO应该是第0层长期? // Layer to Paper.js Raster object
layerAsRaster.visible = false;
var dataString = layerAsRaster.toDataURL();
return dataString;
};

module.exports = {
setCanvas:setCanvas,
setCanvasImage:setCanvasImage,
fitToPage:fitToPage,
addImage:addImage,
setNote :setNote,
flattenToDataUrl:flattenToDataUrl
};

此外,为了清楚起见,下面是SimplePanAndZoom.js文件。它使用最少的纸张函数,它主要只是进行计算:

  //基于http://matthiasberth.com/articles/稳定缩放和平移在纸张/ 

var SimplePanAndZoom =(function(){
function SimplePanAndZoom(){}

SimplePanAndZoom.prototype。 changeZoom = function(oldZoom,delta,centerPoint,offsetPoint,zoomFactor){
var newZoom = oldZoom;
if(delta< 0){
newZoom = oldZoom * zoomFactor;
}
if(delta> 0){
newZoom = oldZoom / zoomFactor;
}

//放大偏移点,而不是centerPoint相同)
var a = null;
if(!centerPoint.equals(offsetPoint)){
var scalingFactor = oldZoom / newZoom;
var difference = offsetPoint.subtract ;
a = offsetPoint.subtract(difference.multiply(scalingFactor))。subtract(centerPoint);
}

return [newZoom,a];
};

SimplePanAndZoom.prototype.changeCenter = function(oldCenter,deltaX,deltaY,factor){
var offset;
offset = new paper.Point(-deltaX,-deltaY);
offset = offset.multiply(factor);
return oldCenter.add(offset);
};

return SimplePanAndZoom;

})();

module.exports = SimplePanAndZoom;

谢谢。

解决方案

我在这里进行一些猜测,但我会解决代码中的一些问题,希望能够解决你看到的行为。



,我认为 paper.project.layers [0] paper.project.activeLayer 。一旦光栅化1)光栅被添加到图层,并且设置 visible = false 确实会导致更新完成后消失。



其次,当您在 imageElement.onload 中调用 paper.setup(canvas)创建一个新的纸张项目。该项目作为活动项目开始,并使之前的项目消失。因此,当您使用 raster = new paper.Raster(...)创建栅格时,它会进入新项目,而不是旧项目。



现在在旧项目中有一个隐藏的( .visible = false )光栅(我们称之为project1) in project2。



我不知道这是否是预期的行为,但是当你调用 paper.setup(canvas) code>似乎是第二次然后纸似乎注意到,他们都指向相同的画布,并保持project1和project2同步。所以创建第二个项目会清除第一个项目的children数组。添加新文件.Raster(...)最后将栅格添加到project1和project2。



我不知道下一个谜题是什么。你需要添加一些信息,如1)鼠标事件处理程序设置和它们附加到什么,2)什么setUpNotes(),3)什么registerCanvasEvents(),和4)什么fitToPage。 p>

有一些全局变量创建, imageHeight raster 可能不是故意的。还不清楚为什么需要使用 new Image() - paper.Raster()接受URL,包括数据网址。



我很惊讶地发现第一个项目。很好奇。



版本2:



让我使用图层构建这个结构。我建议你摆脱多个项目,因为鼠标事件处理程序附加到共享同一画布的多个项目添加了太多的复杂性。



因此,在您的代码初始化: paper.setup(canvas)



在最初由纸张创建的单一图层中设置初始图像。

  //这将被插入到当前图层,project.activeLayer 
var raster = new paper.Raster(imageURL,paper.view.bounds.center);

当队列中的图像发生变化时:

  //使现有的图像/图层不可见
paper.project.activeLayer.visible = false;

//添加一个插入项目并激活的新图层
var layer = new paper.Layer();

//新图层被激活,为图像创建一个光栅
var raster = new paper.Raster(imageURL,paper.view.bounds.center);

//现在为编辑,缩放等做你的正常逻辑。

这真的有点复杂,因为你有一个图像的队列,你只想创建一个图层,第一次访问图像。您可以在一开始就初始化所有的栅格,例如:

  var imageURLs = [url to image1,url to image2,etc]; 
imageURLs.forEach(function(url){
new paper.Layer();
paper.project.activeLayer.visible = false;
new paper.Raster(url,paper .view.bounds.center);
});
//使第一层可见并激活它
paper.project.layers [0] .visible = true;
paper.project.layers [0] .activate();

前面的代码为您的队列中的图像提供了一个并行数组,是不是检查是否已创建该图像:

 函数setImage(index){
paper。 project.activeLayer.visible = false;
paper.project.layers [index] .activate();
paper.project.layers [index] .visible = true;
}



最后,我确保鼠标处理不会导致我的问题。从你发布的新代码看,每个项目都有一个全局工具处理'mousedown','mousedrag'和'mouseup'事件,另一组处理程序 activeLayer 对于'mousedown','mousedrag'和'mouseup'事件,还有 selectedItems 有'mousedrag'的处理程序。我不能跟踪所有不同的处理程序应该在项目中做什么。我猜这些是你看到的闪烁的根本问题。



我可能只是使用 paper.view.on for'mousedown','mousedrag'和'mouseup'事件。当我得到一个事件,我会检查,以查看是否任何东西在层上被击中使用以下:

  paper.project。 activeLayer.hitTest(event.point); 

能够在视图上设置事件是新的纸张,但非常有用。可能有一些其他调整来处理突出显示未选择的项目。处理它的一个相对简单的方法是拥有一组选定的项目和一组未选择的项目:

  unSelectedGroup.on 'mouseenter',function(){
unSelectedGroup.selected = true;
});

unSelectedGroup.on('mouseleave',function(){
unSelectedGroup.selected = false;
});

当每次只有一个图层可见时,这些图层应该是安全的。我将在设置图像时设置这些组处理程序,无论是前面还是根据需要。或者,您还可以添加 paper.view.on('mousemove',...)并使用<$ c $来处理'mouseenter'和'mouseleave'事件



我想使用基于图层的方法来保存图片,同步。有足够的问题与基于项目的方法和许多不同的鼠标事件处理程序,你会在更稳定的地面,无论如何。


We are building an image editor of sorts using Paper.js. We have a queue on the side of the Paper.js canvas that allows switching between images. Each time we switch between images, we want to flatten all the annotations (rasterize) onto the image just being edited.

Each time we switch images, this method is called, which rasterizes the current image and annotations to a data URL. (If we revisit this image, a raster from this data URL will be displayed.)

var flattenToDataUrl = function() {
  layerAsRaster = paper.project.layers[0].rasterize(); // Layer to Paper.js Raster object
  layerAsRaster.visible = false; // Attempt to set not visible
  var dataString = layerAsRaster.toDataURL();
  return dataString;
};

Then we end up calling this method, which changes out the image we're editing:

var setCanvasImage = function(imageObject) {
  if(imageObject != null)
  {
    imageHeight = imageObject.height;
    var imageWidth = imageObject.width;

    // Set up HTMLImage
    var imageElement = new Image(imageObject.width, imageObject.height);
    if(_.has(imageObject, 'imageData')) { // Came as 64 bit data
      imageElement.src = 'data:image/png;base64,' + imageObject.imageData;
    } else if(_.has(imageObject, 'imageUrl')) { // Came as URL
      imageElement.src = imageObject.imageUrl;
    }

    // Add image to Paper.js canvas
    imageElement.onload = function(event) {

      // Initialize Paper.js on the canvas
      paper.setup(canvas);

      raster = new paper.Raster(imageElement, new paper.Point(canvas.width / 2, canvas.height / 2));

      setUpNotes();

      selectedItems = new paper.Group(); // Since Paper.js has been setup we can now initialize this Group
      registerCanvasEvents(); // Panning, zooming, moving selected items, deselecting all selected items

      fitToPage();
    };
  }
};

So, this changes out the image, but when I move my mouse into the canvas after selecting a different image in the queue, it glitches to the image we were just on (with its annotations) until I do something like pan, zoom, etc. Then I see the image I selected and am truly working with.

Removing the flattenToDataUrl() functionality makes the queue work seamlessly. So it seems to me something is fishy there. We are generating a Paper.js Raster object in that method. Rasters seem to automatically add themselves. I attempt to curb this with a call to

layerAsRaster.visible = false;

but to no avail.

What is causing this glitchy behavior and how do I prevent it?

Update

For clarity (hopefully) and completeness, I've decided to post the whole PaperFunctions class we use in conjunction with React, which hosts our <canvas> element. There's a lot of code, and a lot of cleanup to do, especially in registerCanvasEvents(). Bear with this learning beginner. Also it's several hundred lines, so it may be helpful to paste it into your favorite editor. Entry points include setCanvas which is called in componentDidMount of the React class with the <canvas> element, and canvasSetImage which is called from the queue. I agree from bmacnaughton's answer that it's weird to call paper.setup(canvas) every time we load a new image. I'm currently investigating the right solution to this, the right place to put it. setCanvas seems logical but when I drag the image to move it in that setup, it leaves a trail of images in its wake. Anyway, here's PaperFunctions.js:

var JQueryMousewheel = require('jquery-mousewheel')($);

var SimplePanAndZoom = require('./SimplePanAndZoom.js');
var panAndZoom = new SimplePanAndZoom();

var selectedItems;

// We use selection here in two distinct ways.
// An item may be Paper.js selected but not in the selection group.
// This is because we want to show it is selectable.
// A blue bounding box indicates it is selectable.
// A green bounding box indicates it has actually been selected and added to selectedItems.
// Only things in selectedItems are actually operated on.
// So the event handlers in this method basically set up whether or not the item is in selectedItems (and therefore will be operated on for moving, resizing, deleting, etc.).
// That is, the event handlers here are concerned with displaying to the user the status of selection for the item - whether or not it will be operated on when events actually happen on the selectedItems Group.
var registerItemEvents = function(item) {
  // Boolean flag for mouseup to know if was drag or click
  var dragged;

  // For tracking if dragging or clicking is happening
  item.on('mousedown', function(e) {
    dragged = false;
  });

  // On click toggle selection
  item.on('mouseup', function(event) {
    event.stopPropagation(); // Only for item applied to
    event.preventDefault();

    if(!dragged) {
      var justAdded = addIfNotInSelectedItems(item);
      if(!justAdded) { // Item was in selection group, remove it
        item.remove();
        paper.project.activeLayer.addChild(item);

        this.selectedColor = paper.project.activeLayer.selectedColor;
        //item.selected = false;
      }
    }
  });

  // Show as selectable even after has been deselected
  item.on('mousemove', function(event) {
    this.selected = true;
  })

  // If not selected, on mouse enter show that it is selectable
  item.on('mouseenter', function(event) {
    if(!this.selected) {
      this.selected = true;
    }
  });

  // If not selected, on mouse leave remove indicator that is selectable
  item.on('mouseleave', function(event) {
    var isInSelectedItems = selectedItems.getItem(item);
    if(this.selected && isInSelectedItems == null) {
      this.selected = false;
    }
  });

  // On drag, move item
  item.on('mousedrag', function(event) {
    dragged = true;

    // If user starts dragging automatically select the item
    addIfNotInSelectedItems(item);
  });
}

var addIfNotInSelectedItems = function(item) {
  var isInSelectedItems = selectedItems.getItem(item);
  if(isInSelectedItems == null) { // Item not currently in selection group, add it
    selectedItems.addChild(item);
    item.selectedColor = 'green';
    item.selected = true;
    return true; // Was added, return true
  } else {
    return false; // Already in, return false
  }
}

var registerCanvasEvents = function() {
  if(paper.view != null && canvas != null) {
    // Zoom on mousewheel
    $(canvas).mousewheel(function(event) {
      event.preventDefault();
      var mousePosition = new paper.Point(event.offsetX, event.offsetY);
      var viewPosition = paper.view.viewToProject(mousePosition);
      var returnedValues = panAndZoom.changeZoom(paper.view.zoom, (event.deltaY * -1), paper.view.center, viewPosition, 1.1);
      var newZoom = returnedValues[0];
      var offset = returnedValues[1];
      paper.view.zoom = newZoom;
      paper.view.center = paper.view.center.add(offset);
    });

    // For tracking if dragging or clicking is happening
    var dragged;
    paper.project.layers[0].on('mousedown', function(e) { // TODO should be layer 0 in long run?
      dragged = false;
    });


    // Pan on mouse drag
    /*paper.project.layers[0].on('mousedrag', function(event) { // TODO should be layer 0 in long run?
      if(!event.event.ctrlKey && !event.event.altKey && !event.event.shiftKey) { // No keys (that we use) can be pushed
        dragged = true; // We're panning, we don't wish to deselect all items as we would do with a click
        paper.view.center = panAndZoom.changeCenter(paper.view.center, event.delta.x, event.delta.y, 0.7);
        //event.preventDefault();
      }
    });*/

    // Move selected items on mouse drag
    selectedItems.on('mousedrag', function(event) {
      event.stopPropagation(); // Don't propogate up or it will register as a pan event
      event.preventDefault();

      dragged = true; // We're panning, we don't wish to deselect all items as we would do with a click

      this.translate(new paper.Point(event.delta.x, event.delta.y));
    });

    // If was a click and not a drag, deselect selected items
    paper.project.layers[0].on('mouseup', function(event) {
      if(!dragged) {
        var removedItems = selectedItems.removeChildren(); // Remove from selection group, which also removes from display
        paper.project.activeLayer.addChildren(removedItems); // Return to display

        // Reset selection colors for showing selectable
        for(var i =0; i < removedItems.length; i++) {
          removedItems[i].selectedColor = paper.project.activeLayer.selectedColor;
          removedItems[i].selected = false;
        }
      }
    });

    // Initial path object, will be reset for new paths after Alt is released
    var path = newPath();
    var paths = [];
    paths.push(path);

    // On mousedown add point to start from
    paper.project.layers[0].on('mousedown', function(event) {
      if(event.event.altKey && !event.event.ctrlKey) { // Alt key to add a path, but disallow attempting to add text at the same time
        if(paths[paths.length-1].lastSegment == null) {
          //path.add(event.point, event.point);
          paths[paths.length-1].add(event.point, event.point);
        } else {
          //path.add(path.lastSegment.point, path.lastSegment.point);
          paths[paths.length-1].add(paths[paths.length-1].lastSegment.point, paths[paths.length-1].lastSegment.point);
        }
      }
    });

    // On mousedrag add points to path
    paper.project.layers[0].on('mousedrag', function(event) {
      if(event.event.altKey && !event.event.ctrlKey) { // Alt key to add a path, but disallow attempting to add text at the same time
        if(event.event.shiftKey) { // Use shift key for freeform
          //path.add(event.point);
          paths[paths.length-1].add(event.point);
        } else { // Default of straight line added to path
          //path.lastSegment.point = event.point;
          paths[paths.length-1].lastSegment.point = event.point;
        }
      }
    }.bind(this));

    var tool = new paper.Tool();

    var startDragPoint;

    // Capture start of drag selection
    paper.tool.onMouseDown = function(event) {
      if((event.event.ctrlKey && event.event.shiftKey) || (event.event.ctrlKey && event.event.altKey)) {
        startDragPoint = new paper.Point(event.point);
      }
    };

    paper.tool.onMouseDrag = function(event) {
      // Panning
      if(!event.event.ctrlKey && !event.event.altKey && !event.event.shiftKey) { // No keys (that we use) can be pushed
        dragged = true; // We're panning, we don't wish to deselect all items as we would do with a click
        paper.view.center = panAndZoom.changeCenter(paper.view.center, event.delta.x, event.delta.y, 0.7);
        //event.preventDefault();
      }

      // Show box indicating the area that has been selected
      // For moving area and whiting out area
      if((event.event.ctrlKey && event.event.shiftKey) || (event.event.ctrlKey && event.event.altKey)) {
        dragged = true;
        var showSelection = new paper.Path.Rectangle({
            from: startDragPoint,
            to: event.point,
            strokeColor: 'red',
            strokeWidth: 1
        });

        // Stop showing the selected area on drag (new one is created) and up because we're done
        showSelection.removeOn({
            drag: true,
            up: true
        });
      }
    };

    // Capture start of drag selection
    paper.tool.onMouseUp = function(event) {
      if((event.event.ctrlKey && event.event.shiftKey) || (event.event.ctrlKey && event.event.altKey)) {
        var endDragPoint = new paper.Point(event.point);
        if(event.event.ctrlKey && event.event.shiftKey) { // Whiteout area
          whiteoutArea(startDragPoint, endDragPoint);
        } else if(event.event.ctrlKey && event.event.altKey) { // Move selected area
          selectArea(startDragPoint, endDragPoint);
        }
      }
    };

    // Key events
    paper.tool.onKeyUp = function(event) {
      // Delete selected items on delete key
      if(event.key == 'delete') {
        selectedItems.removeChildren();
      } else if (event.key == 'option') {
        registerItemEvents(paths[paths.length-1]);

        // Start a new path
        paths.push(newPath());
      }
    }
  }
}

// These variables are scoped so that all methods in PaperFunctions can access them
var canvas; // Set by setCanvas
var imageHeight; // Set by setCanvasImage

var raster;

var toolsSetup = false;

var setCanvas = function(canvasElement) {
  canvas = canvasElement;
  paper.setup(canvas);
};

var setCanvasImage = function(imageObject) {
  if(imageObject != null)
  {
    imageHeight = imageObject.height;
    var imageWidth = imageObject.width;

    // Set up HTMLImage
    var imageElement = new Image(imageObject.width, imageObject.height);
    if(_.has(imageObject, 'imageData')) { // Came as 64 bit data
      imageElement.src = 'data:image/png;base64,' + imageObject.imageData;
    } else if(_.has(imageObject, 'imageUrl')) { // Came as URL
      imageElement.src = imageObject.imageUrl;
    }

    // Add image to Paper.js canvas
    imageElement.onload = function(event) {
      //canvas.height = $(document).height()-3; // Set canvas height. Why do this here and not in the React component? Because we set the width here too, so we're keeping those together. Perhaps in the future this will be changed when we are responsive to window resizing.
      //scalingFactor = canvas.height / imageObject.height; // Determine the ratio
      //canvas.width = imageElement.width * scalingFactor; // Scale width based on height; canvas height has been set to the height of the document

      // Initialize Paper.js on the canvas
      paper.setup(canvas);

      raster = new paper.Raster(imageElement, new paper.Point(canvas.width / 2, canvas.height / 2));

      //setUpLineAndFreeFormDrawing(); // TODO once we cycle through images will we need to reset this for each new image or can we do this just once?

      setUpNotes(); // TODO once we cycle through images will we need to reset this for each new image or can we do this just once?

      selectedItems = new paper.Group(); // Since Paper.js has been setup we can now initialize this Group
      registerCanvasEvents(); // Panning, zooming, moving selected items, deselecting all selected items

      fitToPage();
    };
  }
};

var fitToPage = function() {
  if(paper.view != null && canvas != null) {
    // Fit image to page so whole thing is displayed
    var scalingFactor = canvas.height / imageHeight; // Constant representation of the ratio of the canvas size to the image size
    var zoomFactor = scalingFactor / paper.view.zoom; // Dynamic representation of the zoom needed to return to viewing the whole image in the canvas

    // Reset the center point to the center of the canvas
    var canvasCenter = new paper.Point(canvas.width/2, canvas.height/2);
    paper.view.center = canvasCenter;

    // Zoom to fit the whole image in the canvas
    var returnedValues = panAndZoom.changeZoom(paper.view.zoom, -1, canvasCenter, canvasCenter, zoomFactor); // Always pass -1 as the delta, not entirely sure why
    var newZoom = returnedValues[0];
    var offset = returnedValues[1];
    paper.view.zoom = newZoom;
    paper.view.center = paper.view.center.add(offset);
  }
};

var addImage = function(imageDataUrl) {
  if(paper.view != null) {
    var img = document.createElement("img");
    img.src = imageDataUrl;
    var presentMomentForId = new Date().getTime() + "-image"; // For purposes of having unique IDs
    img.id = presentMomentForId;
    img.hidden = true;
    document.body.appendChild(img);

    var raster = new paper.Raster(presentMomentForId);

    registerItemEvents(raster);
  }
};

var setUpLineAndFreeFormDrawing = function() {
  if(paper.project != null) {
    // Initial path object, will be reset for new paths after Alt is released
    var path = newPath();
    var paths = [];
    paths.push(path);

    // On mousedown add point to start from
    paper.project.layers[0].on('mousedown', function(event) {
      if(event.event.altKey && !event.event.ctrlKey) { // Alt key to add a path, but disallow attempting to add text at the same time
        if(paths[paths.length-1].lastSegment == null) {
          //path.add(event.point, event.point);
          paths[paths.length-1].add(event.point, event.point);
        } else {
          //path.add(path.lastSegment.point, path.lastSegment.point);
          paths[paths.length-1].add(paths[paths.length-1].lastSegment.point, paths[paths.length-1].lastSegment.point);
        }
      }
    });

    // On mousedrag add points to path
    paper.project.layers[0].on('mousedrag', function(event) {
      if(event.event.altKey && !event.event.ctrlKey) { // Alt key to add a path, but disallow attempting to add text at the same time
        if(event.event.shiftKey) { // Use shift key for freeform
          //path.add(event.point);
          paths[paths.length-1].add(event.point);
        } else { // Default of straight line added to path
          //path.lastSegment.point = event.point;
          paths[paths.length-1].lastSegment.point = event.point;
        }
      }
    }.bind(this));

    // Each time Alt comes up, start a new path
    paper.tool.onKeyUp = function(event) {
      if(event.key == "option") {
        registerItemEvents(paths[paths.length-1]);

        // Start a new path
        paths.push(newPath());
      }
    };
  }
};

// Establishes default line style
var newPath = function() {
  var path = new paper.Path();
  path.strokeColor = 'black';
  path.strokeWidth = 10;
  return path;
};

var note = "";
var setNote = function(newNote) {
  note = newNote;
};

var setUpNotes = function() {
  if(paper.project != null) {
    paper.project.layers[0].on('mousedown', function(event) { // TODO should be layer 0 in long run?
      if(event.event.ctrlKey && !event.event.altKey && !event.event.shiftKey) { // Only Ctrl key to add text

        // Add text box
        var textBox = new paper.PointText(event.point);
        textBox.justification = 'left';
        textBox.fillColor = 'black';
        textBox.fontSize = 60;
        textBox.content = note;

        registerItemEvents(textBox);
      }
    });
  }
};

var selectArea = function(startDragPoint, endDragPoint) {
  var rasterTopLeftCorner = new paper.Point(raster.bounds.topLeft);
  var adjustedStartDragPoint = new paper.Point(startDragPoint.x - rasterTopLeftCorner.x, startDragPoint.y - rasterTopLeftCorner.y);
  var adjustedEndDragPoint = new paper.Point(endDragPoint.x - rasterTopLeftCorner.x, endDragPoint.y - rasterTopLeftCorner.y);
  var boundingRectangleRasterCoordinates = new paper.Rectangle(adjustedStartDragPoint, adjustedEndDragPoint);
  var boundingRectangleCanvasCoordinates = new paper.Rectangle(startDragPoint, endDragPoint);

  var selectedArea = raster.getSubRaster(boundingRectangleRasterCoordinates);

  var whitedOutSelection = new paper.Shape.Rectangle(boundingRectangleCanvasCoordinates);
  whitedOutSelection.fillColor = 'white';
  whitedOutSelection.insertAbove(raster); // Whiteout just above the image we're working with

  registerItemEvents(selectedArea);
}

var whiteoutArea = function(startDragPoint, endDragPoint) {
  var whitedOutSelection = new paper.Shape.Rectangle(startDragPoint, endDragPoint);
  whitedOutSelection.fillColor = 'white';
  whitedOutSelection.insertAbove(raster); // Whiteout just above the image we're working with
}

var flattenToDataUrl = function() {
  layerAsRaster = paper.project.layers[0].rasterize(); // TODO should be layer 0 in long run? // Layer to Paper.js Raster object
  layerAsRaster.visible = false;
  var dataString = layerAsRaster.toDataURL();
  return dataString;
};

module.exports = {
  setCanvas: setCanvas,
  setCanvasImage: setCanvasImage,
  fitToPage: fitToPage,
  addImage: addImage,
  setNote: setNote,
  flattenToDataUrl: flattenToDataUrl
};

Additionally, here's the SimplePanAndZoom.js file for clarity. It uses minimal Paper functions, it mainly just does calculations:

// Based on http://matthiasberth.com/articles/stable-zoom-and-pan-in-paperjs/

var SimplePanAndZoom = (function() {
  function SimplePanAndZoom() { }

  SimplePanAndZoom.prototype.changeZoom = function(oldZoom, delta, centerPoint, offsetPoint, zoomFactor) {
    var newZoom = oldZoom;
    if (delta < 0) {
      newZoom = oldZoom * zoomFactor;
    }
    if (delta > 0) {
      newZoom = oldZoom / zoomFactor;
    }

    // Zoom towards offsetPoint, not centerPoint (unless they're the same)
    var a = null;
    if(!centerPoint.equals(offsetPoint)) {
      var scalingFactor = oldZoom / newZoom;
      var difference = offsetPoint.subtract(centerPoint);
      a = offsetPoint.subtract(difference.multiply(scalingFactor)).subtract(centerPoint);
    }

    return [newZoom, a];
  };

  SimplePanAndZoom.prototype.changeCenter = function(oldCenter, deltaX, deltaY, factor) {
    var offset;
    offset = new paper.Point(-deltaX, -deltaY);
    offset = offset.multiply(factor);
    return oldCenter.add(offset);
  };

  return SimplePanAndZoom;

})();

module.exports = SimplePanAndZoom;

Thanks.

解决方案

I'm taking some guesses here but I'll address some problems in the code that will hopefully address the behavior you're seeing.

First, I presume paper.project.layers[0] is paper.project.activeLayer. Once that has been rasterized 1) the raster is added to the layer and setting visible = false does cause it to disappear when an update is done.

Second, when you invoke paper.setup(canvas) in imageElement.onload you create a new paper project. This project starts out as the active project and makes the previous project "disappear". So when you create a raster with raster = new paper.Raster(...) it goes into the new project, not the old project.

So now there is a hidden (.visible = false) raster in the old project (let's call it project1) and a new version of it in project2.

I'm not sure if this is the intended behavior or not, but when you invoke paper.setup(canvas) for what seems to be the second time then paper seems to notice that they both refer to the same canvas and keeps project1 and project2 in sync. So creating the second project clears the first project's children array. And adding new paper.Raster(...) ends up adding the raster to project1 and project2.

Now I can't tell what the next piece of the puzzle is. You'd need to add some information like 1) where the mouse event handlers are setup and what they are attached to, 2) what setUpNotes() does, 3) what registerCanvasEvents() does, and 4) what fitToPage does.

There are a few globals created, imageHeight and raster that probably aren't intentional. And it's not clear why you need to use new Image() at all - paper.Raster() accepts URLs, including data URLs.

I was surprised paper cleared the first project. It's curious.

Version 2:

Let me take a stab at structuring this using layers. I'd suggest you get rid of multiple projects because having mouse event handlers attached to multiple projects that share the same canvas adds too much complexity.

So, in your code initialization: paper.setup(canvas). Do this once and only once.

Setup the initial image in the single layer initially created by paper.

// this will be inserted into the current layer, project.activeLayer
var raster = new paper.Raster(imageURL, paper.view.bounds.center);

When the image in your queue changes do something like:

// make the existing image/layer invisible
paper.project.activeLayer.visible = false;

// add a new layer which is inserted in the project and activated
var layer = new paper.Layer();

// the new layer is activated, create a raster for the image
var raster = new paper.Raster(imageURL, paper.view.bounds.center);

// now do your normal logic for editing, zooming, etc.

It's really a bit more complicated than that because you have a queue of images and you only want to create a layer the first time you visit an image. You could initialize all the rasters at the outset, something like:

var imageURLs = ["url to image1", "url to image2", "etc"];
imageURLs.forEach(function(url) {
    new paper.Layer();
    paper.project.activeLayer.visible = false;
    new paper.Raster(url, paper.view.bounds.center);
});
// make the first layer visible and activate it
paper.project.layers[0].visible = true;
paper.project.layers[0].activate();

The preceeding code gives you a parallel array to the images in your queue so switching images is straightforward - there is no checking to see if that image has been created or not:

function setImage(index) {
    paper.project.activeLayer.visible = false;
    paper.project.layers[index].activate();
    paper.project.layers[index].visible = true;
}

Finally, I would make sure my mouse handling wasn't causing me problems. From the new code you posted it looks like each project had a global tool that handled 'mousedown', 'mousedrag', and 'mouseup' events, another set of handlers for activeLayer for 'mousedown', 'mousedrag', and 'mouseup' events, and also selectedItems has a handler for 'mousedrag'. I can't keep track of what all the different handlers are supposed to do across projects. I'm guessing that these are the root issue with the flickering you saw.

I would likely just use paper.view.on for 'mousedown', 'mousedrag', and 'mouseup' events. When I get an event I would check to see if anything on the layer was hit by using the following:

paper.project.activeLayer.hitTest(event.point);

Being able to set events on the view is new for paper but very useful. There may be a few other tweaks necessary to handle highlighting unselected items. A relatively straightforward way to handle that is to have a group of selected items and a group of unselected items:

unSelectedGroup.on('mouseenter', function() {
    unSelectedGroup.selected = true;
});

unSelectedGroup.on('mouseleave', function() {
    unSelectedGroup.selected = false;
});

These should be safe across layers when only one layer is visible at a time. I would set up these group handlers when setting up the images, whether all up front or on an as-needed basis. Alternatively, you could also add paper.view.on('mousemove', ...) and handle the 'mouseenter' and 'mouseleave' events yourself using hitTest as shown above, but either approach should work.

I think using a layer-based approach to your images will keep things in sync. There are enough problems with the project-based approach and many different mouse event handlers that you'll be on more stable ground regardless.

这篇关于Paper.js背景光栅化的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆