Image cropping and masking using fabricjs - fabricjs

How can I remove limiter and edit __editingSetCrop method so that I can correctly mask a image in fabricjs? In my current implementation, dragging is not smooth.
When you try to drag close to the edges, it breaks and stops moving. Instead, it should keep dragging on the available space. When I rotate the image and double click it moves outside of the box. also it not smooth.
here is my example https://www.awesomescreenshot.com/video/14279604?key=ee3022f0335fd9aa82c66628e7085cb3
const drawTopRightIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0.5, 12)
ctx.moveTo(0, 0)
ctx.lineTo(-12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawTopLeftIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0.5, 12)
ctx.moveTo(0, 0)
ctx.lineTo(12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawBottomLeftIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0, -12)
ctx.moveTo(0, 0)
ctx.lineTo(12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawBottomRightIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(0, 0)
ctx.lineTo(0, -12)
ctx.moveTo(0, 0)
ctx.lineTo(-12, 0)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawVerticalLineIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
const size = 24
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(-0.5, -size / 4)
ctx.lineTo(-0.5, -size / 4 + size / 2)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const drawHorizontalLineIcon = (
ctx,
left,
top,
__styleOverride,
fabricObject
) => {
const size = 24
ctx.save()
ctx.translate(left, top)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.beginPath()
ctx.lineCap = 'round'
ctx.lineWidth = 3
ctx.shadowBlur = 2
ctx.shadowColor = 'black'
ctx.moveTo(-size / 4, -0.5)
ctx.lineTo(-size / 4 + size / 2, -0.5)
ctx.strokeStyle = '#ffffff'
ctx.stroke()
ctx.restore()
}
const controlPositionIcons = {
tl: drawTopLeftIcon,
t: drawHorizontalLineIcon,
tr: drawTopRightIcon,
r: drawVerticalLineIcon,
br: drawBottomRightIcon,
b: drawHorizontalLineIcon,
bl: drawBottomLeftIcon,
l: drawVerticalLineIcon
}
const ControlPositions = {
TOP_LEFT: 'tl',
TOP: 't',
TOP_RIGHT: 'tr',
RIGHT: 'r',
BOTTOM_RIGHT: 'br',
BOTTOM: 'b',
BOTTOM_LEFT: 'bl',
LEFT: 'l'
}
fabric.StaticImage = fabric.util.createClass(fabric.Image, {
type: 'StaticImage',
_editingMode: false,
__editingImage: null,
cornerLengthEditing: 5,
cornerStrokeColorEditing: 'black',
cornerSizeEditing: 2,
cropType: 'rectangle',
initialize: function (element, options) {
options || (options = {});
this.filters = [];
this.cacheKey = 'texture' + fabric.Object.__uid++;
this.registerEditingEvents();
this.callSuper('initialize', options);
this._initElement(element, options);
this.applyCrop();
},
getElement: function () {
return this._element || {};
},
applyCrop: function () {
if (this.clipPath instanceof fabric.Object) {
this.clipPath = null
}
if (this.cropType === 'circle') {
this.clipPath = new fabric.Ellipse({
rx: this.width / 2,
ry: this.height / 2,
left: -this.width / 2,
top: -this.height / 2,
});
}
if (this.cropType === 'triangle') {
this.clipPath = new fabric.Triangle({
width: this.width,
height: this.height,
left: -this.width / 2,
top: -this.height / 2,
});
}
return this
},
registerEditingEvents: function () {
this.on('mousedblclick', () => {
if (!this._editingMode) {
return this.enterEditingMode()
} else {
this.exitEditingMode()
}
})
this.on('deselected', () => {
this.exitEditingMode()
})
},
enterEditingMode: function () {
if (this.selectable && this.canvas) {
this._editingMode = true
this.__editingImage = fabric.util.object.clone(this)
this.__editingImage.clipPath = null
const element = this.__editingImage.getElement()
const { top = 0, left = 0, cropX = 0, cropY = 0, scaleX = 1, scaleY = 1 } = this.__editingImage
this.__editingImage.set({
top: top - cropY * scaleY,
left: left - cropX * scaleX,
height: element.height,
width: element.width,
cropX: 0,
cropY: 0,
opacity: 0.6,
selectable: true,
evented: false,
excludeFromExport: true
})
this.canvas.add(this.__editingImage)
this.controls = this.__editingControls()
this.canvas.requestRenderAll()
this.on('moving', this.__editingOnMoving)
}
},
exitEditingMode: function () {
if (this.selectable && this.canvas) {
this._editingMode = false
if (this.__editingImage) {
this.canvas.remove(this.__editingImage)
this.__editingImage = null
}
this.off('moving', this.__editingOnMoving)
this.controls = fabric.Object.prototype.controls
this.fire('exit:editing', { target: this })
this.canvas.requestRenderAll()
}
},
__editingControls: function () {
const controls = Object.values(ControlPositions)
return controls.map(this.__createEditingControl.bind(this))
},
__createEditingControl: function (position) {
const cursor = position
.replace('t', 's')
.replace('l', 'e')
.replace('b', 'n')
.replace('r', 'w')
return new fabric.Control({
cursorStyle: cursor + '-resize',
actionName: `edit_${this.type}`,
render: controlPositionIcons[position],
positionHandler: this.__editingControlPositionHandler.bind(this, position),
actionHandler: this.__editingActionHandlerWrapper(position)
})
},
__editingActionHandlerWrapper: function (position) {
return (_event, _transform, x, y) => {
//let localPosition = this.getLocalPointer(_event)
this.__editingSetCrop(position, x, y, true)
return true
}
},
__editingOnMoving: function (event) {
if (this._editingMode && event.pointer) {
this.set('dirty', true) // trigger cache refresh
this.__editingSetCrop(ControlPositions.TOP_LEFT, this.left, this.top)
}
},
__editingSetCrop: function (
position,
x,
y,
resize = false
) {
if (this.__editingImage) {
const { top = 0, left = 0, width = 0, height = 0, scaleX = 1, scaleY = 1 } = this.__editingImage
if (position.includes('t')) {
const maxTop = top + height * scaleY - (resize ? 0 : this.getScaledHeight())
const minTop = Math.min(y, maxTop, this.top + this.getScaledHeight())
this.top = Math.max(minTop, top)
const cropY = Math.min((Math.min(Math.max(y, top), this.top) - top) / scaleY, height)
if (resize) {
this.height = Math.max(0, Math.min(this.height + (this.cropY - cropY), height))
}
this.cropY = cropY
} else if (position.includes('b') && resize) {
const minHeight = Math.min((y - top) / scaleY - this.cropY, height - this.cropY)
this.height = Math.max(0, minHeight)
}
if (position.includes('l')) {
const maxLeft = left + width * scaleX - (resize ? 0 : this.getScaledWidth())
const minLeft = Math.min(x, maxLeft, this.left + this.getScaledWidth())
this.left = Math.max(minLeft, left)
const cropX = Math.min((Math.min(Math.max(x, left), this.left) - left) / scaleX, width)
if (resize) {
this.width = Math.max(0, Math.min(this.width + (this.cropX - cropX), width))
}
this.cropX = cropX
} else if (position.includes('r') && resize) {
const minWidth = Math.min((x - left) / scaleX - this.cropX, width - this.cropX)
this.width = Math.max(0, minWidth)
}
this.applyCrop()
}
},
__editingControlPositionHandler: function (position) {
const xMultiplier = position.includes('l') ? -1 : position.length > 1 || position === 'r' ? 1 : 0
const yMultiplier = position.includes('t') ? -1 : position.length > 1 || position === 'b' ? 1 : 0
const x = (this.width / 2) * xMultiplier
const y = (this.height / 2) * yMultiplier
return fabric.util.transformPoint(
new fabric.Point(x, y),
fabric.util.multiplyTransformMatrices(this.canvas.viewportTransform, this.calcTransformMatrix())
)
},
__renderEditingControl: function (
position,
ctx,
left,
top
) {
ctx.save()
ctx.strokeStyle = this.cornerStrokeColorEditing
ctx.lineWidth = this.cornerSizeEditing
ctx.translate(left, top)
if (this.angle) {
ctx.rotate(fabric.util.degreesToRadians(this.angle))
}
ctx.beginPath()
const x = position.includes('l') ? -ctx.lineWidth : position.includes('r') ? ctx.lineWidth : 0
const y = position.includes('t') ? -ctx.lineWidth : position.includes('b') ? ctx.lineWidth : 0
if (position === 'b' || position === 't') {
ctx.moveTo(x - this.cornerLengthEditing / 2, y)
ctx.lineTo(x + this.cornerLengthEditing, y)
} else if (position === 'r' || position === 'l') {
ctx.moveTo(x, y - this.cornerLengthEditing / 2)
ctx.lineTo(x, y + this.cornerLengthEditing)
} else {
if (position.includes('b')) {
ctx.moveTo(x, y - this.cornerLengthEditing)
} else if (position.includes('t')) {
ctx.moveTo(x, y + this.cornerLengthEditing)
}
ctx.lineTo(x, y)
if (position.includes('r')) {
ctx.lineTo(x - this.cornerLengthEditing, y)
} else if (position.includes('l')) {
ctx.lineTo(x + this.cornerLengthEditing, y)
}
}
ctx.stroke()
ctx.restore()
},
});
fabric.StaticImage.fromURL = function (url, callback, imgOptions) {
fabric.util.loadImage(url, function (img, isError) {
callback && callback(new fabric.StaticImage(img, imgOptions), isError);
}, null, imgOptions && imgOptions.crossOrigin);
};
fabric.StaticImage.fromObject = function (options, callback) {
fabric.util.loadImage(
options.src,
function (img) {
return callback && callback(new fabric.StaticImage(img, options))
},
null,
{ crossOrigin: 'anonymous' }
)
}
tried various fabric js libraries

Related

Generate 1000 pdf with survey pdf

I'm trying to generate more than one thousand pdf files using surveyPDF.
The problem is that i can generate only 80 pdf files...
I'm passing an array with more than 1000 pdf to generate.
Code :
query.map(async items => {
const { generatePdf } = await import("~/lib/survey");
const filename = kebabCase(
`${campaign.title} - ${items.employee.fullName.toLowerCase()} -${moment().format("DD/MM/YYYY - HH:mm")} `
);
return generatePdf(campaign.template.body, items, filename, 210, 297);
});
The code which generate each pdfs :
import autoTable from "jspdf-autotable";
import { SurveyPDF, CommentBrick, CompositeBrick, PdfBrick, TextBrick } from "survey-pdf";
import { format } from "~/utils/date";
class AutoTableBrick extends PdfBrick {
constructor(question, controller, rect, options) {
super(question, controller, rect);
this.margins = {
top: controller.margins.top,
bottom: controller.margins.bot,
right: 30,
left: 30,
};
this.options = options;
}
renderInteractive() {
if (this.controller.doc.lastAutoTable && !this.options.isFirstQuestion) {
this.options.startY = this.yTop;
}
autoTable(this.controller.doc, {
head: [
[
{
content: this.question.title,
colSpan: 2,
styles: { fillColor: "#5b9bd5" },
},
],
],
margin: { ...this.margins },
styles: { fillColor: "#fff", lineWidth: 1, lineColor: "#5b9bd5", minCellWidth: 190 },
alternateRowStyles: { fillColor: "#bdd6ee" },
...this.options,
});
}
}
export async function generatePdf(json, data, filename, pdfWidth, pdfHeight) {
if (!json) {
return Promise.reject("Invalid json for pdf export");
}
for (const page of json.pages) {
page.readOnly = true;
}
const surveyPDF = new SurveyPDF(json, {
fontSize: 11,
format: [pdfWidth, pdfHeight],
commercial: true,
textFieldRenderAs: "multiLine",
});
surveyPDF.showQuestionNumbers = "off";
surveyPDF.storeOthersAsComment = false;
//TODO This does not work well with dynamic dropdown, bug declared
// surveyPDF.mode = "display";
surveyPDF.mergeData({ ...data, _: {} });
surveyPDF.onRenderQuestion.add(function(survey, options) {
const { bricks, question } = options;
if (question.getType() === "comment" && question.value && bricks.length > 0) {
for (const brick of bricks) {
if (brick.value) {
brick.value = question.value.replace(/\t/g, " ");
}
if (brick instanceof CompositeBrick) {
const { bricks } = brick;
for (const brick of bricks) {
if (brick instanceof CommentBrick) {
brick.value = question.value.replace(/\t/g, " ");
}
}
}
}
}
});
surveyPDF.onRenderQuestion.add(async function(survey, options) {
const {
point,
bricks,
question,
controller,
module: { SurveyHelper },
} = options;
if (question.getType() === "multipletext") {
const body = [];
let extraRows = 0;
let rows = question.getRows();
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < rows[i].length; j++) {
let { title, value, inputType } = rows[i][j];
if (inputType === "date") {
value = format(value);
}
if (typeof value === "string" && value.length > 0) {
const valueEstRows = value.match(/.{1,70}/g).length;
if (valueEstRows > 1) {
extraRows += valueEstRows;
}
}
body.push([title, value || "N/A"]);
}
}
//TODO Use SurveyHelper helperDoc do calculate the height of the auto table
const startY = point.yTop;
const height = 21.5 * (body.length + 1) + 8.5 * extraRows;
const isFirstQuestion = question.title === question.parent.questions[0].title;
options.bricks = [
new AutoTableBrick(question, controller, SurveyHelper.createRect(point, bricks[0].width, height), {
startY,
body,
isFirstQuestion,
}),
];
}
});
surveyPDF.onRenderQuestion.add(async function(survey, options) {
const {
point,
question,
controller,
module: { SurveyHelper },
} = options;
if (question.getType() === "text") {
//Draw question background
const { default: backImage } = await import("~/public/assets/images/block.png");
const backWidth = SurveyHelper.getPageAvailableWidth(controller);
const backHeight = SurveyHelper.pxToPt(100);
const imageBackBrick = SurveyHelper.createImageFlat(point, null, controller, backImage, backWidth, backHeight);
options.bricks = [imageBackBrick];
point.xLeft += controller.unitWidth;
point.yTop += controller.unitHeight;
const oldFontSize = controller.fontSize;
const titleBrick = await SurveyHelper.createTitleFlat(point, question, controller);
controller.fontSize = oldFontSize;
titleBrick.unfold()[0]["textColor"] = "#6a6772";
options.bricks.push(titleBrick);
//Draw text question text field border
let { default: textFieldImage } = await import("~/public/assets/images/input.png");
let textFieldPoint = SurveyHelper.createPoint(imageBackBrick);
textFieldPoint.xLeft += controller.unitWidth;
textFieldPoint.yTop -= controller.unitHeight * 3.3;
let textFieldWidth = imageBackBrick.width - controller.unitWidth * 2;
let textFieldHeight = controller.unitHeight * 2;
let imageTextFieldBrick = SurveyHelper.createImageFlat(
textFieldPoint,
null,
controller,
textFieldImage,
textFieldWidth,
textFieldHeight
);
options.bricks.push(imageTextFieldBrick);
textFieldPoint.xLeft += controller.unitWidth / 2;
textFieldPoint.yTop += controller.unitHeight / 2;
let textFieldValue = question.value || "";
if (textFieldValue.length > 90) {
textFieldValue = textFieldValue.substring(0, 95) + "...";
}
const textFieldBrick = await SurveyHelper.createBoldTextFlat(
textFieldPoint,
question,
controller,
textFieldValue
);
controller.fontSize = oldFontSize;
textFieldBrick.unfold()[0]["textColor"] = "#EFF8FF";
options.bricks.push(textFieldBrick);
}
});
surveyPDF.onRenderQuestion.add(async function(survey, options) {
const {
point,
question,
controller,
module: { SurveyHelper },
} = options;
if (question.getType() === "radiogroup" || question.getType() === "rating") {
options.bricks = [];
const oldFontSize = controller.fontSize;
const titleLocation = question.hasTitle ? question.getTitleLocation() : "hidden";
let fieldPoint;
if (["hidden", "matrix"].includes(titleLocation)) {
fieldPoint = SurveyHelper.clone(point);
} else {
const titleBrick = await SurveyHelper.createTitleFlat(point, question, controller);
titleBrick.xLeft += controller.unitWidth;
titleBrick.yTop += controller.unitHeight * 2;
controller.fontSize = oldFontSize;
titleBrick.unfold()[0]["textColor"] = "#6a6772";
options.bricks.push(titleBrick);
fieldPoint = SurveyHelper.createPoint(titleBrick);
fieldPoint.yTop += controller.unitHeight * 1.3;
}
//Draw checkbox question items field
const { default: itemEmptyImage } = await import("~/public/assets/images/unchecked.png");
const { default: itemFilledImage } = await import("~/public/assets/images/checked.png");
const itemSide = controller.unitWidth;
let imageItemBrick;
const choices = question.getType() === "rating" ? question.visibleRateValues : question.visibleChoices;
for (const choice of choices) {
const isItemSelected =
question.getType() === "rating" ? question.value === choice.value : choice === question.selectedItem;
imageItemBrick = SurveyHelper.createImageFlat(
fieldPoint,
null,
controller,
isItemSelected ? itemFilledImage : itemEmptyImage,
itemSide,
itemSide
);
options.bricks.push(imageItemBrick);
const textPoint = SurveyHelper.clone(fieldPoint);
textPoint.xLeft += itemSide + controller.unitWidth / 2;
textPoint.yTop += itemSide / 12;
const itemValue = choice.locText.renderedHtml;
const checkboxTextBrick = await SurveyHelper.createTextFlat(
textPoint,
question,
controller,
itemValue,
TextBrick
);
checkboxTextBrick.unfold()[0]["textColor"] = "#6a6772";
fieldPoint.yTop = imageItemBrick.yBot + SurveyHelper.GAP_BETWEEN_ROWS * controller.unitHeight;
options.bricks.push(checkboxTextBrick);
}
}
});
surveyPDF.onRenderFooter.add(function(survey, canvas) {
canvas.drawText({
text: canvas.pageNumber + "/" + canvas.countPages,
fontSize: 10,
horizontalAlign: "right",
margins: {
right: 12,
},
});
});
return await surveyPDF.raw(`./pdf/${filename}.pdf`);
}
The error :
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
I have already try to increase the node memory using $env:NODE_OPTIONS="--max-old-space-size=8192"

