| @@ -35,7 +35,6 @@ Image: | |||||
| · crop.cpp | · crop.cpp | ||||
| · dither.cpp | · dither.cpp | ||||
| · dither/dbs.cpp | · dither/dbs.cpp | ||||
| · dither/ordered.cpp | |||||
| · filter/blur.cpp | · filter/blur.cpp | ||||
| · filter/color.cpp | · filter/color.cpp | ||||
| · filter/dilate.cpp | · filter/dilate.cpp | ||||
| @@ -117,7 +117,7 @@ liblolcore_sources = \ | |||||
| image/codec/dummy-image.cpp \ | image/codec/dummy-image.cpp \ | ||||
| image/color/cie1931.cpp image/color/color.cpp \ | image/color/cie1931.cpp image/color/color.cpp \ | ||||
| image/dither/random.cpp image/dither/ediff.cpp \ | image/dither/random.cpp image/dither/ediff.cpp \ | ||||
| image/dither/ostromoukhov.cpp \ | |||||
| image/dither/ostromoukhov.cpp image/dither/ordered.cpp \ | |||||
| image/filter/autocontrast.cpp image/filter/convolution.cpp \ | image/filter/autocontrast.cpp image/filter/convolution.cpp \ | ||||
| image/render/noise.cpp \ | image/render/noise.cpp \ | ||||
| \ | \ | ||||
| @@ -1,144 +1,74 @@ | |||||
| /* | |||||
| * libpipi Pathetic image processing interface library | |||||
| * Copyright (c) 2004-2008 Sam Hocevar <sam@zoy.org> | |||||
| * All Rights Reserved | |||||
| * | |||||
| * $Id$ | |||||
| * | |||||
| * This library is free software. It comes without any warranty, to | |||||
| * the extent permitted by applicable law. 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://sam.zoy.org/wtfpl/COPYING for more details. | |||||
| */ | |||||
| // | |||||
| // 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 "core.h" | |||||
| /* | /* | ||||
| * ordered.c: Bayer ordered dithering functions | |||||
| * Bayer ordered dithering functions | |||||
| */ | */ | ||||
| #include "config.h" | |||||
| #include <stdlib.h> | |||||
| #include <math.h> | |||||
| #ifndef M_PI | |||||
| # define M_PI 3.14159265358979323846 | |||||
| #endif | |||||
| #include "pipi.h" | |||||
| #include "pipi-internals.h" | |||||
| pipi_image_t *pipi_dither_halftone(pipi_image_t *img, double r, double angle) | |||||
| namespace lol | |||||
| { | { | ||||
| #define PRECISION 4. | |||||
| pipi_image_t *ret, *kernel; | |||||
| int k = (r * PRECISION / 2. / sqrt(2.) + .5); | |||||
| kernel = pipi_render_halftone(k, k); | |||||
| ret = pipi_dither_ordered_ext(img, kernel, 1. / PRECISION, angle + 45.); | |||||
| pipi_free(kernel); | |||||
| static Image DitherHelper(Image const &image, Array2D<float> const &kernel, | |||||
| float scale, float angle); | |||||
| return ret; | |||||
| Image Image::DitherOrdered(Array2D<float> const &kernel) const | |||||
| { | |||||
| return DitherHelper(*this, kernel, 1.0f, 0.0f); | |||||
| } | } | ||||
| pipi_image_t *pipi_dither_ordered(pipi_image_t *img, pipi_image_t *kernel) | |||||
| Image Image::DitherHalftone(float radius, float angle) const | |||||
| { | { | ||||
| return pipi_dither_ordered_ext(img, kernel, 1.0, 0.0); | |||||
| /* Increasing the precision is necessary or the rotation will look | |||||
| * like crap. So we create a kernel PRECISION times larger, and ask | |||||
| * the ditherer to scale it by 1/PRECISION. */ | |||||
| float const PRECISION = 4.f; | |||||
| int k = (radius * PRECISION * lol::sqrt(2.f) + 0.5f); | |||||
| Array2D<float> kernel = Image::HalftoneKernel(ivec2(k, k)); | |||||
| return DitherHelper(*this, kernel, 1.f / PRECISION, angle + F_PI / 4.f); | |||||
| } | } | ||||
| pipi_image_t *pipi_dither_ordered_ext(pipi_image_t *img, pipi_image_t *kernel, | |||||
| double scale, double angle) | |||||
| static Image DitherHelper(Image const &image, Array2D<float> const &kernel, | |||||
| float scale, float angle) | |||||
| { | { | ||||
| double sint, cost; | |||||
| pipi_image_t *dst; | |||||
| pipi_pixels_t *dstp, *kernelp; | |||||
| float *dstdata, *kerneldata; | |||||
| int x, y, w, h, kx, ky, kw, kh; | |||||
| w = img->w; | |||||
| h = img->h; | |||||
| kw = kernel->w; | |||||
| kh = kernel->h; | |||||
| ivec2 size = image.GetSize(); | |||||
| ivec2 ksize = kernel.GetSize(); | |||||
| cost = cos(angle * (M_PI / 180)); | |||||
| sint = sin(angle * (M_PI / 180)); | |||||
| float cost = lol::cos(angle); | |||||
| float sint = lol::sin(angle); | |||||
| dst = pipi_copy(img); | |||||
| dstp = pipi_get_pixels(dst, PIPI_PIXELS_Y_F32); | |||||
| dstdata = (float *)dstp->pixels; | |||||
| Image ret = image; | |||||
| float *dstp = ret.Lock<PixelFormat::Y_F32>(); | |||||
| kernelp = pipi_get_pixels(kernel, PIPI_PIXELS_Y_F32); | |||||
| kerneldata = (float *)kernelp->pixels; | |||||
| for(y = 0; y < h; y++) | |||||
| for (int y = 0; y < size.y; y++) | |||||
| { | { | ||||
| for(x = 0; x < w; x++) | |||||
| for (int x = 0; x < size.x; x++) | |||||
| { | { | ||||
| float p, q; | |||||
| kx = (int)((cost * x - sint * y + 2 * w * h) / scale) % kw; | |||||
| ky = (int)((cost * y + sint * x + 2 * w * h) / scale) % kh; | |||||
| int kx = (int)((cost * x - sint * y + 2 * size.x * size.y) / scale) % ksize.x; | |||||
| int ky = (int)((cost * y + sint * x + 2 * size.x * size.y) / scale) % ksize.y; | |||||
| p = dstdata[y * w + x]; | |||||
| q = p > kerneldata[ky * kw + kx] ? 1. : 0.; | |||||
| dstdata[y * w + x] = q; | |||||
| float p = dstp[y * size.x + x]; | |||||
| dstp[y * size.x + x] = (p > kernel[kx][ky]) ? 1.f : 0.f; | |||||
| } | } | ||||
| } | } | ||||
| return dst; | |||||
| } | |||||
| typedef struct | |||||
| { | |||||
| int x, y; | |||||
| double val; | |||||
| } | |||||
| dot_t; | |||||
| ret.Unlock(dstp); | |||||
| static int cmpdot(const void *p1, const void *p2) | |||||
| { | |||||
| return ((dot_t const *)p1)->val > ((dot_t const *)p2)->val; | |||||
| return ret; | |||||
| } | } | ||||
| pipi_image_t *pipi_order(pipi_image_t *src) | |||||
| { | |||||
| double epsilon; | |||||
| pipi_image_t *dst; | |||||
| pipi_pixels_t *dstp, *srcp; | |||||
| float *dstdata, *srcdata; | |||||
| dot_t *circle; | |||||
| int x, y, w, h, n; | |||||
| w = src->w; | |||||
| h = src->h; | |||||
| epsilon = 1. / (w * h + 1); | |||||
| srcp = pipi_get_pixels(src, PIPI_PIXELS_Y_F32); | |||||
| srcdata = (float *)srcp->pixels; | |||||
| dst = pipi_new(w, h); | |||||
| dstp = pipi_get_pixels(dst, PIPI_PIXELS_Y_F32); | |||||
| dstdata = (float *)dstp->pixels; | |||||
| circle = malloc(w * h * sizeof(dot_t)); | |||||
| for(y = 0; y < h; y++) | |||||
| for(x = 0; x < w; x++) | |||||
| { | |||||
| circle[y * w + x].x = x; | |||||
| circle[y * w + x].y = y; | |||||
| circle[y * w + x].val = srcdata[y * w + x]; | |||||
| } | |||||
| qsort(circle, w * h, sizeof(dot_t), cmpdot); | |||||
| for(n = 0; n < w * h; n++) | |||||
| { | |||||
| x = circle[n].x; | |||||
| y = circle[n].y; | |||||
| dstdata[y * w + x] = (float)(n + 1) * epsilon; | |||||
| } | |||||
| free(circle); | |||||
| return dst; | |||||
| } | |||||
| } /* namespace lol */ | |||||
| @@ -72,51 +72,73 @@ Array2D<float> Image::BayerKernel(ivec2 size) | |||||
| return ret; | 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 / size.x - 0.5f; | |||||
| float dy = 2.f * (y + 0.07f) / 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); | |||||
| } | |||||
| struct Dot | struct Dot | ||||
| { | { | ||||
| int x, y; | int x, y; | ||||
| float dist; | |||||
| float val; | |||||
| }; | }; | ||||
| static int cmpdot(const void *p1, const void *p2) | static int cmpdot(const void *p1, const void *p2) | ||||
| { | { | ||||
| return ((Dot const *)p1)->dist > ((Dot const *)p2)->dist; | |||||
| return ((Dot const *)p1)->val > ((Dot const *)p2)->val; | |||||
| } | } | ||||
| Array2D<float> Image::HalftoneKernel(ivec2 size) | |||||
| Array2D<float> Image::NormalizeKernel(Array2D<float> const &kernel) | |||||
| { | { | ||||
| Array2D<float> ret(size); | |||||
| ivec2 size = kernel.GetSize(); | |||||
| ivec2 csize = (size + ivec2(1)) / 2; | |||||
| Array<Dot> circle; | |||||
| circle.Resize(csize.x * csize.y); | |||||
| for (int y = 0; y < csize.y; y++) | |||||
| for (int x = 0; x < csize.x; x++) | |||||
| 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++) | |||||
| { | { | ||||
| 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; | |||||
| tmp[y * size.x + x].x = x; | |||||
| tmp[y * size.x + x].y = y; | |||||
| tmp[y * size.x + x].val = kernel[x][y]; | |||||
| } | } | ||||
| /* FIXME: use std::sort here */ | |||||
| std::qsort(circle.Data(), csize.x * csize.y, sizeof(Dot), cmpdot); | |||||
| std::qsort(tmp.Data(), size.x * size.y, sizeof(Dot), cmpdot); | |||||
| float mul = 1.f / (csize.x * csize.y * 4 + 1); | |||||
| for (int n = 0; n < csize.x * csize.y; n++) | |||||
| { | |||||
| int x = circle[n].x; | |||||
| int y = circle[n].y; | |||||
| Array2D<float> dst(size); | |||||
| 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; | |||||
| float 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] = (float)(n + 1) * epsilon; | |||||
| } | } | ||||
| return ret; | |||||
| return dst; | |||||
| } | } | ||||
| Array2D<float> Image::EdiffKernel(EdiffAlgorithm algorithm) | Array2D<float> Image::EdiffKernel(EdiffAlgorithm algorithm) | ||||
| @@ -94,6 +94,7 @@ public: | |||||
| static Array2D<float> BayerKernel(ivec2 size); | static Array2D<float> BayerKernel(ivec2 size); | ||||
| static Array2D<float> HalftoneKernel(ivec2 size); | static Array2D<float> HalftoneKernel(ivec2 size); | ||||
| static Array2D<float> EdiffKernel(EdiffAlgorithm algorithm); | static Array2D<float> EdiffKernel(EdiffAlgorithm algorithm); | ||||
| static Array2D<float> NormalizeKernel(Array2D<float> const &kernel); | |||||
| /* Rendering */ | /* Rendering */ | ||||
| bool Stock(char const *desc); | bool Stock(char const *desc); | ||||
| @@ -107,6 +108,8 @@ public: | |||||
| Image DitherEdiff(Array2D<float> const &kernel, | Image DitherEdiff(Array2D<float> const &kernel, | ||||
| ScanMode scan = ScanMode::Raster) const; | ScanMode scan = ScanMode::Raster) const; | ||||
| Image DitherOstromoukhov(ScanMode scan = ScanMode::Raster) const; | Image DitherOstromoukhov(ScanMode scan = ScanMode::Raster) const; | ||||
| Image DitherOrdered(Array2D<float> const &kernel) const; | |||||
| Image DitherHalftone(float radius, float angle) const; | |||||
| private: | private: | ||||
| class ImageData *m_data; | class ImageData *m_data; | ||||
| @@ -152,6 +152,7 @@ | |||||
| <ClCompile Include="image\filter\autocontrast.cpp" /> | <ClCompile Include="image\filter\autocontrast.cpp" /> | ||||
| <ClCompile Include="image\filter\convolution.cpp" /> | <ClCompile Include="image\filter\convolution.cpp" /> | ||||
| <ClCompile Include="image\dither\ediff.cpp" /> | <ClCompile Include="image\dither\ediff.cpp" /> | ||||
| <ClCompile Include="image\dither\ordered.cpp" /> | |||||
| <ClCompile Include="image\dither\ostromoukhov.cpp" /> | <ClCompile Include="image\dither\ostromoukhov.cpp" /> | ||||
| <ClCompile Include="image\dither\random.cpp" /> | <ClCompile Include="image\dither\random.cpp" /> | ||||
| <ClCompile Include="image\image.cpp" /> | <ClCompile Include="image\image.cpp" /> | ||||
| @@ -358,6 +358,9 @@ | |||||
| <ClCompile Include="image\dither\ediff.cpp"> | <ClCompile Include="image\dither\ediff.cpp"> | ||||
| <Filter>image\dither</Filter> | <Filter>image\dither</Filter> | ||||
| </ClCompile> | </ClCompile> | ||||
| <ClCompile Include="image\dither\ordered.cpp"> | |||||
| <Filter>image\dither</Filter> | |||||
| </ClCompile> | |||||
| <ClCompile Include="image\dither\ostromoukhov.cpp"> | <ClCompile Include="image\dither\ostromoukhov.cpp"> | ||||
| <Filter>image\dither</Filter> | <Filter>image\dither</Filter> | ||||
| </ClCompile> | </ClCompile> | ||||