Pattern drawings dynamically loaded misbehaving in Safari - javascript

Below is provided a script for changing image on click, changes will be affected in canvas.
The script is working properly in Firefox and Chrome but not in Safari
What could be the reason?
<script>
$(function() {
var house = document.getElementById("house");
var ctxHouse = house.getContext("2d");
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var ctxHand = canvas2.getContext("2d");
var FabricLink;
var imageObj = new Image();
var red = new Image();
red.onload = function() {
canvas.width = red.width;
canvas.height = red.height;
var houseImage = new Image();
houseImage.onload = function() {
house.width = houseImage.width;
house.height = houseImage.height;
ctxHouse.drawImage(houseImage, 0, 0);
}
houseImage.src = "images/img.jpg";
ctx.drawImage(red, 0, 0);
imageObj.onload = function() {
var pattern = ctx.createPattern(imageObj, 'repeat');
ctx.globalCompositeOperation = 'source-in';
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = pattern;
ctx.fill();
};
$(imageObj).attr('src', 'images/' + FabricLink + '.png');
}
red.src = "images/img.png";
var imageObj2 = new Image();
var blue = new Image();
blue.onload = function() {
canvas2.width = blue.width;
canvas2.height = blue.height;
var houseImage = new Image();
houseImage.onload = function() {
house.width = houseImage.width;
house.height = houseImage.height;
ctxHouse.drawImage(houseImage, 0, 0);
}
houseImage.src = "images/img.jpg";
ctxHand.drawImage(blue, 0, 0);
imageObj2.onload = function() {
var pattern = ctx.createPattern(imageObj2, 'repeat');
ctxHand.globalCompositeOperation = 'source-in';
ctxHand.rect(0, 0, canvas2.width, canvas2.height);
ctxHand.fillStyle = pattern;
ctxHand.fill();
};
$(imageObj2).attr('src', 'images/' + FabricLink + '.png');
}
blue.src = "images/img.png";
$('#style li').click(
function() {
//we get our current filename and use it for the src
var linkIndex = $(this).attr("data-filename");
$(red).attr('src', 'images/' + linkIndex + '.png');
}
);
$('#Sleeves li').click(
function() {
$("#canvas2").addClass("show-z");
//we get our current filename and use it for the src
var SleevesLink = $(this).attr("data-filename");
$(blue).attr('src', 'images/' + SleevesLink + '.png');
}
);
$('#Fabric li').click(
function() {
//we get our current filename and use it for the src
FabricLink = $(this).attr("data-filename");
$(imageObj2).attr('src', 'images/' + FabricLink + '.png');
$(imageObj).attr('src', 'images/' + FabricLink + '.png');
}
);
$("#Fabric li:first-child").click();
}); // end $(function(){});
</script>
While debugging, we could see that imageObj.onload is not getting fired in the first click , only on refresh the change occurs in Safari.
jsfiddle.net/qvfx3kz7 (In the fiddle images are not loading, but the complete code is added)

