|
- //
- // Lol Engine
- //
- // Copyright: (c) 2004-2014 Sam Hocevar <sam@hocevar.net>
- // This program is free software; you can redistribute it and/or
- // modify it under the terms of the Do What The Fuck You Want To
- // Public License, Version 2, as published by Sam Hocevar. See
- // http://www.wtfpl.net/ for more details.
- //
-
- #if defined HAVE_CONFIG_H
- # include "config.h"
- #endif
-
- #include <lol/main.h>
-
- /*
- * Stock kernels
- */
-
- namespace lol
- {
-
- array2d<float> Image::BayerKernel(ivec2 size)
- {
- array2d<float> ret(size);
-
- int n = 1;
- while (n < size.x || n < size.y)
- n *= 2;
-
- for (int j = 0; j < size.y; j++)
- for (int i = 0; i < size.x; i++)
- {
- int x = 0;
-
- for (int k = 1, l = n * n / 4; k < n; k *= 2, l /= 4)
- {
- if ((i & k) && (j & k))
- x += l;
- else if (i & k)
- x += 3 * l;
- else if (j & k)
- x += 2 * l;
- }
-
- ret[i][j] = (float)(x + 1) / (n * n + 1);
- }
-
- return ret;
- }
-
- array2d<float> Image::HalftoneKernel(ivec2 size)
- {
- array2d<float> ret(size);
-
- for (int y = 0; y < size.y; y++)
- for (int x = 0; x < size.x; x++)
- {
- float dx = 2.f * (x + 0.02f) / size.x - 0.5f;
- float dy = 2.f * (y + 0.03f) / size.y - 0.5f;
- bool flip = false;
- if (dx > 0.5f)
- {
- flip = !flip;
- dx -= 1.0f;
- }
- if (dy > 0.5f)
- {
- flip = !flip;
- dy -= 1.0f;
- }
- /* Using dx²+dy² here creates another interesting halftone. */
- float r = - lol::cos(F_PI * (dx - dy)) - lol::cos(F_PI * (dx + dy));
-
- ret[x][y] = flip ? 10.f - r : r;
- }
-
- return NormalizeKernel(ret);
- }
-
- array2d<float> Image::BlueNoiseKernel(ivec2 size, ivec2 gsize)
- {
- float const epsilon = 1.f / (size.x * size.y + 1);
- gsize = lol::min(size, gsize);
-
- array2d<float> ret(size);
- array2d<vec2> dots(size);
-
- /* Create a small Gaussian kernel for filtering */
- array2d<float> gaussian(gsize);
- for (int j = 0; j < gsize.y; ++j)
- for (int i = 0; i < gsize.x; ++i)
- {
- ivec2 const distance = gsize / 2 - ivec2(i, j);
- gaussian[i][j] = lol::exp(-lol::sqlength(distance)
- / (0.05f * gsize.x * gsize.y));
- }
-
- /* Helper function to find voids and clusters */
- auto setdot = [&] (ivec2 pos, float val)
- {
- float const delta = val - dots[pos][0];
- dots[pos][0] = val;
-
- for (int j = 0; j < gsize.y; ++j)
- for (int i = 0; i < gsize.x; ++i)
- dots[(pos.x + i - gsize.x / 2 + size.x) % size.x]
- [(pos.y + j - gsize.y / 2 + size.y) % size.y]
- [1] += gaussian[i][j] * delta;
- };
-
- auto best = [&] (float val, float mul) -> ivec2
- {
- float maxval = -size.x * size.y;
- ivec2 coord(0, 0);
- for (int y = 0; y < size.y; ++y)
- for (int x = 0; x < size.x; ++x)
- {
- if (dots[x][y][0] != val)
- continue;
-
- float total = dots[x][y][1];
- if (total * mul > maxval)
- {
- maxval = total * mul;
- coord = ivec2(x, y);
- }
- }
-
- return coord;
- };
-
- /* Generate an array with about 10% random dots */
- int const ndots = (size.x * size.y + 9) / 10;
- memset(dots.Data(), 0, dots.Bytes());
- for (int n = 0; n < ndots; )
- {
- ivec2 pos(lol::rand(size.x), lol::rand(size.y));
- if (dots[pos][0])
- continue;
- setdot(ivec2(pos), 1.0f);
- ++n;
- }
-
- /* Rearrange 1s so that they occupy the largest voids */
- for (;;)
- {
- ivec2 bestcluster = best(1.0f, 1.0f);
- setdot(bestcluster, 0.0f);
- ivec2 bestvoid = best(0.0f, -1.0f);
- setdot(bestvoid, 1.0f);
- if (bestcluster == bestvoid)
- break;
- }
-
- /* Reorder all 1s and replace them with 0.0001 */
- for (int n = ndots; n--; )
- {
- ivec2 bestcluster = best(1.0f, 1.0f);
- ret[bestcluster] = (n + 1.0f) * epsilon;
- setdot(bestcluster, 0.0001f);
- }
-
- /* Reorder all 0s and replace them with 0.0001 */
- for (int n = ndots; n < size.x * size.y; ++n)
- {
- ivec2 bestvoid = best(0.0f, -1.0f);
- ret[bestvoid] = (n + 1.0f) * epsilon;
- setdot(bestvoid, 0.0001f);
- }
-
- return ret;
- }
-
- struct Dot
- {
- int x, y;
- float val;
- };
-
- static int cmpdot(const void *p1, const void *p2)
- {
- return ((Dot const *)p1)->val > ((Dot const *)p2)->val;
- }
-
- array2d<float> Image::NormalizeKernel(array2d<float> const &kernel)
- {
- ivec2 size = (ivec2)kernel.GetSize();
-
- array<Dot> tmp;
- tmp.Resize(size.x * size.y);
-
- for (int y = 0; y < size.y; y++)
- for (int x = 0; x < size.x; x++)
- {
- tmp[y * size.x + x].x = x;
- tmp[y * size.x + x].y = y;
- tmp[y * size.x + x].val = kernel[x][y];
- }
- std::qsort(tmp.Data(), size.x * size.y, sizeof(Dot), cmpdot);
-
- array2d<float> dst(size);
-
- float const epsilon = 1.f / (size.x * size.y + 1);
- for (int n = 0; n < size.x * size.y; n++)
- {
- int x = tmp[n].x;
- int y = tmp[n].y;
- dst[x][y] = (n + 1.f) * epsilon;
- }
-
- return dst;
- }
-
- array2d<float> Image::EdiffKernel(EdiffAlgorithm algorithm)
- {
- switch (algorithm)
- {
- case EdiffAlgorithm::FloydSteinberg:
- return { { 0.f, 1.f, 7.f/16, },
- { 3.f/16, 5.f/16, 1.f/16, }, };
-
- case EdiffAlgorithm::JaJuNi:
- return { { 0.f, 0.f, 1.f, 7.f/48, 5.f/48, },
- { 3.f/48, 5.f/48, 7.f/48, 5.f/48, 3.f/48, },
- { 1.f/48, 3.f/48, 5.f/48, 3.f/48, 1.f/48, }, };
-
- case EdiffAlgorithm::Atkinson:
- return { { 0.f, 1.f, 1.f/8, 1.f/8, },
- { 1.f/8, 1.f/8, 1.f/8, 0.f, },
- { 0.f, 1.f/8, 0.f, 0.f, }, };
-
- case EdiffAlgorithm::Fan:
- return { { 0.f, 0.f, 1.f, 7.f/16, },
- { 1.f/16, 3.f/16, 5.f/16, 0.f, }, };
-
- case EdiffAlgorithm::ShiauFan:
- return { { 0.f, 0.f, 1.f, 1.f/2, },
- { 1.f/8, 1.f/8, 1.f/4, 0.f, }, };
-
- case EdiffAlgorithm::ShiauFan2:
- return { { 0.f, 0.f, 0.f, 1.f, 1.f/2, },
- { 1.f/16, 1.f/16, 1.f/8, 1.f/4, 0.f, }, };
-
- case EdiffAlgorithm::Stucki:
- return { { 0.f, 0.f, 1.f, 8.f/42, 4.f/42, },
- { 2.f/42, 4.f/42, 8.f/42, 4.f/42, 2.f/42, },
- { 1.f/42, 2.f/42, 4.f/42, 2.f/42, 1.f/42, }, };
-
- case EdiffAlgorithm::Burkes:
- return { { 0.f, 0.f, 1.f, 4.f/16, 2.f/16, },
- { 1.f/16, 2.f/16, 4.f/16, 2.f/16, 1.f/16, }, };
-
- case EdiffAlgorithm::Sierra:
- return { { 0.f, 0.f, 1.f, 5.f/32, 3.f/32, },
- { 2.f/32, 4.f/32, 5.f/32, 4.f/32, 2.f/32, },
- { 0.f, 2.f/32, 3.f/32, 2.f/32, 0.f, }, };
-
- case EdiffAlgorithm::Sierra2:
- return { { 0.f, 0.f, 1.f, 4.f/16, 3.f/16, },
- { 1.f/16, 2.f/16, 3.f/16, 2.f/16, 1.f/16, }, };
-
- case EdiffAlgorithm::Lite:
- return { { 0.f, 1.f, 1.f/2, },
- { 1.f/4, 1.f/4, 0.f, }, };
- }
-
- return { { 1.f } };
- }
-
- /* Any standard deviation below this value will be rounded up, in order
- * to avoid ridiculously low values. exp(-1/(2*0.2*0.2)) is < 10^-5 so
- * there is little chance that any value below 0.2 will be useful. */
- #define BLUR_EPSILON 0.2f
-
- array2d<float> Image::GaussianKernel(vec2 radius, float angle, vec2 delta)
- {
- array2d<float> kernel;
-
- if (radius.x < BLUR_EPSILON)
- radius.x = BLUR_EPSILON;
- if (radius.y < BLUR_EPSILON)
- radius.y = BLUR_EPSILON;
-
- float const sint = lol::sin(angle);
- float const cost = lol::cos(angle);
-
- /* Compute the final ellipse's bounding box */
- float const bbx = lol::sqrt(sq(radius.x * cost) + sq(radius.y * sint));
- float const bby = lol::sqrt(sq(radius.y * cost) + sq(radius.x * sint));
-
- /* FIXME: the kernel becomes far too big with large values of dx, because
- * we grow both left and right. Fix the growing direction. */
- int const krx = (int)(3.f * bbx + .99999f + lol::ceil(lol::abs(delta.x)));
- int const kry = (int)(3.f * bby + .99999f + lol::ceil(lol::abs(delta.y)));
- ivec2 size(2 * krx + 1, 2 * kry + 1);
- float const Kx = -1.f / (2.f * radius.x * radius.x);
- float const Ky = -1.f / (2.f * radius.y * radius.y);
-
- kernel.SetSize(size);
-
- float t = 0.f;
-
- for (int j = -kry; j <= kry; j++)
- {
- for (int i = -krx; i <= krx; i++)
- {
- /* FIXME: this level of interpolation sucks. We should
- * interpolate on the full NxN grid for better quality. */
- static vec3 const samples[] =
- {
- vec3( 0.0f, 0.0f, 1.0f),
- vec3(-0.4f, -0.4f, 0.8f),
- vec3(-0.3f, 0.0f, 0.9f),
- vec3(-0.4f, 0.4f, 0.8f),
- vec3( 0.0f, 0.3f, 0.9f),
- vec3( 0.4f, 0.4f, 0.8f),
- vec3( 0.3f, 0.0f, 0.9f),
- vec3( 0.4f, -0.4f, 0.8f),
- vec3( 0.0f, -0.3f, 0.9f),
- };
-
- float d = 0.f;
-
- for (auto p : samples)
- {
- float u = (i + p.x) * cost - (j + p.y) * sint + delta.x;
- float v = (i + p.x) * sint + (j + p.y) * cost + delta.y;
- float ex = Kx * u * u;
- float ey = Ky * v * v;
- d += p.z * lol::exp(ex + ey);
-
- /* Do not interpolate if this is a standard gaussian. */
- if (!delta.x && !delta.y && !angle)
- break;
- }
-
- kernel[i + krx][j + kry] = d;
- t += d;
- }
- }
-
- for (int j = 0; j < size.y; j++)
- for (int i = 0; i < size.x; i++)
- kernel[i][j] *= (1.f / t);
-
- return kernel;
- }
-
- } /* namespace lol */
|