Undo with eraser stroke issue - javascript

I am erasing the canvas drawing 1 time then Undo works fine, but when I erase more than 1 time then the Undo work as a line stroke, it works for the last step to erase but other are working as line stroke please see below code:
function Sketchpad(config) {
// Enforces the context for all functions
for (var key in this.constructor.prototype) {
this[key] = this[key].bind(this);
}
// Warn the user if no DOM element was selected
if (!config.hasOwnProperty('element')) {
console.error('SKETCHPAD ERROR: No element selected');
return;
}
this.element = config.element;
// Width can be defined on the HTML or programatically
this._width = config.width || $(this.element).attr('data-width') || 0;
this._height = config.height || $(this.element).attr('data-height') || 0;
// Pen attributes
this.color = config.color || $(this.element).attr('data-color') || '#000000';
this.penSize = config.penSize || $(this.element).attr('data-penSize') || 5;
// ReadOnly sketchpads may not be modified
this.readOnly = config.readOnly ||
$(this.element).attr('data-readOnly') ||
false;
if (!this.readOnly) {
$(this.element).css({cursor: 'crosshair'});
}
// Stroke control variables
this.strokes = config.strokes || [];
console.log(this.strokes)
this._currentStroke = {
color: null,
size: null,
lines: [],
};
// Undo History
this.undoHistory = (!bErasing) ? (config.undoHistory || []) : [];
// Animation function calls
this.animateIds = [];
// Set sketching state
this._sketching = false;
// Setup canvas sketching listeners
this.reset();
}
//
// Private API
//
Sketchpad.prototype._cursorPosition = function(event) {
return {
x: event.pageX - $(this.canvas).offset().left,
y: event.pageY - $(this.canvas).offset().top,
};
};
Sketchpad.prototype._draw = function(start, end, color, size) {
this._stroke(start, end, color, size, 'source-over');
};
Sketchpad.prototype._erase = function(start, end, color, size) {
this._stroke(start, end, color, size, 'destination-out');
};
Sketchpad.prototype._stroke = function(start, end, color, size) {
this.context.save();
if(bErasing){
this.context.globalCompositeOperation = "destination-out";
}else{
this.context.globalCompositeOperation = "source-over";
}
// if(size === 7 || size === 13){
// console.log(size)
// this.context.lineJoin = 'square';
// this.context.lineCap = 'square';
// }
// else{
this.context.lineJoin = 'round';
this.context.lineCap = 'round';
// }
this.context.strokeStyle = color;
this.context.lineWidth = size;
// switch(size){
// case 2:
// this.context.globalAlpha = 1;
// break;
// case 7:
// this.context.globalAlpha = 0.7;
// break;
// case 13:
// this.context.globalAlpha = 0.4;
// break;
// default:
// this.context.globalAlpha = 0.2;
// }
this.context.beginPath();
this.context.moveTo(start.x, start.y);
this.context.lineTo(end.x, end.y);
this.context.closePath();
this.context.stroke();
this.context.restore();
};
//
// Callback Handlers
//
Sketchpad.prototype._mouseDown = function(event) {
this._lastPosition = this._cursorPosition(event);
this._currentStroke.color = this.color;
this._currentStroke.size = this.penSize;
this._currentStroke.lines = [];
// console.log(this._currentStroke.lines)
this._sketching = true;
this.canvas.addEventListener('mousemove', this._mouseMove);
};
Sketchpad.prototype._mouseUp = function(event) {
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('mousemove', this._mouseMove);
};
Sketchpad.prototype._mouseMove = function(event) {
var currentPosition = this._cursorPosition(event);
this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
this._currentStroke.lines.push({
start: $.extend(true, {}, this._lastPosition),
end: $.extend(true, {}, currentPosition),
});
this._lastPosition = currentPosition;
};
Sketchpad.prototype._touchStart = function(event) {
event.preventDefault();
if (this._sketching) {
return;
}
this._lastPosition = this._cursorPosition(event.changedTouches[0]);
this._currentStroke.color = this.color;
this._currentStroke.size = this.penSize;
this._currentStroke.lines = [];
this._sketching = true;
this.canvas.addEventListener('touchmove', this._touchMove, false);
};
Sketchpad.prototype._touchEnd = function(event) {
event.preventDefault();
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('touchmove', this._touchMove);
};
Sketchpad.prototype._touchCancel = function(event) {
event.preventDefault();
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('touchmove', this._touchMove);
};
Sketchpad.prototype._touchLeave = function(event) {
event.preventDefault();
if (this._sketching) {
this.strokes.push($.extend(true, {}, this._currentStroke));
this._sketching = false;
}
this.canvas.removeEventListener('touchmove', this._touchMove);
};
Sketchpad.prototype._touchMove = function(event) {
event.preventDefault();
var currentPosition = this._cursorPosition(event.changedTouches[0]);
this._draw(this._lastPosition, currentPosition, this.color, this.penSize);
this._currentStroke.lines.push({
start: $.extend(true, {}, this._lastPosition),
end: $.extend(true, {}, currentPosition),
});
this._lastPosition = currentPosition;
};
//
// Public API
//
Sketchpad.prototype.reset = function() {
// Set attributes
this.canvas = $(this.element)[0];
this.canvas.width = this._width;
this.canvas.height = this._height;
this.context = this.canvas.getContext('2d');
// Setup event listeners
this.redraw(this.strokes);
if (this.readOnly) {
return;
}
// Mouse
this.canvas.addEventListener('mousedown', this._mouseDown);
this.canvas.addEventListener('mouseout', this._mouseUp);
this.canvas.addEventListener('mouseup', this._mouseUp);
// Touch
this.canvas.addEventListener('touchstart', this._touchStart);
this.canvas.addEventListener('touchend', this._touchEnd);
this.canvas.addEventListener('touchcancel', this._touchCancel);
this.canvas.addEventListener('touchleave', this._touchLeave);
};
Sketchpad.prototype.drawStroke = function(stroke) {
for (var j = 0; j < stroke.lines.length; j++) {
var line = stroke.lines[j];
this._draw(line.start, line.end, stroke.color, stroke.size);
}
};
Sketchpad.prototype.erase = function() {
// this._erase(line.start, line.end, stroke.color, stroke.size);
};
Sketchpad.prototype.redraw = function(strokes) {
for (var i = 0; i < strokes.length; i++) {
this.drawStroke(strokes[i]);
}
};
Sketchpad.prototype.toObject = function() {
return {
width: this.canvas.width,
height: this.canvas.height,
strokes: this.strokes,
undoHistory: this.undoHistory,
};
};
Sketchpad.prototype.toJSON = function() {
return JSON.stringify(this.toObject());
};
Sketchpad.prototype.animate = function(ms, loop, loopDelay) {
this.clear();
var delay = ms;
var callback = null;
for (var i = 0; i < this.strokes.length; i++) {
var stroke = this.strokes[i];
for (var j = 0; j < stroke.lines.length; j++) {
var line = stroke.lines[j];
callback = this._draw.bind(this, line.start, line.end,
stroke.color, stroke.size);
this.animateIds.push(setTimeout(callback, delay));
delay += ms;
}
}
if (loop) {
loopDelay = loopDelay || 0;
callback = this.animate.bind(this, ms, loop, loopDelay);
this.animateIds.push(setTimeout(callback, delay + loopDelay));
}
};
Sketchpad.prototype.cancelAnimation = function() {
for (var i = 0; i < this.animateIds.length; i++) {
clearTimeout(this.animateIds[i]);
}
};
Sketchpad.prototype.clear = function() {
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
};
Sketchpad.prototype.undo = function() {
this.clear();
var stroke = this.strokes.pop();
if (stroke) {
this.undoHistory.push(stroke);
this.redraw(this.strokes);
}
};
Sketchpad.prototype.redo = function() {
var stroke = this.undoHistory.pop();
if (stroke) {
this.strokes.push(stroke);
this.drawStroke(stroke);
}
};
and also having issue while trying the change the texture of brushes according to the pen size.
eraser is working fine and undo is also working fine the only issue is while we erase more than once, then try to undo then last erase is return to real drawing, but other previous works like as the line stroke and display the drawing as per eraser mouse move in canvas.
This is my my function to call above methods:
var x = "#CB7342",
y = 2;
var bErasing = false;
var canvas, canvasWidth, canvasHeight;
var ctx;
var lastPt=null;
var pathsry = [];
var points = [];
var state;
var colour;
// past states
function ySize(size) {
y = size;
sketchpad.penSize = y;
}
var sketchpad;
function undo(){
bErasing = false;
sketchpad.undo();
if($('#erase').hasClass('active')){
bErasing = true;
}
}
function init() {
canvas = document.getElementById('sheet');
ctx = canvas.getContext('2d');
var canvasOffset = $('#sheet').offset();
var parent = canvas.parentNode;
canvas.width = parent.clientWidth;
canvas.height = parent.clientHeight;
// alert(bErasing)
sketchpad = new Sketchpad({
element: '#sheet',
width: canvas.width,
height: canvas.height,
color: null,
penSize: 2,
globalAlpha: 0.1
});
}
$(document).on('click', "#erase", function () {
bErasing = true;
});

