I'm trying to make a simple graphics program for doodling and such, but I'm having some trouble with the line tools provided by PIXI.Graphics().
I draw lines by using events such as pointerdown, pointermove, etc. Saving the last coordinate and then using moveTo and lineTo to draw a continuous line until you trigger pointerup. When drawing a line however gaps appear in semi-regular intervals.
Those gaps are always in the color used in the beginFill() fill statement. Even if I use multiple colors, they will appear in the line, making it not really "gaps" but wrongly colored lines.
When a long line is drawn via code, the same error does not seem to occur.
I'm quite stumped by the behaviour and it would be nice if you could help me out!
Here is the code I used:
let app;
let graphics;
var isDrawing = false;
var x = 0;
var y = 0;
var count = 0;
window.onload = function(){
var canvasContainer = document.getElementById("areaCanvasContainer");
//Create a Pixi Application
app = new PIXI.Application({
width: 600,
height: 500,
antialias: false,
transparent: false,
resolution: 1,
forceCanvas: false,
backgroundColor: 0xFFFFFF
});
canvasContainer.appendChild(app.view);
graphics = new PIXI.Graphics();
graphics.interactive = true;
graphics.buttonMode = true;
graphics.beginFill(0xFFFFFF);
graphics.drawRect(0, 0, 600, 500);
graphics.endFill();
graphics.lineStyle(3, 0x000000,1.0,0.5,false);
graphics.on("pointerdown", mousedown);
graphics.on("pointermove", mousemove);
graphics.on("pointerup", mouseup);
graphics.on("pointerupoutside", mouseup);
app.stage.addChild(graphics);
}
function mousedown(e) {
x = e.data.global.x;
y = e.data.global.y;
isDrawing = true;
}
function mousemove(e) {
if (isDrawing === true) {
if(Math.abs(x - e.data.global.x) > 2 || Math.abs(y - e.data.global.y ) > 2 ){
drawLine(graphics, x, y, e.data.global.x, e.data.global.y);
x = e.data.global.x;
y = e.data.global.y;
}
}
}
function mouseup(e) {
if (isDrawing === true) {
drawLine(graphics, x, y, e.data.global.x, e.data.global.y);
x = 0;
y = 0;
isDrawing = false;
}
}
function drawLine(graphics, x1, y1, x2, y2) {
if(!(x1 === x2 && y1 === y2)){
graphics.endFill();
graphics.moveTo(x1, y1);
graphics.lineTo(x2, y2);
}
}
Related
Iterating over plotData creates a rect around certain values on a chart based on a condition.
plotData.forEach( (each, index) => {
if ( each.a > each.b ) {
try {
ctx.beginPath();
ctx.fillStyle = 'yellow';
ctx.rect(zone[index].x, zone[index].y, zone[index].width, zone[index].height);
ctx.closePath();
ctx.fill();
}
...
I want to change the color of the rect when the mouse hovers over it. The chart is scrollable and zoomable therefore the rect coordinates change dynamically.
Firstly, outside of this function, somewhere else in the document code, in fact: at the very top of your code, define a variable to keep track of the mouses location, and if its clicked or not:
var mouseInfo = {x:0, y:0, clicked: false};
Now, somewhere else make some event listeners that keep track of that location (sometime after the canvas element has been made), I'm assuming the canvas element is stored in a variable called canvas:
canvas.addEventListener("mousemove", e=> {
mouseInfo.x = e.offsetX;
mouseInfo.y = e.offsetY;
});
next, another 2 to keep track of if the mouse is down or not (doesn't have to be on the canvas element, in fact, better that it should be on the window element):
window.addEventListener("mousedown", e=> {
mouseInfo.clicked = true;
});
window.addEventListener("mouseup", e=> {
mouseInfo.clicked = false;
});
Now make a function to determine if a point is intersecting a rectangle, assuming a rectangle is an object with an x, y, width, height:
function isPointInRet(point, rect) {
return (
point.x > rect.x &&
point.x < rect.x + rect.width &&
point.y > rect.y &&
point.y < rect.y + rect.height
);
}
Now, back in your function that you quoted above:
plotData.forEach( (each, index) => {
if ( each.a > each.b ) {
try {
ctx.beginPath();
ctx.fillStyle = 'yellow';
ctx.rect(zone[index].x, zone[index].y, zone[index].width, zone[index].height);
ctx.closePath();
ctx.fill();
}
So, right before the fillStyle part, change it all to the following:
var x = zone[index].x,
y = zone[index].y,
width = zone[index].width,
height = zone[index].height;
if(isPointInRect(mouseInfo, {x, y, width, height}) {
//perhaps if you want to make a hover color, do that here, just un-comment if you want:
/*ctx.fillStyle = "#ffaaee";*/
if(mouseInfo.clicked) {
ctx.fillStyle = "ff0000"; //red
}
}
ctx.fillStyle = 'yellow';
ctx.rect(x, y, width, height);
...
(warning: untested code);
let me know if that helps, and if it works with the zooming etc.
I'm trying to use Fabric.js with Fabric Brush This issue that I'm running into is that Fabric Brush only puts the brush strokes onto the Top Canvas and not the lower canvas. (The stock brushes in fabric.js save to the bottom canvas) I think I need to convert "this.canvas.contextTop.canvas" to an object and add that object to the the lower canvas. Any ideas?
I've tried running:
this.canvas.add(this.canvas.contextTop)
in
onMouseUp: function (pointer) {this.canvas.add(this.canvas.contextTop)}
But I'm getting the error
Uncaught TypeError: obj._set is not a function
So the contextTop is CanvasHTMLElement context. You cannot add it.
You can add to the fabricJS canvas just fabric.Object derived classes.
Look like is not possible for now.
They draw as pixel effect and then they allow you to export as an image.
Would be nice to extend fabricJS brush interface to create redrawable objects.
As of now with fabricJS and that particular version of fabric brush, the only thing you can do is:
var canvas = new fabric.Canvas(document.getElementById('c'))
canvas.freeDrawingBrush = new fabric.CrayonBrush(canvas, {
width: 70,
opacity: 0.6,
color: "#ff0000"
});
canvas.isDrawingMode = true
canvas.on('mouse:up', function(opt) {
if (canvas.isDrawingMode) {
var c = fabric.util.copyCanvasElement(canvas.upperCanvasEl);
var img = new fabric.Image(c);
canvas.contextTopDirty = true;
canvas.add(img);
canvas.isDrawingMode = false;
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.4.1/fabric.min.js"></script>
<script src="https://tennisonchan.github.io/fabric-brush/bower_components/fabric-brush/dist/fabric-brush.min.js"></script>
<button>Enter free drawing</button>
<canvas id="c" width="500" height="500" ></canvas>
That is just creating an image from the contextTop and add as an object.
I have taken the approach suggested by AndreaBogazzi and modified the Fabric Brush so that it does the transfer from upper to lower canvas (as an image) internal to Fabric Brush. I also used some code I found which crops the image to a smaller bounding box so that is smaller than the full size of the canvas. Each of the brushes in Fabric Brush has an onMouseUp function where the code should be placed. Using the case of the SprayBrush, the original code here was:
onMouseUp: function(pointer) {
},
And it is replaced with this code:
onMouseUp: function(pointer){
function trimbrushandcopytocanvas() {
let ctx = this.canvas.contextTop;
let pixels = ctx.getImageData(0, 0, canvas.upperCanvasEl.width, canvas.upperCanvasEl.height),
l = pixels.data.length,
bound = {
top: null,
left: null,
right: null,
bottom: null
},
x, y;
// Iterate over every pixel to find the highest
// and where it ends on every axis ()
for (let i = 0; i < l; i += 4) {
if (pixels.data[i + 3] !== 0) {
x = (i / 4) % canvas.upperCanvasEl.width;
y = ~~((i / 4) / canvas.upperCanvasEl.width);
if (bound.top === null) {
bound.top = y;
}
if (bound.left === null) {
bound.left = x;
} else if (x < bound.left) {
bound.left = x;
}
if (bound.right === null) {
bound.right = x;
} else if (bound.right < x) {
bound.right = x;
}
if (bound.bottom === null) {
bound.bottom = y;
} else if (bound.bottom < y) {
bound.bottom = y;
}
}
}
// Calculate the height and width of the content
var trimHeight = bound.bottom - bound.top,
trimWidth = bound.right - bound.left,
trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight);
// generate a second canvas
var renderer = document.createElement('canvas');
renderer.width = trimWidth;
renderer.height = trimHeight;
// render our ImageData on this canvas
renderer.getContext('2d').putImageData(trimmed, 0, 0);
var img = new fabric.Image(renderer,{
scaleY: 1./fabric.devicePixelRatio,
scaleX: 1./fabric.devicePixelRatio,
left: bound.left/fabric.devicePixelRatio,
top:bound.top/fabric.devicePixelRatio
});
this.canvas.clearContext(ctx);
canvas.add(img);
}
setTimeout(trimbrushandcopytocanvas, this._interval); // added delay because last spray was on delay and may not have finished
},
The setTimeout function was used because Fabric Brush could still be drawing to the upper canvas after the mouseup event occurred, and there were occasions where the brush would continue painting the upper canvas after its context was cleared.
I am working with Processing.js (version 1.4.8).
I have 5 white points, which coordinates I chose specifically. The black dot marks the center of the sketch! I want to be able to translate and scale my sketch. ALSO, I want it to occupy the whole window.
var mapWidth, mapHeight, canvas, pjs, centerX, centerY;
var points = [[100, 100], [300, 100], [100, 300], [300, 300], [200, 200]];
var setSize = function() {
mapWidth = $(window).outerWidth();
mapHeight = $(window).outerHeight();
if (pjs) {
pjs.size(mapWidth, mapHeight);
}
};
var clear = function() {
pjs.background(200);
};
var drawPoint = function(coordinates) {
var radius = 30;
pjs.ellipse(coordinates[0], coordinates[1], radius, radius);
};
var drawPoints = function() {
pjs.fill(255);
points.map(function(point) {
drawPoint(point);
});
};
var calculateCenter = function() {
centerX = Math.floor(mapWidth / 2);
centerY = Math.floor(mapHeight / 2);
};
var drawCenter = function() {
calculateCenter();
var radius = 10;
pjs.fill(0);
pjs.ellipse(centerX, centerY, radius, radius);
console.log("center", centerX, centerY);
};
var move = function() {
pjs.translate(200, 300);
redraw();
};
var zoomIn = function() {
pjs.scale(2, 2);
redraw();
};
var draw = function() {
clear();
drawPoints();
drawCenter();
};
var redraw = function() {
clear();
draw();
};
var addEvent = function(object, type, callback) {
if (object == null || typeof object == "undefined") return;
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on" + type] = callback;
}
};
$(function() {
canvas = document.getElementById("map");
setSize();
var pjsRun = function(processingjs) {
pjs = processingjs;
pjs.setup = function() {
pjs.size(mapWidth, mapHeight);
draw();
};
};
var p = new Processing(canvas, pjsRun);
addEvent(window, "resize", function(event) {
setSize();
redraw();
});
});
Until here, everything is fine, as you can see in this CodePen.
I want to be able to resize the window AND keep the transformations (translations, scales, ...) that I had already performed.
Please, open the CodePen and try to reproduce this weird behaviour:
1) Perform one (or two) transformation(s) using the top-right buttons
The map is translated by 200 to the right and 300 downwards.
Everything OK by now...
But the problem arises now.
2) Resize the window
The five points are again where they were before the "translate" operation.
So... Again... Is there a way to resize without losing all the transformations that had been performed?
Thanks
Like you've discovered, it appears as though calling the size() function resets the transformation matrix. The short answer to your question is that you need to keep track of the transformations, and then apply them whenever you draw something.
The longer answer to your question is that you're using Processing.js a little bit differently than people typically use it. You've left out the draw() function (note that your draw() function is not the draw() function that's automatically called 60 times per second) and are trying to code event handlers yourself. This disconnect is why you're having issues.
If I were you, I'd start with a more basic sketch that starts out using Processing's built-in draw() function. Write code that draws the scene every frame. Make sure you set the translation and scale every frame. Here's an example:
var draw = function() {
scale(scaleX, scaleY);
translate(translateX, translateY);
background(200);
fill(255);
points.map(function(point) {
ellipse(coordinates[0], coordinates[1], 30, 30);
});
fill(0);
ellipse(width/2, height/2, 10, 10);
};
Then setup event listeners that change the values of scaleX and scaleY as well as translateX and translateY. Let Processing handle the rest.
Hello i create program like a paint on HTML5 canvas. I have problem i need create few tools drawing and zoom. I don't have idea how to create zoom without delay. Drawing example: http://jsfiddle.net/x5rrvcr0/
How i can zooming my drawings?
drawing code:
<style>
canvas {
background-color: #CECECE;
}
html, body {
background-color: #FFFFFF;
}
</style>
<script>
$(document).ready(function () {
var paintCanvas = document.getElementById("paintCanvas");
var paintCtx = paintCanvas.getContext("2d");
var size = 500;
paintCanvas.width = size;
paintCanvas.height = size;
var draw = false;
var prevMouseX = 0;
var prevMouseY = 0;
function getMousePos(canvas, evt) {
evt = evt.originalEvent || window.event || evt;
var rect = canvas.getBoundingClientRect();
if (evt.clientX !== undefined && evt.clientY !== undefined) {
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
}
$("#paintCanvas").on("mousedown", function(e) {
draw = true;
var coords = getMousePos(paintCanvas);
prevMouseX = coords.x;
prevMouseY = coords.y;
});
$("#paintCanvas").on("mousemove", function(e) {
if(draw) {
var coords = getMousePos(paintCanvas, e);
paintCtx.beginPath();
paintCtx.lineWidth = 10;
paintCtx.strokeStyle = "#000000";
paintCtx.moveTo(prevMouseX, prevMouseY);
paintCtx.lineTo(coords.x, coords.y);
paintCtx.stroke();
prevMouseX = coords.x;
prevMouseY = coords.y;
}
});
$("#paintCanvas").on("mouseup", function(e) {
draw = false;
});
});
</script>
<body>
<canvas id="paintCanvas"></canvas>
</body>
If you want to keep the pixelated effect in the zoom, you need to draw on a temp canvas, then only after copy that temp canvas to the main screen.
You no longer need to zoom in the temp canvas, just draw on 1:1 scale always. When copying to the view canvas, then you apply the zoom (and maybe translate) that you want.
Keep in mind that drawings are anti-aliased, so you when zooming you will see some shades of grey when drawing in black, for instance.
I kept the recording code of #FurqanZafar since it is a good idea to record things in case you want to perform undo : in that case just delete the last record entry and redraw everything.
http://jsfiddle.net/gamealchemist/x5rrvcr0/4/
function updatePaintCanvas() {
paintContext.clearRect(0, 0, paintContext.canvas.width, paintContext.canvas.height);
paintContext.save();
paintContext.translate(cvSize * 0.5, cvSize * 0.5);
paintContext.scale(scale, scale);
paintContext.drawImage(drawCanvas, -cvSize * 0.5, -cvSize * 0.5);
paintContext.restore();
}
Heres the updated fiddle: http://jsfiddle.net/x5rrvcr0/2/ with basic zooming functionality
If you draw multiple paths on mouse move then your sketch will appear broken or disconnected, instead you should only stroke a single path until "mouseup" event.
You can then store these paths in an array and later redraw them at different zoom levels:
function zoom(context, paths, styles, scale) {
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.save();
applyStyles(context, styles);
scaleFromCenter(context, scale);
for (var i = 0; i < paths.length; i++) {
context.beginPath();
context.moveTo(paths[i][0].x, paths[i][0].y);
for (var j = 1; j < paths[i].length; j++)
context.lineTo(paths[i][j].x, paths[i][j].y);
context.stroke();
}
context.restore();
};
I am using KineticJS to implement a drawing application. I have used it to draw shapes, straight lines. I have used way described in KineticJS - Drawing Lines with Mouse to draw straight lines. But now my requirement is to draw line along the mouse path! What should be the procedure for that? Can we use Kinetic.Path to do that?
Following code is used to draw straight lines,
var Object,startingPoint;
var down = false;
layer.on("mousedown", function(e) {
down = true;
Object = new Kinetic.Line({
points: [e.layerX, e.layerY,e.layerX+1, e.layerY+1],
stroke: "red"
});
layer.add(Object);
});
layer.on("mousemove", function(e) {
if (down) {
var x = e.layerX;
var y = e.layerY;
Object.getPoints()[1].x = e.layerX;
Object.getPoints()[1].y = e.layerY;
down = true;
layer.draw();
}
});
layer.on("mouseup", function(e) {
down = false;
});
I have replaced Kinetic.Line with Kinetic.Path to achieve the target. But it didn't work.
if you need to add points to a line you can do this:
var points = line.getPoints();
var pos = stage.getUserPosition();
points = points.push(pos.x, pos.y);
line.setPoints(points);
Here how I implemented it. The key is to use spline shape of kineticJS and push points in it during mousemove and mouseup. ev._x, ev._y are the x, and y points calculated in the light of this post
Tracking mouse position in canvas when no surrounding element exists
Please let me know if it helps
tools.pencil = function () {
var tool = this;
this.started = false;
var drawObject;
this.mousedown = function (ev) {
drawObject = new DrawObject();
drawObject.Tool = DrawTool.Pencil;
tool.started = true;
drawObject.currentState = DrawState.Started;
drawObject.StartX = ev._x;
drawObject.StartY = ev._y;
tool.DrawIt(drawObject);
};
this.mousemove = function (ev) {
if (tool.started) {
drawObject.currentState = DrawState.Inprogress;
drawObject.CurrentX = ev._x;
drawObject.CurrentY = ev._y;
tool.DrawIt(drawObject);
};
this.mouseup = function (ev) {
if (tool.started) {
tool.started = false;
drawObject.currentState = DrawState.Completed;
drawObject.CurrentX = ev._x;
drawObject.CurrentY = ev._y;
tool.DrawIt(drawObject);
}
};
this.mouseout = function (ev) {
if (tool.started) {
}
tool.started = false;
};
this.DrawIt = function (drawObject) {
switch (drawObject.currentState) {
case DrawState.Started:
var x= drawObject.StartX,
y = drawObject.StartY;
var pencil = new Kinetic.Spline({
points: [{
x: x,
y: y
}],
stroke: 'red',
strokeWidth: 2,
lineCap: 'round',
tension: 1,
name: shapes.length
});
drawObject.Shape = pencil;
layer.add(pencil);
layer.draw();
break;
case DrawState.Inprogress:
case DrawState.Completed:
var x = drawObject.CurrentX,
y = drawObject.CurrentY;
var pencil = drawObject.Shape;
pencil.attrs.points.push({ x: x, y: y });
pencil.setPoints(pencil.attrs.points);
layer.draw();
if (drawObject.currentState == DrawState.Completed) {
// dosomething
}
break;
}
Where draw object is simple empty function in javascript
function DrawObject()
{
}
and drawstate is all the available state of pencil tool
var DrawState =
{
Started: 0,
Inprogress: 1,
Completed: 2
}
and "layer" is simple KineticJS layer already added in KineticJS stage