diff --git a/src/Makefile.am b/src/Makefile.am index 3f167ef3..0ab6a0ed 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -119,7 +119,7 @@ liblolcore_sources = \ image/dither/random.cpp image/dither/ediff.cpp \ image/dither/ostromoukhov.cpp \ image/filter/autocontrast.cpp image/filter/convolution.cpp \ - image/render/noise.cpp image/render/screen.cpp \ + image/render/noise.cpp \ \ loldebug.h \ debug/fps.cpp debug/fps.h debug/lines.cpp \ diff --git a/src/image/dither/ediff.cpp b/src/image/dither/ediff.cpp index e4bbecb8..61636eae 100644 --- a/src/image/dither/ediff.cpp +++ b/src/image/dither/ediff.cpp @@ -27,54 +27,48 @@ namespace lol * Making the matrix generic is not terribly slower: the performance * hit is around 4% for Floyd-Steinberg and 13% for JaJuNi, with the * benefit of a lot less code. */ -Image Image::DitherEdiff(Image &kernel, ScanMode scan) const +Image Image::DitherEdiff(Array2D const &kernel, ScanMode scan) const { Image dst = *this; - float *pixels = dst.Lock(); - int w = dst.GetSize().x; - int h = dst.GetSize().y; - - float *kerdata = kernel.Lock(); - int kw = kernel.GetSize().x; - int kh = kernel.GetSize().y; + ivec2 size = dst.GetSize(); + ivec2 ksize = kernel.GetSize(); int kx; - for (kx = 0; kx < kw; kx++) - if (kerdata[kx] > 0.f) + for (kx = 0; kx < ksize.x; kx++) + if (kernel[kx][0] > 0.f) break; - for (int y = 0; y < h; y++) + float *pixels = dst.Lock(); + for (int y = 0; y < size.y; y++) { bool reverse = (y & 1) && (scan == ScanMode::Serpentine); - for (int x = 0; x < w; x++) + for (int x = 0; x < size.x; x++) { - int x2 = reverse ? w - 1 - x : x; + int x2 = reverse ? size.x - 1 - x : x; int s = reverse ? -1 : 1; - float p = pixels[y * w + x2]; + float p = pixels[y * size.x + x2]; float q = p < 0.5f ? 0.f : 1.f; - pixels[y * w + x2] = q; + pixels[y * size.x + x2] = q; float e = (p - q); - for (int j = 0; j < kh && y < h - j; j++) - for (int i = 0; i < kw; i++) + for (int j = 0; j < ksize.y && y < size.y - j; j++) + for (int i = 0; i < ksize.x; i++) { if (j == 0 && i <= kx) continue; - if (x + i - kx < 0 || x + i - kx >= w) + if (x + i - kx < 0 || x + i - kx >= size.x) continue; - pixels[(y + j) * w + x2 + (i - kx) * s] - += e * kerdata[j * kw + i]; + pixels[(y + j) * size.x + x2 + (i - kx) * s] + += e * kernel[i][j]; } } } - - kernel.Unlock(kerdata); dst.Unlock(pixels); return dst; diff --git a/src/image/render/screen.cpp b/src/image/render/screen.cpp deleted file mode 100644 index 1947f7f3..00000000 --- a/src/image/render/screen.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// -// Lol Engine -// -// Copyright: (c) 2004-2014 Sam Hocevar -// 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 "core.h" - -/* - * Halftoning screen functions - */ - -namespace lol -{ - -bool Image::RenderBayer(ivec2 size) -{ - if (size.x <= 0 || size.y <= 0) - return false; - - int n = 1; - while (n < size.x || n < size.y) - n *= 2; - - SetSize(size); - float *pixels = Lock(); - - 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; - } - - pixels[j * size.x + i] = (float)(x + 1) / (n * n + 1); - } - - Unlock(pixels); - - return true; -} - -typedef struct -{ - int x, y; - float dist; -} -dot_t; - -static int cmpdot(const void *p1, const void *p2) -{ - return ((dot_t const *)p1)->dist > ((dot_t const *)p2)->dist; -} - -bool Image::RenderHalftone(ivec2 size) -{ - if (size.x <= 0 || size.y <= 0) - return false; - - Array circle; - circle.Resize(size.x * size.y); - for (int y = 0; y < size.y; y++) - for (int x = 0; x < size.x; x++) - { - float dy = ((float)y + 0.07f) / size.y - 0.5f; - float dx = (float)x / size.x - 0.5f; - /* Using dx²+dy² here creates another interesting halftone. */ - float r = - lol::cos(F_PI * (dx - dy)) - lol::cos(F_PI * (dx + dy)); - circle[y * size.x + x].x = x; - circle[y * size.x + x].y = y; - circle[y * size.x + x].dist = r; - } - std::qsort(circle.Data(), size.x * size.y, sizeof(dot_t), cmpdot); - - SetSize(2 * size); - float *pixels = Lock(); - float mul = 1.f / (size.x * size.y * 4 + 1); - for (int n = 0; n < size.x * size.y; n++) - { - int x = circle[n].x; - int y = circle[n].y; - - pixels[y * (2 * size.x) + x] = (float)(2 * n + 1) * mul; - pixels[(y + size.y) * (2 * size.x) + x + size.x] = (float)(2 * n + 2) * mul; - pixels[(y + size.y) * (2 * size.x) + x] = 1.0f - (float)(2 * n + 1) * mul; - pixels[y * (2 * size.x) + x + size.x] = 1.0f - (float)(2 * n + 2) * mul; - } - Unlock(pixels); - - return true; -} - -} /* namespace lol */ - diff --git a/src/image/stock.cpp b/src/image/stock.cpp index 59caf3fb..fd346a77 100644 --- a/src/image/stock.cpp +++ b/src/image/stock.cpp @@ -15,7 +15,7 @@ #include "core.h" /* - * Stock images + * Stock images and kernels */ namespace lol @@ -23,50 +23,136 @@ namespace lol bool Image::Stock(char const *name) { - /* Generate a Bayer dithering pattern. */ - if (!strncmp(name, "bayer:", 6)) + /* Generate an error diffusion matrix. */ + if (!strncmp(name, "ediff:", 6)) { + float const *ker = nullptr; ivec2 size(0); - size.x = atoi(name + 6); - name = strchr(name + 6, 'x'); - if (name) - size.y = atoi(name + 1); - if (!size.y) - size.y = size.x; - return RenderBayer(size); + + SetSize(size); + float *pixels = Lock(); + memcpy(pixels, ker, size.x * size.y * sizeof(float)); + Unlock(pixels); + + return true; } - /* Generate a clustered dithering pattern. */ - if (!strncmp(name, "halftone:", 9)) + /* Generate a completely random image. */ + if (!strncmp(name, "random:", 7)) { ivec2 size(0); - size.x = atoi(name + 9); - name = strchr(name + 9, 'x'); + + size.x = atoi(name + 7); + name = strchr(name + 7, 'x'); if (name) size.y = atoi(name + 1); if (!size.y) size.y = size.x; + if (size.x <= 0 || size.y <= 0) + return false; - return RenderHalftone(size); + return RenderRandom(size); } - /* Generate an error diffusion matrix. */ - if (!strncmp(name, "ediff:", 6)) + return false; +} + +Array2D Image::BayerKernel(ivec2 size) +{ + Array2D 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; +} + +struct Dot +{ + int x, y; + float dist; +}; + +static int cmpdot(const void *p1, const void *p2) +{ + return ((Dot const *)p1)->dist > ((Dot const *)p2)->dist; +} + +Array2D Image::HalftoneKernel(ivec2 size) +{ + Array2D ret(size); + + ivec2 csize = (size + ivec2(1)) / 2; + Array circle; + circle.Resize(csize.x * csize.y); + for (int y = 0; y < csize.y; y++) + for (int x = 0; x < csize.x; x++) + { + float dy = ((float)y + 0.07f) / csize.y - 0.5f; + float dx = (float)x / csize.x - 0.5f; + /* Using dx²+dy² here creates another interesting halftone. */ + float r = - lol::cos(F_PI * (dx - dy)) - lol::cos(F_PI * (dx + dy)); + circle[y * csize.x + x].x = x; + circle[y * csize.x + x].y = y; + circle[y * csize.x + x].dist = r; + } + /* FIXME: use std::sort here */ + std::qsort(circle.Data(), csize.x * csize.y, sizeof(Dot), cmpdot); + + float mul = 1.f / (csize.x * csize.y * 4 + 1); + for (int n = 0; n < csize.x * csize.y; n++) { - float const *ker = nullptr; - ivec2 size(0); + int x = circle[n].x; + int y = circle[n].y; + + ret[x][y] = (float)(2 * n + 1) * mul; + ret[x + csize.x][y + csize.y] = (float)(2 * n + 2) * mul; + ret[x][y + csize.y] = 1.0f - (float)(2 * n + 1) * mul; + ret[x + csize.x][y] = 1.0f - (float)(2 * n + 2) * mul; + } - if (!strcmp(name + 6, "fs")) + return ret; +} + +Array2D EdiffKernel(EdiffAlgorithm algorithm) +{ + Array2D ret; + + switch(algorithm) + { + case EdiffAlgorithm::FloydSteinberg: + ret.SetSize(ivec2(3, 2)); { static float const myker[] = { 0., 1., 7./16, 3./16, 5./16, 1./16, }; - ker = myker; size = ivec2(3, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "jajuni")) + return ret; + case EdiffAlgorithm::JaJuNi: + ret.SetSize(ivec2(5, 3)); { static float const myker[] = { @@ -74,9 +160,11 @@ bool Image::Stock(char const *name) 3./48, 5./48, 7./48, 5./48, 3./48, 1./48, 3./48, 5./48, 3./48, 1./48, }; - ker = myker; size = ivec2(5, 3); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "atkinson")) + return ret; + case EdiffAlgorithm::Atkinson: + ret.SetSize(ivec2(4, 3)); { static float const myker[] = { @@ -84,36 +172,44 @@ bool Image::Stock(char const *name) 1./8, 1./8, 1./8, 0., 0., 1./8, 0., 0., }; - ker = myker; size = ivec2(4, 3); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "fan")) + return ret; + case EdiffAlgorithm::Fan: + ret.SetSize(ivec2(4, 2)); { static float const myker[] = { 0., 0., 1., 7./16, 1./16, 3./16, 5./16, 0., }; - ker = myker; size = ivec2(4, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "shiaufan")) + return ret; + case EdiffAlgorithm::ShiauFan: + ret.SetSize(ivec2(4, 2)); { static float const myker[] = { 0., 0., 1., 1./2, 1./8, 1./8, 1./4, 0., }; - ker = myker; size = ivec2(4, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "shiaufan2")) + return ret; + case EdiffAlgorithm::ShiauFan2: + ret.SetSize(ivec2(5, 2)); { static float const myker[] = { 0., 0., 0., 1., 1./2, 1./16, 1./16, 1./8, 1./4, 0., }; - ker = myker; size = ivec2(5, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "stucki")) + return ret; + case EdiffAlgorithm::Stucki: + ret.SetSize(ivec2(5, 3)); { static float const myker[] = { @@ -121,18 +217,22 @@ bool Image::Stock(char const *name) 2./42, 4./42, 8./42, 4./42, 2./42, 1./42, 2./42, 4./42, 2./42, 1./42, }; - ker = myker; size = ivec2(5, 3); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "burkes")) + return ret; + case EdiffAlgorithm::Burkes: + ret.SetSize(ivec2(5, 2)); { static float const myker[] = { 0., 0., 1., 4./16, 2./16, 1./16, 2./16, 4./16, 2./16, 1./16, }; - ker = myker; size = ivec2(5, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "sierra")) + return ret; + case EdiffAlgorithm::Sierra: + ret.SetSize(ivec2(5, 3)); { static float const myker[] = { @@ -140,53 +240,34 @@ bool Image::Stock(char const *name) 2./32, 4./32, 5./32, 4./32, 2./32, 0., 2./32, 3./32, 2./32, 0., }; - ker = myker; size = ivec2(5, 3); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "sierra2")) + return ret; + case EdiffAlgorithm::Sierra2: + ret.SetSize(ivec2(5, 2)); { static float const myker[] = { 0., 0., 1., 4./16, 3./16, 1./16, 2./16, 3./16, 2./16, 1./16, }; - ker = myker; size = ivec2(5, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - else if (!strcmp(name + 6, "lite")) + return ret; + case EdiffAlgorithm::Lite: + ret.SetSize(ivec2(3, 2)); { static float const myker[] = { 0., 1., 1./2, 1./4, 1./4, 0., }; - ker = myker; size = ivec2(3, 2); + memcpy(&ret[0][0], myker, sizeof(myker)); } - - SetSize(size); - float *pixels = Lock(); - memcpy(pixels, ker, size.x * size.y * sizeof(float)); - Unlock(pixels); - - return true; - } - - /* Generate a completely random image. */ - if (!strncmp(name, "random:", 7)) - { - ivec2 size(0); - - size.x = atoi(name + 7); - name = strchr(name + 7, 'x'); - if (name) - size.y = atoi(name + 1); - if (!size.y) - size.y = size.x; - if (size.x <= 0 || size.y <= 0) - return false; - - return RenderRandom(size); + return ret; } - return false; + return ret; } } /* namespace lol */ diff --git a/src/lol/image/image.h b/src/lol/image/image.h index be80204a..c88cbcf3 100644 --- a/src/lol/image/image.h +++ b/src/lol/image/image.h @@ -41,6 +41,21 @@ enum class ScanMode : uint8_t Serpentine, }; +enum class EdiffAlgorithm : uint8_t +{ + FloydSteinberg, + JaJuNi, + Atkinson, + Fan, + ShiauFan, + ShiauFan2, + Stucki, + Burkes, + Sierra, + Sierra2, + Lite, +}; + class Image { public: @@ -75,10 +90,13 @@ public: bool RetrieveTiles(Array& tiles) const; + /* Image processing kernels */ + static Array2D BayerKernel(ivec2 size); + static Array2D HalftoneKernel(ivec2 size); + static Array2D EdiffKernel(EdiffAlgorithm algorithm); + /* Rendering */ bool Stock(char const *desc); - bool RenderBayer(ivec2 size); - bool RenderHalftone(ivec2 size); bool RenderRandom(ivec2 size); /* Image processing */ @@ -86,7 +104,8 @@ public: Image Convolution(Array2D const &kernel); Image DitherRandom() const; - Image DitherEdiff(Image &kernel, ScanMode scan = ScanMode::Raster) const; + Image DitherEdiff(Array2D const &kernel, + ScanMode scan = ScanMode::Raster) const; Image DitherOstromoukhov(ScanMode scan = ScanMode::Raster) const; private: diff --git a/src/lolcore.vcxproj b/src/lolcore.vcxproj index 61d314bc..2f0a2f6a 100644 --- a/src/lolcore.vcxproj +++ b/src/lolcore.vcxproj @@ -157,7 +157,6 @@ - diff --git a/src/lolcore.vcxproj.filters b/src/lolcore.vcxproj.filters index e7f756a3..4317e55d 100644 --- a/src/lolcore.vcxproj.filters +++ b/src/lolcore.vcxproj.filters @@ -373,9 +373,6 @@ image\render - - image\render - image @@ -784,4 +781,4 @@ - \ No newline at end of file +