Quellcode durchsuchen

image: make the various image processing kernels use Array2D instead

of slightly overkill Image objects.
undefined
Sam Hocevar vor 10 Jahren
Ursprung
Commit
08fb9be357
7 geänderte Dateien mit 187 neuen und 207 gelöschten Zeilen
  1. +1
    -1
      src/Makefile.am
  2. +16
    -22
      src/image/dither/ediff.cpp
  3. +0
    -110
      src/image/render/screen.cpp
  4. +147
    -66
      src/image/stock.cpp
  5. +22
    -3
      src/lol/image/image.h
  6. +0
    -1
      src/lolcore.vcxproj
  7. +1
    -4
      src/lolcore.vcxproj.filters

+ 1
- 1
src/Makefile.am Datei anzeigen

@@ -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 \


+ 16
- 22
src/image/dither/ediff.cpp Datei anzeigen

@@ -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<float> const &kernel, ScanMode scan) const
{
Image dst = *this;

float *pixels = dst.Lock<PixelFormat::Y_F32>();
int w = dst.GetSize().x;
int h = dst.GetSize().y;

float *kerdata = kernel.Lock<PixelFormat::Y_F32>();
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<PixelFormat::Y_F32>();
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;


+ 0
- 110
src/image/render/screen.cpp Datei anzeigen

@@ -1,110 +0,0 @@
//
// 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"

/*
* 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<PixelFormat::Y_F32>();

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<dot_t> 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<PixelFormat::Y_F32>();
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 */


+ 147
- 66
src/image/stock.cpp Datei anzeigen

@@ -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<PixelFormat::Y_F32>();
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<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;
}

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<float> Image::HalftoneKernel(ivec2 size)
{
Array2D<float> ret(size);

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++)
{
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<float> EdiffKernel(EdiffAlgorithm algorithm)
{
Array2D<float> 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<PixelFormat::Y_F32>();
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 */


+ 22
- 3
src/lol/image/image.h Datei anzeigen

@@ -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<ivec2, ivec2>& tiles) const;

/* Image processing kernels */
static Array2D<float> BayerKernel(ivec2 size);
static Array2D<float> HalftoneKernel(ivec2 size);
static Array2D<float> 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<float> const &kernel);

Image DitherRandom() const;
Image DitherEdiff(Image &kernel, ScanMode scan = ScanMode::Raster) const;
Image DitherEdiff(Array2D<float> const &kernel,
ScanMode scan = ScanMode::Raster) const;
Image DitherOstromoukhov(ScanMode scan = ScanMode::Raster) const;

private:


+ 0
- 1
src/lolcore.vcxproj Datei anzeigen

@@ -157,7 +157,6 @@
<ClCompile Include="image\image.cpp" />
<ClCompile Include="image\pixels.cpp" />
<ClCompile Include="image\render\noise.cpp" />
<ClCompile Include="image\render\screen.cpp" />
<ClCompile Include="input\controller.cpp" />
<ClCompile Include="input\input.cpp" />
<ClCompile Include="layer.cpp" />


+ 1
- 4
src/lolcore.vcxproj.filters Datei anzeigen

@@ -373,9 +373,6 @@
<ClCompile Include="image\render\noise.cpp">
<Filter>image\render</Filter>
</ClCompile>
<ClCompile Include="image\render\screen.cpp">
<Filter>image\render</Filter>
</ClCompile>
<ClCompile Include="image\pixels.cpp">
<Filter>image</Filter>
</ClCompile>
@@ -784,4 +781,4 @@
</None>
<None Include="Makefile.am" />
</ItemGroup>
</Project>
</Project>

Laden…
Abbrechen
Speichern