I am not sure what is exactly the problem with Safari, but what I can see is that you are over-complicating everything by trying to load your assets only when requested, which leads to this callback nightmare you have set up.
Instead, prepare your assets so that you can load it only once. Since you are using patterns, I will assume that each pattern is actually a small image.
So instead of loading every little pattern images, load a single sprite-sheet, which will contain all your patterns.
From there, you'll be able to generate dynamically your CanvasPattern synchronously, without having to care for any loading callback. In order to do this, you will have to use a second, off-screen, canvas, that you will use as he source of createPattern() method:
// really dummy implementation...
function AssetsLoader() {}
AssetsLoader.prototype = Object.create({
addImage: function(objs, cb) {
if (!objs || typeof objs !== 'object') return;
if (!Array.isArray(objs)) objs = [];
var toLoad = objs.length,
imgs = objs.map(loadImage, this);
function loadImage(obj) {
if (!obj.src) return;
this.toLoad++;
var img = new Image();
img.src = obj.src;
img.onload = onimgload;
img.onerror = onimgerror;
return obj.img = img;
}
function onimgload(evt) {
if (--toLoad <= 0 && typeof cb === 'function') cb(imgs);
}
function onimgerror(evt) {
console.warn('failed to load image at ', evt.target.src);
}
}
});
// Some info about our assets, there are an infinity of ways to deal with it,
// You would have to determine yourself what's best for your own case
var dests = {
sofa: {
name: 'sofa',
src: 'https://i.stack.imgur.com/ryO42.png'
},
shirt: {
name: 'shirt',
src: 'https://i.stack.imgur.com/cPNbe.png'
}
};
var patterns = {
name: 'patterns',
src: 'https://i.stack.imgur.com/TdIAJ.png',
positions: {
orange: {
x: 0,
y: 0,
width: 173,
height: 173,
out_width: 75,
out_height: 75
},
violet: {
x: 173,
y: 0,
width: 173,
height: 173,
out_width: 35,
out_height: 35
},
psyche: {
x: 0,
y: 173,
width: 173,
height: 173,
out_width: 25,
out_height: 25
},
pink: {
x: 173,
y: 173,
width: 173,
height: 173,
out_width: 125,
out_height: 125
}
}
}
var assets = new AssetsLoader();
// first load all our images, and only then, start the whole thing
assets.addImage([dests.shirt, dests.sofa, patterns], init);
function init() {
// populate our selects
for (var key in dests) {
dest_select.appendChild(new Option(key, key));
}
for (var key in patterns.positions) {
pat_select.appendChild(new Option(key, key));
}
dest_select.onchange = pat_select.onchange = draw;
var ctx = canvas.getContext('2d');
var offscreenCtx = document.createElement('canvas').getContext('2d');
draw();
function draw() {
var dest = dests[dest_select.value].img;
ctx.canvas.width = dest.width;
ctx.canvas.height = dest.height;
ctx.globalCompositeOperation = 'source-over';
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(dest, 0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'source-in';
// here we generate the CanvasPattern
ctx.fillStyle = getPattern(pat_select.value);
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'luminosity';
ctx.drawImage(dest, 0, 0, canvas.width, canvas.height);
}
// now that we have our patterns loaded in a single sprite-sheet,
// we can generate the CanvasPatterns synchronously
function getPattern(key) {
var pos = patterns.positions[key]; // get our pattern's position in our dictionary
// resize the offscreen canvas so it matches our pattern
offscreenCtx.canvas.width = pos.out_width;
offscreenCtx.canvas.height = pos.out_height;
offscreenCtx.drawImage(patterns.img, pos.x, pos.y, pos.width, pos.height, 0, 0, pos.out_width, pos.out_height);
return offscreenCtx.createPattern(offscreenCtx.canvas, 'repeat');
}
}
<select id="dest_select"></select>
<select id="pat_select"></select>
<br><br><br>
<canvas id="canvas"></canvas>

Related

Sprite not moving on key press

I am trying to create an endless runner game from scratch using JavaScript, and I am currently in the process of making the character jump. However, when I press the spacebar, my character does not move up, but instead just stays in one place. I have pasted my code below:
let ctx = document.querySelector("canvas").getContext("2d");
// Screen size
ctx.canvas.height = 512;
ctx.canvas.width = 512;
// Images
let bg = new Image; // Background
bg.src = "./Background.png";
bg.onload = function() {
ctx.drawImage(bg, 0, 0);
};
let fl = new Image; // Floor
fl.src = "./Floor.png";
fl.onload = function() {
ctx.drawImage(fl, 0, 384);
ctx.drawImage(fl, 128, 384);
ctx.drawImage(fl, 256, 384);
ctx.drawImage(fl, 384, 384);
};
let pl = new Image; // Player
pl.src = "./Idle.png";
pl.onload = function() {
ctx.drawImage(pl, 0, 256);
};
// Movement
let UP = false;
document.onkeydown = function(e) {
if (e.keyCode == 32) UP = true;
};
document.onkeyup = function(e) {
if (e.keyCode == 32) UP = false;
};
setInterval(update, 10);
function update() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // Erase previous positions
if (UP) {
pl.onload = function() {
ctx.drawImage(pl, 0, 256 += 1); // Move player up
};
};
};
I fixed it with some help from Rojo.
let ctx = document.querySelector("canvas").getContext("2d");
// Screen size
ctx.canvas.height = 512;
ctx.canvas.width = 512;
// Images
let bg = new Image;
bg.src = "./Background.png";
let fl = new Image;
fl.src = "./Floor.png";
let pl = new Image;
pl.src = "./Idle.png";
let y = 256;
pl.onload = function() {
ctx.drawImage(pl, 0, y);
};
let UP = false;
document.onkeydown = function(e) {
if (e.keyCode == 32) UP = true;
};
document.onkeyup = function(e) {
if (e.keyCode == 32) UP = false;
};
function update() {
ctx.clearRect(0, 0, 512, 512);
ctx.drawImage(bg, 0, 0);
ctx.drawImage(fl, 0, 384);
ctx.drawImage(fl, 128, 384);
ctx.drawImage(fl, 256, 384);
ctx.drawImage(fl, 384, 384);
if (UP) {
ctx.drawImage(pl, 0, y -= 1);
} else {
ctx.drawImage(pl, 0, y);
};
};
setInterval(update, 10);

