summary refs log tree commit diff stats
path: root/designer.cpp
diff options
context:
space:
mode:
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}