Notes:

The Wikipedia article defines the bilinear blended Coons patch C(s,t) bounded by the four curves c0(s), c1(s), d0(t), and d1(t) as

Lc(s,t) = (1 t)c0(s) + tc1(s) (1) Ld(s,t) = (1 s)d0(t) + sd1(t) (2) B(s,t) = (1 s)(1 t)c0(0) + s(1 t)c0(1) + (1 s)tc1(0) + stc1(1) (3) < x,y >= C(s,t) = Lc(s,t) + Ld(s,t) B(s,t) (4)

For SVG mesh gradients the four curves are cubic Bézier curves.

The inkscape wiki states that color blending inside the patch is bilinear. (I have not found where this is specified in Advanced Gradients for SVG ) . So presumably given colors Hij for the four corners we can compute a color within the patch using the formula

< r,g,b >= H(s,t) = (1 s)(1 t)H00 + (1 s)tH01 + s(1 t)H10 + stH11 (5)

My goal is to develop automated software for creating a mesh gradient which best matches a masked source image. There has already been research in this area (Image Vectorization using Optimized Gradients for Ferguson patches) .

A reasonable goal is to minimize the error

E = s,t||I(C(s,t)) H(s,t)||2 (6)

which might be an oversimplification since I(x,y) is sampling an RGB image. (What if we tried to optimize in HSV space?)

The Jian Sun et al paper identifies this as a non-linear least squares problem and the paramters include color parameters Hij , and the space parameters are the control points for c0, c1, d0, and d1 (which I will label Pij). They suggest a Levenberg-Marquardt algorithm which depends upon computing a Jacobian (with which I am not very familiar). Maybe it is a Jacobian instead of a gradient because the image has red/green/blue channels.

In any case, I am going to have to figure out how to compute formulas for things like

E Hχij

E Pγij

and evaluate them over the entire image. (I use χ to represent a chroma element like r, g, or b; and γ stands in for a geometric component like x or y.)

Computing these partial differentials is relatively straightforward if we iterate over s and t.

E Hr00 = Hr00 s,t||I(C(s,t)) H(s,t)||2 (7) = s,t Hr00||I(C(s,t)) H(s,t)||2 (8) = s,t Hr00 (Ir(C(s,t)) Hr(s,t))2 + (I g(C(s,t)) Hg(s,t))2 + (I b(C(s,t)) Hb(s,t))2 (9) = s,t Hr00(Ir(C(s,t)) Hr(s,t))2 + Hr00(Ig(C(s,t)) Hg(s,t))2 + Hr00(Ib(C(s,t)) Hb(s,t))2 (10) Hr00(Ir(C(s,t)) Hr(s,t))2 = 2(I r(C(s,t)) Hr(s,t)) Hr00(Ir(C(s,t)) Hr(s,t)) (11) = 2(Ir(C(s,t)) Hr(s,t))( Hr00Ir(C(s,t)) Hr00Hr(s,t)) (12) = 2(Ir(C(s,t)) Hr(s,t))(0 Hr00Hr(s,t)) (13) Hr(s,t) = (1 s)(1 t)Hr00 + (1 s)tHr01 + s(1 t)Hr10 + stHr11 (14) Hr00Hr(s,t) = Hr00(1 s)(1 t)Hr00 + Hr00(1 s)tHr01 + Hr00s(1 t)Hr10 + Hr00stHr11 (15) = (1 s)(1 t) + 0 + 0 + 0 (16) Hr01Hr(s,t) = (1 s)t (17) Hr10Hr(s,t) = s(1 t) (18) Hr11Hr(s,t) = st (19) Hr00(Ig(C(s,t)) Hg(s,t))2 = 0 (20) Hr00(Ib(C(s,t)) Hb(s,t))2 = 0 (21) E Hr00 = s,t2(Ir(C(s,t)) Hr(s,t))((1 s)(1 t)) (22) (23) 

Now that we have an example of the partial differential with respect to one of the chroma parameters we can ask ourselves: do we really want to calculate the error as the sum over s and t?

I feel it more appropriate to calculate it as the sum over x and y. That then raises the question: should our algorithm iterate over the relevant pixels of the image and calculate the correct s and t to give each x and y? Or should it instead iterate over s and t and scale each element of the sum by x s, x t , y s, and y t as appropriate? The nature of the formula I(C(s,t)) especially concerns me since x,y = C(s,t) and I’m not absolutely confident of the proper formulation of E Pγij for a specific x,y.

(Now that I have reacquainted myself with integration by substitution for multiple variables) One option is to sum over s and t and use the determinant of the Jacobian to scale the result to x & y space. This gives us a new formula for E.

detJ = x sx t y s y t

E = s,t||I(C(s,t)) H(s,t)||2 detJ

Let’s find out what that Jacobian determinant will look like:

