Applying CSS on drawn Canvas Elements - css

Is it possible to apply CSS Animations (in my case a glowing effect) to a circle that is drawn on a canvas by javascript?
I am using this angularjs directive: https://github.com/angular-directives/angular-round-progress-directive/blob/master/angular-round-progress-directive.js
I use it as a counter, and I want it to glow every second (got the css code for the animation already.
Is that possible?
Another idea would be, to make the canvas itself circular, and apply the glowing effect to the canvas.

You can't apply CSS to elements drawn in the canvas, because they don't exist on the DOM. It's just as if they were a bitmap image.
You could use an SVG circle though, which will allow you to style the circle with CSS and use animations:
<svg height="100" width="100">
<circle cx="50" cy="50" r="40" stroke="black" stroke-width="3" fill="red" />
</svg>

You can't apply CSS to shapes drawn to canvas, but you can create a glow effect simply by using the shadow.
A demo here
var canvas = document.getElementById('canvas'), // canvas
ctx = canvas.getContext('2d'), // context
w = canvas.width, // cache some values
h = canvas.height,
cx = w * 0.5,
cy = h * 0.5,
glow = 0, // size of glow
dlt = 1, // speed
max = 40; // max glow radius
ctx.shadowColor = 'rgba(100, 100, 255, 1)'; // glow color
ctx.fillStyle = '#fff'; // circle color
function anim() {
ctx.clearRect(0, 0, w, h); // clear frame
ctx.shadowBlur = glow; // set "glow" (shadow)
ctx.beginPath(); // draw circle
ctx.arc(cx, cy, cx * 0.25, 0, 6.28);
ctx.fill(); // fill and draw glow
glow += dlt; // animate glow
if (glow <= 0 || glow >= max) dlt = -dlt;
requestAnimationFrame(anim); // loop
}
anim();
Update
To get a outline with outer glow you can simply "punch out" the center of the circle using a composite operation. Here the example uses save/restore to remove the shadow - you can optimize the code by manually resetting these - but for simplicity, do the following modification:
ctx.fillStyle = '#fff';
// remove shadow from global
function anim() {
ctx.clearRect(0, 0, w, h);
// draw main circle and glow
ctx.save(); // store current state
ctx.shadowColor = 'rgba(100, 100, 255, 1)';
ctx.shadowBlur = glow;
ctx.beginPath();
ctx.arc(cx, cy, cx * 0.25, 0, 6.28);
ctx.fill();
ctx.restore(); //restore -> removes the shadow
// draw inner circle
ctx.globalCompositeOperation = 'destination-out'; // removes what's being drawn
ctx.beginPath();
ctx.arc(cx, cy, cx * 0.23, 0, 6.28); // smaller filled circle
ctx.fill();
ctx.globalCompositeOperation = 'source-over'; // reset
glow += dlt;
if (glow <= 0 || glow >= max) dlt = -dlt;
requestAnimationFrame(anim);
}
The composite operation will remove the pixels from the next draw operation. Simply draw a smaller filled circle on top which will leave an outline of the first circle and its glow.
Modified fiddle here

Related

writing a gauge widget using 2 images in qt

I'm writing a gauge widget, using QT, that is constructed from 2 separate images, one as background and the other as Needle. I reimplement paintEvent function as follow:
void myGaugeWidget::paintEvent(QPaintEvent *pe)
{
QPainter painter(this);
QPixmap bkgImage(bkgImgPath);
painter.drawPixmap(0, 0, width(), height(), bkgImage);
const double thetaDeg = 30.0;
QPixmap needle(needles[i].imgPath);
int needleWidth = 200;
int needleHeight = 200;
int anchorX = 20;
int anchorY = 30;
const int centerX = width()/2;
const int centerY = height()/2;
QTransform rm = QTransform().translate(-anchorX,- anchorY).rotate(thetaDeg).translate(centerX,centerY);
needle = needle.transformed(rm);
painter.drawPixmap(0,0, needle);
}
this code rotates my needle correctly but its position is not correct.
can anybody help me?
thanks.
This most likely would depend on your images and widget size. I have tried your code and it seems to me that QTransform().translate() is not doing anything in a QPixmap. I tried to give extreme values for translate() and removed rotate() - the image does not move.
I already have have my own implementation for a gauge. This is with painter transformation instead of the image. My images are of dimensions:
Gauge Background: 252x252 (there is some external blurring effects around the circle boundaries, making the background image larger than it seems)
Needle: 7x72 ( the image dimensions wrap around the boundaries of the needle itself)
Needle roation center (with respect to the background): 126, 126 (divide background size by 2)
The needle image points upward
For this setup, here is my paintEvent() with some explanations:
void myGaugeWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
//draw the background which is same size as the widget.
painter.drawPixmap(0, 0, bg.width(), bg.height(), bg);
//Calculate the angle of rotation.
//The gauge I am using has a cutout angle of 120 degrees at the bottom (symmetric)
float needleAngle = -120/*offset for start rotation*/ + ((value-minValue)*240/*total sweep of the gauge*//(maxValue-minValue));
painter.save();
//translate the painter to the roation center and then perform the rotation
painter.translate(126, 126);
painter.rotate(needleAngle);
//translate the rotated canvas to adjust for the height of the needle.
//If you don't do this, your needle's tip will be at the rotation center
painter.translate(0, -72);
//draw the needle and adjust for the width with the x value
painter.drawPixmap(-needle.width()/2, 0, needle.width(), needle.height(), needle);
painter.restore();
}

