Repeatedly drawing a semi-opaque black rectangle over the entire canvas before each animation frame is an easy way to get an afterimage effect for moving shapes and it gives me exactly what I need - up to a point. With too slow a fade it doesn't fade all the way to black. Here's an example:
var canv = document.createElement('canvas');
document.body.appendChild(canv);
var ctx = canv.getContext('2d');
ctx.fillStyle = 'rgba(0, 0, 0, 1)';
ctx.fillRect(0, 0, 100, 100);
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(20, 20, 60, 60);
window.requestAnimationFrame(doFade);
function doFade() {
// Never fades away completely
ctx.fillStyle = 'rgba(0, 0, 0, 0.02)';
ctx.fillRect(20, 20, 60, 60);
window.requestAnimationFrame(doFade);
}
jsfiddle
This looks to me like a numeric precision problem - you can't expect the canvas to keep floating point pixel values around - but I'm not sure how to get around this.
I tried reading the image into a pattern, blanking the canvas, and then filling with the pattern at lower opacity in the hope that I could make rounding error work in my favor, but it seems to have the same result.
Short of reading out the image data and setting to black any pixels below a certain threshold, which would be prohibitively slow, I'm running out of ideas and could use some suggestions.
Thanks!
I thought I'd share my solution for the benefit of anyone else who might run into this problem. I was hoping to avoid doing any pixel-level manipulation, but beyond a certain threshold it's just not possible with the built-in canvas operations because the underlying bitmap is only 8 bits per channel and small fades will work out to less than one least significant bit and won't have any effect on the image data.
My solution was to create an array representing the age of each pixel. After each frame is drawn, I scan the imageData array, looking only at the alpha channel. If the alpha is 255 I know the pixel has just been written, so I set the age to 0 and set the alpha to 254. For any other non-zero alpha values, I increment the pixel age and then set the new alpha based on the pixel age.
The mapping of pixel age to alpha value is done with a lookup table that's populated when the fade rate is set. This lets me use whatever decay curve I want without extra math during the rendering loop.
The CPU utilization is a bit higher, but it's not too much of a performance hit and it can do smooth fades over several seconds and always fades entirely to black eventually.
Related
I have a Win32 non-game windowed app that uses a Direct2D device context/HWND render target to draw a window. Currently it uses a DXGI swap chain with the DXGI_SWAP_EFFECT_DISCARD swap effect.
Microsoft recommends using the new flip model swap effects, either DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL or DXGI_SWAP_EFFECT_FLIP_DISCARD. I'm interested in using them primarily because they would allow me to specify a list of dirty rects when calling Present1(), which should improve performance/power usage.
Simply changing the SwapEffect to either of the new flip model values produces a weird (but actually expected) result of drawing a black window each second frame, with artifacts of the previous frames visible onscreen.
So the question is: is it possible to use the new flip model swap effects in this situation, and if yes, how should things be set up?
Given that the app needs to draw the dirty rects into an otherwise valid buffer, it seems that a correct approach would involve maintaining two buffers with essentially the same content (one to draw into, and one to give to the DWM for composition), so not sure if it would be possible to achieve any performance gains this way in an app that doesn't redraw each frame completely. But perhaps I'm missing something important.
The swap chain is currently set up as follows:
swapChainDesc.Width = ...;
swapChainDesc.Height = ...;
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
swapChainDesc.Flags = 0;
EDIT 1
It turns out that DXGI_SWAP_EFFECT_DISCARD forces BufferCount to 1, so my initial value of 2 was somewhat misleading, as only one buffer is used. Source (3rd comment).
Also the docs for DXGI_SWAP_EFFECT say that UWP apps are forced into the flip model, so this should be a solvable problem.
There are two good ways to do it.
The first way is a little heavier on energy usage. You can draw your contents into an intermediate buffer/render texture, and copy it to swapchain just before every present. That way you can only actually render the parts that changed in your intermediate buffer, and not care about what the state of the swapchain is.
The second way is more complicated, but can yield optimal energy usage. Instead of using intermediate buffer and drawing only what changes since the last frame there, you draw directly into the swapchain buffer. For this to work correctly, you need to redraw not what changes between current and last frame, but between current and (current - BufferCount) frame. For instance:
Frame 1 - you draw a green rectancle at (200 x 200) with dimensions of (150 x 150). The dirty region is entire frame because it's the first frame.
Frame 2 - you draw a blue rectangle at (250 x 250) with dimensions of (50 x 50). The dirty region is (250, 250, 300, 300).
Frame 3 - you draw a red rectangle at (225 x 225) with dimensions of (50 x 50). The dirty region is (225, 225, 50, 50).
If your buffer count is 2, that means when you draw frame 3, you need to not only redraw the dirty region of (225, 225, 50, 50), but also the dirty region of (250, 250, 300, 300).
What value fed to strokeWidth() will give a stroke width of one pixel regardless of the current scale() setting?
I think strokeWeight(0) should work. Here is an example:
void setup() {
size(100,100);
noFill();
scale(10);
// 1st square, stroke will be 10 pixels
translate(3,3);
strokeWeight(1);
beginShape();
vertex(-1.0, -1.0);
vertex(-1.0, 1.0);
vertex( 1.0, 1.0);
vertex( 1.0, -1.0);
endShape(CLOSE);
// 2nd square, stroke will be 1 pixel
translate(3,3);
strokeWeight(0);
beginShape();
vertex(-1.0, -1.0);
vertex(-1.0, 1.0);
vertex( 1.0, 1.0);
vertex( 1.0, -1.0);
endShape(CLOSE);
}
Kevin did offer a couple of good approaches.
Your question doesn't make it clear what level of comfort you have with the language. My assumption (and I could be wrong) is that the layers approach isn't clear as you might have not used PGraphics before.
However, this option Kevin provided is simple and straight forward:
multiplying the coordinates manually
Notice most drawing functions take not only the coordinates, but also dimensions ?
Don't use scale(), but keep track of a multiplier floating point variable that you use for the shape dimensions. Manually scale the dimensions of each shape:
void draw(){
//map mouseX to a scale between 10% and 300%
float scale = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(255);
//scale the shape dimensions, without using scale()
ellipse(50,50, 30 * scale, 30 * scale);
}
You can run this as a demo bellow:
function setup(){
createCanvas(100,100);
}
function draw(){
//map mouseX to a scale between 10% and 300%
var scale = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(200);
//scale the shape dimensions, without using scale()
ellipse(50,50, 30 * scale, 30 * scale);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.min.js"></script>
Another answer is in the question itself: what value would you feed to strokeWidth() ? If scale() is making the stroke bigger, but you want to keep it's appearance the same, that means you need to use a smaller stroke weight as scale increases: the thickness is inversely proportional to the scale:
void draw(){
//map mouseX to a scale between 10% and 300%
float scale = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(255);
translate(50,50);
scale(scale);
strokeWeight(1/scale);
//scaled shape, same appearing stroke, just smaller in value as scale increases
ellipse(0,0, 30, 30);
}
You can run this bellow:
function setup(){
createCanvas(100,100);
}
function draw(){
//map mouseX to a scale between 10% and 300%
var scaleValue = map(constrain(mouseX,0,width),0,width,0.1,3.0);
background(240);
translate(50,50);
scale(scaleValue);
strokeWeight(1/scaleValue);
//scale the shape dimensions, without using scale()
ellipse(0,0, 30, 30);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.7/p5.min.js"></script>
Kevin was patient, not only to answer your question, but also your comments, being generous with his time. You need to be patient to carefully read and understand the answers provided. Try it on your own then come back with specific questions on clarifications if that's the case. It's the best way to learn.
Simply asking "how do I do this ?" without showing what you're tried and what your thinking behind the problem is, expecting a snippet to copy/paste will not get your very far and this is not what stackoverflow is about.
You'll have way more to gain by learning, using the available documentation and especially thinking about the problem on your own first. You might not crack the problem at the first go (I know I certainly don't), but reasoning about it and viewing it from different angles will get your gears going.
Always be patient, it will serve you well on the long run, regardless of the situation.
Update Perhaps you mean by
What value fed to strokeWidth() will give a stroke width of one pixel regardless of the current scale() setting?
is how can you draw without anti-aliasing ?
If so, you can disable smoothing via a line: calling noSmooth(); once in setup(). Try it with the example code above.
None.
The whole point of scale() is that it, well, scales everything.
You might want to draw things in layers: draw one scaled layer, and one unscaled layer that contains the single-pixel-width lines. Then combine those layers.
That won't work if you need your layers to be mixed, such as an unscaled line on top of a scaled shape, on top of another scaled line. In that case you'll just have to unscale before drawing your lines, then scale again to draw your shapes.
I'm generating a falloff texture by adding gradient part to the white image I have. If implementation is relevant, I'm doing it with HTML5 canvas. For some reason I'm getting weird ray like while artifacts where it's supposed to be gradient smooth. I couldn't find any way to take care of that on implementation level, so I have to get rid of them after generating. Question is, if I have per pixel access to the image, how do I recognize those white pixels and replace with pixels to keep the gradient smooth?
The rays are caused by overlaps and rounding errors. They can be removed or at least reduced by using a Gaussian blur filter (which in effect act as a low-pass filter).
To avoid new problems such as the inner shape's black pixels leaking into the gradient, I'd suggest these steps:
Fill inner shape in the same color as the start color of the gradient.
Produce gradients
Apply Gaussian blur using either the filter property of context (f.ex context.filter = "blur(7px)";, reset by setting it to none), or by using a manual implementation
Redraw the inner shape in the destination color.
Now it's a simple matter of experimenting with the blur radius to find an optimal value. Note that blurring will add to the gradient so you might want to link the two so that the radius of the gradient is reduced when blur radius is increased.
Pro-tip: you can also drop the gradient production all together and simply make the glow effect using Gaussian blur (run example below).
var ctx = c.getContext("2d");
ctx.moveTo(300, 50);
ctx.quadraticCurveTo(325, 300, 550, 550);
ctx.quadraticCurveTo(300, 500, 50, 550);
ctx.quadraticCurveTo(250, 300, 300, 50);
ctx.closePath();
// blur next drawings
ctx.filter = "blur(20px)"; // glow radius
// produce a full base using fill and heavy stroke
ctx.fillStyle = ctx.strokeStyle = "#fff";
ctx.fill();
ctx.lineWidth = 40; // thicker = stronger spread
ctx.stroke();
// final, fill center in destination color
ctx.filter = "none";
ctx.fillStyle = "#000";
ctx.fill();
#c {background:#000}
<canvas id=c width=600 height=600></canvas>
Let's say we have the following code:
void setup() {
background(0);
size(200, 200);
fill(255);
rect(75, 75, 50, 50);
}
void draw() {
fill(0, 2);
rect(0, 0, width, height);
}
Even after waiting 'forever,' the white 50x50 rectangle is still visible, albeit faded. Why doesn't the fill(0, 2) eventually cover this up?
I suppose this question is twofold:
Why doesn't it eventually fade to black, as in why does drawing another dark rectangle on top of the white one not erase it eventually (I'm thinking along the lines of putting tinted windows over each other; eventually even the brightest light won't shine through), and
Why doesn't it eventually fade to black, as in why is this the behavior intended by the Processing community?
Here's a post explaining what's going on: http://processing.org/discourse/beta/num_1138703939.html
Basically, the problem is that Processing stores colors as ints, but takes float arguments. When combining colors, Processing rounds the floats to ints. In your case, your color is getting stuck at a value of 63, 63, 63 because at that point the blending is too slight to make a difference that is detectable after rounding.
The solution is to do the fading from the source, not by overlaying an alpha color over top.
I had the same issue with fill(0, 0, 0, 5);.
Interestingly, changing the alpha value to 20 helped (and I'm sure many other values work too).
void draw() {
fill(0, 0, 0, 20); // Note the value 20 for the alpha channel.
rect(0, 0, width, height);
}
The default background color is darker than the color you assigned to the first rectangle, thus it gets black sooner.
Why doesn't it eventually fade to black, as in why does drawing another dark rectangle on top of the white one not erase it eventually
(I'm thinking along the lines of putting tinted windows over each
other; eventually even the brightest light won't shine through), and
Why doesn't it eventually fade to black, as in why is this the behavior intended by the Processing community?
Also, in your original code (not the above sample) you're probably drawing the white rectangle continuously, so it will never fade.
Please check this neat piece of code I found:
glEnable(GL_LINE_SMOOTH);
glColor4ub(0, 0, 0, 150);
mmDrawCircle( ccp(100, 100), 20, 0, 50, NO);
glLineWidth(40);
ccDrawLine(ccp(100, 100), ccp(100 + 100, 100));
mmDrawCircle( ccp(100+100, 100), 20, 0, 50, NO);
where mmDrawCircle and ccDrawLine just draws these shapes [FILLED] somehow... (ccp means a point with the given x, y coordinates respectively).
My problem .... Yes, you guessed it, The line overlaps with the circle, and both are translucent (semi transparent). So, the final shape is there, but the overlapping part becomes darker and the overall shape looks ugly.. i.e, I would be fine if I was drawing with 255 alpha.
Is there a way to tell OpenGL to render one of the shapes in the overlapping parts??
(The shape is obviously a rectangle with rounded edges .. half-circles..)
You could turn on GL_DEPTH_TEST and render the line first and a little closer to the camera. When you then render the circle below, the fragments of the line won't be touched.
(You can also use the stencil buffer for an effect like this).
Note that this might still look ugly. If you want to use anti-aliasing you should think quite hard on which blending modes you apply and in what order you render the primitives.