{"id":1495,"date":"2010-09-07T09:33:45","date_gmt":"2010-09-07T06:33:45","guid":{"rendered":"https:\/\/yehar.com\/blog\/?p=1495"},"modified":"2019-10-13T10:00:56","modified_gmt":"2019-10-13T07:00:56","slug":"phased-gaussian-convolution","status":"publish","type":"post","link":"https:\/\/yehar.com\/blog\/?p=1495","title":{"rendered":"Circularly symmetric convolution and lens blur"},"content":{"rendered":"<p>This article describes approaches for efficient<strong> isotropic two-dimensional convolution <\/strong>with disc-like and arbitrary circularly symmetric convolution kernels, and also discusses lens blur effects.<\/p>\n<p>Keywords:<em> depth of field, circle of confusion<\/em>, <em>bokeh<\/em>, <em>circular blur<\/em>, <em>lens blur<\/em>, <em>hexagonal blur, octagonal blur<\/em>, <em>real-time, DOF\n<\/em><\/p>\n<h2>Gaussian function approach<\/h2>\n<p>The circularly symmetric 2-d Gaussian kernel is<em> linearly separable<\/em>; the convolution can be split into a horizontal convolution followed by a vertical convolution. This makes 2-d Gaussian convolution relatively cheap, computationally, and \"Gaussian blur\" has become, partially for this reason, a popular operation in image processing. No other circularly symmetric (isotropic) convolution kernel is linearly separable. Usually a Gaussian kernel is real-valued. However, we can parameterize it with a complex number to obtain a complex 2-d Gaussian function that still satisfies conditions for separability and is circularly symmetric. This section of the article shows that the real part of weighted sums of such separable 2-d kernels can produce arbitrary 2-d circularly symmetric kernels. Special focus is given to implementation of circular blur, or convolution with a solid disk, which is useful for simulation of <em>bokeh<\/em> (circle of confusion) of camera lenses with fully open apertures.<\/p>\n<p><figure id=\"attachment_1496\" aria-describedby=\"caption-attachment-1496\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1496  \" title=\"gauss\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1496\" class=\"wp-caption-text\">A Gaussian convolution kernel, used in Gaussian blur (black = -maximum value, grey = 0, white = maximum value)<\/figcaption><\/figure><\/p>\n<p>A horizontal convolution followed by a vertical convolution (or in the opposite order) by 1-d kernels $f(x)$ and $f(y)$ effectively gives a 2-d convolution by 2-d kernel $f(x)\\times f(y)$. To produce separable circularly symmetric 2-d convolution, the condition that must be satisfied for all $(x, y)$ is $f(\\sqrt{x^2+y^2})\\times f(0) = f(x)\\times f(y)$. The right side of the equation is the 2-d kernel. The left side can be understood as taking a horizontal slice $f(x)\\times f(0)$ of the kernel at $y=0$ and substituting $x\\to r$ in the slice function to obtain a function $f(r)\\times f(0)$ of radius $r = \\sqrt{x^2+y^2}$ which the circularly symmetric kernel must be equal to at all $(x, y)$. If we assume that the 1-d kernels are normalized by $f(0)=1$, then the constraint for circular symmetry simplifies to $f(\\sqrt{x^2+y^2}) = f(x)\\times f(y)$. We will work with that simplifying assumption.<\/p>\n<p>For starters, let's treat magnitude and phase of the complex kernels separately. The circular symmetry condition may be broken down into a magnitude condition and a phase condition that must both be satisfied. The magnitude condition reads $|f(\\sqrt{x^2+y^2})| = |f(x) \\times f(y)| = |f(x)|\\times|f(y)| $, meaning that the kernel must have a Gaussian function as its magnitude function. Because in multiplication of complex numbers the phases are summed, the phase (argument) condition reads $\\theta(\\sqrt{x^2+y^2}) = \\theta(x)+\\theta(y)$, modulo $2\\pi$, with the shorthand $\\theta(x) = \\arg(f(x))$. The only family of phase functions to satisfy this is $\\theta(x) = a x^2$. The kernel function can be constructed as the product of a real Gaussian function, here called the <em>envelope<\/em>, and a <em>complex phasor<\/em> with $x^2$ as its argument. In one dimension these kernels have the form $e^{-a x^2} e^{i b x^2} = e^{- a x^2 + i b x^2} = e^{(- a + b i) x^2}$, where $i$ is the imaginary unit, $x$ is the spatial coordinate, $a$ is the spatial scaling constant of the envelope, and $b$ is the spatial scaling constant of the complex phasor. In two dimensions the form is $e^{- a x^2 + i b x^2} e^{- a y^2 + i b y^2} = e^{- a (x^2 + y^2) + i b (x^2 + y^2)} = e^{(- a + b i) (x^2 + y^2)}$, $y$ being the coordinate in the other dimension. These forms are not normalized to preserve average intensity in image filtering, but to give a value of 1 for $x = 0, y = 0$. Let's see what these look like.<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1497\" aria-describedby=\"caption-attachment-1497\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussreal.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1497  \" title=\"phasegaussreal\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussreal-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussreal-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussreal-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussreal.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1497\" class=\"wp-caption-text\">The real part of a phased Gaussian kernel<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1546\" aria-describedby=\"caption-attachment-1546\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussimag1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1546  \" title=\"phasegaussimag\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussimag1-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussimag1-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussimag1-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussimag1.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1546\" class=\"wp-caption-text\">The imaginary part of a phased Gaussian kernel<\/figcaption><\/figure><\/td>\n<\/tr>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1504\" aria-describedby=\"caption-attachment-1504\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphasereal.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1504  \" title=\"phasegaussphasereal\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphasereal-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphasereal-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphasereal-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphasereal.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1504\" class=\"wp-caption-text\">The real part of a phased Gaussian kernel of infinite spatial scale of the magnitude part, showing the phasing only<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1538\" aria-describedby=\"caption-attachment-1538\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphaseimag1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1538  \" title=\"phasegaussphaseimag\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphaseimag1-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphaseimag1-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphaseimag1-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegaussphaseimag1.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1538\" class=\"wp-caption-text\">The imaginary part of the phased Gaussian kernel of infinite spatial scale of the magnitude part, showing the phasing only<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1496\" aria-describedby=\"caption-attachment-1496\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1496  \" title=\"gauss\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/gauss.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1496\" class=\"wp-caption-text\">Magnitude of a phased Gaussian kernel, identical to the plain Gaussian kernel<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Arbitrary circularly symmetric shapes can be constructed by a weighted sum of the real parts and imaginary parts of complex phasors of different frequencies. Fourier series is the standard approach for such designs. These give a repetitive pattern, but the repeats could be diminished by adjusting the spatial scale of the envelope. As necessary, sloping of the envelope can be compensated for in the weights of the sum, but simultaneous optimization of both will give the best results, as it can take advantage of that the envelopes need not be the same for each of the component kernels.<\/p>\n<p>Let's try a 5-component square wave approximation, designed manually with the <a title=\"Fourier series applet\" href=\"http:\/\/www.falstad.com\/fourier\/\">Fourier series applet<\/a> of Paul Falstad.<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1521\" aria-describedby=\"caption-attachment-1521\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/square5component.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1521 \" title=\"square5component\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/square5component-300x270.png\" alt=\"\" width=\"300\" height=\"270\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/square5component-300x270.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/square5component.png 647w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1521\" class=\"wp-caption-text\">5-component equiripple square wave Fourier series<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1524\" aria-describedby=\"caption-attachment-1524\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/squarewave5componentreal.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1524  \" title=\"squarewave5componentreal\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/squarewave5componentreal-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/squarewave5componentreal-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/squarewave5componentreal-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/squarewave5componentreal.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1524\" class=\"wp-caption-text\">Real part of a square wave approximated by 5 phased Gaussian components of infinite spatial scale of the magnitude part.<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The formula for this series is $0.54100\\ +\\ 0.66318\\ \\cos(x)\\ -\\ 0.20942\\ \\cos(3 x)\\ +\\ 0.10471\\ \\cos(5 x)\\ -\\ 0.08726\\ \\cos(7 x)$. A bit of scaling (by 0.9) and shifting (by -0.01) was then done to make the ripples go evenly around the zero level and to avoid values greater than 1.<\/p>\n<p>This is a pretty good indicator of the capabilities of this approach. The weighted component kernels were:<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1530\" aria-describedby=\"caption-attachment-1530\" style=\"width: 150px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component0.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-thumbnail wp-image-1530 \" title=\"component0\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component0-150x150.png\" alt=\"\" width=\"150\" height=\"150\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component0-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component0-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component0.png 512w\" sizes=\"(max-width: 150px) 85vw, 150px\" \/><\/a><figcaption id=\"caption-attachment-1530\" class=\"wp-caption-text\">Component 0<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1531\" aria-describedby=\"caption-attachment-1531\" style=\"width: 150px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-thumbnail wp-image-1531\" title=\"component1\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component1-150x150.png\" alt=\"\" width=\"150\" height=\"150\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component1-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component1-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component1.png 512w\" sizes=\"(max-width: 150px) 85vw, 150px\" \/><\/a><figcaption id=\"caption-attachment-1531\" class=\"wp-caption-text\">Component 1<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1532\" aria-describedby=\"caption-attachment-1532\" style=\"width: 150px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-thumbnail wp-image-1532\" title=\"component2\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component2-150x150.png\" alt=\"\" width=\"150\" height=\"150\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component2-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component2-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component2.png 512w\" sizes=\"(max-width: 150px) 85vw, 150px\" \/><\/a><figcaption id=\"caption-attachment-1532\" class=\"wp-caption-text\">Component 2<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1533\" aria-describedby=\"caption-attachment-1533\" style=\"width: 150px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component3.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-thumbnail wp-image-1533\" title=\"component3\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component3-150x150.png\" alt=\"\" width=\"150\" height=\"150\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component3-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component3-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component3.png 512w\" sizes=\"(max-width: 150px) 85vw, 150px\" \/><\/a><figcaption id=\"caption-attachment-1533\" class=\"wp-caption-text\">Component 3<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1534\" aria-describedby=\"caption-attachment-1534\" style=\"width: 150px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component4.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-thumbnail wp-image-1534\" title=\"component4\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component4-150x150.png\" alt=\"\" width=\"150\" height=\"150\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component4-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component4-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/component4.png 512w\" sizes=\"(max-width: 150px) 85vw, 150px\" \/><\/a><figcaption id=\"caption-attachment-1534\" class=\"wp-caption-text\">Component 4<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>By lowering the spatial scale of the magnitude part of each component kernel, one gets a result like this:<\/p>\n<p><figure id=\"attachment_1550\" aria-describedby=\"caption-attachment-1550\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlefirsttry.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1550\" title=\"circlefirsttry\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlefirsttry-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlefirsttry-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlefirsttry-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlefirsttry.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1550\" class=\"wp-caption-text\">First try on a circular convolution kernel<\/figcaption><\/figure><\/p>\n<p>Not so bad! The halos would need to be eliminated, and the disk is also fading a bit toward the edges.<\/p>\n<p>I searched for better circular kernels by <a href=\"https:\/\/yehar.com\/blog\/?p=643\">global optimization<\/a> with an equiripple cost function, with the number of component kernels and the transition bandwidth as the parameters. Transition bandwidth defines how sharp the edge of the disk is. The sharper it is, the more ringing there will be both inside and outside the disk. A transition bandwidth setting of 1 means that the width of the transition is as large as the radius of the disk inside. Below, 1-d component kernel formulas are given for a pass band spanning $x \\in -1..1$ and a dual stop band spanning $ x\\ \\in\\ -1.2\\ ..\\ -1\\ \\cup\\ 1\\ ..\\ 1.2$.<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1561\" aria-describedby=\"caption-attachment-1561\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1561\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1561\" class=\"wp-caption-text\">Number of components: 1, transition bandwidth: 0.200000, ripple: \u00b10.232417. Component 0: (cos(x*x*1.624835) * 0.767583 + sin(x*x*1.624835) * 1.862321) * exp(-0.862325*x* x)<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1577\" aria-describedby=\"caption-attachment-1577\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1577\" title=\"components1\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components1-300x94.png\" alt=\"\" width=\"300\" height=\"94\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components1-300x94.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components1.png 420w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1577\" class=\"wp-caption-text\">1-d view<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1567\" aria-describedby=\"caption-attachment-1567\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1567\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc1-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc1-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc1-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc1.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1567\" class=\"wp-caption-text\">Number of components: 2, transition bandwidth: 0.200000, ripple: \u00b10.075459. Component 0: (cos(x*x*5.268909) * 0.411259 + sin(x*x*5.268909) * -0.548794) * exp(-0.886528*x *x) Component 1: (cos(x*x*1.558213) * 0.513282 + sin(x*x*1.558213) * 4.561110) * exp(-1.960518*x* x)<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1575\" aria-describedby=\"caption-attachment-1575\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1575\" title=\"components2\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components2-300x63.png\" alt=\"\" width=\"300\" height=\"63\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components2-300x63.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components2.png 640w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1575\" class=\"wp-caption-text\">1-d view of the components and their sum (magenta)<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1570\" aria-describedby=\"caption-attachment-1570\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1570\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1570\" class=\"wp-caption-text\">Number of components: 3, transition bandwidth: 0.200000, ripple: \u00b10.026297. Component 0: (cos(x*x*5.043495) * 1.621035 + sin(x*x*5.043495) * -2.105439) * exp(-2.176490*x *x) Component 1: (cos(x*x*9.027613) * -0.280860 + sin(x*x*9.027613) * -0.162882) * exp(-1.019306* x*x) Component 2: (cos(x*x*1.597273) * -0.366471 + sin(x*x*1.597273) * 10.300301) * exp(-2.815110* x*x)<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1572\" aria-describedby=\"caption-attachment-1572\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components3.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1572\" title=\"components3\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components3-300x116.png\" alt=\"\" width=\"300\" height=\"116\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components3-300x116.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components3.png 605w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1572\" class=\"wp-caption-text\">1-d view of the components and their sum (cyan)<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1580\" aria-describedby=\"caption-attachment-1580\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc3.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1580\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc3-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc3-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc3-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc3.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1580\" class=\"wp-caption-text\">Number of components: 4, transition bandwidth: 0.200000, ripple: \u00b10.010843. Component 0: (cos(x*x*1.553635) * -5.767909 + sin(x*x*1.553635) * 46.164397) * exp(-4.338459* x*x) Component 1: (cos(x*x*4.693183) * 9.795391 + sin(x*x*4.693183) * -15.227561) * exp(-3.839993* x*x) Component 2: (cos(x*x*8.178137) * -3.048324 + sin(x*x*8.178137) * 0.302959) * exp(-2.791880*x *x) Component 3: (cos(x*x*12.328289) * 0.010001 + sin(x*x*12.328289) * 0.244650) * exp(-1.342190* x*x)<\/figcaption><\/figure><\/td>\n<td>\n<p><figure id=\"attachment_1582\" aria-describedby=\"caption-attachment-1582\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1582\" title=\"components4\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4-300x159.png\" alt=\"\" width=\"300\" height=\"159\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4-300x159.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4.png 435w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1582\" class=\"wp-caption-text\">1-d view of the components and their sum (brown)<\/figcaption><\/figure><\/p>\n<p><figure id=\"attachment_1586\" aria-describedby=\"caption-attachment-1586\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4b.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1586\" title=\"components4b\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4b-300x226.png\" alt=\"\" width=\"300\" height=\"226\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4b-300x226.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components4b.png 523w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1586\" class=\"wp-caption-text\">Close-up<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1607\" aria-describedby=\"caption-attachment-1607\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc4.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1607\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc4-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc4-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc4-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc4.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1607\" class=\"wp-caption-text\">Number of components: 5, transition bandwidth: 0.200000, ripple: \u00b10.004062. Component 0: (cos(x*x*1.685979) * -22.356787 + sin(x*x*1.685979) * 85.912460) * exp(-4.892608 *x*x) Component 1: (cos(x*x*4.998496) * 35.918936 + sin(x*x*4.998496) * -28.875618) * exp(-4.711870 *x*x) Component 2: (cos(x*x*8.244168) * -13.212253 + sin(x*x*8.244168) * -1.578428) * exp(-4.052795 *x*x) Component 3: (cos(x*x*11.900859) * 0.507991 + sin(x*x*11.900859) * 1.816328) * exp(-2.929212* x*x) Component 4: (cos(x*x*16.116382) * 0.138051 + sin(x*x*16.116382) * -0.010000) * exp(-1.512961 *x*x)<\/figcaption><\/figure><\/td>\n<td>\n<p><figure id=\"attachment_1609\" aria-describedby=\"caption-attachment-1609\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5a.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1609\" title=\"components5a\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5a-300x175.png\" alt=\"\" width=\"300\" height=\"175\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5a-300x175.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5a.png 365w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1609\" class=\"wp-caption-text\">1-d view of the components and their sum (magenta)<\/figcaption><\/figure><\/p>\n<p><figure id=\"attachment_1610\" aria-describedby=\"caption-attachment-1610\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5b.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1610\" title=\"components5b\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5b-300x250.png\" alt=\"\" width=\"300\" height=\"250\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5b-300x250.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5b.png 476w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1610\" class=\"wp-caption-text\">Close-up<\/figcaption><\/figure><\/td>\n<td>\n<p><figure id=\"attachment_1612\" aria-describedby=\"caption-attachment-1612\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5c.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1612\" title=\"components5c\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5c-300x189.png\" alt=\"\" width=\"300\" height=\"189\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5c-300x189.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5c.png 357w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1612\" class=\"wp-caption-text\">Close-up of the pass band<\/figcaption><\/figure><\/p>\n<p><figure id=\"attachment_1613\" aria-describedby=\"caption-attachment-1613\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5d.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1613\" title=\"components5d\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5d-300x169.png\" alt=\"\" width=\"300\" height=\"169\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5d-300x169.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/components5d.png 400w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1613\" class=\"wp-caption-text\">Close-up of the stop band<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The last one of the above, with 5 component kernels, is good enough for most practical applications as the ripple is as small as 1\/250, about as much as the least significant bit for 8-bits-per-channel graphics, so invisible in almost all situations. As the number of components is increased, the components have an increasingly large weighted amplitude, as much as 36 times their sum for the 5-component system. This may become a practical problem if one aims for convolution kernel shapes with sharp transitions. A construction via Fourier series would probably give better-behaving components, but require more components than abusing the Gaussian envelope for ripple control as seems to be the case in the above given composite kernels.<\/p>\n<p>Here's a bonus, with 6 components:<\/p>\n<p><figure id=\"attachment_1672\" aria-describedby=\"caption-attachment-1672\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc6.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1672\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc6-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc6-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc6-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc6.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1672\" class=\"wp-caption-text\">Number of components: 6, transition bandwidth: 0.200000, ripple: 0.001918. Component 0: (cos(x*x*2.079813) * -82.326596 + sin(x*x*2.079813) * 111.231024) * exp(-5.14377 8*x*x) Component 1: (cos(x*x*6.153387) * 113.878661 + sin(x*x*6.153387) * 58.004879) * exp(-5.612426 *x*x) Component 2: (cos(x*x*9.802895) * 39.479083 + sin(x*x*9.802895) * -162.028887) * exp(-5.98292 1*x*x) Component 3: (cos(x*x*11.059237) * -71.286026 + sin(x*x*11.059237) * 95.027069) * exp(-6.5051 67*x*x) Component 4: (cos(x*x*14.810520) * 1.405746 + sin(x*x*14.810520) * -3.704914) * exp(-3.869579 *x*x) Component 5: (cos(x*x*19.032909) * -0.152784 + sin(x*x*19.032909) * -0.107988) * exp(-2.20190 4*x*x)<\/figcaption><\/figure><\/p>\n<p>The 1-d composite kernel can be truncated to range $x \\in -(\\textrm{pass band radius + transition bandwidth}\\ ..\\ \\textrm{pass band width + transition bandwidth})$. This only necessitates a stop band equiripple condition for $|x| \\in \\textrm{pass band radius + transition bandwidth}\\ ..\\ \\sqrt{2}(\\textrm{pass band radius+ transition bandwidth})$ to ensure a flat stop band in the corners of the 2-d convolution kernel square. However, at least for up to the above given 5th order composite kernel, the stop band ripples start to slope out already within that range, so no savings can be made.<\/p>\n<p>Let's see the 5-component circular blur in action.<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1651\" aria-describedby=\"caption-attachment-1651\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lena.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1651\" title=\"lena\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lena-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lena-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lena-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lena.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1651\" class=\"wp-caption-text\">The standard test image, Lena<\/figcaption><\/figure><\/td>\n<td><\/td>\n<\/tr>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1656\" aria-describedby=\"caption-attachment-1656\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1656\" title=\"lenacircular\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular-300x300.jpg\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular-300x300.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1656\" class=\"wp-caption-text\">Lena after 5-component circular blur with a disk diameter of 128 pixels and an additional transition band of 25.6 + 25.6 pixels . (contrast adjusted manually)<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1687\" aria-describedby=\"caption-attachment-1687\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss0.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1687\" title=\"lenagauss0\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss0-300x300.jpg\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss0-300x300.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss0-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss0.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1687\" class=\"wp-caption-text\">Gaussian blur of similar size<\/figcaption><\/figure><\/td>\n<\/tr>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1680\" aria-describedby=\"caption-attachment-1680\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular2.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1680\" title=\"lenacircular2\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular2-300x300.jpg\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular2-300x300.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular2-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular2.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1680\" class=\"wp-caption-text\">With halved blur disk radius, 32 pixels (contrast set manually)<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1688\" aria-describedby=\"caption-attachment-1688\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss1.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1688\" title=\"lenagauss1\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss1-300x300.jpg\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss1-300x300.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss1-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss1.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1688\" class=\"wp-caption-text\">Gaussian blur of similar size<\/figcaption><\/figure><\/td>\n<\/tr>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1681\" aria-describedby=\"caption-attachment-1681\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular3.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1681\" title=\"lenacircular3\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular3-300x300.jpg\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular3-300x300.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular3-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenacircular3.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1681\" class=\"wp-caption-text\">With 16 pixels disk radius (contrast set manually)<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1689\" aria-describedby=\"caption-attachment-1689\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss2.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1689\" title=\"lenagauss2\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss2-300x300.jpg\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss2-300x300.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss2-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/lenagauss2.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1689\" class=\"wp-caption-text\">Gaussian blur of similar size<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The circular blur here is the weighted sum of the real and imaginary parts of the 5 component convolutions.<\/p>\n<p>Let's try another image, one which will better show the blur disks.<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1660\" aria-describedby=\"caption-attachment-1660\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubble.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1660\" title=\"hubble\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubble-300x298.jpg\" alt=\"\" width=\"300\" height=\"298\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubble-300x298.jpg 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubble-150x150.jpg 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubble.jpg 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1660\" class=\"wp-caption-text\">Hubble Deep View<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1670\" aria-describedby=\"caption-attachment-1670\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubblecircular.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1670\" title=\"hubblecircular\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubblecircular-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubblecircular-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubblecircular-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hubblecircular.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1670\" class=\"wp-caption-text\">Hubble Deep View after 5-component circular blur (arbitrary contrast)<\/figcaption><\/figure><\/td>\n<\/tr>\n<tr valign=\"top\">\n<td><figure id=\"attachment_1675\" aria-describedby=\"caption-attachment-1675\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc7.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1675\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc7-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc7-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc7-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc7.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1675\" class=\"wp-caption-text\">With halved radius, 32 pixels<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1676\" aria-describedby=\"caption-attachment-1676\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc8.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1676\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc8-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc8-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc8-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc8.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1676\" class=\"wp-caption-text\">disk radius halved once more, now 16 pixels (the bands at the top and at the bottom are due to some programming bug)<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Looks like an out-of-focus camera, doesn't it?<\/p>\n<h3>Recursive approximations<\/h3>\n<p>The examples presented above were done with 1-d <em>finite impulse response<\/em> (FIR) filters of which I now have a FFT-based implementation. More efficient would be to use identical pairs of complex 1-d causal and anti-causal <em>infinite impulse response<\/em> (IIR) filters to achieve a symmetric composite filter. Early results:<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_2262\" aria-describedby=\"caption-attachment-2262\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/FirstRecursiveApprox.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2262\" title=\"FirstRecursiveApprox\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/FirstRecursiveApprox-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/FirstRecursiveApprox-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/FirstRecursiveApprox-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/FirstRecursiveApprox.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2262\" class=\"wp-caption-text\">First attempt at tail-truncated parallel poles approximation of phased Gaussian components of a 3-component composite kernel (on the right). 3 complex poles were used for each direction, employing the structure: (left+right)*(up+down) and identical coefficient sets for all directions. The coefficients used have not yet been re-optimized to increase the resulting kernel quality directly.<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_1570\" aria-describedby=\"caption-attachment-1570\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-1570\" title=\"valodisk\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/valodisc2.png 512w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-1570\" class=\"wp-caption-text\">The \"exact\" composite kernel. Note that the ringing outside the disc is suppressed in the approximation (on the left) due to tail truncation. The transition band spans -1.2..-1 and 1..1.2.<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The above first attempt filter is quite heavy, about 110 complex multiplications (some of which involve real numbers) per color channel per pixel. But it is independent of the size of the blur (disregarding boundary conditioning that may add a penalty proportional to the radius times the number of edge pixels in the image).<\/p>\n<p>Perhaps circular blur is not the best use for the phased Gaussian kernels, as no sharp edges can be achieved. How about constructing an <em>airy disc<\/em> for anisotropic low-pass filtering of the image in a manner that gives an equal upper frequency limit for features in diagonal, horizontal, vertical, or any direction? Food for thought... Update: <a href=\"https:\/\/dsp.stackexchange.com\/a\/58634\/15347\">yes, it works<\/a><\/p>\n<h3>Updates<\/h3>\n<p>There are now GPU implementations of this method out there. Kleber Garcia gave a SIGGRAPH 2017 talk (<a href=\"https:\/\/doi.org\/10.1145\/3084363.3085022\">accompanying conference paper<\/a>) about his 1 and 2-component blur implementations. They can be seen at work in <a href=\"https:\/\/www.fifauteam.com\/fifa-17-screenshots-images\/\">FIFA 17 screenshots<\/a>. The talk inspired Bart Wronski to write <a href=\"https:\/\/www.shadertoy.com\/view\/lsBBWy\">a Shadertoy implementation<\/a>, with <a href=\"https:\/\/bartwronski.com\/2017\/08\/06\/separable-bokeh\/\">an accompanying text<\/a>. Kleber Garcia responded with <a href=\"https:\/\/www.shadertoy.com\/view\/Xd2BWc\">his own Shadertoy implementation<\/a>.<\/p>\n<p>I have also gained more understanding of the math involved. Each 1-d component kernel is really just a Gaussian function, but with a complex width parameter. The original condition I wrote for separability demands that A) the 2-d convolution kernel is a circularly symmetrical revolution of the 1-d kernel (the value of the 2-d kernel at radius $r$ equals the 1-d kernel at position $r$), B) the same coefficients are used for the horizontal and the vertical 1-d kernels. Then the final output for each component is a weighted sum of the real and imaginary parts of the 2-d convolution (implemented as a horizontal and a vertical 1-d convolution) result. The weighed sum with a weight $a$ for the real part and a weight $b$ for the imaginary part can be seen as <a href=\"https:\/\/en.wikipedia.org\/wiki\/Complex_number#Multiplication_and_division\">complex multiplication<\/a> by $a-bi$ followed by discarding of the imaginary part. If we modify requirement A to: \"the 2-d convolution kernel is circularly symmetrical\", then we can absorb the said multiplication into the 1-d convolution kernel by multiplying each coefficient by the complex number $\\sqrt{a-bi}$. Because we have two 1-d convolution passes, that number will be squared, hence the square root. Now we have coefficient-wise identical horizontal and vertical 1-d convolutions that produce a 2-d convolution with a kernel that is circularly symmetrical. The work saved is that we don't need to calculate (or store!) the imaginary output of the 2nd 1-d convolution, because only the real part of its result will be used.<\/p>\n<p>Having done this optimization, the 1-d kernel can be expressed (recycling variable names) simply as $(c+di)e^{(a + b i) x^2} = e^{ax^2}\\big(c\\cos(bx^2) - d\\sin(bx^2)\\big) + ie^{ax^2}\\big(d\\cos(bx^2) + c\\sin(bx^2)\\big),$ resulting in a 2-d kernel $(c+di)^2e^{(a+bi)(x^2+y^2)}.$ One can then globally optimize the coefficients $(a, b, c, d)$ of a horizontal slice $(c+di)^2e^{(a+bi)x^2}$ (note the square) of the 2-d kernel to get a desired shape of the real part $e^{ax^2}\\big((c^2 - d^2)\\cos(bx^2) - 2cd\\sin(bx^2)\\big)$ of the slice, or the sum of these from multiple components.<\/p>\n<p>Using this notation, the $(a, b, c, d)$ coefficients are obtained from the old coefficients $(a, b, r, i)$, where $r$ and $i$ are the weights of the real and the imaginary part of the component:<\/p>\n<pre class=\"crayon:false\">a = -a;\r\nb = b;\r\nc = sqrt(2)*(sqrt(r*r + i*i) + r)*sqrt(sqrt(r*r + i*i) - r)\/(2*i);\r\nd = -sqrt(2)*sqrt(sqrt(r*r + i*i) - r)\/2;\r\n\r\n\/\/ Use these coefs as: \r\n\/\/ real_coef = (c*cos(x*x*a) - d*sin(x*x*a)) * exp(b*x*x);\r\n\/\/ imag_coef = (d*cos(x*x*a) + c*sin(x*x*a)) * exp(b*x*x);\r\n\r\n\/\/ a, b, c, d:\r\n\/\/ 1-component\r\n-0.8623250000, 1.6248350000, 1.1793828124, -0.7895320249\r\n\/\/ 2-component\r\n-0.8865280000, 5.2689090000, -0.7406246191, -0.3704940302\r\n-1.9605180000, 1.5582130000, 1.5973700402, -1.4276936105\r\n\/\/ 3-component\r\n-2.1764900000, 5.0434950000, -1.4625695191, -0.7197739911\r\n-1.0193060000, 9.0276130000, -0.1480093005, -0.5502424493\r\n-2.8151100000, 1.5972730000, 2.2293886172, -2.3101178772\r\n\/\/ 4-component\r\n-4.3384590000, 1.5536350000, 4.5141678065, -5.1132787901\r\n-3.8399930000, 4.6931830000, -3.7350649493, -2.0384600009\r\n-2.7918800000, 8.1781370000, 0.0866540887, -1.7480940853\r\n-1.3421900000, 12.3282890000, 0.3569701172, -0.3426757426\r\n\/\/ 5-component\r\n-4.8926080000, 1.6859790000, 5.7626795783, -7.4542110865\r\n-4.7118700000, 4.9984960000, -6.4033389291, -2.2547313456\r\n-4.0527950000, 8.2441680000, -0.2167382954, -3.6413223544\r\n-2.9292120000, 11.9008590000, 1.0940793322, -0.8300714338\r\n-1.5129610000, 16.1163820000, -0.3717954486, -0.0134482550\r\n\/\/ 6-component\r\n-5.1437780000, 2.0798130000, 5.2941931370, -10.5050024737\r\n-5.6124260000, 6.1533870000, 10.9927011254, -2.6383360349\r\n-5.9829210000, 9.8028950000, -10.1550051566, -7.9777845753\r\n-6.5051670000, 11.0592370000, 4.8737688428, -9.7488280697\r\n-3.8695790000, 14.8105200000, -1.6383505756, -1.1306841329\r\n-2.2019040000, 19.0329090000, -0.1309780866, -0.4122368969\r\n<\/pre>\n<p>Negative values in the 2-d convolution kernel may be off-putting, because they create a cheap-looking sharpening kind of effect. I optimized some low component count coefficient sets that give almost no negative values in the 2-d convolution kernel:<\/p>\n<pre class=\"crayon:false\">\/\/ a, b, c, d\r\n\/\/ 1-component, stp-band maximum = 0.3079699786\r\n-2.0126256644, 1.0462519386, 1.7772360464, -1.5705215513\r\n\/\/ 2-component, stop-band maximum = 0.1422940279\r\n-1.8755934460, 5.6868281810, -1.1341154250, -0.4727562391\r\n-3.0859247607, 0.0311853116, 14.9843211153, -14.9911604837\r\n<\/pre>\n<p><figure id=\"attachment_4192\" aria-describedby=\"caption-attachment-4192\" style=\"width: 636px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/nonneg.jpg\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/nonneg.jpg\" alt=\"\" width=\"636\" height=\"257\" class=\"size-medium wp-image-4192\" \/><\/a><figcaption id=\"caption-attachment-4192\" class=\"wp-caption-text\">Corner-to-corner slice of the (mostly) nonnegative 1 and 2-component 2-d kernels.<\/figcaption><\/figure><\/p>\n<p>As noted by Bart Wronski, in addition to solid discs, also donuts may be desirable bokeh shapes. Donuts are associated at least with large focal length mirror lenses. It would probably be possible to optimize the components to get a visually impressive sharp donut, sharper than the discs shown. I did some experiments about that, but it didn't look very nice.<\/p>\n<p>For approximation of general 2-d (or 3-d) kernels that do not need to be circularly symmetric, as sums of separable 2-d convolutions, see Treitel, S. & Shanks, J. (1971). <a href=\"https:\/\/ieeexplore.ieee.org\/document\/4043447\">The design of multistage separable planar filters.<\/a> IEEE Transactions on Pattern Analysis and Machine Intelligence, 9(1), 10\u201327.<\/p>\n<h2>Circular blur using prefix sums and summed area tables<\/h2>\n<p>Circular blur done using the phased gaussian kernels is not perfect because it has the transition band. A completely different approach would be to calculate each blurred output pixel separately by summing the pixels that fall within the disk radius from the position of the pixel. There is a very efficient way to do this. The area of the disk can be subdivided into a minimum number of rectangular pieces and a <a title=\"Wikipedia: Summed area table\" href=\"http:\/\/en.wikipedia.org\/wiki\/Summed_area_table\">summed area table<\/a> can be used to efficiently calculate the sum of pixels within each rectangle. Here is an illustration of the idea:<\/p>\n<p><figure id=\"attachment_1740\" aria-describedby=\"caption-attachment-1740\" style=\"width: 361px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlepack.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-1740 \" title=\"circlepack\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlepack.png\" alt=\"\" width=\"361\" height=\"360\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlepack.png 602w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlepack-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/circlepack-300x300.png 300w\" sizes=\"(max-width: 361px) 85vw, 361px\" \/><\/a><figcaption id=\"caption-attachment-1740\" class=\"wp-caption-text\">A circle packed with a small number of rectangles.<\/figcaption><\/figure><\/p>\n<p><strong><a href=\"http:\/\/kylemcdonald.net\/\">Kyle McDonald<\/a><\/strong> has been working on the same problem and shows a more efficient packing with <a title=\"Fast Lens Blur Kernel - OpenProcessing\" href=\"http:\/\/www.openprocessing.org\/visuals\/?visualID=14151\">his Processing application<\/a>:<\/p>\n<p><figure id=\"attachment_2216\" aria-describedby=\"caption-attachment-2216\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldPacking.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2216\" title=\"McDonaldPacking\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldPacking-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldPacking-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldPacking-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldPacking.png 524w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2216\" class=\"wp-caption-text\">A more efficient packing by Kyle McDonald, requiring for this particular circle 60 summed area table lookups (or alternatively a total of 40 summed area table or sum table lookups).<\/figcaption><\/figure><\/p>\n<p>A somewhat simpler way would be to subdivide the circle into one-pixel-height strips. The sum of the pixel values of a strip could be calculated from the prefix sum over the length of each column, requiring only two lookups per strip. The number of rows would be roughly the diameter of the circle, and the number of lookups roughly four times the radius, or $4r$.<\/p>\n<p>By combining the use of summed area tables and horizontal and vertical prefix sums we get a rather boring-looking hybrid approach that appears to be the most efficient:<\/p>\n<p><figure id=\"attachment_2218\" aria-describedby=\"caption-attachment-2218\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldNiemitaloPacking.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2218\" title=\"McDonaldNiemitaloPacking\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldNiemitaloPacking-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldNiemitaloPacking-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldNiemitaloPacking-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/McDonaldNiemitaloPacking.png 524w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2218\" class=\"wp-caption-text\">A packing requiring less lookups than McDonald's if both summed area tables and row and column prefix sums are used. For this particular circle a total of 32 summed area table or prefix sum lookups are required.<\/figcaption><\/figure><\/p>\n<p>For large $r$, the hybrid approach takes about $(8-4\\sqrt{2})r \\approx 2.34\\ r$ lookups: 4 for the rectangle (from a summed area table) but dominated for large $r$ by 2 for each stripe (from horizontal or vertical prefix sum). In comparison, Kyle McDonald's approach takes about $(12-4\\sqrt{5})r \\approx 3.06\\ r$ table lookups with both summed area tables and prefix sums available and about $(16-\\frac{24\\sqrt{5}}{5})r \\approx 5.27\\ r$ table lookups with only the summed area table available. The latter result is identical for the hybrid approach. If the whole image is convolved with a disc of constant radius, the center rectangle in the hybrid approach can be done with a <a title=\"W. Jarosz: Fast Image Convolutions\" href=\"http:\/\/elynxsdk.free.fr\/ext-docs\/Blur\/Fast_box_blur.pdf\">constant complexity box blur<\/a> and a summed area table will not be needed, relieving requirements for the bit depth used in the calculations.<\/p>\n<p>To get an anti-aliased edge for the circle, the edge pixels can be summed or subtracted with weights separately, doable in the ready-for-prefix-sum representation. The number of pixels that should receive anti-aliasing is about $(4\\sqrt{2} + 16 - \\frac{24\\sqrt{5}}{5})r \\approx 10.9\\ r$, increasing the total cost of the algorithm to $(24 - \\frac{24\\sqrt{5}}{5})r \\approx 13.27\\ r$. Each weight will be shared by 8 edge pixels, which may give some computational savings. In conclusion, anti-aliased or not, the complexity of this way of blurring will be proportional to the radius of the disc times the number of pixels in the image. The decompositions (lookup recipes) of different size blurs could be stored in tables, which would allow also the use of a <em>depth map<\/em> to blur different parts of the image with a different disk size.<\/p>\n<p>Alternatively to exact antialising, one could approximate the longer monotonic antialias gradations as linear, by use of two horizontal prefix sum passes in series and two vertical prefix sum passes in series. The slope of each linear section would be thrown in first, then after calculation of the first prefix sum, the constant term of the linear section would be added, along with a value that terminates the section. Either of these would coincide with the values that define where the flat section of the disc starts for that horizontal or vertical stripe. The method could be made more accurate by increasing the number of linear sections, perhaps up to two per monotonous gradation.<\/p>\n<p>If different size blurs are used in the same image, then one decision must be made on how the result should look: whether the blur <em>scatters<\/em> or <em>gathers<\/em>. Let's demonstrate with a square box blur, increasing the blur size towards the top of the image:<\/p>\n<table>\n<tbody>\n<tr valign=\"top\">\n<td><figure id=\"attachment_2298\" aria-describedby=\"caption-attachment-2298\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxstart.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2298\" title=\"boxstart\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxstart-300x166.png\" alt=\"\" width=\"300\" height=\"166\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxstart-300x166.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxstart-1024x567.png 1024w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxstart.png 1082w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2298\" class=\"wp-caption-text\">Input image for box blur, just a single bright pixel on black background. Size of the box blur will vary by row as indicated by the width of the blue area<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_2296\" aria-describedby=\"caption-attachment-2296\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxcorrect.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2296\" title=\"boxcorrect\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxcorrect-300x166.png\" alt=\"\" width=\"300\" height=\"166\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxcorrect-300x166.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxcorrect-1024x567.png 1024w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxcorrect.png 1082w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2296\" class=\"wp-caption-text\">Scattering box blur with a width of 5, as designated for the position of the bright input pixel, scatters the intensity of the pixel to the surrounding 5 times 5 pixel area.<\/figcaption><\/figure><\/td>\n<td><figure id=\"attachment_2301\" aria-describedby=\"caption-attachment-2301\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxincorrect1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2301\" title=\"boxincorrect\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxincorrect1-300x166.png\" alt=\"\" width=\"300\" height=\"166\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxincorrect1-300x166.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxincorrect1-1024x567.png 1024w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/boxincorrect1.png 1082w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2301\" class=\"wp-caption-text\">Gathering box blur resulting from outputting at each position the average intensity of surrounding input pixels from an area the size of which is dictated by the output position.<\/figcaption><\/figure><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>With a variable blur kernel, scattering blur is more correct, because it preserves average image intensity, unlike gathering blur. With smooth blur size gradations, gathering blur should also look okay.<\/p>\n<p>If the kernel is constant, blurring can be described mathematically by <a title=\"Wikipedia: Convolution\" href=\"http:\/\/en.wikipedia.org\/wiki\/Convolution\">convolution<\/a> of the input image by the blur kernel: $o = i*K$, where $o$ is the output image, $i$ is the input image and $K$ is the blur kernel. There are two ways to directly implement the convolution: In the scattering way, first the output image is cleared to zero, and then for each input pixel, a copy of the kernel $K$ is multiplied by the intensity of the input pixel, spatially shifted to the position of the input pixel, and added to the output image. In the gathering way each output pixel is formed by shifting a copy of the kernel reversed along each axis, to the position of the output pixel, and setting the output pixel to the sum of products of the pixel intensities in the shifted and reversed kernel and the input image. If the same kernel is used throughout the image, both ways of doing convolution will be identical. But if the size of the blur varies, we must acknowledge whether we do a scattering or a gathering convolution by $K$. We denote these by $\\hat{*}$ for a scattering and $\\check{*}$ for a gathering convolution by the variable kernel. The mnemonic is that the arrowhead points away to indicate scattering.<\/p>\n<p title=\"Wikipedia: Convolution: Properties\">We define the order of calculation of written formulas to be from left to right with the exception that multiplication and convolution are calculated first and addition and subtraction last. The convolution operator $*$ has the following <a title=\"Wikipedia: Convolution: Properties\" href=\"http:\/\/en.wikipedia.org\/wiki\/Convolution#Properties\">algebraic properties<\/a> :<\/p>\n<ul>\n<li>Commutativity: $A*B = B*A$<\/li>\n<li>Associativity: $A*B*C = A*(B*C)$<\/li>\n<li>Distributivity: $A*C+B*C = (A+B)*C$<\/li>\n<li>Associativity with scalar multiplication: $a(A*B) = aA*B = A*(aB)$, where $a$ is a constant.<\/li>\n<\/ul>\n<p>With scattering convolution $\\hat{*}$ and gathering convolution $\\check{*}$ by a variable kernel we have a drastically limited set of algebraic properties:<\/p>\n<ul>\n<li>Distributivity: $A\\ \\hat{*}\\ C+B\\ \\hat{*}\\ C = (A+B)\\ \\hat{*}\\ C$ for scattering convolution and $A\\ \\check{*}\\ C+B\\ \\check{*}\\ C = (A+B)\\ \\check{*}\\ C$ for gathering convolution.<\/li>\n<li>Distributivity, again: $C\\ \\hat{*}\\ A+C\\ \\hat{*}\\ B = C\\ \\hat{*}\\ (A+B)$ for scattering convolution and $C\\ \\check{*}\\ A+C\\ \\check{*}\\ B = C\\ \\check{*}\\ (A+B)$ for gathering convolution. This can be used for simplification of the calculation if both A and B are sparse, because then (A+B) will also be sparse and some coincident values may cancel.<\/li>\n<li>Associativity with scalar multiplication: $a(A\\ \\hat{*}\\ B) = aA\\ \\hat{*}\\ B = A\\ \\hat{*}\\ (aB)$ for scattering convolution and $a(A\\ \\check{*}\\ B) = aA\\ \\check{*}\\ B = A\\ \\check{*}\\ (aB)$ for gathering convolution.<\/li>\n<\/ul>\n<p>When utilizing a summed area table, the constant-kernel calculation can be written as: $o = i*S_{xy}*K_{xy} = i*K_{xy}*S_{xy}$, where $o$ is the output image, $i$ is the input image, $S_{xy}$ is such a function that convolution with it gives the summed area table, and $K_{xy}$ is the sparse representation of the blur kernel that gives the blur kernel if convolved with $S_{xy}$ (or vice versa). It turns out that with a variable blur kernel, scattering convolution requires the scattering-friendly order of calculation: $o = i\\ \\hat{*}\\ K_{xy}*S_{xy}$, and gathering convolution the gathering-friendly order $o = i*S_{xy}\\ \\check{*}\\ K_{xy}$. Using the wrong order with a variable blur kernel would give rather unpredictable results and is not recommended. As a rule, <strong>if the kernel is not constant, scattering convolution should operate directly on the input image and gathering convolution should not be followed by further convolutions<\/strong>.<\/p>\n<p>The computational complexity of the whole calculation is independent of the order of convolutions. Convolving something with $S_{xy}$ is cheap. It is also cheap to convolve something with $K_{xy}$, because it is sparse, that is, zero in all but a few places, amounting to a sum of shifted copies of the function that is being convolved. Rather than discussing the number of table lookups, the computational complexity of the blur can be described by the number of non-zero elements in the sparse representation of its kernel.<\/p>\n<p>For box blur, the order of calculation does not affect the computational complexity, but if we use (for example to calculate circular blur) in addition to a summed area table also prefix sums, calculable by convolution by $S_x$ for the horizontal prefix sum and by $S_y$ for the vertical prefix sum, and noting that $S_{xy} = S_x*S_y$, we can optimize the <strong>gathering-friendly<\/strong> calculation $o = i*S_x*S_y\\ \\check{*}\\ K_{xy} + i*S_x\\ \\check{*}\\ K_x + i*S_y\\ \\check{*}\\ K_y$, where\u00a0$\\{K_x, K_y, K_{xy}\\}$ is a sparse representation of the blur kernel, from 4 prefix sums and 4 steps in a fully parallelized implementation to 3 prefix sums and 4 steps, noting that the term $i*S_x$ appears twice and need only be calculated once. For the <strong>scattering-friendly<\/strong> calculation $o = i\\ \\hat{*}\\ K_{xy}*S_x*S_y+ i\\ \\hat{*}\\ K_x*S_x + i\\ \\hat{*}\\ K_y*S_y = i\\ \\hat{*}\\ K_{x}*S_x + (i\\ \\hat{*}\\ K_y + i\\ \\hat{*}\\ K_{xy}*S_x)*S_y$, we can get from 4 prefix sums and 4 steps to 3 prefix sums and 5 steps by utilizing distributivity of convolution. A <strong>mixed-order<\/strong> calculation $o = ((i*S_x)*K_{xy} + i*K_y)*S_y + (i*S_x)*K_x$ can be implemented with 2 prefix sums and 5 steps. With a constant kernel, any of the orderings could be used.<\/p>\n<h3>Hexagons<\/h3>\n<p>On a hexagonal grid, kernels shaped like a regular hexagon are cheap:<\/p>\n<p><figure id=\"attachment_2397\" aria-describedby=\"caption-attachment-2397\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagon1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2397\" title=\"hexagon\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagon1-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagon1-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagon1-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagon1.png 422w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2397\" class=\"wp-caption-text\">A regular hexagon made from two columns with ends oriented along two of the axes.<\/figcaption><\/figure><\/p>\n<p>We are quite close to a circle already, with a constant number of non-zero elements in the kernel representation, but let's side-track for a bit and think that we actually want a hexagonal lens blur kernel, because for sure some cameras have 6 aperture blades. So we could create hexagonal blur by resampling from the normal square grid (lattice) to a hexagonal grid, doing the convolution with the hexagonal kernel, and then resampling back to the square grid. All three steps can be done in a constant number of computational operations per image pixel.<\/p>\n<p>The <strong>gathering-friendly<\/strong> version of unantialiased hexagonal kernel convolution can be written as $o = i*S_1*S_2\\ \\check{*}\\ K_{12} + i*S_1*S_3\\ \\check{*}\\ K_{31}$, where $S_1$, $S_2$, and $S_3$ are such functions that if one convolves with one of them, the result is a prefix sum along the numbered axis (the columns in the above figure are oriented along axis 1), and $\\{K_{12}, K_{31}\\}$ is a sparse representation of the hexagonal convolution kernel with a total of <strong>8 non-zero elements<\/strong>.\u00a0 <strong><\/strong>The calculation involves 3 prefix sums and 4 steps by reusing the factor $i*S_1$. The <strong>scattering-friendly<\/strong> version $o = (i\\ \\hat{*}\\ K_{12}*S_2 + i\\ \\hat{*}\\ K_{31}*S_3)*S_1$ takes also 3 prefix sums and 4 steps.<\/p>\n<p>For integer size hexagons (measured from center to corner), the hexagonal grid naturally avoids aliasing. To make things perfect, we want antialiasing of non-integral-size hexagons. That can be done by cross-fading between two hexagons that have a size difference of 1 measured from center to corner on the hexagonal grid. The anti-aliased non-integer size hexagon has <strong>16 non-zero elements<\/strong> in the 2-component sparse representation:<\/p>\n<p><figure id=\"attachment_2421\" aria-describedby=\"caption-attachment-2421\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagonaa2.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2421\" title=\"hexagonaa\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagonaa2-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagonaa2-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagonaa2-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexagonaa2.png 422w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2421\" class=\"wp-caption-text\">An anti-aliased hexagon made by cross-fading between two hexagons of successive integral sizes<\/figcaption><\/figure><\/p>\n<p><strong>Condat<\/strong>, <strong>Forster-Heinlein<\/strong>, and <strong>Van de Ville<\/strong> show in their paper <a href=\"http:\/\/miplab.epfl.ch\/pub\/condat0702.pdf\">\"H<sub>2<\/sub>O: Reversible Hexagonal-Orthogonal Grid Conversion by 1-D filtering\"<\/a> that it is possible to shear the coordinate system a few times to change between the hexagonal and the square (orthogonal) grid. In fact, only two shear operations are needed to move from the square grid to hexagonal:<\/p>\n<p><figure id=\"attachment_2310\" aria-describedby=\"caption-attachment-2310\" style=\"width: 248px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/shear.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-2310\" title=\"shear\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/shear.png\" alt=\"\" width=\"248\" height=\"183\" \/><\/a><figcaption id=\"caption-attachment-2310\" class=\"wp-caption-text\">Conversion from a square grid (yellow) to hexagonal (red) can be done by two shearing steps. The image shows the centers of four neighboring pixels in the square grid (dots), then shearing (along the blue line from dot to box) the rows horizontally by amount $a$ (multiplied by the row index), followed by shearing of each tilted column along their direction by a fraction $b$ of the distance between the tilted column pixels (along the blue line from a box to a cross), yielding with the correct parameters $a$ and $b$ an equal distance between the pixel centers that originated from (0, 0), (0, 1) and (1, 0)<\/figcaption><\/figure><\/p>\n<p>The shear amounts that give equal distances between neighboring pixels as in a proper hexagonal grid, are for the first shear, from the square coordinate system to the intermediate one: $a = \\frac{12^{1\/4}}{2}-\\frac{\\sqrt{2}\\ 3^{3\/4}}{6}\\approx 0.3933198931$ and for the second shear, from the intermediate to the hexagonal coordinate system: $b = \\frac{1}{2}-\\frac{\\sqrt{2}\\ 3^{3\/4}}{4}+\\frac{12^{1\/4}}{4}\\approx 0.1593749806$ . Shearing can be implemented by allpass fractional delay filters which are reversible by doing the filtering to the opposite direction. Approximating the shear amounts with rational values $a = \\frac{2}{5} = 0.4$ and $b = \\frac{1}{6} \\approx 0.1667$, the number of different filter coefficient sets can be limited to $2 + 3$ by abusing negative fractional delays, the fact that the fractional part of the delays will repeat the sequence $\\{0, \\frac{2}{5}, \\frac{4}{5}, \\frac{1}{5}, \\frac{3}{5}\\}$ for successive rows and $\\{0, \\frac{1}{6}, \\frac{2}{6}, \\frac{3}{6}, \\frac{4}{6}, \\frac{5}{6}\\}$ for successive tilted columns, and that zero delay needs no fractional delay filter.<\/p>\n<p>The cost of the rational approximation is a slight deformation of the convolution kernel. To assess the amount of distortion due to the rational approximation, we have a look at the triangle between the centers of neighboring sample points in the approximately hexagonal grid to see if it is close to an equilateral triangle:<\/p>\n<p><figure id=\"attachment_2316\" aria-describedby=\"caption-attachment-2316\" style=\"width: 250px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/deformedtriangle.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-full wp-image-2316\" title=\"deformedtriangle\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/deformedtriangle.png\" alt=\"\" width=\"250\" height=\"195\" \/><\/a><figcaption id=\"caption-attachment-2316\" class=\"wp-caption-text\">The triangle connecting the centers of neighboring pixels on the approximately hexagonal grid. Approximate side lengths (in square grid pixel widths) and corner angles are indicated.<\/figcaption><\/figure><\/p>\n<p>The exact lengths of the sides of the triangle are $\\frac{\\sqrt{1049}}{30}$ for the base, $\\frac{\\sqrt{29}}{5}$ for the left side, and $\\frac{\\sqrt{41}}{6}$ for the right side. The angles are within a degree from 60\u00b0, and side lengths differ from each other at most by 1.2 <strong>%<\/strong>, good enough to be a visually transparent approximation of an equilateral triangle. The base of the triangle is tilted 8.88\u00b0 counterclockwise from the horizontal axis, the left side is tilted 21.80\u00b0 clockwise from the vertical axis, and the right side is tilted 6.34\u00b0 clockwise from the diagonal axis. This means that the resulting hexagonal blur will not be oriented along the vertical, the horizontal, or the diagonal axis of the square grid. This is good as a tilted blur is visually pleasing.<\/p>\n<p>The rational approximation also gives an easy-to-trace opportunity for economically storing the hexagonally sampled image in memory, skipping two more samples from the beginning of every 5th row, and one more sample from the beginning of every 6th column. Whether taking advantage of this is advisable or not depends on how much overhead there will be from the more complicated way of addressing.<\/p>\n<p>With variable-size blur, there is the disadvantage that if the blur sizes are given on a square grid, they need to be resampled as well, but only from square grid to hexagonal. In other aspects, the hexagonal blur algorithm is very appealing for real-time use.<\/p>\n<p>But let's go back to circles:<\/p>\n<p><figure id=\"attachment_2401\" aria-describedby=\"caption-attachment-2401\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hugeaahexcircle1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2401\" title=\"hugeaahexcircle\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hugeaahexcircle1-300x300.png\" alt=\"\" width=\"300\" height=\"300\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hugeaahexcircle1-300x300.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hugeaahexcircle1-150x150.png 150w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hugeaahexcircle1-1024x1020.png 1024w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hugeaahexcircle1.png 1803w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2401\" class=\"wp-caption-text\">A large round disc on a hexagonal grid, made from a hexagon, and stripes in three directions to fill in the gaps<\/figcaption><\/figure><\/p>\n<p>The hexagonal grid is very efficient at covering the whole area of a circle with a maximally large hexagon and stripes. To assess the complexity, we must define the grid orientation to equate the radius of the circle in terms of pixel widths in the two coordinate systems. We choose for the hexagonal grid an orientation that gives at least as high resolution in all directions as a square grid:<\/p>\n<p><figure id=\"attachment_2282\" aria-describedby=\"caption-attachment-2282\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexgridorient.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2282\" title=\"hexgridorient\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexgridorient-300x231.png\" alt=\"\" width=\"300\" height=\"231\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexgridorient-300x231.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/hexgridorient.png 957w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2282\" class=\"wp-caption-text\">A definition of a pixel width or step on a hexagonal grid<\/figcaption><\/figure><\/p>\n<p>This differs from the tilted hexagonal grid orientation of the previously presented resampling method based on two shearing steps, but gives perhaps a more fair picture for a complexity analysis. On the hexagonal grid, for a large unantialiased solid circle with a radius $r$, the number of non-zero elements in the already presented 5-component kernel representation will be about $(8\\sqrt{3}-12)r \\approx 1.86\\ r$, about 20 % less than with the square grid, but still proportional to the radius. For the benefit of the box blur and stripes, the difference in efficiencies will be offset by the additional work required with the hexagonal grid to do the resampling back and forth.<\/p>\n<h3>Octagons<\/h3>\n<p>A hexagon got us quite close to a circle, so an octagon should get us even closer. The regular octagon is also a shape that is familiar from camera apertures, and so has value as a blur kernel. An octagon can be created from sections that terminate at right or diagonal angles, and has a sparse representation in the usual square coordinate system if diagonal prefix sums are employed:<\/p>\n<p><figure id=\"attachment_2390\" aria-describedby=\"caption-attachment-2390\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagon1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2390\" title=\"octagon\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagon1-300x291.png\" alt=\"\" width=\"300\" height=\"291\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagon1-300x291.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagon1.png 509w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2390\" class=\"wp-caption-text\">An octagon made from three columns with different end shapes: straight, south-east-diagonal and north-east-diagonal.<\/figcaption><\/figure><\/p>\n<p>The <strong>scattering-friendly<\/strong> calculation goes $o = i\\ \\hat{*}\\ K_{xy}*S_x*S_y + i\\ \\hat{*}\\ K_{y\\nearrow}*S_\\nearrow*S_y + i\\ \\hat{*}\\ K_{y\\searrow}*S_\\searrow*S_y$, for the sparse representation $\\{K_{xy}, K_{y\\nearrow}, K_{y\\searrow}\\}$ with a total of <strong>12 non-zero elements<\/strong> for hexagons that have corners at integer positions. Convolution by $S_{\\searrow}$ or by $S_{\\nearrow}$ equals calculating a prefix sum. The calculation can be optimized from 6 prefix sums and 5 steps to $o = (i\\ \\hat{*}\\ K_{xy}*S_x + i\\ \\hat{*}\\ K_{y\\nearrow}*S_\\nearrow + i\\ \\hat{*}\\ K_{y\\searrow}*S_\\searrow)*S_y$ with 4 prefix sums and 5 steps. The <strong>gathering-friendly<\/strong> calculation is $o = i*S_y*S_x\\ \\check{*}\\ K_{xy} + i*S_y*S_\\nearrow\\ \\check{*}\\ K_{y\\nearrow} + i*S_y*S_\\searrow\\ \\check{*}\\ K_{y\\searrow}$ with 4 prefix sums and 5 steps, reusing the term $i*S_y$.<\/p>\n<p>For octagons that have the corners at integer coordinates, it suffices as antialiasing to set the diagonal edge pixels to half intensity. In the <strong>scattering-friendly<\/strong> approach, this can be done very easily by subtracting the for the most part already calculated $(i\\ \\hat{*}\\ K_{y\\nearrow}*S_\\nearrow + i\\ \\hat{*}\\ K_{y\\searrow}*S_\\searrow)\/2$ from the output image, keeping the number of prefix sums at 4 and increasing the number of steps to 6. Cross-fading between such octagons gives a crude way of anti-aliasing for non-integer-size octagons and doubles the number of non-zero elements in the sparse representation to <strong>24 non-zero elements<\/strong>.<\/p>\n<p>A circle can be approximated using an octagon and horizontal, vertical, and diagonal stripes to fill in the empty spaces:<\/p>\n<p><figure id=\"attachment_2411\" aria-describedby=\"caption-attachment-2411\" style=\"width: 300px\" class=\"wp-caption alignnone\"><a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagoncircle1.png\"><img loading=\"lazy\" decoding=\"async\" class=\"size-medium wp-image-2411\" title=\"octagoncircle\" src=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagoncircle1-300x291.png\" alt=\"\" width=\"300\" height=\"291\" srcset=\"https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagoncircle1-300x291.png 300w, https:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/octagoncircle1.png 509w\" sizes=\"(max-width: 300px) 85vw, 300px\" \/><\/a><figcaption id=\"caption-attachment-2411\" class=\"wp-caption-text\">An octagon used as a starting point to fill a circle, followed by stripes in 4 directions.<\/figcaption><\/figure><\/p>\n<p>The <em>regular<\/em> octagon is not the ideal choice as a starting point to fill a circle, because it takes more diagonal lines to fill in the same space compared to horizontal or vertical lines. Therefore, the diagonal portion of the hexagon should be shorter. Optimality can be reached by setting one of the corners of the hexagon close to coordinates $(\\frac{2\\sqrt{5}}{5}r, \\frac{\\sqrt{5}}{5}r) \\approx (0.8944271909\\ r, 0.4472135954\\ r)$, where $r$ is the radius of the circle centered at $(0, 0)$. For large $r$, the number of non-zero elements in the representation of the kernel will be about $(8\\sqrt{2}+8-8\\sqrt{5})r \\approx 1.43\\ r$. That is about 40 % less than with the box and stripes method, making the octagon a good starting point for large unantialiased circular blurs.<\/p>\n<h2>Source code<\/h2>\n<p>Source code for convolution using phased Gaussian kernels, and for optimization of kernel sets: <a href=\"http:\/\/yehar.com\/blog\/wp-content\/uploads\/2010\/09\/phasegauss1.1.zip\">phasegauss1.1.zip<\/a>. The project is for <a title=\"Dev-C++\" href=\"http:\/\/www.bloodshed.net\/devcpp.html\">Dev-C++<\/a>, for Windows, but shouldn't need much work to compile elsewhere.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article describes approaches for efficient isotropic two-dimensional convolution with disc-like and arbitrary circularly symmetric convolution kernels, and also discusses lens blur effects. Keywords: depth of field, circle of confusion, bokeh, circular blur, lens blur, hexagonal blur, octagonal blur, real-time, DOF Gaussian function approach The circularly symmetric 2-d Gaussian kernel is linearly separable; the convolution &hellip; <a href=\"https:\/\/yehar.com\/blog\/?p=1495\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Circularly symmetric convolution and lens blur&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":1524,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,1],"tags":[],"_links":{"self":[{"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1495"}],"collection":[{"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1495"}],"version-history":[{"count":39,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1495\/revisions"}],"predecessor-version":[{"id":4555,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1495\/revisions\/4555"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=\/wp\/v2\/media\/1524"}],"wp:attachment":[{"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/yehar.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}