Recently I started working on HTML5 Canvas, I'm new to it.
I've a problem as follows:
I'm loading a Canvas with Body Chart Image (Predefined Image) and on that User will Draw some lines, shapes, etc.
After that I'll generate an image object as follows
var canvas = document.getElementById("MyCanvas");
var dataURL = canvas.toDataURL();
var image = new Image();
image.src = dataURL;
But, Here it generates only those elements which are drawn by users (lines, shapes) as PNG Image. It won't take that Predefined canvas background Image.
I need to generate a PNG image which should include both the Canvas background Image as well as User entered drawing elements.
How to do this?
Try to actually draw you image onto your canvas, utilizing these functions:
var canvas = document.getElementById("MyCanvas");
var img = new Image();
img.src = 'pathToYourImageHere';
canvas.drawImage(img,0,0); /* 0,0 is x and y from the top left */
When you now try to save it, it should also save your background image.
EDIT:
In response to your comment:
You can circument your layering problem by using two different canvases. One for the image, and one for your drawing. Then layer them on top of each other using absolute positioning.
You can read more here: Save many canvas element as image
EDIT2:
But actually you shouldn't have a layering problem, since the following code will first draw the image and then draw the arc, and the layering will be fine:
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var imageObj = new Image();
imageObj.src = "somePathToAnImage";
context.drawImage(imageObj, 50, 50);
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius = 75;
var startAngle = 1.1 * Math.PI;
var endAngle = 1.9 * Math.PI;
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = 15;
// line color
context.strokeStyle = "red";
context.stroke();
Even though the layering is fine, you will be better of by using two canvases, in case you would like to only save the drawing, without the background. You can always save both into a new canvas and save that, when you only use one canvas you'll have a hard time separating the drawing from the background.
This is because the image needs time to load, you have to use the onload function.
imgObj.onload = function() { context.drawImage(imageObj, 50, 50); imgLoaded = true;}
if (imgLoaded) { /*you draw shapes here */ }
Related
Is there a quick way using System.Drawing to quickly enlarge the image canvas of an .png image? (see example below). The caveat is the background might be transparent and I want to keep it transparent.
Edit: Needs to be in ASP .Net CORE
Alternatively, is there a way of putting the image on a white background that is slightly larger than the image?
After a few days of trial and error, I think I found something that works
Image overlayImage = //get your image here from file or url.
xloc = //x coord where to start overlay image.
yloc = //y coord where to start overlay image.
canvasWidth = //width of background canvas
canvasHeight = //height of background canvas
Bitmap baseImage = new Bitmap(canvasWidth, canvasHeight, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
using (Graphics graphics = Graphics.FromImage(baseImage))
{
using (System.Drawing.SolidBrush myBrush = new System.Drawing.SolidBrush(System.Drawing.Color.White))
{
graphics.FillRectangle(myBrush, new Rectangle(0, 0, canvasWidth, canvasHeight)); // white rectangle
}
graphics.CompositingMode = CompositingMode.SourceOver;
graphics.DrawImage(overlayImage, xloc, yloc);
} // graphics will be disposed at this line
I was following the Google-maps overlay example and have modified it to fit my needs. This is my new draw prototype for USGSOverlay:
USGSOverlay.prototype.draw = function()
{
//need to use projection to get image position from latLng to Px
var overlayProjection = this.getProjection();
var mapPos = new google.maps.LatLng(this.y_, this.x_);
var posInPx = overlayProjection.fromLatLngToDivPixel(mapPos);
//calculate the size in current zoom
//imageScaleFactor is a global variable placed on top in document
var dx = Math.abs(this.img_.naturalWidth * Math.pow(2, map.getZoom()) * imageScaleFactor);
var dy = (dx / this.img_.naturalWidth) * this.img_.naturalHeight;
// Resize the image's div to fit the indicated dimensions.
//places the image so that the given input (x_, y_) is in the center
var div = this.div_;
div.style.left = posInPx.x - dx / 2 + 'px';
div.style.top = posInPx.y - dy / 2 + 'px';
div.style.width = dx + 'px';
div.style.height = dy + 'px';
};
When I load the page for the first time my overlays would not appear until I translated the map for a long enough distance or zoomed in/out.
The problem is that I the operations this.img_.naturalWidth and this.img_.naturalHeight was taking too long and didn't give a value in time for the map to be initiated where I assume it runs the draw function for each overlay once.
My solution was to add a listener for the image in the onAdd prototype for USGSOverlay:
var thisOverlay = this;
//draw country when image is finished loading
this.img_.onload = function(e){
thisOverlay.draw();
}
This will call the draw function of the overlay once it's image is loaded.
The thisOverlay variable is needed because the object itself cannot be accessed from the onload function as far as I can tell.
I'm trying to draw an image on a canvas, then use css to fit the canvas within a certain size. It turns out that many browsers don't scale the canvas down very nicely. Firefox on OS X seems to be one of the worst, but I haven't tested very many. Here is a minimal example of the problem:
HTML
<img>
<canvas></canvas>
CSS
img, canvas {
width: 125px;
}
JS
var image = document.getElementsByTagName('img')[0],
canvas = document.getElementsByTagName('canvas')[0];
image.onload = function() {
canvas.width = image.width;
canvas.height = image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"
Running in a codepen: http://codepen.io/ford/pen/GgMzJd
Here's the result in Firefox (screenshot from a retina display):
What's happening is that both the <img> and <canvas> start at the same size and are scaled down by the browser with css (the image width is 783px). Apparently, the browser does some nice smoothing/interpolation on the <img>, but not on the <canvas>.
I've tried:
image-rendering, but the defaults seem to already be what I want.
Hacky solutions like scaling the image down in steps, but this didn't help: http://codepen.io/ford/pen/emGxrd.
Context2D.imageSmoothingEnabled, but once again, the defaults describe what I want.
How can I make the image on the right look like the image on the left? Preferably in as little code as possible (I'd rather not implement bicubic interpolation myself, for example).
You can fix the pixelation issue by scaling the canvas's backing store by the window.devicePixelRatio value. Unfortunately, the shoddy image filtering seems to be a browser limitation at this time, and the only reliable fix is to roll your own.
Replace your current onload with:
image.onload = function() {
var dpr = window.devicePixelRatio;
canvas.width = image.width * dpr;
canvas.height = image.height * dpr;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
Results:
Tested on Firefox 35.0.1 on Windows 8.1. Note that your current code doesn't handle browser zoom events, which could reintroduce pixelation. You can fix this by handling the resize event.
Canvas is not quite meant to be css zoomed : Try over-sampling : use twice the required canvas size, and css scaling will do a fine job in down-scaling the canvas.
On hi-dpi devices you should double yet another time the resolution to reach the
same quality.
(even on a standard display, X4 shines a bit more).
(Image, canvas 1X, 2X and 4X)
var $ = document.getElementById.bind(document);
var image = $('fntimg');
image.onload = function() {
drawAllImages();
}
image.src = "http://upload.wikimedia.org/wikipedia/commons/thumb/0/00/Helvetica_Neue_typeface_weights.svg/783px-Helvetica_Neue_typeface_weights.svg.png"
function drawAllImages() {
drawImage(1);
drawImage(2);
drawImage(4);
}
function drawImage(x) {
console.log('cv' + x + 'X');
var canvas = $('cv' + x + 'X');
canvas.width = x * image.width;
canvas.height = x * image.height;
var context = canvas.getContext('2d');
context.drawImage(image, 0, 0, canvas.width, canvas.height);
}
img,
canvas {
width: 125px;
}
<br>
<img id='fntimg'>
<canvas id='cv1X'></canvas>
<canvas id='cv2X'></canvas>
<canvas id='cv4X'></canvas>
<br>
It's not good idea to scale canvas and think that you solved the image scale problem.you can pass your dynamic value to canvas,and then draw with that size whatever you want.
here is link of canvas doc: http://www.w3docs.com/learn-javascript/canvas.html
Simple answer, you can't do it. The canvas is just like a bitmap, nothing more.
My idea:
You should redraw the whole surface on zooming, and make sure you scale the image you're drawing to the canvas. As it is a vector graphic, this should work. But you're going to have to redraw the canvas for sure.
UPDATE: I have posted and accepted a fully working solution in the answers section. Any code in this section is to be used as reference for comparison to your own NON-WORKING code, but is not to be used as the solution.
I'm building a dashboard and using d3.js to add a world map that will plot tweets in real time based on geo location.
The world.json file referenced in the d3.json() line is downloadable HERE (it's called world-countries.json).
The map is on the page as an SVG container and is rendered using d3.
Below are the relevant code slices.
<div id="mapContainer">
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="500"></svg>
</div>
#mapContainer svg {
display:block;
margin:0 auto;
}
#mapContainer path {
fill:#DDD;
stroke:#FFF;
}
// generate US plot
function draw() {
var map = d3.select("svg");
var width = $("svg").parent().width();
var height = $("svg").parent().height();
var projection = d3.geo.equirectangular().scale(185).translate([width/2, height/2]);
var path = d3.geo.path().projection(projection);
d3.json('plugins/maps/world.json', function(collection) {
map.selectAll('path').data(collection.features).enter()
.append('path')
.attr('d', path)
.attr("width", width)
.attr("height", height);
});
}
draw();
latestLoop();
$(window).resize(function() {
draw();
});
UPDATE: I have scaled the map to an acceptable size (for my particular browser size), but it still will not scale and center when I change the size of the window. IF, however, I resize the window, then hit refresh, then the map will be centered once the page is reloaded. However, since the scale is static, it is not scaled properly.
COMPLETE SOLUTION:
Here's the solution which will resize the map AFTER the user has released the edge of the window to resize it, and center it in the parent container.
<div id="mapContainer"></div>
function draw(ht) {
$("#mapContainer").html("<svg id='map' xmlns='http://www.w3.org/2000/svg' width='100%' height='" + ht + "'></svg>");
map = d3.select("svg");
var width = $("svg").parent().width();
var height = ht;
// I discovered that the unscaled equirectangular map is 640x360. Thus, we
// should scale our map accordingly. Depending on the width ratio of the new
// container, the scale will be this ratio * 100. You could also use the height
// instead. The aspect ratio of an equirectangular map is 2:1, so that's why
// our height is half of our width.
projection = d3.geo.equirectangular().scale((width/640)*100).translate([width/2, height/2]);
var path = d3.geo.path().projection(projection);
d3.json('plugins/maps/world.json', function(collection) {
map.selectAll('path').data(collection.features).enter()
.append('path')
.attr('d', path)
.attr("width", width)
.attr("height", width/2);
});
}
draw($("#mapContainer").width()/2);
$(window).resize(function() {
if(this.resizeTO) clearTimeout(this.resizeTO);
this.resizeTO = setTimeout(function() {
$(this).trigger('resizeEnd');
}, 500);
});
$(window).bind('resizeEnd', function() {
var height = $("#mapContainer").width()/2;
$("#mapContainer svg").css("height", height);
draw(height);
});
The selection object is an multidimensional array, although in most cases it will probably have only one object in it. That object has a "clientWidth" field that tells you how wide its parent is.
So you can do this:
var selection = d3.select("#chart");
width = selection[0][0].clientWidth;
This should work:
<svg
xmlns="http://www.w3.org/2000/svg"
width="860"
height="500"
viewBox="0 0 860 500"
preserveAspectRatio="xMinYMin meet">
The best choice is to have a combined use of aspect ratio on normal definition of d3 graph's width and height. This has helped me in lot of my graph works.
Step 1 : Dynamically get the height of the div to which the graph has to be appended.
Step 2 : Declare width as aspect ratio with respect to the dynamically caught height.
var graph_div = document.getElementById(graph.divId);
graph.height = graph_div.clientHeight;
graph.width = (960/1200)*graph.height;
In d3 v4, we could do this
const projection = d3.geo.equirectangular().fitSize([width, height], geojson);
const path = d3.geo.path().projection(projection);
fitSize is equivalent to
fitExtent([[0, 0], [width, height]], geojson)
fill free to add padding
In a drag+drop situation using Flex, I am trying to get the object center aligned to the point of drop- somehow, irrespective of the adjustments to height and width, it is always positioning drop point to left top.
here is the code..
imageX = SkinnableContainer(event.currentTarget).mouseX;
imageY = SkinnableContainer(event.currentTarget).mouseY;
// Error checks if imageX/imageY dont satisfy certain conditions- move to a default position
// img.width and img.height are both defined and traced to be 10- idea to center image to drop point
Image(event.dragInitiator).x = imageX-(img.width)/2;
Image(event.dragInitiator).y = imageY-(img.height)/2
The last 2 lines don't seem to have any effect. Any ideas why-must be something straightforward, that I am missing...
You can use the following snippet:
private function on_drag_start(event:MouseEvent):void
{
var drag_source:DragSource = new DragSource();
var drag_initiator:UIComponent = event.currentTarget as UIComponent;
var thumbnail:Image = new Image();
// Thumbnail initialization code goes here
var offset:Point = this.localToGlobal(new Point(0, 0));
offset.x -= event.stageX;
offset.y -= event.stageY;
DragManager.doDrag(drag_initiator, drag_source, event, thumbnail, offset.x + thumbnail.width / 2, offset.y + thumbnail.height / 2, 1.0);
}
Here is one important detail. The snippet uses stage coordinate system.
If you use event.localX and event.localY, this approach will fail in some cases. For example, you click-and-drag a movie clip. If you use localX and localY instead of stage coordinates, localX and localY will define coordinates in currently clicked part of the movie clip, not in the whole movie clip.
Use the xOffset and yOffset properties in the doDrag method of DragManager.
Look here for an example.