Problem subclassing fabricjs Image and restoring it

I am trying to subclass a fabricjs Image for a barcode field.
import {fabric} from 'fabric';
import bwipjs from 'bwip-js';
class Barcode extends fabric.Image {
constructor(options) {
console.log("constructor", options);
var wrkOptions = {
width: options.position.width,
height: options.position.height,
left: options.position.left,
top: options.position.top,
angle: options.direction || 0,
form: options.form || {}
};
super(wrkOptions);
this.type = 'barcode';
let canvas1 = document.createElement('canvas');
canvas1.width = options.position.width;
canvas1.height = options.position.height;
var bcOptions = { bcid: 'code128', // Barcode type
text: '0123456789', // Text to encode
scale: 3, // 3x scaling factor
height: 10, // Bar height, in millimeters
includetext: true, // Show human-readable text
textxalign: 'center', // Always good to set this
};
bwipjs.toCanvas(canvas1, bcOptions);
var dataUrl = canvas1.toDataURL("image/png");
this.setSrc(dataUrl, () => {
if (!options.restore) {
curCanvas.add(this);
this.setOptions(options);
this.setCoords();
}
},
wrkOptions);
}
toObject = () =>{
var ret = {
type: this.type,
position: {top: this.top, left: this.left, width: this.width * this.scaleX, height: this.height * this.scaleY},
color: this.fill,
direction: this.angle,
form: this.form
};
console.log(ret);
return ret;
}
}
fabric.Barcode = Barcode;
fabric.Barcode.fromObject = function (options, callback) {
var wrkOptions = {restore: true, ...options};
var bc = new Barcode(wrkOptions);
callback(bc);
};
Creating the barcode field works:
onAddBarcode = () => {
var color = Math.floor(Math.random() * 256 * 256 *256).toString(16);
var options = {
color: "#" + color,
position: {
top: Math.floor(Math.random() * 500),
left: Math.floor(Math.random() * 500),
width: Math.floor(Math.random() * 100),
height: Math.floor(Math.random() * 100)
}
};
return ret;
...this.getRandom()
};
var barcode = new Barcode(options);
}
But serializing and restoring the canvas does not work:
onReload = () => {
var json = JSON.stringify(this.canvas, null, 2);
this.canvas.clear();
this.canvas.loadFromJSON(json, this.canvas.renderAll.bind(this.canvas));
}
It does restore a barcode image, but it is positioned wrongly outside of the controls.
What's wrong?
I got it positioned correctly (after restoring) by resetting the position attributes in the setSrc callback:
....
this.setSrv(dataUrl,
() => {
this.left = options.position.left;
this.top = options.position.top;
this.angle = options.direction || 0;
this.width = options.position.width;
this.height = options.position.height;
this.setCoords();
callback(this);
});

