13using medialoader::Colorf;
15float clamp01(
float x) {
return std::max(0.f, std::min(1.f,
x)); }
16float mixf(
float a,
float b,
float t) {
return a + (
b -
a) * t; }
17float luma(
const Colorf &
c) {
return c.r * 0.299f +
c.g * 0.587f +
c.b * 0.114f; }
19float hash21(
int x,
int y) {
20 float n = std::sin(
float(
x) * 127.1f +
float(
y) * 311.7f) * 43758.5453f;
21 return n - std::floor(
n);
24Colorf sample(
const ImageData *img,
int x,
int y) {
25 x = std::max(0, std::min(img->getWidth() - 1,
x));
26 y = std::max(0, std::min(img->getHeight() - 1,
y));
27 return img->getPixel(
x,
y);
30float sobelAt(
const ImageData *img,
int x,
int y) {
31 auto L = [&](
int dx,
int dy) {
return luma(sample(img,
x + dx,
y + dy)); };
32 float gx = -L(-1, -1) - 2.f * L(-1, 0) - L(-1, 1) + L(1, -1) + 2.f * L(1, 0) + L(1, 1);
33 float gy = -L(-1, -1) - 2.f * L(0, -1) - L(1, -1) + L(-1, 1) + 2.f * L(0, 1) + L(1, 1);
34 return std::sqrt(gx * gx + gy * gy);
37ImageData *ensureRgba8Clone(ImageData *src) {
39 if (src->getFormat() !=
"RGBA8")
40 throw eve::Exception(
"processImageCpu: only RGBA8 supported (got %s)",
41 src->getFormat().c_str());
42 return new ImageData(*src);
45void processCartoon(ImageData *src, ImageData *dst) {
46 const int w = src->getWidth();
47 const int h = src->getHeight();
48 constexpr float bands = 4.f;
49 constexpr float posterize = 6.f;
50 for (
int y = 0;
y <
h; ++
y) {
51 for (
int x = 0;
x <
w; ++
x) {
52 Colorf
c = src->getPixel(
x,
y);
54 float cel = std::floor(Y * bands) / std::max(bands - 1.f, 1.f);
55 float scale = (Y > 1e-4f) ? (cel / Y) : 1.f;
56 c.r = clamp01(std::floor(
c.r *
scale * posterize + 0.5f) / posterize);
57 c.g = clamp01(std::floor(
c.g *
scale * posterize + 0.5f) / posterize);
58 c.b = clamp01(std::floor(
c.b *
scale * posterize + 0.5f) / posterize);
59 float edge = sobelAt(src,
x,
y);
60 float line = clamp01((edge - 0.35f) / 0.08f);
61 c.r = mixf(
c.r, 0.05f,
line);
62 c.g = mixf(
c.g, 0.04f,
line);
63 c.b = mixf(
c.b, 0.08f,
line);
64 dst->setPixel(
x,
y,
c);
69void processWatercolor(ImageData *src, ImageData *dst) {
70 const int w = src->getWidth();
71 const int h = src->getHeight();
72 for (
int y = 0;
y <
h; ++
y) {
73 for (
int x = 0;
x <
w; ++
x) {
74 Colorf acc{0, 0, 0, 0};
75 for (
int dy = -1; dy <= 1; ++dy) {
76 for (
int dx = -1; dx <= 1; ++dx) {
77 Colorf
s = sample(src,
x + dx,
y + dy);
88 Colorf sharp = src->getPixel(
x,
y);
90 c.r = mixf(sharp.r, acc.r, 0.65f);
91 c.g = mixf(sharp.g, acc.g, 0.65f);
92 c.b = mixf(sharp.b, acc.b, 0.65f);
94 float edge = sobelAt(src,
x,
y);
95 float darken = 1.f - clamp01(edge * 1.4f) * 0.85f;
99 float grain = mixf(0.85f, 1.15f, hash21(
x,
y));
100 c.r = clamp01(
c.r * grain);
101 c.g = clamp01(
c.g * grain * 0.98f);
102 c.b = clamp01(
c.b * grain * 0.94f);
103 dst->setPixel(
x,
y,
c);
108void processInk(ImageData *src, ImageData *dst) {
109 const int w = src->getWidth();
110 const int h = src->getHeight();
111 constexpr float levels = 5.f;
112 for (
int y = 0;
y <
h; ++
y) {
113 for (
int x = 0;
x <
w; ++
x) {
114 float Y = std::pow(clamp01(luma(src->getPixel(
x,
y))), 1.35f);
115 float tone = std::floor(Y * levels) / std::max(levels - 1.f, 1.f);
116 float edge = sobelAt(src,
x,
y);
117 float ink = clamp01((1.f - tone) * 0.85f + clamp01((edge - 0.28f) / 0.15f));
118 ink = clamp01(ink + (hash21(
x * 3,
y * 5) - 0.5f) * 0.08f);
120 c.r = mixf(0.93f, 0.05f, ink);
121 c.g = mixf(0.90f, 0.05f, ink);
122 c.b = mixf(0.82f, 0.07f, ink);
123 c.a = src->getPixel(
x,
y).a;
124 dst->setPixel(
x,
y,
c);
129void processPixel(ImageData *src, ImageData *dst) {
130 const int w = src->getWidth();
131 const int h = src->getHeight();
132 constexpr int pixelSize = 4;
133 constexpr float steps = 8.f;
134 static const int bayer[16] = {0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5};
135 for (
int y = 0;
y <
h; ++
y) {
136 for (
int x = 0;
x <
w; ++
x) {
137 int sx = (
x / pixelSize) * pixelSize + pixelSize / 2;
138 int sy = (
y / pixelSize) * pixelSize + pixelSize / 2;
139 Colorf
c = sample(src, sx, sy);
140 int bi = (
x & 3) + (
y & 3) * 4;
141 float d = (float(bayer[bi]) / 16.f - 0.5f) * 0.35f / steps;
142 c.r = clamp01(std::floor(clamp01(
c.r +
d) * steps) / std::max(steps - 1.f, 1.f));
143 c.g = clamp01(std::floor(clamp01(
c.g +
d) * steps) / std::max(steps - 1.f, 1.f));
144 c.b = clamp01(std::floor(clamp01(
c.b +
d) * steps) / std::max(steps - 1.f, 1.f));
145 dst->setPixel(
x,
y,
c);
153 ImageData *dst = ensureRgba8Clone(src);
155 if (style ==
"cartoon")
156 processCartoon(src, dst);
157 else if (style ==
"watercolor")
158 processWatercolor(src, dst);
159 else if (style ==
"ink")
160 processInk(src, dst);
161 else if (style ==
"pixel")
162 processPixel(src, dst);
165 throw eve::Exception(
"processImageCpu: unknown style '%s'", style.c_str());