summary refs log tree commit diff stats
path: root/designer.cpp
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-01-18 16:49:54 -0500
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-01-18 16:49:54 -0500
commitda3bc860f66d34f233028e819beee32dd1c43dd8 (patch)
tree8cc7f5ad0a8dad69ea5c3ae0405f803d3ba80051 /designer.cpp
parent46db0368fbee4cfba97178837e62f4469c4fa884 (diff)
downloadsap-da3bc860f66d34f233028e819beee32dd1c43dd8.tar.gz
sap-da3bc860f66d34f233028e819beee32dd1c43dd8.tar.bz2
sap-da3bc860f66d34f233028e819beee32dd1c43dd8.zip
Modernized project
This rewrite brings the codebase of this project more in line with the format of the other bots, including things like C++ randomization, better abstraction, use of exceptions, etc. Notably, any FFMPEG objects that get allocated are wrapped in simple objects so that they get properly destroyed if an exception is thrown. Some more error detection and cleanliness stuff can probably be done but my wrists really hurt.

Also updated librawr, and thus also removed the yaml-cpp submodule.
Diffstat (limited to 'designer.cpp')
-rw-r--r--designer.cpp244
1 files changed, 244 insertions, 0 deletions
diff --git a/designer.cpp b/designer.cpp new file mode 100644 index 0000000..3bdcbeb --- /dev/null +++ b/designer.cpp
@@ -0,0 +1,244 @@
1#include "designer.h"
2#include <dirent.h>
3#include <stdexcept>
4#include <iostream>
5#include "util.h"
6
7designer::designer(std::string fontsDir)
8{
9 DIR* fontdir;
10 struct dirent* ent;
11 if ((fontdir = opendir(fontsDir.c_str())) == nullptr)
12 {
13 throw std::invalid_argument("Couldn't find fonts");
14 }
15
16 while ((ent = readdir(fontdir)) != nullptr)
17 {
18 std::string dname(ent->d_name);
19 if ((dname.find(".otf") != std::string::npos)
20 || (dname.find(".ttf") != std::string::npos))
21 {
22 fonts.push_back(fontsDir + "/" + dname);
23 }
24 }
25
26 closedir(fontdir);
27}
28
29Magick::Image designer::generate(
30 size_t width,
31 size_t height,
32 const std::string& text,
33 std::mt19937& rng) const
34{
35 // Initialize two layers: the text, and a shadow to increase contrast.
36 Magick::Image textimage(Magick::Geometry(width, height), "transparent");
37 Magick::Image shadowimage(Magick::Geometry(width, height), "transparent");
38
39 textimage.fillColor(Magick::Color(MaxRGB, MaxRGB, MaxRGB, MaxRGB * 0.0));
40 shadowimage.fillColor(Magick::Color(0, 0, 0, 0));
41 shadowimage.strokeColor("black");
42
43 bool hasFont = false;
44 std::uniform_int_distribution<size_t> fontDist(0, fonts.size() - 1);
45
46 std::vector<std::string> words = split<std::vector<std::string>>(text, " ");
47 std::vector<std::string>::const_iterator curWord = std::begin(words);
48
49 size_t top = V_PADDING;
50 size_t minWords = 1;
51 while (curWord != std::end(words))
52 {
53 // There is a 1 in 10 chance of randomly changing the font. We also have to
54 // set the font if this is the beginning of generation.
55 if (!hasFont || (std::bernoulli_distribution(0.1)(rng)))
56 {
57 std::string font = fonts[fontDist(rng)];
58
59 textimage.font(font);
60 shadowimage.font(font);
61
62 hasFont = true;
63 }
64
65 // Choose a font size for the current line.
66 std::uniform_int_distribution<size_t> sizeDist(
67 MIN_FONT_SIZE, MAX_FONT_SIZE);
68
69 size_t size = sizeDist(rng);
70 textimage.fontPointsize(size);
71
72 // Decide what words to put on this line.
73 size_t maxWords = maxWordsInLine(curWord, std::end(words), textimage);
74
75 size_t lineLen;
76 if (minWords > maxWords)
77 {
78 lineLen = maxWords;
79 } else {
80 std::uniform_int_distribution<size_t> lenDist(minWords, maxWords);
81 lineLen = lenDist(rng);
82 }
83
84 std::string prefixText = implode(curWord, curWord + lineLen, " ");
85
86 // Determine if the choice of font, font size, and number of words, would
87 // prevent the algorithm from being about to layout the entire string even
88 // if the rest of it were rendered in the smallest font size.
89 Magick::TypeMetric metric;
90 textimage.fontTypeMetrics(prefixText, &metric);
91
92 textimage.fontPointsize(MIN_FONT_SIZE);
93
94 // This is how much vertical space would be required to render the rest of
95 // the string at the minimum font size.
96 int lowpadding = minHeightRequired(
97 curWord + lineLen,
98 std::end(words),
99 textimage);
100
101 // This is the amount of space that would be left over if the rest of the
102 // string were rendered at the minimum font size.
103 int freespace =
104 static_cast<int>(height) - static_cast<int>(V_PADDING)
105 - static_cast<int>(top) - lowpadding - metric.textHeight();
106
107 // Some debug text.
108 std::cout << "top of " << top << " with lowpad of " << lowpadding
109 << " and textheight of " << metric.textHeight() << " with freespace="
110 << freespace << std::endl;
111
112 // If there wouldn't be enough room to render the rest of the string at the
113 // minimum font size, go back to the top of the loop and choose a different
114 // font, font size, and number of words.
115 if (freespace < 0)
116 {
117 minWords = lineLen;
118
119 continue;
120 }
121
122 minWords = 1;
123
124 // Determine how much space to leave between this line and the previous one.
125 size_t toppadding;
126 if (std::bernoulli_distribution(0.5)(rng))
127 {
128 // Exponential distribution, biased toward top
129 std::uniform_int_distribution<size_t> expDist(
130 1, static_cast<size_t>(exp(freespace + 1)));
131
132 toppadding = log(expDist(rng));
133 } else {
134 // Linear distribution, biased toward bottom
135 std::uniform_int_distribution<size_t> linDist(0, freespace);
136
137 toppadding = linDist(rng);
138 }
139
140 // Determine the x-coordinate of this line.
141 std::uniform_int_distribution<size_t> leftDist(
142 H_PADDING,
143 width - H_PADDING - static_cast<size_t>(metric.textWidth()) - 1);
144
145 size_t leftx = leftDist(rng);
146
147 // Render this line.
148 size_t ycor = top + toppadding + metric.ascent();
149
150 std::cout << "printing at " << leftx << "," << ycor << std::endl;
151
152 textimage.fontPointsize(size);
153 textimage.annotate(prefixText, Magick::Geometry(0, 0, leftx, ycor));
154
155 shadowimage.fontPointsize(size);
156 shadowimage.strokeWidth(size / 10);
157 shadowimage.annotate(prefixText, Magick::Geometry(0, 0, leftx, ycor));
158
159 top += toppadding + metric.textHeight();
160
161 std::advance(curWord, lineLen);
162 }
163
164 // Make the shadow layer semi-transparent.
165 Magick::PixelPacket* shadpixels = shadowimage.getPixels(0, 0, width, height);
166 Magick::PixelPacket* textpixels = textimage.getPixels(0, 0, width, height);
167 for (size_t j = 0; j < height; j++)
168 {
169 for (size_t i = 0; i < width; i++)
170 {
171 size_t ind = j * width + i;
172
173 if (shadpixels[ind].opacity != MaxRGB)
174 {
175 shadpixels[ind].opacity = MaxRGB * 0.25;
176 }
177 }
178 }
179
180 shadowimage.syncPixels();
181 textimage.syncPixels();
182
183 // Add some blur to the text and shadow.
184 shadowimage.blur(10.0, 20.0);
185 textimage.blur(0.0, 0.5);
186
187 // Put the text layer on top of the shadow.
188 shadowimage.composite(textimage, 0, 0, Magick::OverCompositeOp);
189
190 return shadowimage;
191}
192
193size_t designer::maxWordsInLine(
194 std::vector<std::string>::const_iterator first,
195 std::vector<std::string>::const_iterator last,
196 Magick::Image& textimage) const
197{
198 size_t result = 0;
199
200 std::string curline = "";
201 for (; first != last; first++)
202 {
203 curline += " " + *first;
204
205 Magick::TypeMetric metric;
206 textimage.fontTypeMetrics(curline, &metric);
207
208 if (metric.textWidth() > ((textimage.columns() / 10) * 9))
209 {
210 break;
211 } else {
212 result++;
213 }
214 }
215
216 return result;
217}
218
219size_t designer::minHeightRequired(
220 std::vector<std::string>::const_iterator first,
221 std::vector<std::string>::const_iterator last,
222 Magick::Image& textimage) const
223{
224 if (first == last)
225 {
226 return 0;
227 } else {
228 size_t result = 0;
229
230 while (first != last)
231 {
232 size_t prefixlen = maxWordsInLine(first, last, textimage);
233 std::string prefixText = implode(first, first + prefixlen, " ");
234
235 Magick::TypeMetric metric;
236 textimage.fontTypeMetrics(prefixText, &metric);
237 result += metric.textHeight() + V_PADDING;
238
239 std::advance(first, prefixlen);
240 }
241
242 return result - V_PADDING;
243 }
244}