Js Phaser 3 game window not displaying

I'm fairly new to Phaser 3, and I just started dipping my feet in scenes. The problem here is that the game window won't display, and I can't figure out why.
Leaving my entire code here just in case there's a problem somewhere else. Sorry about the wall of text. Any help would be appreciated, even if it isn't a direct fix.
class scene1 extends Phaser.Scene{
constructor() {
super({ key: 'scene1' });
this.pause = false;
this.q = null;
this.player = null;
this.roundTxt = null;
this.handUp = true;
this.round = 1;
this.flash = true;
this.platforms = null;
this.hudStar = null;
this.starCountTxt = null;
this.bombs = null;
this.left = false;
this.lives = 3;
this.hudLives = null;
this.starCount = 0;
this.gameOver = false;
this.bCol = null;
this.pCol = null;
this.invincible = false;
this.invFlash = null;
this.tw = null;
this.slam = false;
this.bullets = null;
this.keySpace = null;
this.shot = false;
this.game = new Phaser.Game(config);
this.direction = 1;
this.bombs = null;
this.bullets = new Bullets(this);
this.cursors = this.input.keyboard.createCursorKeys();
//initialize stars
this.stars = this.physics.add.group({
key: 'star',
repeat: 9,
setXY: { x: 12, y: 0, stepX: 80 },
});
// Define spacebar
this.keySpace = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
this.window.bullets = this.bullets;
}
addBomb() {
var x = Phaser.Math.Between(0, 800);
var y = Phaser.Math.Between(30, 450);
this.bombs.children.iterate(function (child) {
x = Phaser.Math.Between(0, 800);
y = Phaser.Math.Between(30, 450);
if (child.active) {
child.enableBody(true, child.x, child.y, true, true);
} else {
child.enableBody(true, x, y, true, true);
}
child.setVelocityX(50 * Phaser.Math.Between(10, 2));
child.setVelocityY(50 * Phaser.Math.Between(10, 2));
});
x = Phaser.Math.Between(0, 800);
y = Phaser.Math.Between(30, 450);
scene1.bomb = this.bombs.create(x, y, 'bomb').setBounce(1).setCollideWorldBounds(true).setVelocityX(50 * Phaser.Math.Between(10, 2)).setVelocityY(50 * Phaser.Math.Between(10, 2));
}
preload () {
this.load.image('sky', 'assets/tut/sky.png');
this.load.image('ground', 'assets/tut/platform.png');
this.load.image('star', 'assets/tut/star.png');
this.load.image('bomb', 'assets/tut/bomb.png');
this.load.spritesheet('dude', 'assets/tut/dude.png', { frameWidth: 32, frameHeight: 48 });
this.load.image('life', 'assets/tut/dudeLife.png');
this.load.image('bullet', 'assets/bullet7.png');
}
//end preload
//create
create () {
//add sky background
this.add.image(400, 300, 'sky');
//add player
this.player = this.physics.add.sprite(100, 450, 'dude');
//player bounces
//set world bounds
this.player.setCollideWorldBounds(true);
//animations
this.anims.create({
key: 'left',
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 3 }),
frameRate: 10,
repeat: -1
});
this.anims.create({
key: 'turn',
frames: [ { key: 'dude', frame: 4 } ],
frameRate: 20
});
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('dude', { start: 5, end: 8 }),
frameRate: 10,
repeat: -1
});
//create group 'platforms'
this.platforms = this.physics.add.staticGroup();
//add ground platforms
this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
//add sky platforms
this.platforms.create(600, 400, 'ground');
this.platforms.create(50, 250, 'ground');
this.platforms.create(750, 220, 'ground');
//cursors
//add stars
this.stars.children.iterate(function(child) {
child.setBounceY(Phaser.Math.FloatBetween(0.4, 0.8));
child.setCollideWorldBounds(true);
});
//set bombs
this.bombs = this.physics.add.group();
this.addBomb();
//collision
this.pCol = this.physics.add.collider(this.player, this.platforms);
this.physics.add.collider(this.stars, this.platforms);
this.physics.add.collider(this.stars, this.bombs);
this.physics.add.collider(this.bombs, this.platforms);
this.bCol = this.physics.add.collider(this.player, this.bombs, hitBomb, null, this);
this.physics.add.overlap(this.player, this.stars, collectStar, null, this);
function collectStar(player, star) {
scene1.star.disableBody(true, true);
scene1.starCount++;
if (scene1.stars.countActive(true) === 0) {
scene1.flash = true;
scene1.round++;
scene1.bCol.active = false;
scene1.stars.children.iterate(function (child) {
child.enableBody(true, child.x, 0, true, true);
});
scene1.addBomb();
}
}
//function for player/bomb col
function hitBomb(player, bomb) {
if (scene1.slam) {
bomb.disableBody(true, true);
player.setVelocityY(-300);
} else {
if (scene1.invincible == false) {
scene1.bCol.active = false;
bomb.disableBody(true, true);
scene1.invincible = true;
scene1.lives--;
if (scene1.lives == 2) {
scene1.hudLives.three.destroy();
} else if (scene1.lives == 1) {
scene1.hudLives.two.destroy();
} else {
scene1.gameOver = true;
scene1.hudLives.one.destroy();
}
player.setAlpha(0);
scene1.add.tween({
targets: player,
alpha: 1,
duration: 100,
ease: 'Linear',
repeat: 5,
onComplete: function() {
scene1.invincible = false;
scene1.bCol.active = true;
}
});
}
}
}
// bullets = new Bullet(this);
this.physics.add.overlap(scene1.bullets, scene1.bombs, scene1.shootBomb, null, this );
//define pause button
this.q = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q);
/////////////////////////
/////////////////////////
/////////////////////////
//////////HUD////////////
/////////////////////////
/////////////////////////
scene1.starCountTxt = this.add.text(35, 10, scene1.starCount, { font: ' 20px sans-serif', fill: '#ffffff' });
//star hud img
scene1.hudStar = this.add.image(20, 20, 'star');
//adds lives on hud
scene1.hudLives = {
one: this.add.image(20, 90, 'life'),
two: this.add.image(30, 90, 'life'),
three: this.add.image(40, 90, 'life')
};
scene1.roundTxt = {
label: this.add.image(20, 55, 'bomb'),
num: this.add.text(35, 43, scene1.round, { font: '20px sans-serif', fill: '#ffffff'})
};
//end of create
} //this curly bracket
//end of create
update () {
scene1.roundTxt.num.setText(scene1.round);
if (scene1.bombs.countActive(true) == 0) {
scene1.addBomb();
scene1.flash = true;
scene1.round++;
}
if (scene1.flash) {
scene1.flash = false;
scene1.bCol.active = false;
scene1.bombs.children.iterate(function(child) {
child.setTint(0x00ff00);
setTimeout(function(){
child.setTint(0xffffff);
scene1.bCol.active = true;
}, 1000);
});
}
if (scene1.cursors.left.isDown) {
scene1.direction = -1;
}
else if (scene1.cursors.right.isDown) {
scene1.direction = 1;
}
scene1.starCountTxt.setText(scene1.starCount);
if (scene1.gameOver) {
this.physics.pause();
scene1.player.setTint(0xff0000);
scene1.player.anims.play('turn');
var die = this.add.tween({
targets: scene1.player,
scaleX: '-=0.5',
scaleY: '-=0.5',
angle: '-=360',
repeat: 1,
duration: 500,
onComplete: function() {
die.delete();
}
});
}
if (scene1.cursors.left.isDown) {
scene1.player.setVelocityX(-300);
scene1.left = true;
scene1.player.anims.play('left', true);
}
else if (scene1.cursors.right.isDown) {
scene1.player.setVelocityX(300);
scene1.left = false;
scene1.player.anims.play('right', true);
}
else {
scene1.player.setVelocityX(0);
scene1.player.anims.play('turn');
}
if (scene1.cursors.up.isDown && scene1.player.body.touching.down) {
scene1.player.setVelocityY(-400);
}
if (scene1.cursors.down.isDown && scene1.player.body.touching.down && scene1.player.y < 500) {
if (!scene1.slam) {
scene1.player.y += 5;
scene1.slam = true;
}
} else if (scene1.player.body.touching.down == false && scene1.cursors.down.isDown) {
if (!scene1.slam) {
scene1.player.setVelocityY(1000);
scene1.slam = true;
}
} else if (scene1.cursors.down.isDown == false) {
scene1.slam = false;
}
if (scene1.player.body.velocity.y < 0) {
scene1.pCol.active = false;
} else {
scene1.pCol.active = true;
}
if (scene1.keySpace.isDown) {
if (!scene1.shot && scene1.handUp) {
this.bullets.fireBullet(scene1.player.x, scene1.player.y+5);
console.log(this.bullets);
scene1.shot = true;
this.tweens.addCounter({
from: 50,
to: 200,
duration: 200,
onUpdate: function (tween)
{
var value = Math.floor(tween.getValue());
scene1.player.setTint(Phaser.Display.Color.GetColor(value, value, value));
}
});
this.time.delayedCall(300, function(){
scene1.shot = false;
scene1.player.setTint(0xffffff);
}, [], this);
}
scene1.handUp = false;
}
if (scene1.keySpace.isUp) {
scene1.handUp = true;
}
if (Phaser.Input.Keyboard.JustDown(scene1.q)) {
if (scene1.pause) {
this.physics.resume();
scene1.pause = false;
} else {
this.physics.pause();
scene1.pause = true;
}
}
//end of update
} //this curly bracket
//end of update
shootBomb(bullets, bombs) {
bombs.disableBody(true, true);
bullets.disableBody(true, true);
}
}
class Bullet extends Phaser.Physics.Arcade.Sprite {
constructor(scene1, x, y) {
super(scene1, x, y, 'bullet');
}
fire(x, y) {
this.body.reset(x, y);
this.setVelocityX(1000 * scene1.direction);
this.enableBody();
this.body.setAllowGravity(false);
this.setActive(true);
this.setVisible(true);
}
preUpdate(time, delta) {
super.preUpdate(time, delta);
// Reset the bullets when it reaches end of screen
if (this.x > 2600) {
this.setActive(false);
this.setVisible(false);
}
}
}
class Bullets extends Phaser.Physics.Arcade.Group {
constructor(scene) {
super(scene.physics.world, scene);
this.createMultiple({
frameQuantity: 20,
key: 'bullet',
active: false,
visible: false,
classType: Bullet
});
}
fireBullet(x, y) {
let bullet = this.getFirstDead(false);
if (bullet) {
bullet.fire(x, y);
}
}
}
var config = {
type: Phaser.GAME,
width: 800,
height: 600,
parent: 'gameDiv',
physics: {
default: 'arcade',
arcade: {
gravity: { y: 300 },
debug: false
}
},
scene: [scene1],
debug: true
};
When configuring the game you're asking it to load inside of an element with an id of gameDiv, by using parent on the game configuration.
var config = {
type: Phaser.GAME,
width: 800,
height: 600,
parent: 'gameDiv',
// ...
};
If the game isn't displaying at all, that suggests that it may not be able to find an element with the id of gameDiv.
Can you verify that an element (typically a div, so <div id="gameDiv"></div>) exists in your HTML file?

