summary refs log tree commit diff stats
path: root/wizard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'wizard.cpp')
-rw-r--r--wizard.cpp569
1 files changed, 119 insertions, 450 deletions
diff --git a/wizard.cpp b/wizard.cpp index e4b79e6..4eaefb3 100644 --- a/wizard.cpp +++ b/wizard.cpp
@@ -1,443 +1,114 @@
1#include "wizard.h"
1 2
3#include <Magick++.h>
4#include <allheaders.h>
5#include <hkutil/string.h>
6#include <tesseract/baseapi.h>
2 7
3#include <json.hpp>
4#include <fstream>
5#include <sstream>
6#include <list>
7#include <iostream> 8#include <iostream>
8#include <vector> 9#include <list>
9#include <tuple> 10#include <memory>
10#include <random> 11#include <random>
11#include <set> 12#include <set>
12#include <memory> 13#include <tuple>
13#include <hkutil/string.h> 14#include <vector>
14#include <tesseract/baseapi.h>
15#include <leptonica/allheaders.h>
16#include <curl_easy.h>
17#include <Magick++.h>
18#include "prefix_search.h"
19
20
21
22
23 15
16#include "designer.h"
17#include "prefix_search.h"
24 18
25std::string stripSpaces(std::string in) 19std::string stripSpaces(std::string in) {
26{ 20 in.erase(std::remove_if(std::begin(in), std::end(in), ::isspace),
27 in.erase( 21 std::end(in));
28 std::remove_if(
29 std::begin(in),
30 std::end(in),
31 ::isspace),
32 std::end(in));
33 22
34 return in; 23 return in;
35} 24}
36 25
37
38
39
40
41using ps_type = prefix_search<std::tuple<size_t, size_t>>;
42
43enum class card_frame {
44 m2015,
45 modern
46};
47
48struct card {
49 size_t id;
50 std::string name;
51 std::string imageUri;
52 card_frame frame;
53
54 card(
55 size_t id,
56 std::string name,
57 std::string imageUri,
58 card_frame frame) :
59 id(id),
60 name(std::move(name)),
61 imageUri(std::move(imageUri)),
62 frame(frame)
63 {
64 }
65};
66
67struct usage {
68 size_t cardId;
69 size_t strIndex;
70 size_t strLen;
71
72 usage(
73 size_t ci,
74 size_t si,
75 size_t sl) :
76 cardId(ci),
77 strIndex(si),
78 strLen(sl)
79 {
80 }
81};
82
83struct solution {
84 const ps_type& prefix;
85 std::vector<size_t> lengths;
86 size_t score;
87};
88
89class designer {
90public:
91
92 designer(
93 std::string text,
94 const ps_type& titles) :
95 text_(std::move(text)),
96 titles_(titles),
97 solutions_(text_.length() + 1)
98 {
99 }
100
101 std::list<usage> generate(std::mt19937& rng) const;
102
103
104private:
105
106 const solution& get(size_t i) const;
107
108 solution calculate(size_t i) const;
109
110 const std::string text_;
111 const ps_type& titles_;
112
113 mutable std::vector<std::unique_ptr<solution>> solutions_;
114};
115
116
117
118
119
120
121
122
123
124std::list<usage> designer::generate(std::mt19937& rng) const
125{
126 std::list<usage> result;
127 size_t cur = 0;
128
129 while (cur < text_.length())
130 {
131 const solution& curSol = get(cur);
132 const std::vector<size_t>& posLens = curSol.lengths;
133
134 std::uniform_int_distribution<size_t> lenDist(0, posLens.size() - 1);
135 size_t len = posLens.at(lenDist(rng));
136
137 const ps_type& prefix = curSol.prefix;
138 std::uniform_int_distribution<size_t> cardDist(0, prefix.getCount() - 1);
139 size_t cardIndex = cardDist(rng);
140 std::tuple<size_t, size_t> pd = prefix.at(cardIndex);
141
142 result.emplace_back(std::get<0>(pd), std::get<1>(pd), len);
143
144 cur += len;
145 }
146
147 return result;
148}
149
150
151
152
153solution designer::calculate(size_t i) const
154{
155 if (i == text_.length())
156 {
157 return {
158 titles_,
159 {},
160 0
161 };
162 }
163
164 const ps_type& prefix = titles_.find(text_, i);
165
166 bool foundScore = false;
167 size_t bestScore;
168 std::vector<size_t> bestLens;
169
170 for (int j = 1;
171 (j <= prefix.getDepth()) && (i + j <= text_.length());
172 j++)
173 {
174 const solution& subSol = get(i + j);
175
176 if (subSol.score > 0 || (i + j == text_.length()))
177 {
178 size_t tempScore = subSol.score + 1;
179
180 if (!foundScore || tempScore < bestScore)
181 {
182 foundScore = true;
183 bestScore = tempScore;
184
185 bestLens.clear();
186 bestLens.push_back(j);
187 } else if (tempScore == bestScore)
188 {
189 bestLens.push_back(j);
190 }
191 }
192 }
193
194 if (!foundScore)
195 {
196 return {
197 titles_,
198 {},
199 0
200 };
201 } else {
202 return {
203 prefix,
204 std::move(bestLens),
205 bestScore
206 };
207 }
208}
209
210const solution& designer::get(size_t i) const
211{
212 if (!solutions_.at(i))
213 {
214 solutions_[i] = std::make_unique<solution>(calculate(i));
215 }
216
217 return *solutions_.at(i);
218}
219
220
221
222
223Magick::Image downloadImage(const std::string& url)
224{
225 std::ostringstream imgbuf;
226 curl::curl_ios<std::ostringstream> imgios(imgbuf);
227 curl::curl_easy imghandle(imgios);
228
229 imghandle.add<CURLOPT_URL>(url.c_str());
230 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30);
231 imghandle.add<CURLOPT_TIMEOUT>(300);
232
233 imghandle.perform();
234
235 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200)
236 {
237 throw std::runtime_error("Could not download image");
238 }
239
240 std::string content_type = imghandle.get_info<CURLINFO_CONTENT_TYPE>().get();
241 if (content_type.substr(0, 6) != "image/")
242 {
243 throw std::runtime_error("Could not download image");
244 }
245
246 std::string imgstr = imgbuf.str();
247 Magick::Blob img(imgstr.c_str(), imgstr.length());
248
249 Magick::Image pic;
250
251 try
252 {
253 pic.read(img);
254 } catch (const Magick::ErrorOption& e)
255 {
256 // Occurs when the the data downloaded from the server is malformed
257 std::cout << "Magick: " << e.what() << std::endl;
258
259 throw std::runtime_error("Could not download image");
260 }
261
262 return pic;
263}
264
265
266
267
268
269class tesseract_deleter { 26class tesseract_deleter {
270public: 27 public:
271 28 void operator()(tesseract::TessBaseAPI* ptr) const { ptr->End(); }
272 void operator()(tesseract::TessBaseAPI* ptr) const
273 {
274 ptr->End();
275 }
276}; 29};
277 30
278using tesseract_ptr = 31using tesseract_ptr =
279 std::unique_ptr<tesseract::TessBaseAPI, tesseract_deleter>; 32 std::unique_ptr<tesseract::TessBaseAPI, tesseract_deleter>;
280 33
281class pix_deleter { 34class pix_deleter {
282public: 35 public:
283 36 void operator()(Pix* ptr) const { pixDestroy(&ptr); }
284 void operator()(Pix* ptr) const
285 {
286 pixDestroy(&ptr);
287 }
288}; 37};
289 38
290using pix_ptr = std::unique_ptr<Pix, pix_deleter>; 39using pix_ptr = std::unique_ptr<Pix, pix_deleter>;
291 40
292 41wizard::wizard(std::string cardsPath, std::string cachePath,
293 42 std::string outputPath, std::string jsonPath, std::mt19937& rng)
294 43 : cards_(cardsPath),
295 44 images_(cachePath),
296 45 outputPath_(outputPath),
297int main(int argc, char** argv) 46 jsonPath_(jsonPath),
298{ 47 rng_(rng) {
299 Magick::InitializeMagick(nullptr);
300
301 std::random_device randomDevice;
302 std::mt19937 rng(randomDevice());
303
304 std::cout << "Compiling prefix search..." << std::endl;
305
306 std::vector<card> cards;
307 ps_type titles;
308
309 std::set<char> chars;
310
311 {
312 std::ifstream in(
313 "/Users/hatkirby/Downloads/scryfall-default-cards.json",
314 std::ios::in | std::ios::binary);
315 std::ostringstream contents;
316 contents << in.rdbuf();
317
318
319 nlohmann::json cardsJson = nlohmann::json::parse(contents.str());
320
321
322
323 for (const auto& cardJson : cardsJson)
324 {
325 if (
326 // The object needs to be a card
327 cardJson["object"] == "card" &&
328 // It needs to have a downloadable image
329 cardJson.count("image_uris") &&
330 // Make sure we can support the card layout
331 (
332 cardJson["layout"] == "normal" ||
333 cardJson["layout"] == "leveler" ||
334 cardJson["layout"] == "saga"
335 ) &&
336 // Digital cards look slightly different so ignore them
337 !cardJson["digital"] &&
338 // Only use english printings
339 cardJson["lang"] == "en" &&
340 // Currently not supporting silver bordered cards
341 cardJson["border_color"] != "silver" &&
342 // It is hard to read the name of a planeswalker
343 cardJson["type_line"].get<std::string>()
344 .find("Planeswalker") == std::string::npos &&
345 // This cuts out checklists and special tokens
346 cardJson["type_line"] != "Card" &&
347 // Amonkhet invocations are impossible
348 cardJson["set"] != "mp2")
349 {
350 card_frame frame;
351
352 if (cardJson["frame"] == "2015")
353 {
354 frame = card_frame::m2015;
355 } else if (cardJson["frame"] == "2003")
356 {
357 frame = card_frame::modern;
358 } else {
359 continue;
360 }
361
362 size_t cardId = cards.size();
363 cards.emplace_back(
364 cardId,
365 cardJson["name"],
366 cardJson["image_uris"]["png"],
367 frame);
368
369 std::string canon = hatkirby::lowercase(cardJson["name"]);
370
371 for (int i = 0; i < canon.length(); i++)
372 {
373 titles.add(canon, {cardId, i}, i);
374
375 chars.insert(canon.at(i));
376 }
377 }
378 }
379 }
380
381 std::cout << "Characters: "; 48 std::cout << "Characters: ";
382 for (char ch : chars) 49
383 { 50 for (char ch : cards_.getCharacters()) {
384 std::cout << ch; 51 std::cout << ch;
385 } 52 }
53
386 std::cout << std::endl; 54 std::cout << std::endl;
55}
56
57void wizard::run() {
58 std::string text = "what the heck, it's just some gay guy";
59 // getline(std::cin, text);
60 if (text.back() == '\r') {
61 text.pop_back();
62 }
387 63
388 std::cout << "Calculating card list..." << std::endl; 64 std::cout << "Calculating card list..." << std::endl;
389 65
390 std::string text = "is it pretentious that i'm sending someone over 100 common and uncommon magic cards in an iphone six box";
391 std::string canonText = hatkirby::lowercase(text); 66 std::string canonText = hatkirby::lowercase(text);
392 designer des(canonText, titles); 67 designer des(canonText, cards_.getTitles());
393 std::list<usage> res = des.generate(rng); 68 std::list<usage> res = des.generate(rng_);
394 69
395 Magick::Image endImage; 70 Magick::Image endImage;
396 bool firstSlice = false; 71 bool firstSlice = false;
72 int ui = 0;
397 73
398 for (const usage& u : res) 74 for (const usage& u : res) {
399 { 75 const card& theCard = cards_.getCard(u.cardId);
400 const card& theCard = cards.at(u.cardId);
401 const std::string& cardName = theCard.name; 76 const std::string& cardName = theCard.name;
402 77
403 std::cout << cardName.substr(0, u.strIndex) 78 std::cout << cardName.substr(0, u.strIndex) << "["
404 << "[" << cardName.substr(u.strIndex, u.strLen) 79 << cardName.substr(u.strIndex, u.strLen) << "]"
405 << "]" << cardName.substr(u.strIndex + u.strLen) 80 << cardName.substr(u.strIndex + u.strLen) << std::endl;
406 << std::endl;
407 81
408 std::cout << "Downloading image..." << std::endl; 82 std::cout << "Downloading image..." << std::endl;
409 83
410 Magick::Image cardImg = downloadImage(theCard.imageUri); 84 Magick::Image cardImg = images_.get(theCard.uuid, theCard.imageUri);
411 85
412 std::cout << "Reading text..." << std::endl; 86 std::cout << "Reading text..." << std::endl;
413 87
414 Magick::Image titleImg = cardImg; 88 Magick::Image titleImg = cardImg;
415 titleImg.magick("TIFF"); 89 titleImg.magick("TIFF");
416 90
417 //titleImg.threshold(MaxRGB / 2); 91 // titleImg.threshold(MaxRGB / 2);
418 titleImg.write("pre.tif"); 92 titleImg.write("pre.tif");
419 93
420
421 Magick::Geometry margin; 94 Magick::Geometry margin;
422 95
423 if (theCard.frame == card_frame::m2015) 96 if (theCard.frame == card_frame::m2015) {
424 { 97 margin = Magick::Geometry{595, 46, 57, 54};
425 margin = Magick::Geometry { 595, 46, 57, 54 }; 98 } else if (theCard.frame == card_frame::modern) {
426 } else if (theCard.frame == card_frame::modern) 99 margin = Magick::Geometry{581, 50, 63, 57};
427 {
428 margin = Magick::Geometry { 581, 50, 63, 57 };
429 } 100 }
430 101
431 titleImg.crop(margin); 102 titleImg.crop(margin);
432 titleImg.zoom({ margin.width() * 5, margin.height() * 5 }); 103 titleImg.zoom({margin.width() * 5, margin.height() * 5});
433 104
434 //titleImg.quantizeColorSpace(Magick::GRAYColorspace); 105 // titleImg.quantizeColorSpace(Magick::GRAYColorspace);
435 //titleImg.quantizeColors(2); 106 // titleImg.quantizeColors(2);
436 //titleImg.quantize(); 107 // titleImg.quantize();
437 titleImg.backgroundColor("white"); 108 titleImg.backgroundColor("white");
438 titleImg.matte(false); 109 titleImg.matte(false);
439 titleImg.resolutionUnits(Magick::PixelsPerInchResolution); 110 titleImg.resolutionUnits(Magick::PixelsPerInchResolution);
440 titleImg.density({ 300, 300 }); 111 titleImg.density({300, 300});
441 titleImg.type(Magick::GrayscaleType); 112 titleImg.type(Magick::GrayscaleType);
442 113
443 titleImg.write("title.tif"); 114 titleImg.write("title.tif");
@@ -445,15 +116,13 @@ int main(int argc, char** argv)
445 Magick::Blob titleBlob; 116 Magick::Blob titleBlob;
446 titleImg.write(&titleBlob); 117 titleImg.write(&titleBlob);
447 118
448 pix_ptr titlePix { pixReadMemTiff( 119 pix_ptr titlePix{
449 reinterpret_cast<const unsigned char*>(titleBlob.data()), 120 pixReadMemTiff(reinterpret_cast<const unsigned char*>(titleBlob.data()),
450 titleBlob.length(), 121 titleBlob.length(), 0)};
451 0) };
452 122
453 tesseract_ptr api { new tesseract::TessBaseAPI() }; 123 tesseract_ptr api{new tesseract::TessBaseAPI()};
454 124
455 if (api->Init(nullptr, "eng")) 125 if (api->Init(nullptr, "eng")) {
456 {
457 throw std::runtime_error("Could not initialize tesseract"); 126 throw std::runtime_error("Could not initialize tesseract");
458 } 127 }
459 128
@@ -463,48 +132,48 @@ int main(int argc, char** argv)
463 tesseract::ResultIterator* ri = api->GetIterator(); 132 tesseract::ResultIterator* ri = api->GetIterator();
464 tesseract::PageIteratorLevel level = tesseract::RIL_TEXTLINE; 133 tesseract::PageIteratorLevel level = tesseract::RIL_TEXTLINE;
465 bool foundName = false; 134 bool foundName = false;
135 size_t extraChars = 0;
466 136
467 if (ri) 137 if (ri) {
468 { 138 do {
469 do
470 {
471 const char* line = ri->GetUTF8Text(level); 139 const char* line = ri->GetUTF8Text(level);
472 140
473 if (line) 141 if (line) {
474 {
475 std::string lineStr(line); 142 std::string lineStr(line);
476 143
477 //if (stripSpaces(hatkirby::lowercase(lineStr)).find(stripSpaces(hatkirby::lowercase((cardName)))) == 0) 144 size_t leadin = hatkirby::lowercase(lineStr).find(
478 { 145 hatkirby::lowercase((cardName)));
146 if (leadin != std::string::npos) {
479 foundName = true; 147 foundName = true;
148 extraChars = leadin;
480 149
481 break; 150 break;
482 }/* else { 151 } /* else {
483 std::cout << "WRONG: " << lineStr << std::endl; 152 std::cout << "WRONG: " << lineStr << std::endl;
484 }*/ 153 }*/
485 } 154 }
486 } while (ri->Next(level)); 155 } while (ri->Next(level));
487 } 156 }
488 157
489 if (foundName) 158 if (foundName) {
490 {
491 level = tesseract::RIL_SYMBOL; 159 level = tesseract::RIL_SYMBOL;
492 160
161 for (int i = 0; i < extraChars; i++) {
162 ri->Next(level);
163 }
164
493 std::vector<std::tuple<unsigned int, unsigned int>> characters; 165 std::vector<std::tuple<unsigned int, unsigned int>> characters;
494 size_t cur = 0; 166 size_t cur = 0;
495 167
496 do 168 do {
497 {
498 int x1, y1, x2, y2; 169 int x1, y1, x2, y2;
499 ri->BoundingBox(level, &x1, &y1, &x2, &y2); 170 ri->BoundingBox(level, &x1, &y1, &x2, &y2);
500 171
501 x1 /= 5; 172 x1 /= 5;
502 x2 /= 5; 173 x2 /= 5;
503 174
504 if (cardName.at(cur) == ' ') 175 if (cardName.at(cur) == ' ') {
505 { 176 if (cur == 0) {
506 if (cur == 0)
507 {
508 characters.emplace_back(0, x1); 177 characters.emplace_back(0, x1);
509 } else { 178 } else {
510 const auto& prev = characters.back(); 179 const auto& prev = characters.back();
@@ -515,73 +184,73 @@ int main(int argc, char** argv)
515 } 184 }
516 185
517 characters.emplace_back(x1, x2); 186 characters.emplace_back(x1, x2);
187 std::cout << characters.size() << " " << x1 << "," << x2 << " ("
188 << ri->GetUTF8Text(level) << ")" << std::endl;
518 189
519 cur++; 190 cur++;
520 } while (ri->Next(level) && (cur < cardName.length())); 191 } while (ri->Next(level) && (cur < cardName.length()));
521 192
522 if (cur != cardName.length()) 193 if (cur != cardName.length()) {
523 {
524 throw std::runtime_error("Error detecting character bounds"); 194 throw std::runtime_error("Error detecting character bounds");
525 } 195 }
526 196
527 cardImg.crop({ 197 Magick::Image bakImg = cardImg;
528 std::get<1>(characters[u.strIndex + u.strLen - 1])
529 - std::get<0>(characters[u.strIndex]),
530 cardImg.rows(),
531 margin.xOff() + std::get<0>(characters[u.strIndex]),
532 0
533 });
534
535 cardImg.magick("PNG");
536 cardImg.write("slice.png");
537 } else {
538 std::cout << "Didn't find name" << std::endl;
539 }
540
541
542 198
199 unsigned int left = margin.xOff() + std::get<0>(characters[u.strIndex]);
200 unsigned int right =
201 margin.xOff() + std::get<1>(characters[u.strIndex + u.strLen - 1]);
543 202
203 cardImg.crop({right - left, cardImg.rows(), left, 0});
544 204
205 if (ui == 0) {
206 bakImg.crop({margin.xOff(), bakImg.rows(), 0, 0});
545 207
208 cardImg.extent({cardImg.columns() + bakImg.columns(), cardImg.rows()},
209 "black", Magick::GravityType::EastGravity);
546 210
211 cardImg.composite(bakImg, 0, 0);
212 } else if (ui == (res.size() - 1)) {
213 bakImg.crop(
214 {bakImg.columns() - margin.xOff() - std::get<1>(characters.back()),
215 bakImg.rows(), margin.xOff() + std::get<1>(characters.back()), 0});
547 216
217 int xoff = cardImg.columns();
548 218
219 cardImg.extent({cardImg.columns() + bakImg.columns(), cardImg.rows()});
549 220
221 cardImg.composite(bakImg, xoff, 0);
222 }
550 223
224 cardImg.magick("PNG");
225 cardImg.write("slice.png");
226 } else {
227 std::cout << "Didn't find name" << std::endl;
228 }
551 229
552 if (!firstSlice) 230 if (!firstSlice) {
553 {
554 firstSlice = true; 231 firstSlice = true;
555 endImage = cardImg; 232 endImage = cardImg;
556 } else {
557 233
234 if (theCard.frame == card_frame::m2015) {
235 // endImage.extent({endImage.columns(), endImage.rows() + 6},
236 // Magick::SouthGravity);
237 }
238 } else {
558 int xoff = endImage.columns(); 239 int xoff = endImage.columns();
559 240
560 endImage.backgroundColor("black"); 241 endImage.backgroundColor("black");
561 242
562 endImage.extent( 243 endImage.extent({endImage.columns() + cardImg.columns(), cardImg.rows()},
563 {endImage.columns() + cardImg.columns(), cardImg.rows()}, 244 Magick::WestGravity);
564 Magick::WestGravity);
565 245
566 endImage.composite( 246 endImage.composite(cardImg, xoff,
567 cardImg, 247 (theCard.frame == card_frame::m2015) ? 6 : 0);
568 xoff,
569 (theCard.frame == card_frame::m2015) ? 6 : 0);
570 } 248 }
571 249
572 250 // break;
573 //break; 251 ui++;
574 } 252 }
575 253
576
577 endImage.magick("PNG"); 254 endImage.magick("PNG");
578 endImage.write("output.png"); 255 endImage.write(outputPath_);
579
580
581
582
583
584
585} 256}
586
587