How can I concatenate two images in one? Without any framework, only with JS

I need some help with code review and re-translate it out of jQuery to plain JS.
I've tried to translate this into pure JS, but I don't get clearly why it doesn't work. If needed, I can show what I've made.
After the user selects an image from the first input, it should be rendered on canvas.
After the user selects an image from the second input, it should be rendered on the same canvas to the right of the first image.
Images should be rendered in scale, so two pictures will have the same height on canvas, but with the original aspect ratio.
User can select images in any order, but rendering order should stay the same: image from first input appears on canvas before image from the second input.
Inputs should accept only images.
$('.file1, .file2').on('change', function() {
var reader = new FileReader(),
imageSelector = $(this).data('image-selector');
if (this.files && this.files[0]) {
reader.onload = function(e) {
imageIsLoaded(e, imageSelector)
};
reader.readAsDataURL(this.files[0]);
}
});
$('.btn-merge').on('click', merge);
function imageIsLoaded(e, imageSelector) {
$(imageSelector).attr('src', e.target.result);
$(imageSelector).removeClass('hidden');
};
function merge() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
imageObj1 = new Image(),
imageObj2 = new Image();
imageObj1.src = $('.image1').attr('src');
imageObj1.onload = function() {
ctx.globalAlpha = 1;
ctx.drawImage(imageObj1, 0, 0, 328, 526);
imageObj2.src = $('.image2').attr('src');;
imageObj2.onload = function() {
ctx.globalAlpha = 0.5;
ctx.drawImage(imageObj2, 15, 85, 300, 300);
var img = canvas.toDataURL('image/jpeg');
$('.merged-image').attr('src', img);
$('.merged-image').removeClass('hidden');
}
};
}
You should define the onload before changing source and not inside another onload
function merge() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
imageObj1 = new Image(),
imageObj2 = new Image();
imageObj1.onload = function() {
ctx.globalAlpha = 1;
ctx.drawImage(imageObj1, 0, 0, 328, 526);
}
imageObj2.onload = function() {
ctx.globalAlpha = 0.5;
ctx.drawImage(imageObj2, 15, 85, 300, 300);
var img = canvas.toDataURL('image/jpeg');
$('.merged-image').attr('src', img);
$('.merged-image').removeClass('hidden');
}
imageObj1.src = $('.image1').attr('src');
imageObj2.src = $('.image2').attr('src');
}

FabricJS canvas drawImage not displaying image