Clip object to path drawn using free drawing brush

Fabric.js 2.3.6
I'm trying to clip an object to a path drawn with the free drawing bush. The code fails to show the image inside the path and I'm not sure what I'm doing wrong.
Multiple objects could be clipped, so I can't apply the path to the canvas itself.
let image = new Image();
let object;
let canvas;
// canvas
canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function(options) {
clip(options.path);
});
// clip
function clip(path) {
object.set({
clipTo: function(ctx) {
path.render(ctx);
}
});
canvas.requestRenderAll();
}
// image
image.onload = function() {
object = new fabric.Image(image, {
width: 500,
height: 500,
top: 50,
left: 50
});
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
https://jsfiddle.net/o91rv38q/7/
In order to clip on the same spot when is path has been drawn, you need to reset pathOffset for a SVG path, and setTransform to the ctx. Your clip function will look like:
function clip(path) {
path.set({pathOffset: {x: 0, y: 0}});
object.set({
clipTo: function(ctx) {
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
ctx.beginPath();
path._renderPathCommands(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
_renderPathCommands - renders a path.
Updated fiddle
To clip multiple objects you'll need to have some sort of an array of objects and then combine then into single path:
function combinePaths (paths) {
if (!paths.length) {
return null;
}
let singlePath = paths[0].path;
for (let i = 1; i < paths.length; i++){
singlePath = [...singlePath, ...paths[i].path];
}
return new fabric.Path(singlePath, {
top: 0,
left: 0,
pathOffset: {
x: 0,
y: 0
}
});
}
Here is an example with multiple paths to clip:
// canvas
let canvas = new fabric.Canvas("canvas", {
backgroundColor: "lightgray",
width: 1280,
height: 720,
preserveObjectStacking: true,
selection: false
});
let paths = [];
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.color = "black";
canvas.freeDrawingBrush.width = 2;
canvas.on("path:created", function (options) {
paths.push(options.path);
clip(combinePaths(paths));
});
function combinePaths(paths) {
if (!paths.length) {
return null;
}
let singlePath = paths[0].path;
for (let i = 1; i < paths.length; i++) {
singlePath = [...singlePath, ...paths[i].path];
}
return new fabric.Path(singlePath, {
top: 0,
left: 0,
pathOffset: {
x: 0,
y: 0
}
});
}
function clip(path) {
if (!path) {
return;
}
object.set({
clipTo: function (ctx) {
var retina = this.canvas.getRetinaScaling();
ctx.save();
ctx.setTransform(retina, 0, 0, retina, 0, 0);
ctx.beginPath();
path._renderPathCommands(ctx);
ctx.restore();
}
});
canvas.requestRenderAll();
}
// image
let image = new Image();
let object;
image.onload = function () {
object = new fabric.Image(image, {
width: 500,
height: 500,
top: 50,
left: 50
});
object.globalCompositeOperation = 'source-atop';
canvas.add(object);
};
image.src = "http://i.imgur.com/8rmMZI3.jpg";
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.6/fabric.js"></script>
<canvas id="canvas" width="1280" height="720"></canvas>

Adding fabric.js Lines to group

How do I add lines to group in fabric.js? It seems as if each line top and left property is set to 0, and because of that final size of grid is 0 as well.
Code of adding to group:
let group = new fabric.Group();
for (let i = 0; i < canvas.getWidth() / gridSize; i++) {
let horizontalLine = new fabric.Line(
[i * gridSize, 0, i * gridSize, canvas.getWidth()]
);
let verticalLine = new fabric.Line(
[0, i * gridSize, canvas.getWidth(), i * gridSize]
);
group.add(horizontalLine, verticalLine);
}
canvas.add(group);
Self-explaining fiddler
That could be accomplished in the following way ...
var canvas = new fabric.Canvas('c');
let gridSize = 15;
$("#addAsGroup").click(() => {
canvas.clear();
let separateLines = [];
for (let i = 0; i < canvas.getWidth() / gridSize; i++) {
let horizontalLine = new fabric.Line(
[i * gridSize, 0, i * gridSize, canvas.getWidth()], {
stroke: '#000'
});
let verticalLine = new fabric.Line(
[0, i * gridSize, canvas.getWidth(), i * gridSize], {
stroke: '#000'
});
separateLines.push(horizontalLine);
separateLines.push(verticalLine);
}
// add lines to group
let group = new fabric.Group(separateLines);
canvas.add(group);
});
$("#addAsSeparateObjects").click(() => {
canvas.clear();
let separateLines = [];
for (let i = 0; i < canvas.getWidth() / gridSize; i++) {
let horizontalLine = new fabric.Line(
[i * gridSize, 0, i * gridSize, canvas.getWidth()], {
stroke: '#000'
});
let verticalLine = new fabric.Line(
[0, i * gridSize, canvas.getWidth(), i * gridSize], {
stroke: '#000'
});
separateLines.push(horizontalLine);
separateLines.push(verticalLine);
}
separateLines.forEach((line) => {
canvas.add(line);
})
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://rawgit.com/kangax/fabric.js/master/dist/fabric.js"></script>
<canvas id="c" width="300" height="300"></canvas>
<button id="addAsGroup">AddAsGroup</button>
<button id="addAsSeparateObjects">AddAsSeparateObjects</button>

Resources