So i'm trying to map color space in HTML and my knowledge is pretty much limited to CSS HTML and Javascript. I am looking for a way to construct a 2 dimensional gradient, with 2 variable along 2 vectors. My research has indicated that CSS and SVG tech only has capacity for single dimension grdaients. Or rather Linear Grads can only have a single vector. So to make up for this limitation I am using JS to iterate over the 256 changes I need so that I can get a gradient on 2 RGB color channels. So picture if you will an x-axis that is relative to for example purposes - Red and Grads from 0 to 255 and y-axis that is likewise relative - Green and Grads from 0 to 255 but with a JS iteration instead of a CSS linear-grad.
What I end up with is a beautiful representation of RGB color space !BUT! changes to the z-axis -blue channel in this example- means that I have to call on a JS function that iterates through 256 loops updating the background of 256 DOM elements with new CSS linear grads.
I am making this web-app because of the limitations that I see in current web-based color pickers a 256 step loop for each change of the Z-axis will place an unacceptable amount of computation overhead into the program.
Any Idea's for a better way to make a dual vector gradient? Perhaps I could make an app specific library for the HTML 5 canvas element??? Where I would be operating on a bitmap instead of DOM elements maybe significantly lower the processor cost-per-call?
You can use the canvas element for that. Here are some examples of colorpickers.
Basically you want to create two linear gradients, one horizontal one vertical, moving from transparent to whatever rgba colors you want. Then draw one gradient over the other on the canvas. There's kind of a catch though, I've found that canvas doesn't make very clean rgba gradients, but you can uses half transparent colors, draw the first one once, the second one twice, then the first one again and it seems to give pretty good results. You can play with it though, here's some code to work off of.
var Draw = function(clr1, clr2){
clr1 = clr1 || 'rgba(255, 0, 0, 0.5)';
clr2 = clr2 || 'rgba(0, 0, 255, 0.5)';
var bg1 = document.getElementById('canvas').getContext('2d'),
grad1 = bg1.createLinearGradient(0, 128, 256, 128),
grad2 = bg1.createLinearGradient(128, 0, 128, 256);
grad1.addColorStop(0, 'rgba(255, 0, 0, 0)');
grad1.addColorStop(1, clr1);
grad2.addColorStop(0, 'rgba(0, 0, 255, 0)');
grad2.addColorStop(1, clr2);
bg1.fillStyle = grad1;
bg1.fillRect(0, 0, 256, 256);
bg1.fillStyle = grad2;
bg1.fillRect(0, 0, 256, 256);
bg1.fillRect(0, 0, 256, 256);
bg1.fillStyle = grad1;
bg1.fillRect(0, 0, 256, 256);
}
Here's a simple example showing how to create an arbitrary gradient on a canvas, with per-pixel control: http://jsfiddle.net/j85FQ/3/
colorField( myCanvas, 500, 500, pretty );
function colorField(canvas,width,height,colorLookup){
var w = width-1, h = height-1;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d'),
idata = ctx.getImageData(0,0,width,height),
data = idata.data;
for (var x=0;x<width;++x){
for (var y=0;y<height;++y){
var rgba = colorLookup(x/w,y/h);
var o = (width*y+x)*4;
for (var i=0;i<4;++i) data[o+i] = rgba[i]*255;
}
}
ctx.putImageData(idata,0,0);
}
function pretty(xPct,yPct){
return [ xPct, yPct, xPct*(1-yPct), 1];
}
Thanks guys I was able to work it out with the canvas element. I used a bucket fill for the z channel value and horizontal & vertical linear gradients from 0 to 255 for x and y channels. Setting context.globalCompositeOperation = "lighter" was the key I was missing. That was the simple additive mode I needed much easier then trying to find a suitable alpha compositing method. The following is the canvas init function I wrote.
function init() {
var c = document.getElementById('myCanvas');
var ctx = c.getContext('2d');
ctx.globalCompositeOperation = "lighter";
var grd = ctx.createLinearGradient(0, 0, 512, 0);
grd.addColorStop(0, "#000000");
grd.addColorStop(1, "#FF0000");
var grd2 = ctx.createLinearGradient(0, 0, 0, 512);
grd2.addColorStop(0, "#000000");
grd2.addColorStop(1, "#00FF00");
ctx.fillStyle = "#0000FF";
ctx.fillRect(0, 0, 512, 512);
ctx.fillStyle = grd;
ctx.fillRect(0, 0, 512, 512);
ctx.fillStyle = grd2
ctx.fillRect(0, 0, 512, 512)
}
Related
Suppose I draw two overlapping translucent shapes in a canvas:
context.fillStyle = "rgba(0, 0, 0, 0.25)";
context.globalCompositeOperation = "copy";
context.fillRect(50, 50, 200, 200);
context.globalCompositeOperation = "??????";
context.fillRect(150, 150, 200, 200);
After this, I want the RGB values of each pixel to be (0, 0, 0) and for the alpha to be the maximum of the alpha channels of the two shapes. In other words, what I should get is that if a pixel is in either of the two squares it has alpha 0.25, otherwise it's blank. So the entire drawn region should be a uniform color.
I can't find any blending operation that does this. If I use things like "lighter" or "darker", I get a darker region where the two squares overlap.
I want to create visualization, where whatever is drawn fades away slowly. This should be continuous, so that content added later will be less faded than old content, like in this picture:
I don't want to clear entire canvas, I would like to just make everything on it more transparent - then draw any new objects.
My instinct would be to do this in each frame:
ctx.fillStyle = "rgba(0,0,0,0)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
However the problem is that canvas doesn't blend alpha - it uses it for blending the other colors.
How can I get canvas to continuously decrease opacity of everything drawn in it?
You're almost definitely looking for canvas compositing operations! It looks like "destination-in" should achieve what you're looking for.
let canvas = document.getElementsByTagName('canvas')[0];
let ctx = canvas.getContext('2d');
let rand = n => Math.floor(Math.random() * n);
setInterval(() => {
ctx.beginPath();
ctx.arc(rand(300), rand(120), rand(60), Math.PI * 2, 0);
ctx.fillStyle = `rgba(${rand(256)}, ${rand(256)}, ${rand(256)}, 1)`;
ctx.globalCompositeOperation = 'source-over';
ctx.fill();
}, 150);
let fadeOut = () => {
let fadeAmount = 0.002;
// Note that the colour here doesn't matter! Only the alpha matters.
// The colour here is red, but you'll see no red appear
ctx.fillStyle = `rgba(255, 0, 0, ${1 - fadeAmount})`;
ctx.globalCompositeOperation = 'destination-in';
ctx.fillRect(0, 0, 300, 120);
requestAnimationFrame(fadeOut);
};
requestAnimationFrame(fadeOut);
canvas { border: 3px solid #808080; background-color: #000000; }
<canvas width="300" height="120"></canvas>
Note: When you set ctx.globalCompositeOperation, it will apply until you either do ctx.restore(), or set ctx.globalCompositeOperation to a new value! This is a bit of a gotcha.
why you don't decrease the opacity of the canvas and when it reach 0 you erase everything from the canvas then reset the opacity value to 1
When I transform an HTML5 canvas context and draw an ellipse, as the transform becomes larger the ellipse outline becomes completely distorted. Below is some sample code and the results I see in both Firefox and Chrome.
<body>
<canvas id="myCanvas" width=900 height=900></canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(0, 51, 255, 0.5)";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.transform(1, 0, 0, 1, -16776544, -16776916);
ctx.ellipse(16776994, 16777316, 400, 400, 0, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
</script>
</body>
Chrome Results
Firefox Results
Is there a way to fix the distortion, or is it somewhere documented that there are limits on canvas transformation?
This is due to the limited precision of floating point numbers. There is not much you can do about it but to avoid really big and really small numbers.
With a pixel size of 0.1mm you are addressing a pixel that is near one mile from the origin (that's a very big screen).
I am not sure if the canvas rendering uses 32bit floats (thinking it does from your example and I know that webGL definitely uses floats) you need to do the transformations in javascript as it uses doubles 64bit which will give you a screen 9 orders (approx) greater (screen now size of the sun)
eg
var x = 16776544;
var y = 16776916;
var x1 = 16776234;
var y1 = 16776446;
ctx.setTransform(1,0,0,1,0,0);
x -= x1; // apply transform using doubles
y -= y1;
ctx.ellipse(x,y,100,100,0,0,Math.PI*2);
There are thousands of moving particles on an HTML5 canvas, and my goal is to draw a short fading trail behind each one. A nice and fast way to do this is to not completely clear the canvas each frame, but overlay it with semi-transparent color. Here is an example with just one particle:
var canvas = document.getElementById('display');
var ctx = canvas.getContext('2d');
var displayHeight = canvas.height;
var backgroundColor = '#000000';
var overlayOpacity = 0.05;
var testParticle = {
pos: 0,
size: 3
};
function render(ctx, particle) {
ctx.globalAlpha = overlayOpacity;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#FFF';
ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}
function update(particle) {
particle.pos += 1;
}
// Fill with initial color
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
function mainLoop() {
update(testParticle);
render(ctx, testParticle);
requestAnimationFrame(mainLoop);
}
mainLoop();
<canvas id="display" width="320" height="240"></canvas>
There is an apparent problem: with low opacity values, the trail never fades away completely. You can see the horizontal line that (almost) does not fade in my single-particle example. I understand why this happens. ColorA overlayed by semi-transparent ColorB is basically a linear interpolation, and ColorA never fully converges to ColorB if we repeatedly do the following:
ColorA = lerp(ColorA, ColorB, opacityOfB)
My question is, what can I do to make it converge to the background color, so that trails don't remain there forever? Using WebGL or drawing trails manually are not valid options (because of compatibility and performance reasons respectively). One possibility is to loop over all canvas pixels and manually set pixels with low brightness to background color, although it may get expensive for large canvases. I wonder if there are better solutions.
As a workaround which could work in some cases is to set the overlayOpacity up to 0.1 (this value converges) but draw it only every x times and not in every render call.
So when drawn only every other time it keeps more or less the same trail length.
var renderCount = 0;
var overlayOpacity = 0.1;
function render(ctx, particle) {
if((renderCount++)%2 == 0) {
ctx.globalAlpha = overlayOpacity;
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#FFF';
ctx.fillRect(particle.pos, displayHeight / 2, particle.size, particle.size);
}
Obviously the disadvantage is that it looks more jerked and perhaps this may not be acceptable in your case.
Best solution is to use the composite operation "destination-out" and fade to a transparent background. Works well for fade rates down to globalAlpha = 0.01 and event a little lower 0.006 but it can be troublesome below that. Then if you need even slower fade just doe the fade every 2nd or 3rd frame.
ctx.globalAlpha = 0.01; // fade rate
ctx.globalCompositeOperation = "destination-out" // fade out destination pixels
ctx.fillRect(0,0,w,h)
ctx.globalCompositeOperation = "source-over"
ctx.globalAlpha = 1; // reset alpha
If you want a coloured background you will need to render the animation on an offscreen canvas and render it over the onscreen canvas each frame. Or make the canvas background the colour you want.
If someone struggles with this, here is a workaround that worked for me:
// Do this instead of ctx.fillStyle some alpha value and ctx.fillRect
if(Math.random() > 0.8){
ctx.fillStyle = 'rgba(255, 255, 255, '+getRandomNumber(0.1,0.001)+')';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// Define this helper function somewhere in your code
function getRandomNumber(minValue, maxValue) {
return Math.random() * (maxValue - minValue) + minValue;
}
It also works for different colored backgrounds. Adjust trail length by playing around with Math.random() > 0.8 and getRandomNumber(0.1,0.001).
I'm working with some canvas and processing.js but i cant figure out how to fill an arc/ellipse etc with an image.
Using JavaScript usually i do something like this:
ctx.save();
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
ctx.drawImage(thumbImg, 0, 0, 400, 400);
ctx.beginPath();
ctx.arc(x, y, size, Math.PI * 2, true);
ctx.clip();
ctx.closePath();
ctx.restore();
and the game is done, but how can i do it with processing.js?
I've tried those options but seems that I'm doing something wrong:
b = loadImage("nicola.png");
fill(b)
background(b);
ellipse(x, y, size, size);
any idea?
I believe that what you are trying to get at is called image masking
an example of masking
Description:
Masks part of an image from displaying by loading another image and using it as an alpha channel. This mask image should only contain grayscale data, but only the blue color channel is used. The mask image needs to be the same size as the image to which it is applied.
In addition to using a mask image, an integer array containing the alpha channel data can be specified directly. This method is useful for creating dynamically generated alpha masks. This array must be of the same length as the target image's pixels array and should contain only grayscale data of values between 0-255.
Example:
var g2;
var setup = function(){
createCanvas(200,200);
background(0, 0, 0, 0);
smooth();
fill(255, 255, 255);
ellipse(100, 100, 200, 200);
var g1 = get(0, 0, 200, 200);
background(0, 0, 0, 0);
noStroke();
for(let i = 0; i < 360; i++){
fill(sin(radians(i))*255, i, 200);
rect(0, i, 200, 1);
}
g2 = get(0, 0, 200, 200);
g2.mask(g1);
}
var draw = function(){
background(255, 255, 255);
image(g2, 0, 0);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.js"></script>
an image of what the above code returns:
You can either use img.mask(maskImg) to apply an (pixel based) alpha mask or use img.blend(…) as described here for example.
A semicolon ';' is missing after fill(b)
So it should be fill(b);
I hope this solves your problem.