Related

Hover and onclick functionality issue in jquery

I want to have onclick and hover functionality simultaneously but if you have clicked somewhere then hover should not work until I click somewhere else. I have tried alot but I didn't find any working code. Kindly help
canvas.addEventListener('mousedown', function(evt) {
}, false);
canvas.onmousemove = function(evt) {
};
Well, I am not quite sure what you need and why you need it. But, in that short piece of code you wrote I saw the word "canvas" and thought "what the heck, that could be fun!". I have not much experience with the canvas element since earlier, so I realize there may be better ways of writing this code.
But, I hope what I wrote in the below example is at least close to what you were looking for. Otherwise, go nuts and change and adapt the way you like... and while you do that, try to learn something of it.
var Canvas = function() {
this.$canvas = $('canvas');
this.$currPos = $('#currPos');
this.$currClick = $('#currClick');
this.$clickInfo = $('#clickInfo');
this.canvsWidth = 150;
this.cavasHeight = 150;
this.ctx = ctx = this.$canvas[0].getContext('2d');
this.rect = this.$canvas[0].getBoundingClientRect();
this.squares = [];
this.sqm = 50;
this.tracker = 0;
this.latestHover = {};
this._events();
this._prepare();
};
Canvas.prototype._events = function() {
var self = this;
this.$canvas.on('mousemove', function(e) {
var posX = e.clientX - self.rect.left,
posY = e.clientY - self.rect.top,
newX = Math.floor(posX / self.sqm),
newY = Math.floor(posY / self.sqm);
if($.isEmptyObject(self.latestHover) || (self.latestHover.x !== newX || self.latestHover.y !== newY)) {
self.latestHover.x = newX;
self.latestHover.y = newY;
self.squares.map(function(k, v) {
let obj = self.squares[v];
if(!obj.fixedBackground) obj.reverseBackgroundColor();
});
var square = self.findObject(newX, newY)[0];
if(square) {
square.setBackgroundColor('#ff0000');
self.$currPos.html(newX +'x'+ newY);
self._redraw();
}
}
});
this.$canvas.on('click', function() {
if(self.tracker === 2) {
return self._reset();
}
if(!($.isEmptyObject(self.latestHover))) {
var x = self.latestHover.x,
y = self.latestHover.y;
var square = self.findObject(x, y)[0];
square.setFixedBackground();
self.$currClick.html(x +'x'+ y);
self.setTracker();
}
});
};
Canvas.prototype._prepare = function() {
for(var row = 0; row < 3; row++) {
for(var col = 0; col < 3; col++) {
this.squares.push(new Square(row, col, this.ctx, this.sqm));
}
}
};
Canvas.prototype._redraw = function() {
var self = this;
this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
this.squares.filter(function(k, v) {
self.squares[v].draw();
});
};
Canvas.prototype.setTracker = function() {
this.tracker++;
if(this.tracker === 2) this.$clickInfo.html('Click one more time to start over');
};
Canvas.prototype.findObject = function(x, y) {
var self = this;
return square = self.squares.filter(function(k, v) {
var obj = self.squares[v];
if(obj.posX === x && obj.posY === y) return obj;
});
};
Canvas.prototype._reset = function() {
var self = this;
this.squares.map(function(k, v) {
let obj = self.squares[v];
obj.reverseBackgroundColor();
obj.unsetFixedBackground();
});
this.$currClick.html('');
this.$clickInfo.html('');
this.tracker = 0;
this._redraw();
};
var Square = function(x, y, ctx, sqm) {
this.ctx = ctx;
this.sqm = sqm;
this.posX = x;
this.posY = y;
this.background = '#fff';
this.strokeThickness = 1;
this.fixedBackground = false;
this.draw();
};
Square.prototype.setBackgroundColor = function(color) {
return this.background = color;
};
Square.prototype.reverseBackgroundColor = function() {
return this.background = '#fff';
};
Square.prototype.setFixedBackground = function() {
return this.fixedBackground = true;
};
Square.prototype.unsetFixedBackground = function() {
return this.fixedBackground = false;
};
Square.prototype.draw = function() {
this.ctx.fillStyle = this.background;
this.ctx.fillRect(this.posX * this.sqm, this.posY * this.sqm, this.sqm, this.sqm);
this.ctx.strokeRect(this.posX * this.sqm, this.posY * this.sqm, this.sqm, this.sqm);
};
window.Canvas = new Canvas();
canvas {
border: 1px solid #ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas width="150" height="150"></canvas>
<div>
Current position: <span id="currPos"></span> <br/>
Last click: <span id="currClick"></span> <span id="clickInfo"></span>
</div>

HTML Canvas & Javascript - Triggering Audio by Selection (From Multiple Places)

I have a selection menu in my HTML canvas that I would like to trigger corresponding audio files. I have tried implementing this by declaring the images inside the if (this.hovered) & (this.clicked) part of the makeSelection function within the selectionForMenu prototype, such that on each new selection the selected audio file is redefined, but this causes problems like slow loading and overlapping audio. It is also problematic as I am trying to get the speaker button at the bottom of the screen to play the audio corresponding to the current selection too, so if it is only defined within that function it is not accessible to the makeButton function.
You can see the selection menu and speaker button in the snippet below. Each new selection in the menu should play once an audio file that corresponds to it (which I have not been able to add to this demonstration). It can be replayed by re-clicking the selection or clicking the speaker button, but each click should only provoke one play of the audio and of course overlapping is undesired. Any help will be appreciated.
var c=document.getElementById('game'),
canvasX=c.offsetLeft,
canvasY=c.offsetTop,
ctx=c.getContext('2d');
var button = function(id, x, strokeColor) {
this.id = id;
this.x = x;
this.strokeColor = strokeColor;
this.hovered = false;
this.clicked = false;
}
button.prototype.makeInteractiveButton = function() {
if (this.hovered) {
if (this.clicked) {
this.fillColor = '#DFBCDE';
} else {
this.fillColor = '#CA92C8'
}
} else {
this.fillColor = '#BC77BA'
}
ctx.strokeStyle=this.strokeColor;
ctx.fillStyle=this.fillColor;
ctx.beginPath();
ctx.lineWidth='5';
ctx.arc(this.x, 475, 20, 0, 2*Math.PI);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
button.prototype.hitTest = function(x, y) {
return (Math.pow(x-this.x, 2) + Math.pow(y-475, 2) < Math.pow(20, 2));
}
var selectionForMenu = function(id, text, y) {
this.id = id;
this.text = text;
this.y = y;
this.hovered = false;
this.clicked = false;
this.lastClicked = false;
}
selectionForMenu.prototype.makeSelection = function() {
var fillColor='#A84FA5';
if (this.hovered) {
if (this.clicked) {
if (this.lastClicked) {
fillColor='#E4C7E2';
} else {
fillColor='#D5A9D3';
}
} else if (this.lastClicked) {
fillColor='#D3A4D0';
} else {
fillColor='#BA74B7';
}
} else if (this.lastClicked) {
fillColor='#C78DC5';
} else {
fillColor='#A84FA5';
}
ctx.beginPath();
ctx.fillStyle=fillColor;
ctx.fillRect(0, this.y, 350, 30)
ctx.stroke();
ctx.font='10px Noto Sans';
ctx.fillStyle='white';
ctx.textAlign='left';
ctx.fillText(this.text, 10, this.y+19);
}
selectionForMenu.prototype.hitTest = function(x, y) {
return (x >= 0) && (x <= (350)) && (y >= this.y) && (y <= (this.y+30)) && !((x >= 0) && (y > 450));
}
var Paint = function(element) {
this.element = element;
this.shapes = [];
}
Paint.prototype.addShape = function(shape) {
this.shapes.push(shape);
}
Paint.prototype.render = function() {
ctx.clearRect(0, 0, this.element.width, this.element.height);
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeSelection();
}
catch(err) {}
}
ctx.beginPath();
ctx.fillStyle='#BC77BA';
ctx.fillRect(0, 450, 750, 50);
ctx.stroke();
for (var i=0; i<this.shapes.length; i++) {
try {
this.shapes[i].makeInteractiveButton();
}
catch(err) {}
}
var speaker = new Image(25, 25);
speaker.src='https://i.stack.imgur.com/lXg2I.png';
ctx.drawImage(speaker, 162.5, 462.5);
}
Paint.prototype.setHovered = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].hovered = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setClicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = this.shapes[i] == shape;
}
this.render();
}
Paint.prototype.setUnclicked = function(shape) {
for (var i=0; i<this.shapes.length; i++) {
this.shapes[i].clicked = false;
if (Number.isInteger(this.shapes[i].id)) {
this.shapes[i].lastClicked = this.shapes[i] == shape;
}
}
this.render();
}
Paint.prototype.select = function(x, y) {
for (var i=this.shapes.length-1; i >= 0; i--) {
if (this.shapes[i].hitTest(x, y)) {
return this.shapes[i];
}
}
return null
}
var paint = new Paint(c);
var btn = new button('speaker', 175, '#FFFCF8');
var selection = [];
for (i=0; i<15; i++) {
selection.push(new selectionForMenu(i+1, i, i*30));
}
paint.addShape(btn);
for (i=0; i<15; i++) {
paint.addShape(selection[i])
}
paint.render();
function mouseDown(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setClicked(shape);
}
function mouseUp(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setUnclicked(shape);
}
function mouseMove(event) {
var x = event.x - canvasX;
var y = event.y - canvasY;
var shape = paint.select(x, y);
paint.setHovered(shape);
}
c.addEventListener('mousedown', mouseDown);
c.addEventListener('mouseup', mouseUp);
c.addEventListener('mousemove', mouseMove);
canvas {
z-index: -1;
margin: 1em auto;
border: 1px solid black;
display: block;
background: #9F3A9B;
}
img {
z-index: 0;
position: absolute;
pointer-events: none;
}
#speaker {
top: 480px;
left: 592px;
}
#snail {
top: 475px;
left: 637.5px;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>uTalk Demo</title>
<link rel='stylesheet' type='text/css' href='wordpractice.css' media='screen'></style>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css">
</head>
<body>
<canvas id="game" width = "350" height = "500"></canvas>
<script type='text/javascript' src='wordpractice copy.js'></script>
</body>
</html>
When you want responsiveness with audio, forget about MediaElements, and go with the Web Audio API.
MediaElements (<audio> and <video>) are slow, and http caching is an nightmare.
With the Web Audio API, you can first download all you media as arrayBuffers, decode their audio data to AudioBuffers, that you'll attach to your js objects.
From there, you'll be able to play new instances of these media in µs.
Beware, ES6 syntax below, for older browsers, here is an ES5 rewrite, also note that Internet Explorer < Edge does not support the Web Audio API, if you need to support these browsers, you'll have to make an fallback with audio elements.
(function myFirstDrumKit() {
const db_url = 'https://dl.dropboxusercontent.com/s/'; // all our medias are stored on dropbox
// we'll need to first load all the audios
function initAudios() {
const promises = drum.parts.map(part => {
return fetch(db_url + part.audio_src) // fetch the file
.then(resp => resp.arrayBuffer()) // as an arrayBuffer
.then(buf => drum.a_ctx.decodeAudioData(buf)) // then decode its audio data
.then(AudioBuf => {
part.buf = AudioBuf; // store the audioBuffer (won't change)
return Promise.resolve(part); // done
});
});
return Promise.all(promises); // when all are loaded
}
function initImages() {
// in this version we have only an static image,
// but we could have multiple per parts, with the same logic as for audios
var img = new Image();
img.src = db_url + drum.bg_src;
drum.bg = img;
return new Promise((res, rej) => {
img.onload = res;
img.onerror = rej;
});
}
let general_solo = false;
let part_solo = false;
const drum = {
a_ctx: new AudioContext(),
generate_sound: (part) => {
// called each time we need to play a source
const source = drum.a_ctx.createBufferSource();
source.buffer = part.buf;
source.connect(drum.gain);
// to keep only one playing at a time
// simply store this sourceNode, and stop the previous one
if(general_solo){
// stop all playing sources
drum.parts.forEach(p => (p.source && p.source.stop(0)));
}
else if (part_solo && part.source) {
// stop only the one of this part
part.source.stop(0);
}
// store the source
part.source = source;
source.start(0);
},
parts: [{
name: 'hihat',
x: 90,
y: 116,
w: 160,
h: 70,
audio_src: 'kbgd2jm7ezk3u3x/hihat.mp3'
},
{
name: 'snare',
x: 79,
y: 192,
w: 113,
h: 58,
audio_src: 'h2j6vm17r07jf03/snare.mp3'
},
{
name: 'kick',
x: 80,
y: 250,
w: 200,
h: 230,
audio_src: '1cdwpm3gca9mlo0/kick.mp3'
},
{
name: 'tom',
x: 290,
y: 210,
w: 110,
h: 80,
audio_src: 'h8pvqqol3ovyle8/tom.mp3'
}
],
bg_src: '0jkaeoxls18n3y5/_drumkit.jpg?dl=0',
};
drum.gain = drum.a_ctx.createGain();
drum.gain.gain.value = .5;
drum.gain.connect(drum.a_ctx.destination);
function initCanvas() {
const c = drum.canvas = document.createElement('canvas');
const ctx = drum.ctx = c.getContext('2d');
c.width = drum.bg.width;
c.height = drum.bg.height;
ctx.drawImage(drum.bg, 0, 0);
document.body.appendChild(c);
addEvents(c);
}
const isHover = (x, y) =>
(drum.parts.filter(p => (p.x < x && p.x + p.w > x && p.y < y && p.y + p.h > y))[0] || false);
function addEvents(canvas) {
let mouse_hovered = false;
canvas.addEventListener('mousemove', e => {
mouse_hovered = isHover(e.pageX - canvas.offsetLeft, e.pageY - canvas.offsetTop)
if (mouse_hovered) {
canvas.style.cursor = 'pointer';
} else {
canvas.style.cursor = 'default';
}
})
canvas.addEventListener('mousedown', e => {
e.preventDefault();
if (mouse_hovered) {
drum.generate_sound(mouse_hovered);
}
});
const checkboxes = document.querySelectorAll('input');
checkboxes[0].onchange = function() {
general_solo = this.checked;
general_solo && (checkboxes[1].checked = part_solo = true);
};
checkboxes[1].onchange = function() {
part_solo = this.checked;
!part_solo && (checkboxes[0].checked = general_solo = false);
};
}
Promise.all([initAudios(), initImages()])
.then(initCanvas);
})()
/*
Audio Samples are from https://sampleswap.org/filebrowser-new.php?d=DRUMS+%28FULL+KITS%29%2FSpasm+Kit%2F
Original image is from http://truimg.toysrus.co.uk/product/images/UK/0023095_CF0001.jpg?resize=500:500
*/
<label>general solo<input type="checkbox"></label><br>
<label>part solo<input type="checkbox"></label><br>
You could create an Audio Loader, that loads all the audios and keeps track of them:
function load(srcs){
var obj={};
srcs.forEach(src=>obj[src]=new Audio(src));
return obj;
}
Then you could do sth like this onload:
var audios=load(["audio1.mp3", "audio2.mp3"]);
And later:
(audios[src] || new Audio(src)).play();
This will just load the audio if it isnt already in the audios object.

