#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "prefix_search.h" std::string stripSpaces(std::string in) { in.erase( std::remove_if( std::begin(in), std::end(in), ::isspace), std::end(in)); return in; } using ps_type = prefix_search>; enum class card_frame { m2015, modern }; struct card { size_t id; std::string name; std::string imageUri; card_frame frame; card( size_t id, std::string name, std::string imageUri, card_frame frame) : id(id), name(std::move(name)), imageUri(std::move(imageUri)), frame(frame) { } }; struct usage { size_t cardId; size_t strIndex; size_t strLen; usage( size_t ci, size_t si, size_t sl) : cardId(ci), strIndex(si), strLen(sl) { } }; struct solution { const ps_type& prefix; std::vector lengths; size_t score; }; class designer { public: designer( std::string text, const ps_type& titles) : text_(std::move(text)), titles_(titles), solutions_(text_.length() + 1) { } std::list generate(std::mt19937& rng) const; private: const solution& get(size_t i) const; solution calculate(size_t i) const; const std::string text_; const ps_type& titles_; mutable std::vector> solutions_; }; std::list designer::generate(std::mt19937& rng) const { std::list result; size_t cur = 0; while (cur < text_.length()) { const solution& curSol = get(cur); const std::vector& posLens = curSol.lengths; std::uniform_int_distribution lenDist(0, posLens.size() - 1); size_t len = posLens.at(lenDist(rng)); const ps_type& prefix = curSol.prefix; std::uniform_int_distribution cardDist(0, prefix.getCount() - 1); size_t cardIndex = cardDist(rng); std::tuple pd = prefix.at(cardIndex); result.emplace_back(std::get<0>(pd), std::get<1>(pd), len); cur += len; } return result; } solution designer::calculate(size_t i) const { if (i == text_.length()) { return { titles_, {}, 0 }; } const ps_type& prefix = titles_.find(text_, i); bool foundScore = false; size_t bestScore; std::vector bestLens; for (int j = 1; (j <= prefix.getDepth()) && (i + j <= text_.length()); j++) { const solution& subSol = get(i + j); if (subSol.score > 0 || (i + j == text_.length())) { size_t tempScore = subSol.score + 1; if (!foundScore || tempScore < bestScore) { foundScore = true; bestScore = tempScore; bestLens.clear(); bestLens.push_back(j); } else if (tempScore == bestScore) { bestLens.push_back(j); } } } if (!foundScore) { return { titles_, {}, 0 }; } else { return { prefix, std::move(bestLens), bestScore }; } } const solution& designer::get(size_t i) const { if (!solutions_.at(i)) { solutions_[i] = std::make_unique(calculate(i)); } return *solutions_.at(i); } Magick::Image downloadImage(const std::string& url) { std::ostringstream imgbuf; curl::curl_ios imgios(imgbuf); curl::curl_easy imghandle(imgios); imghandle.add(url.c_str()); imghandle.add(30); imghandle.add(300); imghandle.perform(); if (imghandle.get_info().get() != 200) { throw std::runtime_error("Could not download image"); } std::string content_type = imghandle.get_info().get(); if (content_type.substr(0, 6) != "image/") { throw std::runtime_error("Could not download image"); } std::string imgstr = imgbuf.str(); Magick::Blob img(imgstr.c_str(), imgstr.length()); Magick::Image pic; try { pic.read(img); } catch (const Magick::ErrorOption& e) { // Occurs when the the data downloaded from the server is malformed std::cout << "Magick: " << e.what() << std::endl; throw std::runtime_error("Could not download image"); } return pic; } class tesseract_deleter { public: void operator()(tesseract::TessBaseAPI* ptr) const { ptr->End(); } }; using tesseract_ptr = std::unique_ptr; class pix_deleter { public: void operator()(Pix* ptr) const { pixDestroy(&ptr); } }; using pix_ptr = std::unique_ptr; int main(int argc, char** argv) { Magick::InitializeMagick(nullptr); std::random_device randomDevice; std::mt19937 rng(randomDevice()); std::cout << "Compiling prefix search..." << std::endl; std::vector cards; ps_type titles; std::set chars; { std::ifstream in( "/Users/hatkirby/Downloads/scryfall-default-cards.json", std::ios::in | std::ios::binary); std::ostringstream contents; contents << in.rdbuf(); nlohmann::json cardsJson = nlohmann::json::parse(contents.str()); for (const auto& cardJson : cardsJson) { if ( // The object needs to be a card cardJson["object"] == "card" && // It needs to have a downloadable image cardJson.count("image_uris") && // Make sure we can support the card layout ( cardJson["layout"] == "normal" || cardJson["layout"] == "leveler" || cardJson["layout"] == "saga" ) && // Digital cards look slightly different so ignore them !cardJson["digital"] && // Only use english printings cardJson["lang"] == "en" && // Currently not supporting silver bordered cards cardJson["border_color"] != "silver" && // It is hard to read the name of a planeswalker cardJson["type_line"].get() .find("Planeswalker") == std::string::npos && // This cuts out checklists and special tokens cardJson["type_line"] != "Card" && // Amonkhet invocations are impossible cardJson["set"] != "mp2") { card_frame frame; if (cardJson["frame"] == "2015") { frame = card_frame::m2015; } else if (cardJson["frame"] == "2003") { frame = card_frame::modern; } else { continue; } size_t cardId = cards.size(); cards.emplace_back( cardId, cardJson["name"], cardJson["image_uris"]["png"], frame); std::string canon = hatkirby::lowercase(cardJson["name"]); for (int i = 0; i < canon.length(); i++) { titles.add(canon, {cardId, i}, i); chars.insert(canon.at(i)); } } } } std::cout << "Characters: "; for (char ch : chars) { std::cout << ch; } std::cout << std::endl; std::cout << "Calculating card list..." << std::endl; std::string text = "is it pretentious that i'm sending someone over 100 common and uncommon magic cards in an iphone six box"; std::string canonText = hatkirby::lowercase(text); designer des(canonText, titles); std::list res = des.generate(rng); Magick::Image endImage; bool firstSlice = false; for (const usage& u : res) { const card& theCard = cards.at(u.cardId); const std::string& cardName = theCard.name; std::cout << cardName.substr(0, u.strIndex) << "[" << cardName.substr(u.strIndex, u.strLen) << "]" << cardName.substr(u.strIndex + u.strLen) << std::endl; std::cout << "Downloading image..." << std::endl; Magick::Image cardImg = downloadImage(theCard.imageUri); std::cout << "Reading text..." << std::endl; Magick::Image titleImg = cardImg; titleImg.magick("TIFF"); //titleImg.threshold(MaxRGB / 2); titleImg.write("pre.tif"); Magick::Geometry margin; if (theCard.frame == card_frame::m2015) { margin = Magick::Geometry { 595, 46, 57, 54 }; } else if (theCard.frame == card_frame::modern) { margin = Magick::Geometry { 581, 50, 63, 57 }; } titleImg.crop(margin); titleImg.zoom({ margin.width() * 5, margin.height() * 5 }); //titleImg.quantizeColorSpace(Magick::GRAYColorspace); //titleImg.quantizeColors(2); //titleImg.quantize(); titleImg.backgroundColor("white"); titleImg.matte(false); titleImg.resolutionUnits(Magick::PixelsPerInchResolution); titleImg.density({ 300, 300 }); titleImg.type(Magick::GrayscaleType); titleImg.write("title.tif"); Magick::Blob titleBlob; titleImg.write(&titleBlob); pix_ptr titlePix { pixReadMemTiff( reinterpret_cast(titleBlob.data()), titleBlob.length(), 0) }; tesseract_ptr api { new tesseract::TessBaseAPI() }; if (api->Init(nullptr, "eng")) { throw std::runtime_error("Could not initialize tesseract"); } api->SetImage(titlePix.get()); api->Recognize(nullptr); tesseract::ResultIterator* ri = api->GetIterator(); tesseract::PageIteratorLevel level = tesseract::RIL_TEXTLINE; bool foundName = false; if (ri) { do { const char* line = ri->GetUTF8Text(level); if (line) { std::string lineStr(line); //if (stripSpaces(hatkirby::lowercase(lineStr)).find(stripSpaces(hatkirby::lowercase((cardName)))) == 0) { foundName = true; break; }/* else { std::cout << "WRONG: " << lineStr << std::endl; }*/ } } while (ri->Next(level)); } if (foundName) { level = tesseract::RIL_SYMBOL; std::vector> characters; size_t cur = 0; do { int x1, y1, x2, y2; ri->BoundingBox(level, &x1, &y1, &x2, &y2); x1 /= 5; x2 /= 5; if (cardName.at(cur) == ' ') { if (cur == 0) { characters.emplace_back(0, x1); } else { const auto& prev = characters.back(); characters.emplace_back(std::get<1>(characters.back()), x1); } cur++; } characters.emplace_back(x1, x2); cur++; } while (ri->Next(level) && (cur < cardName.length())); if (cur != cardName.length()) { throw std::runtime_error("Error detecting character bounds"); } cardImg.crop({ std::get<1>(characters[u.strIndex + u.strLen - 1]) - std::get<0>(characters[u.strIndex]), cardImg.rows(), margin.xOff() + std::get<0>(characters[u.strIndex]), 0 }); cardImg.magick("PNG"); cardImg.write("slice.png"); } else { std::cout << "Didn't find name" << std::endl; } if (!firstSlice) { firstSlice = true; endImage = cardImg; } else { int xoff = endImage.columns(); endImage.backgroundColor("black"); endImage.extent( {endImage.columns() + cardImg.columns(), cardImg.rows()}, Magick::WestGravity); endImage.composite( cardImg, xoff, (theCard.frame == card_frame::m2015) ? 6 : 0); } //break; } endImage.magick("PNG"); endImage.write("output.png"); }