载入中...
搜索中...
未找到
ImageStylize.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "image/ImageData.h"
5
6#include <algorithm>
7#include <cmath>
8
9namespace eve::stylize {
10namespace {
11
13using medialoader::Colorf;
14
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; }
18
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);
22}
23
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);
28}
29
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);
35}
36
37ImageData *ensureRgba8Clone(ImageData *src) {
38 if (!src) throw eve::Exception("processImageCpu: null ImageData");
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);
43}
44
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);
53 float Y = luma(c);
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);
65 }
66 }
67}
68
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);
78 acc.r += s.r;
79 acc.g += s.g;
80 acc.b += s.b;
81 acc.a += s.a;
82 }
83 }
84 acc.r /= 9.f;
85 acc.g /= 9.f;
86 acc.b /= 9.f;
87 acc.a /= 9.f;
88 Colorf sharp = src->getPixel(x, y);
89 Colorf c;
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);
93 c.a = sharp.a;
94 float edge = sobelAt(src, x, y);
95 float darken = 1.f - clamp01(edge * 1.4f) * 0.85f;
96 c.r *= darken;
97 c.g *= darken;
98 c.b *= darken;
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);
104 }
105 }
106}
107
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);
119 Colorf c;
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);
125 }
126 }
127}
128
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);
146 }
147 }
148}
149
150} // namespace
151
152image::ImageData *processImageCpu(image::ImageData *src, const std::string &style) {
153 ImageData *dst = ensureRgba8Clone(src);
154 try {
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);
163 else {
164 delete dst;
165 throw eve::Exception("processImageCpu: unknown style '%s'", style.c_str());
166 }
167 } catch (...) {
168 delete dst;
169 throw;
170 }
171 return dst;
172}
173
174} // namespace eve::stylize
int line
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
uint32_t a
uint32_t b
uint32_t c
int d
float scale
Definition TreeMesh.cpp:122
uint32_t s
Definition Weather.cpp:28
Represents raw pixel data.
Definition ImageData.h:26
image::ImageData * processImageCpu(image::ImageData *src, const std::string &style)
CPU NPR helpers — returns a new RGBA8 ImageData (caller owns).