summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--.clang-format2
-rw-r--r--.gitignore3
-rw-r--r--CMakeLists.txt26
-rw-r--r--cardset.cpp66
-rw-r--r--cardset.h46
-rw-r--r--designer.cpp69
-rw-r--r--designer.h49
-rw-r--r--imagestore.cpp52
-rw-r--r--imagestore.h18
-rw-r--r--main.cpp46
-rw-r--r--prefix_search.h53
-rw-r--r--wizard.cpp569
-rw-r--r--wizard.h25
13 files changed, 532 insertions, 492 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8de7fe6 --- /dev/null +++ b/.clang-format
@@ -0,0 +1,2 @@
1---
2 BasedOnStyle: Google
diff --git a/.gitignore b/.gitignore index 2f07f9e..96d50bf 100644 --- a/.gitignore +++ b/.gitignore
@@ -4,3 +4,6 @@ Makefile
4CMakeCache.txt 4CMakeCache.txt
5CMakeFiles 5CMakeFiles
6build 6build
7build-pluralize
8build2024
9newbuild
diff --git a/CMakeLists.txt b/CMakeLists.txt index f6380ee..ed3f7e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -1,25 +1,39 @@
1cmake_minimum_required (VERSION 3.1) 1cmake_minimum_required (VERSION 3.1)
2project (wizard) 2project (wizard)
3 3
4set(CMAKE_BUILD_TYPE Debug)
5
4find_package(PkgConfig) 6find_package(PkgConfig)
5pkg_check_modules(tesseract tesseract REQUIRED) 7pkg_check_modules(tesseract tesseract REQUIRED)
8pkg_check_modules(lept lept REQUIRED)
6pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED) 9pkg_check_modules(GraphicsMagick GraphicsMagick++ REQUIRED)
7 10pkg_check_modules(tclap tclap REQUIRED)
8add_subdirectory(vendor/curlcpp) 11pkg_check_modules(curlcpp curlcpp REQUIRED)
9 12
10include_directories( 13include_directories(
11 vendor/json 14 vendor/json
12 vendor/hkutil 15 vendor/hkutil
13 ${tesseract_INCLUDE_DIRS} 16 ${tesseract_INCLUDE_DIRS}
17 ${lept_INCLUDE_DIRS}
14 ${GraphicsMagick_INCLUDE_DIRS} 18 ${GraphicsMagick_INCLUDE_DIRS}
15 ${CURLCPP_SOURCE_DIR}/include) 19 ${curlcpp_INCLUDE_DIRS}
20 ${tclap_INCLUDE_DIRS}
21)
22
23link_directories(
24 ${tesseract_LIBRARY_DIRS}
25 ${lept_LIBRARY_DIRS}
26 ${GraphicsMagick_LIBRARY_DIRS}
27 ${curlcpp_LIBRARY_DIRS}
28)
16 29
17add_executable(wizard wizard.cpp) 30add_executable(wizard cardset.cpp imagestore.cpp designer.cpp wizard.cpp main.cpp)
18set_property(TARGET wizard PROPERTY CXX_STANDARD 14) 31set_property(TARGET wizard PROPERTY CXX_STANDARD 14)
19set_property(TARGET wizard PROPERTY CXX_STANDARD_REQUIRED ON) 32set_property(TARGET wizard PROPERTY CXX_STANDARD_REQUIRED ON)
20target_link_libraries( 33target_link_libraries(
21 wizard 34 wizard
22 ${tesseract_LIBRARIES} 35 ${tesseract_LIBRARIES}
36 ${lept_LIBRARIES}
23 ${GraphicsMagick_LIBRARIES} 37 ${GraphicsMagick_LIBRARIES}
24 lept 38 ${curlcpp_LIBRARIES}
25 curlcpp) 39)
diff --git a/cardset.cpp b/cardset.cpp new file mode 100644 index 0000000..4003eed --- /dev/null +++ b/cardset.cpp
@@ -0,0 +1,66 @@
1#include "cardset.h"
2
3#include <hkutil/string.h>
4
5#include <fstream>
6#include <json.hpp>
7#include <sstream>
8
9cardset::cardset(std::string filename) {
10 std::ifstream in(filename, std::ios::in | std::ios::binary);
11 std::ostringstream contents;
12 contents << in.rdbuf();
13
14 nlohmann::json cardsJson = nlohmann::json::parse(contents.str());
15
16 for (const auto& cardJson : cardsJson) {
17 if (
18 // The object needs to be a card
19 cardJson["object"] == "card" &&
20 // It needs to have a downloadable image
21 cardJson.count("image_uris") &&
22 // Make sure we can support the card layout
23 (cardJson["layout"] == "normal" || cardJson["layout"] == "leveler" ||
24 cardJson["layout"] == "saga") &&
25 // Digital cards look slightly different so ignore them
26 !cardJson["digital"] &&
27 // Only use english printings
28 cardJson["lang"] == "en" &&
29 // Currently not supporting silver bordered cards
30 cardJson["border_color"] != "silver" &&
31 // It is hard to read the name of a planeswalker
32 cardJson["type_line"].get<std::string>().find("Planeswalker") ==
33 std::string::npos &&
34 // This cuts out checklists and special tokens
35 cardJson["type_line"] != "Card" &&
36 // Amonkhet invocations are impossible
37 cardJson["set"] != "mp2" &&
38 // Unknown Event is not a real thing huh
39 cardJson["set"] != "da1" &&
40 // Ignore cards with the special legendary flare
41 (!cardJson.count("frame_effects") ||
42 !cardJson["frame_effects"].count("legendary"))) {
43 card_frame frame;
44
45 if (cardJson["frame"] == "2015") {
46 frame = card_frame::m2015;
47 } else if (cardJson["frame"] == "2003") {
48 frame = card_frame::modern;
49 } else {
50 continue;
51 }
52
53 size_t cardId = cards_.size();
54 cards_.emplace_back(cardId, cardJson["name"],
55 cardJson["image_uris"]["png"], frame, cardJson["id"]);
56
57 std::string canon = hatkirby::lowercase(cardJson["name"]);
58
59 for (int i = 0; i < canon.length(); i++) {
60 titles_.add(canon, {cardId, i}, i);
61
62 chars_.insert(canon.at(i));
63 }
64 }
65 }
66}
diff --git a/cardset.h b/cardset.h new file mode 100644 index 0000000..217d859 --- /dev/null +++ b/cardset.h
@@ -0,0 +1,46 @@
1#ifndef CARDSET_H_09C0428F
2#define CARDSET_H_09C0428F
3
4#include <set>
5#include <string>
6#include <tuple>
7
8#include "prefix_search.h"
9
10using ps_type = prefix_search<std::tuple<size_t, size_t>>;
11
12enum class card_frame { m2015, modern };
13
14struct card {
15 size_t id;
16 std::string name;
17 std::string imageUri;
18 card_frame frame;
19 std::string uuid;
20
21 card(size_t id, std::string name, std::string imageUri, card_frame frame,
22 std::string uuid)
23 : id(id),
24 name(std::move(name)),
25 imageUri(std::move(imageUri)),
26 frame(frame),
27 uuid(std::move(uuid)) {}
28};
29
30class cardset {
31 public:
32 explicit cardset(std::string filename);
33
34 const card& getCard(size_t id) const { return cards_.at(id); }
35
36 const ps_type& getTitles() const { return titles_; }
37
38 const std::set<char>& getCharacters() const { return chars_; }
39
40 private:
41 std::vector<card> cards_;
42 ps_type titles_;
43 std::set<char> chars_;
44};
45
46#endif /* end of include guard: CARDSET_H_09C0428F */
diff --git a/designer.cpp b/designer.cpp new file mode 100644 index 0000000..024796f --- /dev/null +++ b/designer.cpp
@@ -0,0 +1,69 @@
1#include "designer.h"
2
3std::list<usage> designer::generate(std::mt19937& rng) const {
4 std::list<usage> result;
5 size_t cur = 0;
6
7 while (cur < text_.length()) {
8 const solution& curSol = get(cur);
9 const std::vector<size_t>& posLens = curSol.lengths;
10
11 std::uniform_int_distribution<size_t> lenDist(0, posLens.size() - 1);
12 size_t len = posLens.at(lenDist(rng));
13
14 const ps_type& prefix = curSol.prefix;
15 std::uniform_int_distribution<size_t> cardDist(0, prefix.getCount() - 1);
16 size_t cardIndex = cardDist(rng);
17 std::tuple<size_t, size_t> pd = prefix.at(cardIndex);
18
19 result.emplace_back(std::get<0>(pd), std::get<1>(pd), len);
20
21 cur += len;
22 }
23
24 return result;
25}
26
27solution designer::calculate(size_t i) const {
28 if (i == text_.length()) {
29 return {titles_, {}, 0};
30 }
31
32 const ps_type& prefix = titles_.find(text_, i);
33
34 bool foundScore = false;
35 size_t bestScore;
36 std::vector<size_t> bestLens;
37
38 for (int j = 1; (j <= prefix.getDepth()) && (i + j <= text_.length()); j++) {
39 const solution& subSol = get(i + j);
40
41 if (subSol.score > 0 || (i + j == text_.length())) {
42 size_t tempScore = subSol.score + 1;
43
44 if (!foundScore || tempScore < bestScore) {
45 foundScore = true;
46 bestScore = tempScore;
47
48 bestLens.clear();
49 bestLens.push_back(j);
50 } else if (tempScore == bestScore) {
51 bestLens.push_back(j);
52 }
53 }
54 }
55
56 if (!foundScore) {
57 return {titles_, {}, 0};
58 } else {
59 return {prefix, std::move(bestLens), bestScore};
60 }
61}
62
63const solution& designer::get(size_t i) const {
64 if (!solutions_.at(i)) {
65 solutions_[i] = std::make_unique<solution>(calculate(i));
66 }
67
68 return *solutions_.at(i);
69}
diff --git a/designer.h b/designer.h new file mode 100644 index 0000000..38ae3c3 --- /dev/null +++ b/designer.h
@@ -0,0 +1,49 @@
1#ifndef DESIGNER_H_06F8DE64
2#define DESIGNER_H_06F8DE64
3
4#include <cstddef>
5#include <list>
6#include <memory>
7#include <random>
8#include <string>
9#include <vector>
10
11#include "cardset.h"
12#include "prefix_search.h"
13
14struct usage {
15 size_t cardId;
16 size_t strIndex;
17 size_t strLen;
18
19 usage(size_t ci, size_t si, size_t sl)
20 : cardId(ci), strIndex(si), strLen(sl) {}
21};
22
23struct solution {
24 const ps_type& prefix;
25 std::vector<size_t> lengths;
26 size_t score;
27};
28
29class designer {
30 public:
31 designer(std::string text, const ps_type& titles)
32 : text_(std::move(text)),
33 titles_(titles),
34 solutions_(text_.length() + 1) {}
35
36 std::list<usage> generate(std::mt19937& rng) const;
37
38 private:
39 const solution& get(size_t i) const;
40
41 solution calculate(size_t i) const;
42
43 const std::string text_;
44 const ps_type& titles_;
45
46 mutable std::vector<std::unique_ptr<solution>> solutions_;
47};
48
49#endif /* end of include guard: DESIGNER_H_06F8DE64 */
diff --git a/imagestore.cpp b/imagestore.cpp new file mode 100644 index 0000000..09bef92 --- /dev/null +++ b/imagestore.cpp
@@ -0,0 +1,52 @@
1#include "imagestore.h"
2
3#include <curlcpp/curl_easy.h>
4
5#include <fstream>
6#include <sstream>
7
8Magick::Image imagestore::get(std::string key, std::string url) const {
9 std::string filename = path_ + "/" + key;
10
11 Magick::Image pic;
12
13 if (std::ifstream(filename)) {
14 pic.read(filename);
15 } else {
16 std::ostringstream imgbuf;
17 curl::curl_ios<std::ostringstream> imgios(imgbuf);
18 curl::curl_easy imghandle(imgios);
19
20 imghandle.add<CURLOPT_URL>(url.c_str());
21 imghandle.add<CURLOPT_CONNECTTIMEOUT>(30);
22 imghandle.add<CURLOPT_TIMEOUT>(300);
23
24 imghandle.perform();
25
26 if (imghandle.get_info<CURLINFO_RESPONSE_CODE>().get() != 200) {
27 throw std::runtime_error("Could not download image");
28 }
29
30 std::string content_type =
31 imghandle.get_info<CURLINFO_CONTENT_TYPE>().get();
32 if (content_type.substr(0, 6) != "image/") {
33 throw std::runtime_error("Could not download image");
34 }
35
36 std::string imgstr = imgbuf.str();
37 Magick::Blob img(imgstr.c_str(), imgstr.length());
38
39 try {
40 pic.read(img);
41 } catch (const Magick::ErrorOption& e) {
42 // Occurs when the the data downloaded from the server is malformed
43 std::cout << "Magick: " << e.what() << std::endl;
44
45 throw std::runtime_error("Could not download image");
46 }
47
48 pic.write(filename);
49 }
50
51 return pic;
52}
diff --git a/imagestore.h b/imagestore.h new file mode 100644 index 0000000..8c3fecf --- /dev/null +++ b/imagestore.h
@@ -0,0 +1,18 @@
1#ifndef IMAGESTORE_H_80B1E49F
2#define IMAGESTORE_H_80B1E49F
3
4#include <Magick++.h>
5
6#include <string>
7
8class imagestore {
9 public:
10 explicit imagestore(std::string path) : path_(std::move(path)) {}
11
12 Magick::Image get(std::string key, std::string url) const;
13
14 private:
15 std::string path_;
16};
17
18#endif /* end of include guard: IMAGESTORE_H_80B1E49F */
diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..a0ba7bb --- /dev/null +++ b/main.cpp
@@ -0,0 +1,46 @@
1#include <Magick++.h>
2#include <tclap/CmdLine.h>
3
4#include <iostream>
5#include <string>
6
7#include "wizard.h"
8
9int main(int argc, char** argv) {
10 Magick::InitializeMagick(nullptr);
11
12 std::random_device randomDevice;
13 std::mt19937 rng(5); // randomDevice());
14
15 try {
16 TCLAP::CmdLine cmd("Spelling things with MTG cards", ' ', "1.0");
17
18 TCLAP::ValueArg<std::string> cardsPath("c", "cards",
19 "Path to the card definitions file",
20 true, "", "filename");
21 cmd.add(cardsPath);
22
23 TCLAP::ValueArg<std::string> cachePath(
24 "i", "image-cache", "Path to store cached card image downloads", true,
25 "", "filename");
26 cmd.add(cachePath);
27
28 TCLAP::ValueArg<std::string> outputPath(
29 "o", "output", "Path to write image output to", true, "", "filename");
30 cmd.add(outputPath);
31
32 TCLAP::ValueArg<std::string> jsonPath(
33 "j", "json", "Path to write JSON output to", false, "", "filename");
34 cmd.add(jsonPath);
35
36 cmd.parse(argc, argv);
37
38 wizard app(cardsPath.getValue(), cachePath.getValue(),
39 outputPath.getValue(), jsonPath.getValue(), rng);
40
41 app.run();
42 } catch (const TCLAP::ArgException& e) {
43 std::cerr << "Error: " << e.error() << " for arg " << e.argId()
44 << std::endl;
45 }
46}
diff --git a/prefix_search.h b/prefix_search.h index fe69ef7..39bc457 100644 --- a/prefix_search.h +++ b/prefix_search.h
@@ -1,59 +1,43 @@
1#ifndef PREFIX_SEARCH_H_A6672A1D 1#ifndef PREFIX_SEARCH_H_A6672A1D
2#define PREFIX_SEARCH_H_A6672A1D 2#define PREFIX_SEARCH_H_A6672A1D
3 3
4#include <stdexcept>
4#include <unordered_map> 5#include <unordered_map>
5#include <vector> 6#include <vector>
6#include <stdexcept>
7 7
8template <typename T> 8template <typename T>
9class prefix_search { 9class prefix_search {
10public: 10 public:
11 prefix_search(size_t depth = 0) : depth_(depth), count_(0) {}
11 12
12 prefix_search(size_t depth = 0) : depth_(depth), count_(0) 13 size_t getDepth() const { return depth_; }
13 {
14 }
15 14
16 size_t getDepth() const 15 size_t getCount() const { return count_; }
17 {
18 return depth_;
19 }
20 16
21 size_t getCount() const 17 void add(std::string& key, T val, size_t i = 0) {
22 {
23 return count_;
24 }
25
26 void add(std::string& key, T val, size_t i = 0)
27 {
28 count_++; 18 count_++;
29 19
30 if (i == key.length()) 20 if (i == key.length()) {
31 {
32 elements_.push_back(val); 21 elements_.push_back(val);
33 } else { 22 } else {
34 char next = key.at(i); 23 char next = key.at(i);
35 24
36 if (!children_.count(next)) 25 if (!children_.count(next)) {
37 { 26 children_.emplace(next, prefix_search<T>{depth_ + 1});
38 children_.emplace(next, prefix_search<T> { depth_ + 1 } );
39 } 27 }
40 28
41 children_.at(next).add(key, val, i + 1); 29 children_.at(next).add(key, val, i + 1);
42 } 30 }
43 } 31 }
44 32
45 T at(size_t i) const 33 T at(size_t i) const {
46 { 34 if (i < elements_.size()) {
47 if (i < elements_.size())
48 {
49 return elements_.at(i); 35 return elements_.at(i);
50 } else { 36 } else {
51 i -= elements_.size(); 37 i -= elements_.size();
52 38
53 for (const auto& mapping : children_) 39 for (const auto& mapping : children_) {
54 { 40 if (mapping.second.count_ > i) {
55 if (mapping.second.count_ > i)
56 {
57 return mapping.second.at(i); 41 return mapping.second.at(i);
58 } else { 42 } else {
59 i -= mapping.second.count_; 43 i -= mapping.second.count_;
@@ -64,18 +48,15 @@ public:
64 throw std::out_of_range("Index out of range"); 48 throw std::out_of_range("Index out of range");
65 } 49 }
66 50
67 const prefix_search<T>& find(const std::string& val, size_t i) const 51 const prefix_search<T>& find(const std::string& val, size_t i) const {
68 { 52 if (i == val.length() || !children_.count(val.at(i))) {
69 if (i == val.length() || !children_.count(val.at(i)))
70 {
71 return *this; 53 return *this;
72 } 54 }
73 55
74 return children_.at(val.at(i)).find(val, i+1); 56 return children_.at(val.at(i)).find(val, i + 1);
75 } 57 }
76 58
77private: 59 private:
78
79 size_t depth_; 60 size_t depth_;
80 size_t count_; 61 size_t count_;
81 std::unordered_map<char, prefix_search<T>> children_; 62 std::unordered_map<char, prefix_search<T>> children_;
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
diff --git a/wizard.h b/wizard.h new file mode 100644 index 0000000..9b27143 --- /dev/null +++ b/wizard.h
@@ -0,0 +1,25 @@
1#ifndef WIZARD_H_1014B13E
2#define WIZARD_H_1014B13E
3
4#include <random>
5#include <string>
6
7#include "cardset.h"
8#include "imagestore.h"
9
10class wizard {
11 public:
12 wizard(std::string cardsPath, std::string cachePath, std::string outputPath,
13 std::string jsonPath, std::mt19937& rng);
14
15 void run();
16
17 private:
18 cardset cards_;
19 imagestore images_;
20 std::string outputPath_;
21 std::string jsonPath_;
22 std::mt19937& rng_;
23};
24
25#endif /* end of include guard: WIZARD_H_1014B13E */