- From: Philip Taylor <excors+whatwg@gmail.com>
- Date: Fri, 15 Jun 2007 01:59:54 +0100

I've looked at how Safari renders shadows - the spec should probably define something similar, since it works and it's not insane or anything. Just before a shape/image is drawn, a shadow image is created (based on the original shape/image's alpha values (ignoring the RGB entirely) and the shadow colour/offset/blur). That shadow image is then drawn as normal (affected by globalAlpha and globalCompositeOperation), and then the original shape/image is drawn on top as normal. The shadow image copies the original alpha values, then gets Gaussian-blurred (http://en.wikipedia.org/wiki/Gaussian_blur etc). The ? parameter in the Gaussian function is derived from shadowBlur: as far as I can tell, the best approximation to Safari's behaviour is with ? = (if shadowBlur < 8 then shadowBlur/2 else sqrt(2*shadowBlur)). <http://canvex.lazyilluminati.com/misc/shadow/shadow1.html> (in Safari) shows its shadow rendering compared to that Gaussian function. There's not a perfect correspondence, but there's at least one place where Safari is simply buggy (it cuts off the left edge by one pixel when shadowBlur = 6) so it's never going to be a perfect correspondence, and it looks close enough to me. But if anyone has a better idea of the exact equation, that would be good to know, since I got fed up with trying to guess :-) After that, it's just multiplied by the shadow colour and then drawn. <http://canvex.lazyilluminati.com/misc/shadow/shadow2.html> shows (in the middle column) that it works the same as Safari's shadows when manually drawing the shadow image (using lots of temporary bitmaps for the blurring) then compositing that and then compositing the original image on top. The shadowOffset and shadowBlur are unaffected by transformations, as in <http://canvex.lazyilluminati.com/misc/shadow/shadow3.html>. I think the definition would be like: -------------------------------- 3.14.11.1.6. Shadows All drawing operations are affected by the four global shadow attributes. The shadowColor attribute sets the color of the shadow. When the context is created, the shadowColor attribute initially must be fully-transparent black. The shadowOffsetX and shadowOffsetY attributes specify the distance that the shadow will be offset in the positive horizontal and positive vertical distance respectively. Their values are in coordinate space units, and are not affected by the transformation matrix. When the context is created, the shadow offset attributes initially have the value 0. The shadowBlur attribute specifies the number of coordinate space units that the blurring is to cover, and is not affected by the transformation matrix. On setting, negative numbers must be ignored, leaving the attribute unmodified. When the context is created, the shadowBlur attribute must initially have the value 0. Support for shadows is optional. When they are supported, then, when shadows are drawn, they must be rendered using the specified color, offset, and blur radius as described below. When they are not supported, shadows must be rendered as if the shadow color was transparent black. [...] 3.14.11.1.11. Drawing model When a shape or image is painted, user agents must follow these steps, in the order given (or act as if they do): * If the current transformation matrix is infinite, then do nothing. Abort these steps. * The coordinates are transformed by the current transformation matrix. * The shape or image is rendered, creating image A, as described in the previous sections. For shapes, the current fill, stroke, and line styles must be honoured. * The shadow image is rendered, as a Gaussian-blurred version of the alpha channel from image A: * Create a shadow bitmap, filled with transparent black. * For every pixel in image A, with position (x, y): * For every pixel in the shadow image, with position (x', y'): * Let u = x' - (x + shadowOffsetX). Let v = y' - (y + shadowOffsetY). * If shadowBlur is zero, then: * If u = v = 0 then let G = 1. Otherwise, let G = 0. Otherwise, shadowBlur in nonzero: * If shadowBlur < 8, let ? = shadowBlur/2. Otherwise, let ? = sqrt(2*shadowBlur). * Let G = 1/(2 ? ?^2) e^-(u^2 + v^2)/(2 ?^2). * Let (r, g, b, a) be the components of shadowColor. Let a' be the alpha component of the pixel in image A at (x, y). Add the value (r, g, b, a * a' * G) onto the shadow image at (x', y'), using the Porter-Duff 'plus' operator. * The shadow image has its alpha adjusted by globalAlpha. * Within the clip region (as affected by the current transformation matrix), the shadow image is composited over the current canvas bitmap using the current composition operator. * The previous two steps are repeated, using image A instead of the shadow image. -------------------------------- (I haven't tried actually implementing it in the way detailed above, so the description may be buggy, but I can't see anything wrong myself so I guess it's probably alright.) It is assumed that all the images (particularly image A) have infinite size, so a shape drawn entirely off-screen can still cast shadows into the visible area. The algorithm as specified is quite horrendously inefficient, but the obvious optimisations are to skip the entire shadow part if the shadow colour is fully transparent, and to perform the Gaussian blur by doing the horizontal and vertical components separately and ignoring the bits where max(u,v) > shadowBlur (because G will be so small that its contribution to the shadow will be lost in the rounding errors). I assume implementors can work that out for themselves, since it's just a standard Gaussian blur - the only peculiar bit is the mapping from shadowBlur to ?. So that's all alright. One odd issue is with clearRect - Safari sort of applies shadows to that, as in <http://canvex.lazyilluminati.com/misc/shadow/shadow4.html>, except it ignores the colour and just uses the blur. It seems sensible to just call that a bug, and require that shadows never apply to clearRect since it doesn't go through the Drawing Model at all. I'll try to look out for any other possible problem areas. -- Philip Taylor excors at gmail.com

Received on Thursday, 14 June 2007 17:59:54 UTC