Blend HTML text with canvas

I am currently rendering a canvas below some HTML elements (currently a h1 and a span). The canvas contains a kaleidoscope based on an image with two major colors: one pretty dark (almost black), and one really bright, and it can be moved by moving the mouse. The HTML elements are rendered with a color: white style.
The problem I encounter is when the kaleidoscope renders a huge white part. The text becomes invisible. Is it possible to make the text display the negative color of the part of the canvas right under it ? So for example, if the part of the canvas under the text is white, the text would be black ? Here is a screenshot of the problem:
You can set the color to white and use css blending-mode difference:
h1{
color:white;
mix-blend-mode: difference;
}
Demo
Sure, you can use the "difference" blending mode to make the text change color:
ctx.globalCompositeOperation = "difference";
ctx.fillText(myText, x, y);
Example
var ctx = c.getContext("2d");
var toggle = false;
for(var x = 0, step = c.width / 16; x < c.width; x += step) {
toggle = !toggle;
ctx.fillStyle = toggle ? "#000" : "#fff";
ctx.fillRect(x, 0, step, c.height);
}
ctx.globalCompositeOperation = "difference";
ctx.font = "32px sans-serif";
ctx.textAlign = "center";
ctx.fillText("ALTERNATING", c.width>>1, c.height>>1);
<canvas id=c></canvas>
Creative options: draw background slightly transparent to make white light-grey (setting the canvas element's CSS opacity), use shadow or outline for the text.

Interpret box-shadow as canvas shadowXXX options

I have to draw a bow object in a canvas (2d). The box has an external shadow specified as css definition
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15)
I don't know how to convert this to the canvas way of defining shadows, using shadowOffsetX/Y, shadowColor and shadowBlur.
If I look at the shadowBlur spec, it's explicitely not related to pixels, but it only says "it's a parameter for a gaussian blur effect" (paraphrasing). Actually, I find this to be under-specified.
Would a better approximation using a gradient to transparent instead ? But then won't it miss the blurring effect ?
Now there are filters which are similar to the CSS filters properties and accepts the same functions. Although it is still a experimental technology.
// create canvas
const canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 500;
// set context
const context = canvas.getContext("2d");
context.fillStyle = '#ff0';
context.rect(50, 50, 100, 100);
// set shadow filter
let offsetX = 20;
let offsetY = 0;
let blurRadius = 5;
let color = 'rgba(255, 0, 0, 0.8)';
context.filter = 'drop-shadow('+ offsetX + 'px ' + offsetY + 'px ' + blurRadius + 'px ' + color + ')';
context.fill();
document.body.appendChild(canvas);
The shadow blur is in pixels, and only supports pixel dimensions. A blur of 0 has a sharp edge, 1 is a blur of one pixel, 2 two pixels and so on. It is not affected by the 2d API transformation. Shadow spread is not supported by the canvas 2D API.
BTW values should be qualified. I have added px where you forgot them in the CSS.
So CSS
box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.15);
becomes
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.15)";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 1;