Create Eraser in EaselJs - what next

I want to write Bitmap eraser on easelJS. I think, i'm on half of my road to do this....
Here is demo:
http://jsfiddle.net/bordeux/g2Lwvsuv/2/
and my code:
var example = function() {
this.constructor.apply(this, arguments);
};
example.prototype.constructor = function() {
this.$canvas = $("canvas");
this.container = {};
this.objects = {};
this.resizeEvents = [];
this.prepareCanvas();
this.refreshSize();
this.bindEvents();
};
example.prototype.bindEvents = function() {
var self = this;
$(window).resize(function(){
self.refreshSize();
});
};
example.prototype.refreshSize = function() {
this.stage.canvas.width = window.innerWidth;
this.stage.canvas.height = window.innerHeight;
this.resizeEvents.forEach(function(item) {
item();
});
};
example.prototype.getObject = function(name) {
return this.objects[name];
};
example.prototype.setObject = function(name, obj) {
this.objects[name] = obj;
return obj;
};
example.prototype.addResizeEvent = function(foo) {
this.resizeEvents.push(foo);
return this;
};
example.prototype.initCursor = function() {
var self = this;
var size = 50;
/**
* Circle cursor
*/
var circle = new createjs.Shape();
circle.graphics.setStrokeStyle(2).beginStroke("#171717").drawCircle(0, 0, size);
/**
* Hit area
*/
var hit = new createjs.Shape();
hit.graphics.beginFill("#000").drawCircle(0, 0, size);
circle.hitArea = hit;
var container = this.getContainer("cursor");
container.addChild(circle);
this.stage.on("stagemousemove", function(evt) {
circle.setTransform(evt.stageX, evt.stageY);
this.setChildIndex( container, this.getNumChildren()-1);
});
var drawing = this.setObject("image.mask", new createjs.Shape());
drawing.visible = false;
var lastPoint = new createjs.Point();
container.addChild(drawing);
circle.on("mousedown", function(event) {
lastPoint.x = event.stageX;
lastPoint.y = event.stageY;
});
circle.on("pressmove", function(event) {
drawing.graphics
.setStrokeStyle(100, "round", "round")
.beginStroke("rgba(255, 255, 255, 0.2)")
.beginRadialGradientFill(
["rgba(0,0,0,0)", "rgba(0,0,0,1)"],
[0.1, 1],
50, 50, 0,
50, 50, 50
)
.moveTo(lastPoint.x, lastPoint.y)
.lt(event.stageX, event.stageY);
lastPoint.x = event.stageX;
lastPoint.y = event.stageY;
self.updateCache();
});
circle.on("pressup", function(event) {
self.updateCache();
});
};
example.prototype.updateCache = function(update) {
(update === undefined) && (update = false);
var drawingCanvas = this.getObject("image.mask");
var image = this.getObject("image");
if (update) {
drawingCanvas.updateCache();
} else {
drawingCanvas.cache(0, 0, image.image.naturalWidth, image.image.naturalHeight);
}
image.filters = [
new createjs.AlphaMaskFilter(drawingCanvas.cacheCanvas)
];
if (update) {
image.updateCache(0, 0, image.image.naturalWidth, image.image.naturalHeight);
} else {
image.cache(0, 0, image.image.naturalWidth, image.image.naturalHeight);
}
};
example.prototype.prepareCanvas = function() {
this.stage = new createjs.Stage(this.$canvas[0]);
createjs.Ticker.setFPS(60);
createjs.Ticker.addEventListener("tick", this.stage);
this.initCursor();
this.loadImage();
};
/**
* Get container. If not exist, this function create new one
* #param {String} name
* #returns {createjs.Container}
*/
example.prototype.getContainer = function(name) {
if(this.container[name]){
return this.container[name];
}
this.container[name] = new createjs.Container();
this.stage.addChild(this.container[name]);
return this.container[name];
};
example.prototype.loadImage = function() {
var self = this;
var url = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/aa/Logo_Google_2013_Official.svg/1280px-Logo_Google_2013_Official.svg.png";
var background = this.setObject("image.background", new createjs.Shape());
this.getContainer("image").addChild(background);
var image = null;
return utils.image(url).then(function(img){
image = img;
self.getContainer("image").addChild(self.setObject(
"image",
new createjs.Bitmap(img)
));
self.updateCache(false);
return utils.image("http://i.imgur.com/JKaeYwv.png");
}).then(function(imgBackground){
background
.graphics
.beginBitmapFill(imgBackground,'repeat')
.drawRect(0,0, image.naturalWidth, image.naturalHeight);
});
};
utils = {};
utils.image = function(url){
var deferred = Q.defer();
//deferred.resolve(text);
var img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function(){
deferred.resolve(this);
};
img.onerror = function(){
deferred.reject(arguments);
};
img.src = url;
return deferred.promise;
};
$(function(){
var test = new example();
});
But when i draw something on my canvas, my path of draw showing image behind mask... This is opposed result what i want.
I want remove part of image (like eraser in Photoshop). I don't know what i should do next...
Thanks
You can use compositeOperation in conjunction with layers or caching to "erase" content. For example, if you use updateCache("source-over"), that will draw the current visual content over the existing cache (normal compositing). However, if you use updateCache("destination-out"), that will essentially punch out the current content from the existing cache, which gives you an eraser effect.
Here's a simple example of this in action: http://jsfiddle.net/17xec9y5/4

