I need to add an outer border to a JPG image (image has a solid background).
Using context.stroke() only adds an inner border and covers up the inner edges of the image, however I need to add an outer border to the image.
Example Image
If you want to add to an image, convert it to a canvas that is bigger than the original image by n pixels and draw the border.
The function creates a new image with a border amount is the border width in pixels, style is the colour/style you want the border. and image is the original image. Returns the new image with border
function borderImage(image,amount,style){
var paddedImage = document.createElement("canvas"); // create a new image
amount = Math.round(amount); // ensure that the amount is a int value
paddedImage.width = image.width + amount * 2; // set the size
paddedImage.height = image.height + amount * 2;
// get a context so you can draw on it
var ctx = paddedImage.getContext("2d");
ctx.strokeStyle = style; // set the colour;
ctx.lineWidth = amount;
// draw the border
ctx.strokeRect(amount / 2 , amount / 2, image.width + amount, image.height + amount);
// draw the image on top
ctx.drawImage(image, amount, amount);
return paddedImage ; // return the new image
}
To use
var image = new Image();
image.src = "http://i.stack.imgur.com/u2n6E.png";
image.onload = function(){
image = borderImage(this,8,"black");
document.body.appendChild(image);
}
function borderImage(image,amount,style){
var paddedImage = document.createElement("canvas"); // create a new image
amount = Math.round(amount); // ensure that the amount is a int value
paddedImage.width = image.width + amount * 2; // set the size
paddedImage.height = image.height + amount * 2;
// get a context so you can draw on it
var ctx = paddedImage.getContext("2d");
ctx.strokeStyle = style; // set the colour;
ctx.lineWidth = amount;
// draw the border
ctx.strokeRect(amount / 2 , amount / 2, image.width + amount, image.height + amount);
// draw the image on top
ctx.drawImage(image, amount, amount);
return paddedImage ; // return the new image
}
var image = new Image();
image.src = "http://i.stack.imgur.com/u2n6E.png";
image.onload = function(){
image = borderImage(this,8,"black");
document.body.appendChild(image);
}
You could try this
//assuming cvs is your canvas reference
var ctx = cvs.getContext('2d');
cvs.width = yourImage.width;
cvs.height= yourImage.height;
ctx.lineWidth = x; // This is your border thickness
ctx.strokeStyle = '#000000';
ctx.rect(0,0,cvs.width,cvs.height);
ctx.stroke();
ctx.drawImage(yourImage,x,x,cvs.width-2*x,cvs.height-2*x);
Your image size has been reduced a bit though to accomodate the borders. However there will be some lose in height and width (2 times the border width ).
If you want to preserve the aspect ratio of the original image after adding the border, you can change the last line to
ctx.drawImage(yourImage,x,x,cvs.width*(1- (2*x/cvs.width)),cvs.height*(1- (2*x/cvs.height)));
Related
In the image below there is a canvas of 1000x1000, there is a rectangle object with the grey backgroundd of 900x300 at position 50,50 and a border black. Inside the rectangle is a fabric image object which is just loading In a square image of a pug.
The image object is the same size as the rectangle 900x300 positioned at 50,50 on the canvas (same as the rectangle).You can see the highlighted image object bounding box is the same size as the rectangle, how it should be, but the image itself isn't stretching to fill the size of the image object. What I want to see is the pug being the 900 width of the image object and the height based on the actual JPG ratio; so if the image itself is 300x300 and the width is 900 then the height of the image should also be 900 but obviously the bottom would be hidden because the object is only 300 high.
Does anyone know how I can make the JPG stretch to fit the image object?
var img02URL = 'http://fabricjs.com/lib/pug.jpg';
var canvas = new fabric.Canvas('mainc');
var clipRect1 = new fabric.Rect({
originX:'left',
originY:'top',
left:50,
top:50,
width:900,
height:300,
fill:'#DDD',
stroke:'black',
strokeWidth:2,
selectable:false
});
clipRect1.set({
clipFor:'test1'
});
canvas.add(clipRect1);
var image1 = new Image();
image1.onload = function(img){
var image = new fabric.Image(image1,{
width:900,
height:300,
left:50,
top:50,
clipName:'test1',
clipTo:function(ctx){
return _.bind(clipByName,image)(ctx);
}
});
canvas.add(image);
}
image1.src = img02URL;
var clipByName = function (ctx) {
this.setCoords();
var clipRect = findByClipName(this.clipName);
var scaleXTo1 = (1 / this.scaleX);
var scaleYTo1 = (1 / this.scaleY);
ctx.save();
var ctxLeft = -( this.width / 2 ) + clipRect.strokeWidth;
var ctxTop = -( this.height / 2 ) + clipRect.strokeWidth;
var ctxWidth = clipRect.width - clipRect.strokeWidth + 1;
var ctxHeight = clipRect.height - clipRect.strokeWidth + 1;
ctx.translate( ctxLeft, ctxTop );
ctx.rotate(degToRad(this.angle * -1));
ctx.scale(scaleXTo1, scaleYTo1);
ctx.beginPath();
ctx.rect(
clipRect.left - this.oCoords.tl.x,
clipRect.top - this.oCoords.tl.y,
ctxWidth,
ctxHeight
);
ctx.closePath();
ctx.restore();
}
function degToRad(degrees) {
return degrees * (Math.PI / 180);
}
function findByClipName(name) {
return _(canvas.getObjects()).where({
clipFor: name
}).first()
}
I am trying to animate using a sprite sheet and use it in my website, however I am knew to animation and I am having some trouble fixing it, currently nothing shows up on my webpage when I use this. I am using a sprite sheet which is called Speech.png, is 18176 x 256 and each frame is 256x256. Here is my code so far.
<script language="javascript">
// screen size variables
var SCREEN_WIDTH = window.innerWidth,
SCREEN_HEIGHT = window.innerHeight;
var canvas = document.createElement('canvas');
var c = canvas.getContext('2d');
canvas.width = SCREEN_WIDTH;
canvas.height = SCREEN_HEIGHT;
var xpos=0,
ypos=0,
index=0,
numFrames = 70,
frameSize= 255;
// Add our drawing canvas
document.body.appendChild(canvas);
//load the image
image = new Image();
image.src = "Speech.png";
image.onload = function() {
//we're ready for the loop
setInterval(loop, 1000 / 30);
}
function loop() {
//clear the canvas!
c.clearRect(0,0, SCREEN_HEIGHT,SCREEN_WIDTH);
/*our big long list of arguments below equates to:
1: our image source
2 - 5: the rectangle in the source image of what we want to draw
6 - 9: the rectangle of our canvas that we are drawing into
the area of the source image we are drawing from will change each time loop() is called.
the rectangle of our canvas that we are drawing into however, will not.
tricky!
*/
c.drawImage(image,xpos,ypos,frameSize,frameSize,0,0,frameSize, frameSize);
//each time around we add the frame size to our xpos, moving along the source image
xpos += frameSize;
//increase the index so we know which frame of our animation we are currently on
index += 1;
//if our index is higher than our total number of frames, we're at the end and better start over
if (index >= numFrames) {
xpos = 0;
ypos = 0;
index = 0;
//if we've gotten to the limit of our source image's width, we need to move down one row of frames
} else if (xpos + frameSize > image.width){
xpos =0;
ypos += frameSize;
}
}
</script>
I am trying to show previews of uploaded images in a canvas. The preview should be a square version of image.
What I have:
var img = new Image;
img.src = imageSrc;
var c = document.getElementById('file-preview-' + data.response.id);
var ctx = c.getContext("2d");
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
And the preview:
So how can I draw on the canvas a 150x150 square with maintained ratio cropped version of the image?
For example for this image:
I should get:
Here's a method that yields precisely the result you desire, cropped, resized and centered (with commented out lines that yield a left based cropping instead)...
var ctx;
function init(event) {
ctx = document.getElementById('canvas').getContext('2d');
// Your preview is actually 120x120,
// but I've stuck with the textual description of
// your requirements here.
ctx.canvas.width = 150;
ctx.canvas.height = 150;
loadBackground('http://i.stack.imgur.com/PCKEo.png');
}
function loadBackground(path) {
var img = new Image();
img.onload = drawBackground;
img.src = path;
}
function drawBackground(event) {
var img = event.target;
var imgSize = Math.min(img.width, img.height);
// The following two lines yield a central based cropping.
// They can both be amended to be 0, if you wish it to be
// a left based cropped image.
var left = (img.width - imgSize) / 2;
var top = (img.height - imgSize) / 2;
//var left = 0; // If you wish left based cropping instead.
//var top = 0; // If you wish left based cropping instead.
ctx.drawImage(event.target, left, top, imgSize, imgSize, 0, 0, ctx.canvas.width, ctx.canvas.height);
}
window.addEventListener('load', init, false);
<canvas id="canvas"></canvas>
Based on information from this question: SO Question
I was able to modify what was done to create a square cropped image in canvas:
var img = new Image;
img.src = "http://cdn.bmwblog.com/wp-content/uploads/2016/02/M-Performance-Parts-BMW-M2-3.jpg"; //Or whatever image source you have
var c= document.getElementById('file-preview');
var ctx=c.getContext("2d");
img.onload = function(){
ctx.drawImage(img, img.width/2, img.height/2, img.width, img.height, 0, 0, img.width, img.height);
};
You just have to modify the arguments you are passing to the drawImage function as well as add some more.
You can see a fiddle here: https://jsfiddle.net/4d93pkoa/3/
Remember that the first two parameters specify the image's top left coordinates within the canvas element.
Remember to set the width and height of the canvas element in HTML to a square shape.
I have a canvas which is loaded with a png image. I get its jpg base64 string by .toDataURL() method like this:
$('#base64str').val(canvas.toDataURL("image/jpeg"));
But the transparent parts of the png image are shown black in the new jpg image.
Any solutions to change this color to white? Thanks in advance.
This blackening occurs because the 'image/jpeg' conversion involves setting the alpha of all canvas pixels to fully opaque (alpha=255). The problem is that transparent canvas pixels are colored fully-black-but-transparent. So when you turn these black pixels opaque, the result is a blackened jpeg.
The workaround is to manually change all non-opaque canvas pixels to your desired white color instead of black.
That way when they are made opaque they will appear as white instead of black pixels.
Here's how:
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i]=255;
data[i+1]=255;
data[i+2]=255;
data[i+3]=255;
}
}
ctx.putImageData(imgData,0,0);
After spending a lot of time on this and this post specifically, and these solutions kinda worked expect I just couldn't get the canvas to look right. Anyway I found this solution elsewhere and wanted to post it here incase it helps someone else from spending hours trying to get the black background to white and look like the original.
public getURI(): string {
let canvas = <HTMLCanvasElement>document.getElementById('chartcanvas');
var newCanvas = <HTMLCanvasElement>canvas.cloneNode(true);
var ctx = newCanvas.getContext('2d');
ctx.fillStyle = "#FFF";
ctx.fillRect(0, 0, newCanvas.width, newCanvas.height);
ctx.drawImage(canvas, 0, 0);
return newCanvas.toDataURL("image/jpeg");
}
This answer is a bit longer, but I find it to be more 'correct' in that it handles these things without directly modifying raw canvas data. I find that to be a pretty messy and theoretically unsatisfying solution. There are built in functions to achieve that, and they ought to be used. Here is the solution I found/pilfered:
function canvasToImage(backgroundColor){
var context = document.getElementById('canvas').getContext('2d');
canvas = context.canvas;
//cache height and width
var w = canvas.width;
var h = canvas.height;
var data;
//get the current ImageData for the canvas.
data = context.getImageData(0, 0, w, h);
//store the current globalCompositeOperation
var compositeOperation = context.globalCompositeOperation;
//set to draw behind current content
context.globalCompositeOperation = "destination-over";
//set background color
context.fillStyle = backgroundColor;
//draw background / rect on entire canvas
context.fillRect(0,0,w,h);
//get the image data from the canvas
var imageData = this.canvas.toDataURL("image/jpeg");
//clear the canvas
context.clearRect (0,0,w,h);
//restore it with original / cached ImageData
context.putImageData(data, 0,0);
//reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;
//return the Base64 encoded data url string
return imageData;
}
Basically, you create a white background image and underlay it under the canvas and then print that. This function is mostly plagiarized from someone's blog, but it required a bit of modification -- such as actually getting the context -- and copied directly from my (working) code, so as long as your canvas element has the id 'canvas', you should be able to copy/paste it and have it work.
This is the blog post I modified it from:
http://www.mikechambers.com/blog/2011/01/31/setting-the-background-color-when-generating-images-from-canvas-todataurl/
The big advantage of my function over this is that it outputs to jpeg instead of png, which is more likely to work well in chrome, which has a dataurl limit of 2MB, and it actually grabs the context, which was a glaring omission in the original function.
Marks answer is correct, but when a picture has some antialiasing applied, the exported image won't be as good as it should be (mainly text). I would like to enhance his solution:
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
data[i+3] = 255 - data[i+3];
}
}
ctx.putImageData(imgData,0,0);
If you want to move to white only full transparent pixels just check for (data[i+3]==0) instead of (data[i+3]<255).
Why not to save it as PNG?
canvas.toDataURL("image/png");
// change non-opaque pixels to white
var imgData=ctx.getImageData(0,0,canvas.width,canvas.height);
var data=imgData.data;
for(var i=0;i<data.length;i+=4){
if(data[i+3]<255){
data[i] = 255 - data[i];
data[i+1] = 255 - data[i+1];
data[i+2] = 255 - data[i+2];
data[i+3] = 255 - data[i+3];
}
Here is my function that resizes a photo and handles the black transparent background problem:
resizeImage({ file, maxSize, backgroundColor }) {
const fr = new FileReader();
const img = new Image();
const dataURItoBlob = (dataURI) => {
const bytes = (dataURI.split(',')[0].indexOf('base64') >= 0)
? window.atob(dataURI.split(',')[1])
: window.unescape(dataURI.split(',')[1]);
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0];
const max = bytes.length;
const ia = new Uint8Array(max);
for (let i = 0; i < max; i += 1) {
ia[i] = bytes.charCodeAt(i);
}
return new Blob([ia], { type: mime });
};
const resize = () => {
// create a canvas element to manipulate
const canvas = document.createElement('canvas');
canvas.setAttribute('id', 'canvas');
const context = canvas.getContext('2d');
// setup some resizing definitions
let { width, height } = img;
const isTooWide = ((width > height) && (width > maxSize));
const isTooTall = (height > maxSize);
// resize according to `maxSize`
if (isTooWide) {
height *= maxSize / width;
width = maxSize;
} else if (isTooTall) {
width *= maxSize / height;
height = maxSize;
}
// resize the canvas
canvas.width = width;
canvas.height = height;
// place the image on the canvas
context.drawImage(img, 0, 0, width, height);
// get the current ImageData for the canvas
const data = context.getImageData(0, 0, width, height);
// store the current globalCompositeOperation
const compositeOperation = context.globalCompositeOperation;
// set to draw behind current content
context.globalCompositeOperation = 'destination-over';
// set background color
context.fillStyle = backgroundColor;
// draw background / rect on entire canvas
context.fillRect(0, 0, width, height);
// get the image data from the canvas
const imageData = canvas.toDataURL('image/jpeg');
// clear the canvas
context.clearRect(0, 0, width, height);
// restore it with original / cached ImageData
context.putImageData(data, 0, 0);
// reset the globalCompositeOperation to what it was
context.globalCompositeOperation = compositeOperation;
// return the base64-encoded data url string
return dataURItoBlob(imageData);
};
return new Promise((resolve, reject) => {
if (!file.type.match(/image.*/)) {
reject(new Error('VImageInput# Problem resizing image: file must be an image.'));
}
fr.onload = (readerEvent) => {
img.onload = () => resolve(resize());
img.src = readerEvent.target.result;
};
fr.readAsDataURL(file);
});
},
That is a Vue JS instance method that can be used like this:
// this would be the user-uploaded file from the input element
const image = file;
const settings = {
file: image,
maxSize: 192, // to make 192x192 image
backgroundColor: '#FFF',
};
// this will output a base64 string you can dump into your database
const resizedImage = await this.resizeImage(settings);
My solution here is a combination of about 74 different StackOverflow answers related to resizing images client-side, and the final boss was to handle transparent PNG files.
My answer would not be possible without Laereom's answer here.
A spritesheet image object that contains sprites...
var spritesheet = new Image(); spritesheet.src ="foo.png";
I would like to be able to get a sub image from that spritesheet variable with the desired x, y, width, and height. And then assign a variable that is the sub image of the spritesheet.
How do I do that?
Using a div with backgroundPosition makes this easy since it will auto-clip for us without having to use overflow.
For example, here is the texture atlas from the popular Cookie Clicker idle game.
Using a negative offset for x and y we can select a sub-sprite from the texture atlas:
// Inputs -- change this based on your art
var tw = 48; // Texture Atlas Tile Width (pixels)
var th = 48; // Texture Atlas Tile Height (pixels)
var tx = 3; // tile index x (relative) (column)
var ty = 2; // tile index y (relative) (row)
var src = 'http://orteil.dashnet.org/cookieclicker/img/icons.png';
// Calculations -- common code to sub-image of texture atlas
var div = document.getElementById('clip');
var x = (tx*tw); // tile offset x position (absolute)
var y = (ty*th); // tile offset y position (absolute)
div.style.width = tw + 'px';
div.style.height = th + 'px';
div.style.backgroundImage = "url('" + src + "')";
div.style.backgroundPosition = '-' + x + 'px -' + y + 'px';
// You don't need the remaining parts. They are only to
// show the sub-sprite in relation to the texture atlas
div.style.border = "1px solid blue"; // only for demo purposes
// highlight where in the original texture the sub sprite is
var rect = document.getElementById('sprites').parentNode.getBoundingClientRect();
var hi = document.getElementById('highlight');
hi.style.zIndex = 999; // force to be on top
hi.style.position = "absolute";
hi.style.width = tw + 'px';
hi.style.height = th + 'px';
hi.style.left = (rect.left + x) + 'px';
hi.style.top = (rect.top + th + y) + 'px'; // skip sub-sprite height
hi.style.border = '1px solid red';
<div id="clip"></div>
<img id="sprites" src="http://orteil.dashnet.org/cookieclicker/img/icons.png">
<div id="highlight"></div>
Here's one way:
Create an in-memory canvas to clip the subsprite pixels from the spritesheet and draw those clipped pixels on that canvas
Create a new subsprite image from that canvas's dataURL.
Example code (not tested--may need tweeking):
var spritesheet = new Image();
spritesheet.onload=function(){
// specify desired x,y,width,height
// to be clipped from the spritesheet
var x=0;
var y=0;
var w=10;
var h=10;
// create an in-memory canvas
var canvas=document.createElement('canvas');
var ctx=canvas.getContext('2d');
// size the canvas to the desired sub-sprite size
canvas.width=w;
canvas.height=h;
// clip the sub-sprite from x,y,w,h on the spritesheet image
// and draw the clipped sub-sprite on the canvas at 0,0
ctx.drawImage(spritesheet, x,y,w,h, 0,0,w,y);
// convert the canvas to an image
var subsprite=new Image();
subsprite.onload=function(){ doCallback(subsprite); };
subsprite.src=canvas.toDataURL();
}
spritesheet.src ="foo.png";
function doCallback(img){
// do something with the new subsprite image
}