I have a lot of css filter classes that can be applied to an image using the the CSS filter. My goal is to convert the image with the filter applied to dataURL.
To do so, I'm placing the image into a canvas then saving the image after I applied the filter. Here's an example
const img = this.img // my <img />
const canvas = document.createElement('canvas')
const context = canvas.getContext('2d')
context.filter = 'grayscale(2)'
context.drawImage(img, 0, 0)
const finalImg = canvas.toDataURL()
While this works fine applying a single filter, I have more than 30 filters made in my css class, and I would like to know if there's a way to apply a css class to a canvas object. Worst case scenario is for me to convert all of my filters into an array of string objects, but I'm just very curious. Thanks!
Link for reference to canvas context: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
You can simply read the value returned by getComputedStyle(canvasElement).filter and use it as your context's filter.
var img=new Image();img.crossOrigin=1;img.onload=draw;
img.src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg";
function draw() {
canvas.width = this.width/4; canvas.height = this.height/4;
var ctx = canvas.getContext('2d');
ctx.font = '15px sans-serif';
ctx.fillStyle = 'white';
for(var i=1; i<5; i++) {
// set the class
canvas.className = 'filter' + i;
// retrieve the filter value
ctx.filter = getComputedStyle(canvas).getPropertyValue('filter');
ctx.drawImage(img, 0,0, img.width/4, img.height/4);
ctx.filter = 'none';
ctx.fillText('filter' + i, 20, 20);
// export
canvas.toBlob(saveAsIMG);
}
ctx.drawImage(img, 0,0, img.width/4, img.height/4);
ctx.fillText('canvas - no filter', 20, 20);
}
function saveAsIMG(blob) {
var img = new Image();
img.onload = function(){URL.revokeObjectURL(img.src);};
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
}
.filter1 {
filter: blur(5px);
}
.filter2 {
filter: grayscale(60%) brightness(120%);
}
.filter3 {
filter: invert(70%);
}
.filter4 {
filter: none;
}
<canvas id="canvas"></canvas>
Related
Given Safari does not support adoptedStyleSheets to attach a constructed CSSStyleSheet, what alternative is there to achieve the same intent?
Apart from creating a <style> with CSS inside it, I'm not aware of any other method.
Reference: https://developer.mozilla.org/en-US/docs/Web/API/ShadowRoot/adoptedStyleSheets
You can use the following:
export default function generateStyles(styleCollection) {
if (document.adoptedStyleSheets) { //adoptedStyleSheets is supported
//Add to existing collection
document.adoptedStyleSheets = [...document.adoptedStyleSheets, styleCollection]
} else {
//Construct style text and add to head
var styleString = ''
for (let i = 0; i < styleCollection.cssRules.length; i++) {
styleString = styleString.concat(styleCollection.cssRules[i].cssText)
}
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = styleString;
document.getElementsByTagName('head')[0].appendChild(style);
}
}
I need to view the segments and handles of the path that defines a SymbolItem. It is a related issue to this one but in reverse (I want the behavior displayed on that jsfiddle).
As per the following example, I can view the bounding box of the SymbolItem, but I cannot select the path itself in order to view its segments/handles. What am I missing?
function onMouseDown(event) {
project.activeLayer.selected = false;
// Check whether there is something on that position already. If there isn't:
// Add a circle centered around the position of the mouse:
if (event.item === null) {
var circle = new Path.Circle(new Point(0, 0), 10);
circle.fillColor = '#' + Math.floor(Math.random() * 16777215).toString(16);
var circleSymbol = new SymbolDefinition(circle);
multiply(circleSymbol, event.point);
}
// If there is an item at that position, select the item.
else {
event.item.selected = true;
}
}
function multiply(item, location) {
for (var i = 0; i < 10; i++) {
var next = item.place(location);
next.position.x = next.position.x + 20 * i;
}
}
Using SymbolDefinition/SymbolItem prevent you from changing properties of each symbol items.
The only thing you can do in this case is select all symbols which share a common definition.
To achieve what you want, you have to use Path directly.
Here is a sketch showing the solution.
function onMouseDown(event) {
project.activeLayer.selected = false;
if (event.item === null) {
var circle = new Path.Circle(new Point(0, 0), 10);
circle.fillColor = Color.random();
// just pass the circle instead of making a symbol definition
multiply(circle, event.point);
}
else {
event.item.selected = true;
}
}
function multiply(item, location) {
for (var i = 0; i < 10; i++) {
// use passed item for first iteration, then use a clone
var next = i === 0 ? item : item.clone();
next.position = location + [20 * i, 0];
}
}
In my demo. I'm able to create a few ellipses that overlay each other. Each ellipse is slightly rotated and when click should stretch outwards.
However, right now I'm using .scale(x,y) and the ellipses's height increases vertically.
I'm not sure how I would accomplish this type of effect using paper.js
DEMO
Code Pen Demo
paper.install(window);
var canvas = document.getElementById("myCanvas");
window.onload = function() {
paper.setup('myCanvas');
var numberOfRings = 6,
rings = [],
size = [225,400],
colors = ['black','green','orange','blue','yellow','grey'],
max_frame = 50,
negative_scale = 0.99,
positive_scale = 1.01;
for(var i = 0; i < numberOfRings; i++)
{
var path = new Path.Ellipse({
center:view.center,
size: size,
strokeColor: colors[i],
strokeWidth :10
});
var rotate = 30*i +30;
path.rotate(rotate);
path.animation = false;
path.rotateValue = rotate;
path.animationStartFrame = 0;
path.animationScale = positive_scale;
path.smooth();
path.animationIndex = i;
path.onClick = function(event) {
rings[this.animationIndex].animation = true;
}
rings.push(path);
}
view.onFrame = function(event) {
for(var i = 0; i < numberOfRings; i++)
{
if (rings[i].animation == true){
if (rings[i].animationStartFrame == 0)
{
rings[i].animationStartFrame = event.count;
}
if (rings[i].animationStartFrame > 0 && event.count < (rings[i].animationStartFrame + max_frame)){
// TODO
rings[i].scale(1,rings[i].animationScale);
} else if ( event.count > (rings[i].animationStartFrame + max_frame)){
rings[i].animation = false;
rings[i].animationStartFrame = 0;
if (rings[i].animationScale == negative_scale)
rings[i].animationScale = positive_scale;
else
rings[i].animationScale = negative_scale;
}
}
}
}
}
canvas{
width: 100%;
height: 100%;
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-full.min.js"></script>
</head>
<body>
<canvas id="myCanvas" resize></canvas>
</body>
</html>
first for this kind of things i think its easier to use paper.js / items with the applyMatrix option set to false - this way transformations are not applied / baked into the item/children/paths but stay separate in the transformation and so can also get manipulated later in an absolute fashion..
additionally to get to your desired effect i used a trick: Group
i encapsulate the ellipse path in an group. so i can rotate the group only.
the coordinate-system of the child is not modified - and i can manipulate the ellipses scaling as in its original coordinate-system / as it was not rotated..
i have made some other modifications based on your example - mainly so its easier to test in different canvas-sizes and with different numbers of ellipses.
i first tested / developed it at sketch.paperjs.org (i find it easier to test/debug it there)
and then converted it to fit the the plain js version here.
if you want to do more complex animations please checkout the great library animatePaper.js - i used it heavily and loved to work with it :-)
it supports simple and more complex animations of attributes of paper-objects.
paper.install(window);
var canvas = document.getElementById('myCanvas');
window.onload = function() {
paper.setup('myCanvas');
var numberOfRings = 5;
var rings = [];
let view_max_length = Math.min(view.size.width, view.size.height);
var ringStartSize = new Size(view_max_length * 0.4, view_max_length * 0.45);
var ringTargetLength = view_max_length * 0.9;
var scaleStepSize = 0.01;
function createRing(index) {
let path = new Path.Ellipse({
center: view.center,
size: ringStartSize,
strokeColor: {
hue: (360 / numberOfRings) * index,
saturation: 1,
brightness: 1
},
strokeWidth: 10,
applyMatrix: false,
strokeScaling: false,
});
// add custom properties
path.animate = false;
path.animationDirectionOut = true;
path.animationIndex = index;
path.onClick = function(event) {
//console.log(this);
this.animate = true;
};
let rotationgroup = new Group(path);
rotationgroup.applyMatrix = false;
let offsetAngle = (360 / 2) / numberOfRings;
let rotate = offsetAngle * index;
rotationgroup.pivot = path.bounds.center;
rotationgroup.rotation = rotate;
return path;
}
function init() {
for (let i = 0; i < numberOfRings; i++) {
rings.push(createRing(i));
}
}
function animateRing(event, ring) {
if (ring.animate) {
let tempScaleStep = scaleStepSize;
if (!ring.animationDirectionOut) {
tempScaleStep = tempScaleStep * -1;
}
ring.scaling.y += tempScaleStep;
// test if we have reached destination size.
if (
(ring.bounds.height >= ringTargetLength) ||
(ring.bounds.height <= ringStartSize.height)
) {
ring.animate = false;
// change direction
ring.animationDirectionOut = !ring.animationDirectionOut;
}
}
}
view.onFrame = function(event) {
if (rings && (rings.length > 0)) {
for (var i = 0; i < numberOfRings; i++) {
animateRing(event, rings[i]);
}
}
};
init();
}
canvas {
widht: 100%;
height: 100%;
background-color: rgb(0,0,50);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.11.5/paper-full.min.js"></script>
</head>
<body>
<canvas id="myCanvas" resize></canvas>
</body>
</html>
I am trying to switch a project from here maps 2.5.4 to 3.0.5.
I have a map with a custom animated image overlay. In 2.5.4 it is realized via ImageProvider:
var imageProvider = new nokia.maps.map.provider.ImageProvider({
opacity: 0.8,
getBoundingBox: function() {
return new nokia.maps.geo.BoundingBox(
new nokia.maps.geo.Coordinate(55.599073, 3.550307),
new nokia.maps.geo.Coordinate(47.27036232672, 15.434621365086)
)},
getUrl: function() {
return images[index]; // return the current image
},
updateCycle: 86400,
cache: new nokia.maps.util.Cache(100)
});
//add listener to show next image
imageProvider.addListener("update", function(evt) {
index++;
}
In v3.0.5 there is no ImageProvider and I didn't find another solution in the api documentation. Does anyone know how to realize it in v3 ?
Yup, there seems to be no ImageProvider (yet?).
You can hack it in, though:
// assuming you have a H.Map instance call "map"
// that's where we want the image
var rect = new H.geo.Rect(51, 12, 54, 15),
// that's the images we want
images = [
'http://newnation.sg/wp-content/uploads/random-pic-internet-06.jpg',
'http://newnation.sg/wp-content/uploads/random-pic-internet-04.jpg',
'http://newnation.sg/wp-content/uploads/random-pic-internet-06.jpg'
],
current = 0,
// that the image node we'll use
image = document.createElement('img');
// you could probably use CSS3 matrix transforms to improve performance
// but for demo purposes, I'lluse position:absolute and top/left for the image
image.style.position = "absolute";
image.style.opacity = "0.8";
// this function updates the image whenever something changes
var update = function() {
// project the rectangle's geo-coords to screen space
var topLeft = map.geoToScreen(rect.getTopLeft());
var bottomRight = map.geoToScreen(rect.getBottomRight());
// calculate top/left and width/height
var offsetX = topLeft.x;
var offsetY = topLeft.y;
var width = bottomRight.x - topLeft.x;
var height = bottomRight.y - topLeft.y;
// set image source (update is also called, when we choose another image)
image.src = images[current];
// set image position and size
image.style.top = offsetY + "px";
image.style.left = offsetX + "px";
image.style.width = width + "px";
image.style.height = height + "px";
};
// append the image
map.getViewPort().element.appendChild(image);
// set initial values
update();
// update whenever viewport or viewmodel changes
map.getViewPort().addEventListener('sync', function() {
update();
});
map.getViewModel().addEventListener('sync', function() {
update();
});
// zoom to rectangle (just to get the images nicely in view)
map.setViewBounds(rect);
// start the image change interval
setInterval(function() {
current = (current + 1) % 3;
update();
}, 3000);
I've got a whole bunch of rects on my canvas.
I'd like to change the stroke on whatever rect the user clicks, as well as running some other javascript. My simplified code is below.
var canvas = Raphael("test");
var st = canvas.set();
for (var i = 0; i < 2; i++) {
var act = canvas.rect(///edited for brevity////).attr({"stroke":"none"});
st.push(act)
act.node.onclick = function() {
st.attr({stroke: "none"});
act.attr({stroke: "yellow"});
}
}
Right now, no matter what rect I click on, it's only changing the stroke on the last rect drawn.
Any ideas?
Not a Raphaƫl problem but rather lack of closure understanding. Easily could be fixed by self invoking function:
for (var i = 0; i < 2; i++) {
var act = canvas.rect(///edited for brevity////).attr({"stroke":"none"});
st.push(act)
(function (act) {
act.node.onclick = function() {
st.attr({stroke: "none"});
act.attr({stroke: "yellow"});
}
})(act);
}
//Try and then embellish
st[i].click(function (e)
{
this.attr({stroke: "yellow"});
}