HTML5 Canvas issue

Is there a way to stop users on dragging the mouse down so they only can click instead of click and drag. I want htem to be only to draw dots on the canvas or any pattern i define similar to a stamp tool.
function Sketcher( canvasID, brushImage ) {
this.renderFunction = (brushImage == null || brushImage == undefined) ? this.updateCanvasByLine : this.updateCanvasByBrush;
this.brush = brushImage;
this.touchSupported = Modernizr.touch;
this.canvasID = canvasID;
this.canvas = $("#"+canvasID);
this.context = this.canvas.get(0).getContext("2d");
this.context.strokeStyle = "#000000";
this.context.lineWidth = 3;
this.lastMousePoint = {x:0, y:0};
if (this.touchSupported) {
this.mouseDownEvent = "touchstart";
this.mouseMoveEvent = "touchmove";
this.mouseUpEvent = "touchend";
}
else {
this.mouseDownEvent = "mousedown";
this.mouseMoveEvent = "mousemove";
this.mouseUpEvent = "mouseup";
}
this.canvas.bind( this.mouseDownEvent, this.onCanvasMouseDown() );
}
Sketcher.prototype.onCanvasMouseDown = function () {
var self = this;
return function(event) {
self.mouseMoveHandler = self.onCanvasMouseMove()
self.mouseUpHandler = self.onCanvasMouseUp()
$(document).bind( self.mouseMoveEvent, self.mouseMoveHandler );
$(document).bind( self.mouseUpEvent, self.mouseUpHandler );
self.updateMousePosition( event );
self.renderFunction( event );
}
}
Sketcher.prototype.onCanvasMouseMove = function () {
var self = this;
return function(event) {
self.renderFunction( event );
event.preventDefault();
return false;
}
}
Sketcher.prototype.onCanvasMouseUp = function (event) {
var self = this;
return function(event) {
$(document).unbind( self.mouseMoveEvent, self.mouseMoveHandler );
$(document).unbind( self.mouseUpEvent, self.mouseUpHandler );
self.mouseMoveHandler = null;
self.mouseUpHandler = null;
}
}
Sketcher.prototype.updateMousePosition = function (event) {
var target;
if (this.touchSupported) {
target = event.originalEvent.touches[0]
}
else {
target = event;
}
var offset = this.canvas.offset();
this.lastMousePoint.x = target.pageX - offset.left;
this.lastMousePoint.y = target.pageY - offset.top;
}
Sketcher.prototype.updateCanvasByLine = function (event) {
this.context.beginPath();
this.context.moveTo( this.lastMousePoint.x, this.lastMousePoint.y );
this.updateMousePosition( event );
this.context.lineTo( this.lastMousePoint.x, this.lastMousePoint.y );
this.context.stroke();
}
Sketcher.prototype.updateCanvasByBrush = function (event) {
var halfBrushW = this.brush.width/2;
var halfBrushH = this.brush.height/2;
var start = { x:this.lastMousePoint.x, y: this.lastMousePoint.y };
this.updateMousePosition( event );
var end = { x:this.lastMousePoint.x, y: this.lastMousePoint.y };
var distance = parseInt( Trig.distanceBetween2Points( start, end ) );
var angle = Trig.angleBetween2Points( start, end );
var x,y;
for ( var z=0; (z<=distance || z==0); z++ )
{
x = start.x + (Math.sin(angle) * z) - halfBrushW;
y = start.y + (Math.cos(angle) * z) - halfBrushH;
//console.log( x, y, angle, z );
this.context.drawImage(this.brush, x, y);
}
}
Sketcher.prototype.toString = function () {
var dataString = this.canvas.get(0).toDataURL("image/png");
var index = dataString.indexOf( "," )+1;
dataString = dataString.substring( index );
return dataString;
}
Sketcher.prototype.toDataURL = function () {
var dataString = this.canvas.get(0).toDataURL("image/png");
return dataString;
}
Sketcher.prototype.clear = function () {
var c = this.canvas[0];
this.context.clearRect( 0, 0, c.width, c.height );
}
You can achieve this by simply ignoring the mousemove and mouseup event handlers by either not initializing them or using flags to indicate that you are in stamp mode.
All you need is:
canvas.onmousedown = function(e) {
var pos = getMousePos(e);
render(pos.x, pos.y);
}
That's it, and it works similar for touch events.
To use flag (I would not recommend adding and removing handlers all the time):
var isStampMode = true;
canvas.onmousemove = function(e) {
if (isStampMode) return;
...
}
and similar for mouse up (but not really necessary unless it depends on what you do in mousedown).
Live example here
Hope this helps!

