EDIT: I've made a video showing the current issue. Recorded it on an old macbook so it took a while haha. Anyway, In the video you can see me uploading an image, cropping it and generating a pdf with it. The preview does not have a black border whatsoever, the pdf DOES have a black border. Hope this helped.
So I got a webapplication that lets the user upload a profile picture and later generate a pdf with the profile picture. The problem is that I would like to have the profile picture displayed with rounded corners / in a circle.
I am using jspdf to generate the pdf. With that library I can add an image with the addImage method, that takes a base64 encoded DataUrl. Unfortunately, there isn't a native method to display the image with rounded corners / in a circle, so I decided to let the user crop their image before encoding the url to base64. That's when the weird behavior starts... If the cropped image is displayed in a img tag, it's all good, a nice circle profile picture is displayed. When that dataUrl is used to generate a pdf, for some reason a 1px black border is generated around the image... If I inspect the dataurl in the browser or with a online base64 previewer, no black border is visible, only when it gets generated as a pdf...
For demonstration purposes I made a codesandbox. Images with a white background show the problem best. For example, use this image: profilepicture
This method is most likely what is causing the problem (I took this one directly from the cropperjs examples on github):
function getRoundedCanvas(sourceCanvas) {
var canvas = document.createElement("canvas");
var context = canvas.getContext("2d");
var width = sourceCanvas.width;
var height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = "destination-in";
context.beginPath();
context.arc(
width / 2,
height / 2,
Math.min(width, height) / 2,
0,
2 * Math.PI,
true
);
context.fill();
return canvas;
}
This method is used in the crop method like this: roundedCanvas = getRoundedCanvas(croppedCanvas); If I take out that method and just use roundedCanvas = croppedCanvas; the image gets cropped to a square and is displayed without the black borders.
I have no idea how I can resolve this issue, and if it is even possible to resolve. Any help in the right direction is very much appreciated. Even alternative methods to display rounded / circle images on a pdf are welcome (I already tried html2canvas and that didn't work).
Related
I have a requirement to take the canvas from a webpage and convert it to PDF in the backend so it can be saved on the server and downloaded & printed at a later time at 600 DPI.
I have followed this question, and I have a working prototype of the code: AJAX call to send the canvas to backend in Base64 and then a Java function to convert it to PDF.
However, the problem is that the quality of the image is dependent on the screen/browser window size the user has when he clicks the button to trigger the image creation - a fullscreen browser will create a higher-res image than a partial window browser. Example: Both taken on my PC but on the latter the window is about half the screen size.
I was thinking of somehow creating the canvas on a headless browser with preset size, and that would at least make the quality consistent across users, but I have no idea how to dynamically change the image so I can keep it at 600 DPI no matter the paper size the user chooses to use.
Do I have to draw the canvas shapes directly onto PDF? I know that would fulfill the DPI requirement, but is that even possible to do from an AngularJS/Java stack?
You can decide the proper size for the canvas and then modify the way it's displayed via CSS. Here, the final size is set as 2000x2000 and it will be saved as such (by clicking on it), regardless of the viewport size:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Draw the ellipse
ctx.beginPath();
ctx.lineWidth = 10;
ctx.ellipse(1000, 1000, 500, 800, Math.PI / 4, 0, 2 * Math.PI);
ctx.stroke();
// Draw the ellipse's line of reflection
ctx.beginPath();
ctx.setLineDash([5, 5]);
ctx.moveTo(0, 2000);
ctx.lineTo(2000, 0);
ctx.stroke();
canvas.addEventListener('click', function (e) {
var dataURL = canvas.toDataURL('image/png');
var link = document.createElement("a");
link.download = "finalsize.png";
link.href = dataURL;
link.click();
});
<body style="display:flexbox">
<canvas id="canvas" width="2000" height="2000" style="width:100%; "></canvas>
</body>
I am building a retro styled game, that uses pixelated images. I have not yet created these images, because I wanted to know the best way of doing things.
These images will probably be a 16 or 32 PX square, but I would like to be able to scale the images as big as I like, just without any blur/distortion.
What format should I use? And how should I import them to my canvas. as well?
EDIT#1: Fixed typo & put Q back on topic. (Thank you Spence for pointing it out)
Try "Inkscape", its free
https://inkscape.org/en/
it uses SVG format (scalar vector graphics) so you will be able to scale the images as big as you like, just without any blur/distortion.
The only way to enlarge without any blur or distortion is turn each 1 pixel into a set of 2x2, 3x3, ... pixels.
For example, a single blue pixel in the top-left of the image would become a set of 4 blue pixels at [0,0], [1,0], [0,1] & [1,1]. And the same for every other pixel on the original image. The resulting image would be twice the width & height of the original image.
Since your graphics style is pixelated images, this adjustment would preserve your pixilation while also enlarging the original image.
You can code a function that uses an in-memory html5 canvas to "resize-by-multiplying" your original images as needed. This will use canvas's ability to set the RGBA values every pixel using context.getImageData and context.putImageData.
CanvasContext2d does have an option to disable the image smoothing : imageSmoothingEnabled which is set to true by default.
According to the specs, if set to false,
The image [drawn by drawImage() method] must be rendered using
nearest-neighbor interpolation.
This algorithm is the same as the one proposed by #markE in his answer.
Unfortunately, browsers still use vendor-prefix for this attribute and it wasn't implemented in both IE9 and IE10...
var img = document.querySelector('img'),
canvas = document.querySelector('canvas'),
ctx = canvas.getContext('2d');
// draw the image
img.onload = function(){
canvas.width = img.width*50;
canvas.height = img.height*50;
// disable smoothing after we change canvas' width/height
ctx.mozImageSmoothingEnabled = false;
ctx.webkitImageSmoothingEnabled = false;
ctx.msImageSmoothingEnabled = false;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, 0,0, canvas.width, canvas.height);
}
//32x32px image taken from https://stackoverflow.com/q/31910043/3702797
img.src="http://i.stack.imgur.com/3Sp5x.png"
canvas{border:.5px solid}
<img/>
<canvas></canvas>
Scroll to see the resized image in canvas
Create large icon images to which you apply a 16x16 or 32x32 tile effect. Then when you write them to the canvas (after loading the images of course) scale them down to the size you want using
context.drawImage(img,x,y,width,height);
File sizes are unlikely to jump greatly since each tile should compress fairly easily.
In my webapplication the user can take a photo with the camera on his mobile device. The photo is then displayed in a canvas.
<canvas id="photo" ></canvas>
Now I want to send the photo to a backend server. The best way to do this seems to be encoding the image to a base64 string.
However, I want the photo to always be the same resolution, for example 100 x 100 pixels. Otherwise the base64 string is too big.
I am currently using the second parameter of toDataURL to determine the export quality of the picture. (here 2%).
var base64 = document.getElementById("photo").toDataURL("image/jpeg", 0.02);
This does not seem to be a good way because I don't know the initial resolution of the photo. So I would like to always get the photo in the same resolution. How to achieve this?
You could use an invisible background canvas with a size of 100x100 and copy the image from the photo-canvas to it. The drawImage function allows you to specify a size for the destination rectangle. When it's not the same as the source rectangle, the content will be scaled:
// Create a temporary, invisible 100x100 canvas
var tmpCanvas = document.createElement('canvas');
tmpCanvas.width = 100;
tmpCanvas.height = 100;
var tmpContext = canvas.getContext('2d');
// copy the content of the photo-canvas to the temp canvas rescaled
var photoCanvas = document.getElementById("photo");
tmpContext.drawImage(photoCanvas, // source
0, 0, photoCanvas.width, photoCanvas.height, // source rect
0, 0, 100, 100 // destination rect
);
// get data-url of temporary canvas
var base64 = tmpCanvas.toDataURL("image/jpeg");
But keep in mind that when the source-canvas isn't rectangular, the aspect ratio won't be preserved. When you don't want this, there are different ways to deal with this issue, like cropping or adding a border. Most of these would be implemented by choosing different source- or destination rectangles in the drawImage call.
For a Project I want to take the content of a canvas (Called SAVE_CV) and display it in another, smaller canvas.
Some things that I am aware of so far that could be causing me problems: resizing a canvas clears its content, the JS-size of a canvas is different from the CSS-size.
I want the smaller canvas to be 500px wide and appropriately high.
function restoreTaggingCV() {
var cv = document.getElementById( 'taggingCV' );
var ctx = cv.getContext( "2d" );
var styleHeight = SAVE_CV.height * 500 / SAVE_CV.width;
ctx.drawImage(SAVE_CV, 0, 0, cv.width, cv.height);
}
This is my Code so far. Whenever I try to resize the smaller canvas appropriately it only gives me a blank canvas with nothing in it. I tried to set the size with "cv.height = X" and "cv.style.height = styleHeight + 'px'" but neither worked. Also I would love to set the width of the canvas using CSS.
Appreciate any help.
EDIT
I want the image in a picture because later I want the user to mark areas in the smaller version which I then want to use to create individual imaged from the big version. I want to visualise thise area to the user. I probably could do all this by using an image and putting divs over it or something but I just fell more comfident using a canvas since I am pritty new to HTML and CSS.
Try using the CanvasRenderingContext2d.prototype.scale method. It sets the scale factor of the canvas and renders anything in the current state with it's dimensions multiplied by the factor.
So before you use the drawImage function, you scale the context appropriately (in this case, down). For example:
context.save();
context.scale(0.5, 0.5);
context.drawImage(canvas, 0, 0);
context.restore();
This would render the canvas on the context at 0.5 times it's current size. See in this fiddle how I used it to mirror a larger canvas onto a smaller, separate one.
Canvas objects don't like to be resised. After drawing Your image simply convert it toDataURL() and set as image source. They You may resize image as you want.
$img.attr('src',canvas.toDataURL());
I'm using the HTML5 canvas to load a single instance of an image which I then blit multiple times onto a single canvas. The image needs some slight pixel-based manipulation in order to customise it. My initial plan of attack had been to load the image, blit it to a backing canvas, draw my modifications on-top of it, and then grab the image data and cache it for future use.
Here's some code I've written to that effect:
context.drawImage(img, 0, 0);
context.fillStyle = '#ffffff';
context.fillRect(0, 0, 2, 2); // Draw a 2x2 rectangle of white pixels on the top left of the image
imageData = context.getImageData(0, 0, img.width, img.height);
cusomImage = imageData;
While this works, I've noticed that my image (which is a transparent PNG) does not maintain transparency. Instead, when using putImageData to place it onto my front-facing canvas, it is rendered with a black background. How do I maintain transparency?
Any suggestions are welcome!
putImageData() does not do what you might first expect:
http://dropshado.ws/post/1244700472/putimagedata-is-a-complete-jerk
putImageData() direct overrides the pixels of the canvas. So if you draw over something else on the same canvas it will not draw "over" it, it will instead replace the pixels of the canvas in the area with it's pixels.
I ran into this exact issue and finally found out why.
As for a solution, I haven't tried this yet but it seems promising:
Why is putImageData so slow?
[EDIT]: I tested this method and it works fine for me, my data is now displaying transparency correctly.
The canvas is black after being created. Make it transparent first with:
context.fillStyle = 'rgba(0,0,0,0)';
context.fillRect(0, 0, width, height);