Microsoft Edge Image Scaling

How do i make an image scale with bicubic for MS Edge? Is there some CSS or similar that can change the behavour.
See this page: http://duttongarage.com/Race-Workshop~317
On the right there are two images that have textured background, you can see the weird artifacts quite clearly
Chrome on the Left, MS Edge on the Right. As you can see there is some weird moire effect from the resize being nearest neighbor or linear, not bicubic.
Another example that is more typical:
Microsoft Edge on Top, Chrome on the Bottom. Notice the pixelation, its like what i would expect from browsers from the last decade.
Sorry for stupidness of answer, but, as I can see, Edge doesn't support any image-rendering options, so, please, try to use jQuery to resize picture.
For example, you can use solution from this answer:
just create <canvas id="canvas"></canvas> under your image and see:
screenshot
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
img = new Image();
img.onload = function () {
canvas.height = canvas.width * (img.height / img.width);
/// step 1
var oc = document.createElement('canvas'),
octx = oc.getContext('2d');
oc.width = img.width * 0.5;
oc.height = img.height * 0.5;
octx.drawImage(img, 0, 0, oc.width, oc.height);
/// step 2
octx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5);
ctx.drawImage(oc, 0, 0, oc.width * 0.5, oc.height * 0.5,
0, 0, canvas.width, canvas.height);
}
img.src = "http://duttongarage.com/img/2167/824";
You can easily adjust oc.width with math. For example, you can use
oc.width = $(".me-wrap-image").width();
oc.height = $(".me-wrap-image").height();
Better, if you adjust your structure by
| .me-wrap-image
| .some-class-to-get-width-and-height
-> img
for img.src you can use
$("div.some-class-to-get img").each(function(){
img.src = $(this).attr('src');
});
But I'm not sure, how to make it work properly.
Hope you fix it :)
Obsolete
This is obsolete solution and does not work on recent versions of MS Edge.
===========
This little css tweak fixed problem for me:
img {
-ms-interpolation-mode: bicubic;
}

CSS Saturate Filter on Canvas

I have a canvas which is currently drawing a grey scale diagram (JSBin example).
It's effectively a radial progress meter, that will be used a lot in the application. However, rather than colouring it with Javascript, I'd prefer to be able to give it a colour based on a class.
I thought it would be an ideal use case for CSS filters. I'd draw the default progress meter in gray, then use CSS filters to add saturation and do a hue rotation, in order to achieve blue, orange and green too.
canvas {
-webkit-filter: saturate(8);
}
The rule was supported and valid in Chrome, but the problem was, it doesn't seem to change the saturation at all.
I'm imagining that #aaa is transformed into it's HSL counterpart hsl(0, 0%, 67%). Then when I increase the saturation with a filter, it should become more saturated, but for the same hue.
I was hoping to end up with something like hsl(0, 50%, 67%) but instead, the filter doesn't seem to change the colour at all, no matter what value I use.
Any ideas?
It turns out if you draw the meter with some saturation initially, you can use the hue-rotate filter and then you can desaturate them to achieve grey scale again.
http://jsbin.com/qohokivobo/2/edit?html,css,output
Conceptually, this isn't an answer. But in the meantime, it's a solution.
What about picking the color from the CSS style ?
canvas {
color: red;
}
function init() {
let canvas = document.getElementById('test'),
context = canvas.getContext('2d');
let style = window.getComputedStyle (canvas);
let color = style.color;
canvas.width = 300;
canvas.height = 300;
let x = canvas.width / 2,
y = canvas.height / 2;
context.beginPath( );
context.arc(x, y, 100, 0, 2 * Math.PI, false);
context.strokeStyle = color;
context.lineWidth = 20;
context.stroke();
context.globalAlpha = 0.85;
context.beginPath();
context.arc(x, y, 100, 0, Math.PI + 0.3, false);
context.strokeStyle = '#eee';
context.stroke();
}
demo

Resources