Attempting to create a custom class that will display an image on the top left corner of a Fabric rectangle.
Extending the fabric class and calling ctx.drawImage isn't displaying anything.
var CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = { });
const image = new Image();
image.src = signBtn;
this.callSuper('initialize', options);
this.set('label', options.label || '');
this.set('image', image);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
ctx.drawImage(this.image, this.left+10, this.top-5, 15, 15);
}
});
fillText is working fine and displaying the label properly.
Tried a few different things thinking the image isn't loaded. It's imported as an SVG. I've gotten it to display in other ways but prefer to have a custom class handle this instead of event handlers.
You need to set image after load. You can use image.onload event callback, or use fabric.util.loadImage
DEMO
fabric.CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'customRect',
initialize: function(options) {
options || (options = { });
var that = this;
this.imageLoaded = false;
fabric.util.loadImage(options.signBtn,function(img){
that.set('image', img);
that.imageLoaded = true;
that.dirty = true;
//if canvas is global
canvas.renderAll();
},{
crossOrigin : 'annonymous'
});
this.callSuper('initialize', options);
this.set('label', options.label || '');
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.label,
image : this.image
});
},
_render: function(ctx) {
this.callSuper('_render', ctx);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
this.imageLoaded && ctx.drawImage(this.image, 10, -5, 15, 15);
}
});
fabric.CustomRect.fromObject = function(object, callback) {
return fabric.Object._fromObject('CustomRect', object, callback);
};
var url = 'https://upload.wikimedia.org/wikipedia/en/8/80/Wikipedia-logo-v2.svg';
var canvas = new fabric.Canvas('canvas');
var rect = new fabric.CustomRect({
signBtn: url,
left : 10,
top : 10,
width : 200,
height: 200,
fill: 'green',
label : 'test'
});
canvas.add(rect);
function loadfromjson() {
var json = canvas.toJSON();
canvas.clear();
setTimeout(function() {
canvas.loadFromJSON(json, canvas.renderAll.bind(canvas));
}, 1000)
}
canvas{
border:2px solid #000;
}
<script src='https://rawgit.com/kangax/fabric.js/master/dist/fabric.js'></script>
<canvas id="canvas" width="300" height="300"></canvas>
<button onclick="loadfromjson()">loadfromjson </button>
I believe there are some missing values like this.width and this.height?
In addition to that, you have to wait until the image is loaded to draw it. You can do something like this:
var CustomRect = fabric.util.createClass(fabric.Rect, {
type: 'labeledRect',
initialize: function(options) {
options || (options = { });
const image = new Image();
image.src = 'https://i.stack.imgur.com/CYp8Y.jpg';
this.callSuper('initialize', options);
image.onload = (function() {
this.width = image.width;
this.height = image.height;
this.image_loaded = true;
}).bind(this);
this.set('label', options.label || '');
this.set('image', image);
},
toObject: function() {
return fabric.util.object.extend(this.callSuper('toObject'), {
label: this.get('label')
});
},
_render: function(ctx) {
if (this.image_loaded) {
this.callSuper('_render', ctx);
console.log(-this.width / 2, -this.height / 2);
ctx.font = '20px Helvetica';
ctx.fillStyle = '#333';
ctx.fillText(this.label, -this.width/2, -this.height/2 + 20);
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
}
}
});
Note these two changes:
image.onload = (function() {
this.width = image.width;
this.height = image.height;
this.image_loaded = true;
}).bind(this);
I set the image_loaded to true inside the initialize method once the image is loaded. Then in the _render method I check if the image is loaded, and then draw it.
if(this.loaded){
//............
ctx.drawImage(this.image, -this.width / 2, -this.height / 2);
}
Here's a fiddle: https://jsfiddle.net/nimeshka/enL61g0w/55/
You might need to click on the canvas to trigger the render event. Otherwise you won't see the rectangle in the output box. (I haven't added it)
Hope it helps :)

JCrop: Manipulate image from external URL

