From e064118dc137ed2c9bc4f683d7c224784a7ef9a5 Mon Sep 17 00:00:00 2001 From: Sam Hocevar Date: Sat, 21 Jun 2014 15:30:28 +0000 Subject: [PATCH] image: ordered dithering is now complete. --- TODO | 1 - src/Makefile.am | 2 +- src/image/dither/ordered.cpp | 166 ++++++++++------------------------- src/image/stock.cpp | 78 ++++++++++------ src/lol/image/image.h | 3 + src/lolcore.vcxproj | 1 + src/lolcore.vcxproj.filters | 3 + 7 files changed, 106 insertions(+), 148 deletions(-) diff --git a/TODO b/TODO index ba6a073e..55b77af1 100644 --- a/TODO +++ b/TODO @@ -35,7 +35,6 @@ Image: · crop.cpp · dither.cpp · dither/dbs.cpp - · dither/ordered.cpp · filter/blur.cpp · filter/color.cpp · filter/dilate.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 733989f3..d20a508a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -117,7 +117,7 @@ liblolcore_sources = \ image/codec/dummy-image.cpp \ image/color/cie1931.cpp image/color/color.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/render/noise.cpp \ \ diff --git a/src/image/dither/ordered.cpp b/src/image/dither/ordered.cpp index e459e8ac..0e23c452 100644 --- a/src/image/dither/ordered.cpp +++ b/src/image/dither/ordered.cpp @@ -1,144 +1,74 @@ -/* - * libpipi Pathetic image processing interface library - * Copyright (c) 2004-2008 Sam Hocevar - * 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 +// 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 -#include -#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 const &kernel, + float scale, float angle); - return ret; +Image Image::DitherOrdered(Array2D 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 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 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(); - 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 */ diff --git a/src/image/stock.cpp b/src/image/stock.cpp index 376ffaa2..78715dee 100644 --- a/src/image/stock.cpp +++ b/src/image/stock.cpp @@ -72,51 +72,73 @@ Array2D Image::BayerKernel(ivec2 size) return ret; } +Array2D Image::HalftoneKernel(ivec2 size) +{ + Array2D 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 { int x, y; - float dist; + float val; }; 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 Image::HalftoneKernel(ivec2 size) +Array2D Image::NormalizeKernel(Array2D const &kernel) { - Array2D ret(size); + ivec2 size = kernel.GetSize(); - 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++) + Array 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 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 Image::EdiffKernel(EdiffAlgorithm algorithm) diff --git a/src/lol/image/image.h b/src/lol/image/image.h index c88cbcf3..514dde55 100644 --- a/src/lol/image/image.h +++ b/src/lol/image/image.h @@ -94,6 +94,7 @@ public: static Array2D BayerKernel(ivec2 size); static Array2D HalftoneKernel(ivec2 size); static Array2D EdiffKernel(EdiffAlgorithm algorithm); + static Array2D NormalizeKernel(Array2D const &kernel); /* Rendering */ bool Stock(char const *desc); @@ -107,6 +108,8 @@ public: Image DitherEdiff(Array2D const &kernel, ScanMode scan = ScanMode::Raster) const; Image DitherOstromoukhov(ScanMode scan = ScanMode::Raster) const; + Image DitherOrdered(Array2D const &kernel) const; + Image DitherHalftone(float radius, float angle) const; private: class ImageData *m_data; diff --git a/src/lolcore.vcxproj b/src/lolcore.vcxproj index 7dfacffe..05c4dc00 100644 --- a/src/lolcore.vcxproj +++ b/src/lolcore.vcxproj @@ -152,6 +152,7 @@ + diff --git a/src/lolcore.vcxproj.filters b/src/lolcore.vcxproj.filters index 3f9ebd7f..3977e36f 100644 --- a/src/lolcore.vcxproj.filters +++ b/src/lolcore.vcxproj.filters @@ -358,6 +358,9 @@ image\dither + + image\dither + image\dither