x s, y s = sC(s,t) = sLc(s,t) + sLd(s,t) sB(s,t) (24) sLc(s,t) = s (1 t)c0(s) + tc1(s) (25) = (1 t) sc0(s) + t sc1(s) (26) c0(s) = (1 s)3P 00 + 3(1 s)2sP 10 + 3(1 s)s2P 20 + s3P 30 (27) sc0(s) = s (1 s)3P 00 + s 3(1 s)2sP 10 + s 3(1 s)s2P 20 + s s3P 30 (28) = 3(1 s)2P 00 + 3 (1 s)2 2(1 s)sP 10 + 3 (1 s)2s s2 P 20 + 3s2P 30 (29) = 3(1 s)2P 00 + 3(1 s)2P 10 6(1 s)sP10 + 6(1 s)sP20 3s2P 20 + 3s2P 30 (30) = 3(1 s)2(P 10 P00) + 6(1 s)s(P20 P10) + 3s2(P 30 P20) (31) c1(s) = (1 s)3P 03 + 3(1 s)2sP 13 + 3(1 s)s2P 23 + s3P 33 (32) sc1(s) = 3(1 s)2(P 13 P03) + 6(1 s)s(P23 P13) + 3s2(P 33 P23) (33) sLc(s,t) = (1 t) sc0(s) + t sc1(s) (34) = (1 t) 3(1 s)2(P 10 P00) + 6(1 s)s(P20 P10) + 3s2(P 30 P20) (35) + t 3(1 s)2(P 13 P03) + 6(1 s)s(P23 P13) + 3s2(P 33 P23) sLd(s,t) = s (1 s)d0(t) + sd1(t) (36) = d0(t) + d1(t) (37) sB(s,t) = s (1 s)(1 t)c0(0) + s(1 t)c0(1) + (1 s)tc1(0) + stc1(1) (38) = (1 t)c0(0) + (1 t)c0(1) tc1(0) + tc1(1) (39) = (1 t)(c0(1) c0(0)) + t(c1(1) c1(0)) (40) = (1 t)(P30 P00) + t(P33 P03) (41) sC(s,t) = sLc(s,t) + sLd(s,t) sB(s,t) (42) = (1 t) 3(1 s)2(P 10 P00) + 6(1 s)s(P20 P10) + 3s2(P 30 P20) (43) + t 3(1 s)2(P 13 P03) + 6(1 s)s(P23 P13) + 3s2(P 33 P23) d0(t) + d1(t) (1 t)(P30 P00) + t(P33 P03)

And after all that I need a vacation. tC(s,t) will be just as tortuous and have a similar structure, although the structure of the formulas that fall out of Ld and Lc will be swapped.

The source image will be a discrete matrix of RGB triples. C(s,t) will often not give integer x and y values, so some form of interpolation will be necessary. In images with noise (any photograph, or even a JPEG of a smooth computer painting) it is reasonable to perform some smoothing which might synergize with the interpolation nicely.

Computing geometric component E Pγij is somewhat more complicated than the chroma components.