I need to rotate/scale/flip/crop an image on an HTML page.
I'm using JCrop.js for image manipulation on client side. I'm able to do this successfully with a file selector, where the user selects an image file on their local computer. My requirement is to do the same, but for an image loaded from another URL (without selecting a local image).
Need help on the next step.
Here is the complete code that works well with local file selection:
HTML:
<img scr="externalURL" id="imgMain" alt=""/>
<input type="button" id="cropbutton" value="Crop" />
<input type="button" id="rotatebutton" value="Rotate" />
<input type="button" id="hflipbutton" value="Flip Horizontal" />
<input type="button" id="vflipbutton" value="Flip Vertical" />
JS:
var cropWidth = 800;
var cropHeight = 800;
var crop_max_width = 400;
var crop_max_height = 400;
var jcrop_api;
var canvas;
var context;
var image;
var prefsize;
$(document).ready(function () {
$("#file").change(function () {
loadImage(this);
});
$("#btnCrop").click(function () {
SaveData();
});
});
function SaveData() {
formData = new FormData($(this)[0]);
var blob = dataURLtoBlob(canvas.toDataURL('image/jpeg'));
formData.append("cropped_image[]", blob, "CroppedImage.jpeg");
$.ajax({
url: urlToSendData
type: "POST",
data: formData,
contentType: false,
cache: false,
processData: false,
success: function (data) {
alert("Image cropped!");
},
error: function (data) {
alert('There was an error');
}
});
}
function loadImage(input) {
if (input.files && input.files[0]) {
var reader = new FileReader();
canvas = null;
reader.onload = function (e) {
image = new Image();
image.onload = validateImage;
image.src = e.target.result;
}
reader.readAsDataURL(input.files[0]);
applyCrop();
}
}
function dataURLtoBlob(dataURL) {
var BASE64_MARKER = ';base64,';
if (dataURL.indexOf(BASE64_MARKER) == -1) {
var parts = dataURL.split(',');
var contentType = parts[0].split(':')[1];
var raw = decodeURIComponent(parts[1]);
return new Blob([raw], {
type: contentType
});
}
var parts = dataURL.split(BASE64_MARKER);
var contentType = parts[0].split(':')[1];
var raw = window.atob(parts[1]);
var rawLength = raw.length;
var uInt8Array = new Uint8Array(rawLength);
for (var i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i);
}
return new Blob([uInt8Array], {
type: contentType
});
}
function validateImage() {
if (canvas != null) {
image = new Image();
image.onload = restartJcrop;
image.src = canvas.toDataURL('image/png');
} else restartJcrop();
}
function restartJcrop() {
if (jcrop_api != null) {
jcrop_api.destroy();
}
$("#views").empty();
$("#views").append("<canvas id=\"canvas\">");
canvas = $("#canvas")[0];
context = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
$("#canvas").Jcrop({
onSelect: selectcanvas,
onRelease: clearcanvas,
boxWidth: crop_max_width,
boxHeight: crop_max_height,
allowResize: false,
allowSelect: false,
setSelect: [0, 0, cropWidth, cropHeight],
aspectRatio: 1
}, function () {
jcrop_api = this;
});
clearcanvas();
}
function clearcanvas() {
prefsize = {
x: 0,
y: 0,
w: canvas.width,
h: canvas.height,
};
}
function selectcanvas(coords) {
prefsize = {
x: Math.round(coords.x),
y: Math.round(coords.y),
w: Math.round(coords.w),
h: Math.round(coords.h)
};
}
function applyCrop() {
canvas.width = prefsize.w;
canvas.height = prefsize.h;
context.drawImage(image, prefsize.x, prefsize.y, prefsize.w, prefsize.h, 0, 0, canvas.width - 10, canvas.height - 10);
validateImage();
}
function applyScale(scale) {
if (scale == 1) return;
canvas.width = canvas.width * scale;
canvas.height = canvas.height * scale;
context.drawImage(image, 0, 0, canvas.width, canvas.height);
validateImage();
}
function applyRotate() {
canvas.width = image.height;
canvas.height = image.width;
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(image.height / 2, image.width / 2);
context.rotate(Math.PI / 2);
context.drawImage(image, -image.width / 2, -image.height / 2);
validateImage();
}
function applyHflip() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(image.width, 0);
context.scale(-1, 1);
context.drawImage(image, 0, 0);
validateImage();
}
function applyVflip() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.translate(0, image.height);
context.scale(1, -1);
context.drawImage(image, 0, 0);
validateImage();
}
$("#scalebutton").click(function (e) {
var scale = prompt("Scale Factor:", "1");
applyScale(scale);
});
$("#rotatebutton").click(function (e) {
applyRotate();
});
$("#hflipbutton").click(function (e) {
applyHflip();
});
$("#vflipbutton").click(function (e) {
applyVflip();
});
Looks like loadImage reads the file and sets the image global.
As there is now already an image on the page, we should be able to skip this step.
So we can probably just set the image in ready like this:
$(document).ready(function () {
// (this code replaces the file selecting)
image = $("#imgMain"); // set image from the page
canvas = null; // this was done in loadImage
applyCrop(); // this was called from loadImage
// (this code is the same as before)
$("#btnCrop").click(function () {
SaveData();
});
});

how to fill a pattern(rotated 25 degree)

Let's say i have a image
i want to fill this with with 25 degree.
i rotate the image in php GD which gave me
then when i create a new pattern and apply. i got the
below is the code
Html
<h1>sleeve long normal</h1>
<canvas id="sleevelongnormal" width=436 height=567></canvas>
js
function doD(var1,var2,var3) //var1=convas var2=img1 var3=img aka design
{
// alert(var3);
t = document.getElementById(''+var1+'');
ctxt = t.getContext('2d');
var img1 = new Image();
var img = new Image();
img.onload = function () {
img1.onload = function () {
//start();
ctxt.drawImage(img1, 0, 0);
ctxt.globalCompositeOperation = "source-atop";
var pattern = ctxt.createPattern(img, 'repeat');
ctxt.rect(0, 0, sourceCanvas.width, sourceCanvas.height);
ctxt.fillStyle = pattern;
//var rotation = 90;
//ctxt.rotate(rotation * Math.PI/180);
ctxt.fill();
ctxt.globalAlpha = .10;
ctxt.drawImage(img1, 0, 0);
ctxt.drawImage(img1, 0, 0);
ctxt.drawImage(img1, 0, 0);
}
img1.src = var2;
}
img.src = var3;
//alert(t);
}
i want to achieve more or less like this

Categories