OpenLayers: Rotate image with ModifyFeature as Polygon or other

I need to allow user to rotate bitmap features on map with OpenLayers.Control.ModifyFeature or another way as it work for Polygons or other geometry objects, except Point, but only for Point I can set "externalGraphic" with my bitmap. Example of ModifyFeature to rotation as I expected here: ModifyFeature example
When I add Vector with Point geometry and activate ModifyFeature there is no rotation tool showing - only drag-drop. I know what is a point, but I need to have tool for rotate bitmap features. It may be image on any another geometry object, but with custom image.
After long research I found an example in gis.stackexchange.com, and fix it.
Here is a code which works for me:
OpenLayers.Control.RotateGraphicFeature = OpenLayers.Class(OpenLayers.Control.ModifyFeature, {
rotateHandleStyle: null,
initialize: function(layer, options) {
OpenLayers.Control.ModifyFeature.prototype.initialize.apply(this, arguments);
this.mode = OpenLayers.Control.ModifyFeature.ROTATE; // This control can only be used to rotate the feature
this.geometryTypes = ['OpenLayers.Geometry.Point'] // This control can only be used to rotate point because the 'exteralGraphic' is a point style property
var init_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style.select);
this.rotateHandleStyle = OpenLayers.Util.extend(init_style, {
externalGraphic: "./static/resources/images/cross.png",
graphicWidth: 12,
graphicHeight: 12,
fillOpacity: 1
});
},
resetVertices: function() {
// You need to set yours renderIntent or use "vertex"
if (this.feature && this.feature.renderIntent == "vertex") {
var vertex = this.feature;
this.feature = this.backup_feature;
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.collectRadiusHandle();
return;
}
if (this.dragControl.feature) {
this.dragControl.outFeature(this.dragControl.feature);
}
if (this.vertices.length > 0) {
this.layer.removeFeatures(this.vertices, {silent: true});
this.vertices = [];
}
if (this.virtualVertices.length > 0) {
this.layer.removeFeatures(this.virtualVertices, {silent: true});
this.virtualVertices = [];
}
if (this.dragHandle) {
this.layer.destroyFeatures([this.dragHandle], {silent: true});
this.dragHandle = null;
}
if (this.radiusHandle) {
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.radiusHandle = null;
}
if (this.feature && this.feature.geometry &&
this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
if ((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
this.collectDragHandle();
}
if ((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
OpenLayers.Control.ModifyFeature.RESIZE))) {
this.collectRadiusHandle();
}
if (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE) {
if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)) {
this.collectVertices();
}
}
}
this.collectRadiusHandle();
},
collectRadiusHandle: function() {
var scope = this,
feature = this.feature,
geometry = this.feature.geometry || this.backup_feature.geometry,
centroid = geometry.getCentroid().transform(this.WGS84_google_mercator, this.WGS84),
lon = centroid.x, lat = centroid.y;
if (this.feature.geometry) {
this.backup_feature = this.feature;
} else {
this.feature.geometry = this.backup_feature.geometry;
}
var originGeometry = new OpenLayers.Geometry.Point(lon, lat);
// radius geometry position.
var pixel_dis_x = 10,
pixel_dis_y = -10;
var rotationFeatureGeometry = new OpenLayers.Geometry.Point(lon+pixel_dis_x, lat+pixel_dis_y);
var rotationFeature = new OpenLayers.Feature.Vector(rotationFeatureGeometry, null, this.rotateHandleStyle);
var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
rotationFeatureGeometry.move = function(x, y) {
OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
var dx1 = this.x - originGeometry.x;
var dy1 = this.y - originGeometry.y;
var dx0 = dx1 - x;
var dy0 = dy1 - y;
if (rotate) {
var a0 = Math.atan2(dy0, dx0);
var a1 = Math.atan2(dy1, dx1);
var angle = a1 - a0;
angle *= 180 / Math.PI;
var old_angle = feature.attributes.angle;
var new_angle = old_angle - angle;
feature.attributes.angle = new_angle;
// redraw the feature
scope.feature.layer.redraw.call(scope.feature.layer);
}
};
rotationFeature._sketch = true;
this.radiusHandle = rotationFeature;
this.radiusHandle.renderIntent = this.vertexRenderIntent;
this.layer.addFeatures([this.radiusHandle], {silent: true});
},
CLASS_NAME: "OpenLayers.Control.RotateGraphicFeature"
});
Style of your Vector may be as:
new OpenLayers.StyleMap({
"default": new OpenLayers.Style({
externalGraphic: "link/to/icon",
graphicHeight: "32px",
graphicWidth: "25px",
fillOpacity: 1,
rotation: "${angle}",
graphicZIndex: 1
})
})
UPD: I fixed it for OpenLayers 2.13.1
OpenLayers.Control.RotateGraphicFeature = OpenLayers.Class(OpenLayers.Control.ModifyFeature, {
rotateHandleStyle: null,
initialize: function (layer, options) {
OpenLayers.Control.ModifyFeature.prototype.initialize.apply(this, arguments);
this.mode = OpenLayers.Control.ModifyFeature.ROTATE; // This control can only be used to rotate the feature
this.geometryTypes = ['OpenLayers.Geometry.Point'] // This control can only be used to rotate point because the 'exteralGraphic' is a point style property
var init_style = OpenLayers.Util.extend({}, OpenLayers.Feature.Vector.style.select);
this.rotateHandleStyle = OpenLayers.Util.extend(init_style, {
externalGraphic: "./static/resources/images/cross.png",
graphicWidth: 12,
graphicHeight: 12,
fillOpacity: 1
});
},
resetVertices: function () {
// You need to set yours renderIntent or use "vertex"
if (this.feature && this.feature.renderIntent == "vertex") {
var vertex = this.feature;
this.feature = this.backup_feature;
if (this.dragControl.feature) {
this.dragControl.outFeature(this.dragControl.feature);
}
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
delete this.radiusHandle;
this.collectRadiusHandle();
return;
}
if (this.vertices.length > 0) {
this.layer.removeFeatures(this.vertices, {silent: true});
this.vertices = [];
}
if (this.virtualVertices.length > 0) {
this.layer.removeFeatures(this.virtualVertices, {silent: true});
this.virtualVertices = [];
}
if (this.dragHandle) {
this.layer.destroyFeatures([this.dragHandle], {silent: true});
this.dragHandle = null;
}
if (this.radiusHandle) {
this.layer.destroyFeatures([this.radiusHandle], {silent: true});
this.radiusHandle = null;
}
if (this.feature && this.feature.geometry &&
this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
if ((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
this.collectDragHandle();
}
if ((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
OpenLayers.Control.ModifyFeature.RESIZE))) {
this.collectRadiusHandle();
}
if (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE) {
if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)) {
this.collectVertices();
}
}
}
this.collectRadiusHandle();
},
collectRadiusHandle: function () {
var scope = this,
feature = this.feature,
data = feature.attributes,
geometry = this.feature.geometry || this.backup_feature.geometry,
center = this.feature.geometry.bounds.getCenterLonLat();
centroid = geometry.getCentroid().transform(this.WGS84_google_mercator, this.WGS84),
lon = centroid.x, lat = centroid.y;
if (data.type && Tms.settings.roadObjectTypeSettings[data.type].NoAzimuth) {
return;
}
if (this.feature.geometry) {
this.backup_feature = this.feature;
} else {
this.feature.geometry = this.backup_feature.geometry;
}
var originGeometry = new OpenLayers.Geometry.Point(lon, lat);
var center_px = this.map.getPixelFromLonLat(center);
// you can change this two values to get best radius geometry position.
var pixel_dis_x = 20,
pixel_dis_y = 20;
var radius_px = center_px.add(pixel_dis_x, pixel_dis_y);
var rotation_lonlat = this.map.getLonLatFromPixel(radius_px);
var rotationFeatureGeometry = new OpenLayers.Geometry.Point(
rotation_lonlat.lon, rotation_lonlat.lat
);
var rotationFeature = new OpenLayers.Feature.Vector(rotationFeatureGeometry, null, this.rotateHandleStyle);
var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
var reshape = (this.mode & OpenLayers.Control.ModifyFeature.RESHAPE);
var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
rotationFeatureGeometry.move = function(x, y) {
OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
var dx1 = this.x - originGeometry.x;
var dy1 = this.y - originGeometry.y;
var dx0 = dx1 - x;
var dy0 = dy1 - y;
if (rotate) {
var a0 = Math.atan2(dy0, dx0);
var a1 = Math.atan2(dy1, dx1);
var angle = a1 - a0;
angle *= 180 / Math.PI;
var old_angle = feature.attributes.angle;
var new_angle = old_angle - angle;
feature.attributes.angle = new_angle;
// redraw the feature
scope.feature.layer.redraw.call(scope.feature.layer);
}
};
rotationFeature._sketch = true;
this.radiusHandle = rotationFeature;
this.radiusHandle.renderIntent = this.vertexRenderIntent;
this.layer.addFeatures([this.radiusHandle], {silent: true});
},
CLASS_NAME: "OpenLayers.Control.RotateGraphicFeature"
});

Categories