E Pγij = Pγij s,t||I(C(s,t)) H(s,t)||2 detJ (44)  = s,t Pγij ||I(C(s,t)) H(s,t)||2 detJ (45)  = s,t detJ Pγij ||I(C(s,t)) H(s,t)||2 + ||I(C(s,t)) H(s,t)||2 Pγij detJ (46)  Pγij ||I(C(s,t)) H(s,t)||2 = Pγij Ir(C(s,t)) Hr(s,t)2 + I g(C(s,t)) Hg(s,t)2 + I b(C(s,t)) Hb(s,t)2 (47)  = Pγij Ir(C(s,t)) Hr(s,t)2 + Pγij Ig(C(s,t)) Hg(s,t)2 + Pγij Ib(C(s,t)) Hb(s,t)2 (48)  Pγij Ir(C(s,t)) Hr(s,t)2 = 2 I r(C(s,t)) Hr(s,t) Pγij Ir(C(s,t)) Hr(s,t) (49)  = 2 Ir(C(s,t)) Hr(s,t) PγijIr(C(s,t)) PγijHr(s,t) (50)  PγijHr(s,t) = 0 (51)   because the bilinear color interpolation has no Pγij terms in it x,y = C(s,t) (52)  PγijIr(C(s,t)) = xIr(x,y) x PγijC(s,t) + yIr(x,y) y PγijC(s,t) (53)  y PxijC(s,t) = 0 (54)  x PyijC(s,t) = 0 (55)   because the Pxij does not affect y, and Pyij does not affect x, but the homogenous combos require work. γ PγijC(s,t) = γ Pγij Lc(s,t) + Ld(s,t) B(s,t) (56)  = γ PγijLc(s,t) + γ PγijLd(s,t) γ PγijB(s,t) (57)  γ PγijLc(s,t) = γ Pγij (1 t)c0(s) + tc1(s) (58)  = γ Pγij (1 t) (1 s)3P 00 + 3(1 s)2sP 10 + 3(1 s)s2P 20 + s3P 30 + t (1 s)3P 03 + 3(1 s)2sP 13 + 3(1 s)s2P 23 + s3P 33 (59)  γ Pγ00Lc(s,t) = (1 t)(1 s)3 (60)  γ Pγ10Lc(s,t) = 3(1 t)(1 s)2s (61)  γ Pγ20Lc(s,t) = 3(1 t)(1 s)ss (62)  γ Pγ30Lc(s,t) = (1 t)s3 (63)  γ Pγ03Lc(s,t) = t(1 s)3 (64)  γ Pγ13Lc(s,t) = 3t(1 s)2s (65)  γ Pγ23Lc(s,t) = 3t(1 s)ss (66)  γ Pγ33Lc(s,t) = ts3 (67)  γ Pγi1Lc(s,t) = 0 (68)  γ Pγi2Lc(s,t) = 0 (69)  γ PγijLd(s,t) = γ Pγij (1 s)d0(t) + sd1(t) (70)  = γ Pγij (1 s) (1 t)3P 00 + 3(1 t)2tP 01 + 3(1 t)t2P 02 + t3P 03 + s (1 t)3P 30 + 3(1 t)2tP 31 + 3(1 t)t2P 32 + t3P 33 (71)  γ Pγ00Ld(s,t) = (1 s)(1 t)3 (72)  γ Pγ01Ld(s,t) = 3(1 s)(1 t)2t (73)  γ Pγ02Ld(s,t) = 3(1 s)(1 t)t2 (74)  γ Pγ03Ld(s,t) = (1 s)t3 (75)  γ Pγ30Ld(s,t) = s(1 t)3 (76)  γ Pγ31Ld(s,t) = 3s(1 t)2t (77)  γ Pγ32Ld(s,t) = 3s(1 t)t2 (78)  γ Pγ33Ld(s,t) = st3 (79)  γ Pγ1jLd(s,t) = 0 (80)  γ Pγ2jLd(s,t) = 0 (81)  γ PγijB(s,t) = γ Pγij (1 s)(1 t)c0(0) + s(1 t)c0(1) + (1 s)tc1(0) + stc1(1) (82)  = γ Pγij (1 s)(1 t)P00 + s(1 t)P30 + (1 s)tP03 + stP33 (83)  γ Pγ00B(s,t) = (1 s)(1 t) (84)  γ Pγ30B(s,t) = s(1 t) (85)  γ Pγ03B(s,t) = (1 s)t (86)  γ Pγ33B(s,t) = st (87)  γ Pγi1B(s,t) = 0 (88)  γ Pγi2B(s,t) = 0 (89)  γ Pγ1jB(s,t) = 0 (90)  γ Pγ2jB(s,t) = 0 (91)  and now let us start gluing things back together γ Pγ00C(s,t) = (1 t)(1 s)3 + (1 s)(1 t)3 (1 s)(1 t) (92)  γ Pγ10C(s,t) = 3(1 t)(1 s)2s (93)  γ Pγ20C(s,t) = 3(1 t)(1 s)s2 (94)  γ Pγ30C(s,t) = (1 t)s3 + s(1 t)3 s(1 t) (95)  γ Pγ01C(s,t) = 3(1 s)(1 t)2t (96)  γ Pγ31C(s,t) = 3s(1 t)2t (97)  γ Pγ02C(s,t) = 3(1 s)(1 t)t2 (98)  γ Pγ32C(s,t) = 3s(1 t)t2 (99)  γ Pγ03C(s,t) = t(1 s)3 + (1 s)t3 (1 s)t (100)  γ Pγ13C(s,t) = 3t(1 s)2s (101)  γ Pγ23C(s,t) = 3t(1 s)s2 (102)  γ Pγ33C(s,t) = ts3 + st3 st (103)  (104) 

That leaves γI(x,y). One tactic for computing this would be to use the derivative of the bilinear interpolation of the samples on the boundary of the cell that x,y falls within, but that definition of the derivative is discontinous at cell boundaries (where x = x or y = y). Upgrading to a bicubic might be an option when I stop being lazy and figure out the related math. Then again, let’s be super-lazy and apply a Gaussian blur to the image!

Let us call the original image J(u,v) over integer u and v coordinates. Then

I(x,y) = u,vJ(u,v) 1 2πσ2e(xu)2+(yv)2 2σ2 (105)

xI(x,y) = x u,vJ(u,v) 1 2πσ2e(xu)2+(yv)2 2σ2 (106) = u,vJ(u,v) 1 2πσ2 x e(xu)2+(yv)2 2σ2 (107) = u,vJ(u,v) 1 2πσ2 e(xu)2+(yv)2 2σ2 x (x u)2 + (y v)2 2σ2 (108) = u,vJ(u,v) 1 2πσ2 e(xu)2+(yv)2 2σ2 1 2σ2 x((x u)2 + (y v)2) (109) = u,vJ(u,v) 1 2πσ2 e(xu)2+(yv)2 2σ2 1 2σ22(x u) (110) yI(x,y) = u,vJ(u,v) 1 2πσ2 e(xu)2+(yv)2 2σ2 1 2σ22(y v) (111)

In practice the algorithm does not need to iterate over all of J(u,v) for every I(x,y). A radius of a couple of σ is enough to capture the dominant elements (because outside that radius the exponential term scales the values to inconsequentiality). Although I am torn on if it is a good idea to replace the 2πσ2 denominator with

u,ve(xu)2+(yv)2 2σ2 (112)