When I add an image in canvas and I do a transformMatrix to the image, the roundingBox is shifted.
Look my jsfiddle : http://jsfiddle.net/ULsr4/2/
canvas = this.__canvas = new fabric.Canvas('canvas');
fabric.Image.fromURL('http://icons.iconarchive.com/icons/archigraphs/lovely-bones/256/Tree-icon.png', function(img) {
img.transformMatrix = [1, 0, 0.7, 1, 0, 0];
canvas.add(img);
img.setCoords();
});
canvas.renderAll();
can someone help me ?
Don't know if it still needed for you but try to use group object inside you fabric.Image.fromURL callback, ie:
var group = new fabric.Group([img], {
left: 10,
top: 10
});
canvas.add(group);
group.setCoords();
Related
$("#NoBidsChart").get(0).toBlob(function(value) {
saveAs(value, "Summary.jpg");
});
Here i am using Chart JS(v2.5.0) for rendering charts. When i try to export the charts using Canvas to Blob converter and filesaver.js, i get the black background. So how do i get the image with customized background color(preferably white)?
If you want a customized background color then, you'd have to draw a background with your preferred color, and you can do so, like this ...
var backgroundColor = 'white';
Chart.plugins.register({
beforeDraw: function(c) {
var ctx = c.chart.ctx;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, c.chart.width, c.chart.height);
}
});
DEMO
// draw background
var backgroundColor = 'white';
Chart.plugins.register({
beforeDraw: function(c) {
var ctx = c.chart.ctx;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, c.chart.width, c.chart.height);
}
});
// chart
var canvas = $('#NoBidsChart').get(0);
var myChart = new Chart(canvas, {
type: 'line',
data: {
labels: [1, 2, 3, 4, 5],
datasets: [{
label: 'Line Chart',
data: [1, 2, 3, 4, 5],
backgroundColor: 'rgba(255, 0, 0, 0.2)',
borderColor: 'rgba(255, 0, 0, 0.5)',
pointBackgroundColor: 'black'
}]
}
});
// save as image
$('#save').click(function() {
canvas.toBlob(function(blob) {
saveAs(blob, "pretty image.png");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
<button id="save">Save</button>
<canvas id="NoBidsChart"></canvas>
As I stated in my comment to the accepted answer, it bothered me that the beforeDraw event causes the fillRect code to get called multiple times. (Once per data point as far as I can see.)
But I couldn't get that approach to work when called on any other event. However, I just took the coding approach described in this answer and plugged it into code registered to run on the afterRender event and it does just what I want: run once and leave the background white.
Chart.plugins.register({
afterRender: function(c) {
console.log("afterRender called");
var ctx = c.chart.ctx;
ctx.save();
// This line is apparently essential to getting the
// fill to go behind the drawn graph, not on top of it.
// Technique is taken from:
// https://stackoverflow.com/a/50126796/165164
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, c.chart.width, c.chart.height);
ctx.restore();
}
});
Please visit (and up vote) the linked answer to the other posted question.
In React, with react-chartjs-2, i was able to set background color of chart like so:
const plugin = {
beforeDraw: (chartCtx) => {
const ctx = chartCtx.canvas.getContext('2d');
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, chartCtx.width, chartCtx.height);
ctx.restore();
}
};
And then add the plugin to the chart:
<Line ref={chartRef} data={chartData} options={options} plugins={[plugin]} />
Reference to the Docs
To save the chart as an image:
I created a function that uses the toBase64Image function to extract the image. I attached this function to a button to help me download chart image on click of button.
function downloadImage(){
const link = document.createElement("a");
link.download = `${chart.name || 'chart'}.jpg`
link.href = chartRef.current.toBase64Image('image/jpeg', 1);
link.click();
}
I am using the following to change the style of the cursor when the mouse is over the circle:
circle1.on('mouseover', function () {
document.body.style.cursor = 'pointer';
});
circle1.on('mouseout', function () {
document.body.style.cursor = 'default';
});
It works great if I draw the circle using:
var circle1 = new Kinetic.Circle({
x: 512,
y: 512,
radius: 140,
stroke: '#00ffff',
strokeWidth: 4,
opacity: 0.5
});
However if I use:
var circle1 = new Kinetic.Circle({
drawFunc: function (canvas) {
var context1 = canvas.getContext();
context1.beginPath();
context1.arc(512, 512, this.getRadius(), 0, 2 * Math.PI, false);
context1.lineWidth = this.getStrokeWidth();
context1.strokeStyle = this.getStroke();
context1.stroke();
},
radius: 140,
stroke: '#00ffff',
strokeWidth: 15,
opacity: 0.5
});
It does not work! The cursor does not change its style; can we just use radius for mouse over. I would appreciate your suggestions, thanks in advance.
As I know you also need to define "drawHitFunc":
circle1.setDrawHitFunc(function (canvas) {
var context2 = canvas.getContext();
context2.beginPath();
context2.arc(100, 100, this.getRadius(), 0, 2 * Math.PI, false);
context2.closePath();
canvas.fillStroke(this);
});
Example: http://jsfiddle.net/lavrton/4DJdU/1/
no, you just need to correctly structure the drawFunc when creating custom shapes. Here's an example:
http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-shape-tutorial/
The problem is that you're using context.stroke(). You need to use canvas.stroke(this);
Anytime you actually render something, like strokes and fills, you need to use the canvas renderer object because it draws onto both the scene graph (what you see) and a specialized hit graph (used for event detection)
Docs:
http://kineticjs.com/docs/symbols/Kinetic.Canvas.php
Is it possible to change the background with an image fading in after a set time?
I am using the following code, but the image is not fading in. All I am achieving now is fading out the image with the transitionTo method, however I want it to fade in.
This is the code I am playing with:
var stage = new Kinetic.Stage({
container: 'container',
width: 1770,
height: 900
});
var layer = new Kinetic.Layer();
var imageObj = new Image();
imageObj.onload = function() {
var myBg = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
width: 1770,
height: 900
});
// add the shape to the layer
layer.add(myBg);
// add the layer to the stage
stage.add(layer);
setTimeout(function() {
myBg.transitionTo({
opacity: 0,
duration: 4,
});
}, 3000);
};
imageObj.src = 'bg.png';
Can someone kindly shed some light?
It happens because you did not set initial opacity. What you were doing is setting opacity from 1 to 1, not from 0 to 1. please refer the following code.
var myBg = new Kinetic.Image({
x: 0,
y: 0,
image: imageObj,
opacity: 0,
width: 1770,
height: 900
});
myBg.transitionTo({
opacity: 1,
duration: 4,
})
Check out my html5 based clipping constraint on
http://shedlimited.debrucellc.com/test3/canvaskinclip.html
(messing with jsfiddle on http://jsfiddle.net/aqaP7/4/)
So, in html5 I can easily draw a shaped boundary like the following:
context.beginPath();
context.moveTo(5, 5);
context.lineTo(34, 202);
context.lineTo(2, 405);
context.lineTo(212, 385);
context.lineTo(425, 405);
context.lineTo(400, 202);
context.lineTo(415, 10);
context.lineTo(212, 25);
context.clip();
In kinetic.js though, all I see for clipping options is: height, width, and x, y,
I came across the following : Mask/Clip an Image using a Polygon in KineticJS, but the inner/fill image can't be set to draggable
any help please!
In the new kineticJS versions, a lot of the work is done in the background for you.
Take a look at this tutorial:
This fiddle gets you pretty close, here's the code:
<body>
<div id="container"></div>
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v4.3.0-beta2.js"></script>
<script>
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
function draw(images) {
var stage = new Kinetic.Stage({
container: 'container',
width: 600,
height: 700
});
var layer = new Kinetic.Layer();
var patternPentagon = new Kinetic.RegularPolygon({
x: 220,
y: stage.getHeight() / 4,
sides: 5,
radius: 70,
fillPatternImage: images.yoda,
fillPatternOffset: [-220, 70],
stroke: 'black',
strokeWidth: 4,
draggable: true
});
patternPentagon.on('dragmove', function() {
//this.setFillPatternImage(images.yoda);
//this.setFillPatternOffset(-100, 70);
var userPos = stage.getUserPosition();
this.setFillPatternOffset(-userPos.x,-userPos.y);
layer.draw();
this.setX(220);
this.setY(stage.getHeight() / 4);
});
layer.add(patternPentagon);
stage.add(layer);
}
var sources = {
darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg',
yoda: 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg'
};
loadImages(sources, function(images) {
draw(images);
});
</script>
</body>
There is a more complex/accurate way of doing this without making it a background pattern, like with grouping objects together
I have a problem an hope to find any solution for it.
I am using Kinetic.js to create a HMI solution with special look-and-feel. Therefor I have created a function that creates 3 layers for a stage: a background layer with a grid, a layer with static shapes for the base layout of the HMI-screen and the third layer for all interactive elements (like buttons, valuedisplays and so on...). Now I want to cache the grid and the static layer to improve performance, because this layers will never change until the whole HMI-screen will change...
As a test I started to code the caching for the grid layer using the following code:
// Create a grid layer if showGrid is set to TRUE...
console.log('Start to create background grid if required');
if (this.actualPageConfig.showGrid) {
var grid = new Kinetic.Layer();
for (var x=1; x <= this.cols; x++) {
var eLoc = LCARSElements.posToRealPos(x, 1, this.cols, this.rows);
if (x <= this.actualPageConfig.columns) {
grid.add(new Kinetic.Line({
points: [eLoc.x, eLoc.y, eLoc.x, eLoc.height],
stroke: "red",
strokeWidth: 1,
lineCap: "round",
lineJoin: "round"
}));
}
}
for (var y=1; y <= this.rows; y++) {
var eLoc = LCARSElements.posToRealPos(1, y, this.cols, this.rows);
if (y <= this.actualPageConfig.rows) {
grid.add(new Kinetic.Line({
points: [eLoc.x, eLoc.y, eLoc.width, eLoc.y],
stroke: "red",
strokeWidth: 1,
lineCap: "round",
lineJoin: "round"
}));
}
}
// Add grid layer to stage
//this.stage.add(grid); <-- to be replaced by cache image
// Convert grid into an image and add this to the stage
console.log('convert grid to image to increase performance');
grid.toImage({
width: displayManager.stage.getWidth(),
height: displayManager.stage.getHeight(),
callback: function(img) {
var cacheGrid = new Kinetic.Image({
image: img,
x: 0,
y: 0,
width: displayManager.stage.getWidth(),
height: displayManager.stage.getHeight()
});
console.log('insert grid-image to stage');
displayManager.stage.add(cacheGrid);
console.log('redraw stage...');
displayManager.stage.draw();
}
});
}
My problem is, that's not working. The grid is not visible any more and the console log shows the following error information:
Type error: layer.canvas is undefined
layer.canvas.setSize(this.attrs.width, this.attrs.height); kinetic.js (Zeile 3167)
As I already figured out the error rise when the code "displayManger.stage.add(cacheGrid) will be executed (displayManager is the outside-class where this code snipped reside).
Can anyone see where I made the mistake? When I directly add the layer grid anything works fine...
I have created a jsfiddle to demonstrate the problem: jsfiddle
In fiddle you can run both versions by changing one parameter. Hope this helps....
Thanks for help.
Best regards
Thorsten
Actually, the problem is simpler than you might think - after caching the layer into an image, you're trying to add an image object directly to the stage (you can't do that).
To fix the problem, you need to create a new layer, say cahcedLayer, add the image to cachedLayer, and then add cachedLayer to the stage.
Check out the KineticJS info page to learn more about Node nesting:
https://github.com/ericdrowell/KineticJS/wiki
http://rvillani.com/testes/layer-to-image/
I've made this test and it worked. First, I draw 1000 squares to a layer, add this layer to a hidden stage then make an image from this stage using stage.toDataURL(). When the callback returns, I just create an Image from the data and a Kinetic.Image from the Image. Then I add it to a layer on my main (visible) stage.
Code (be sure to have a div called 'invisible'):
window.onload = function()
{
var stage = new Kinetic.Stage({
width: 520,
height: 480,
container: 'container'
});
var outerStage = new Kinetic.Stage({
width: stage.getWidth(),
height: stage.getHeight(),
container: 'invisible'
});
var layerToCache = new Kinetic.Layer();
var layer = new Kinetic.Layer();
var group = new Kinetic.Group({offset: [stage.getWidth(), stage.getHeight()]});
var anim = new Kinetic.Animation(function(frame){
group.rotate(0.02);
}, layer);
var fills = ['red', 'green', 'blue', 'orange', 'purple', 'cyan',
'black', 'brown', 'forestgreen', 'gray', 'pink'];
for (var i = 0; i < 10000; ++i)
{
(function ()
{
var size = Math.random() * 60 + 20;
var square = new Kinetic.Rect({
width: size,
height: size,
fill: fills[i % fills.length],
x: Math.random() * stage.getWidth() - 20,
y: Math.random() * stage.getHeight() - 20
});
layerToCache.add(square);
})();
}
var squaresImg = new Kinetic.Image();
outerStage.add(layerToCache);
outerStage.toDataURL({
callback: function (dataURL){
outerStage.clear();
var img = new Image();
img.src = dataURL;
img.onload = function () {
squaresImg.setImage(img);
squaresImg.setX(squaresImg.getWidth() >> 1);
squaresImg.setY(squaresImg.getHeight() >> 1);
group.setX(stage.getWidth() >> 1);
group.setY(stage.getHeight() >> 1);
group.add(squaresImg);
layer.add(group);
layer.draw();
anim.start();
}
}
});
var div = document.getElementById('invisible');
div.parentNode.removeChild(div);
stage.add(layer);
}