From a3056929ea8381884655a46f80b4c0e1fdb66341 Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Sun, 7 Oct 2018 15:09:37 -0400 Subject: Working prototype --- wizard.cpp | 561 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 561 insertions(+) create mode 100644 wizard.cpp (limited to 'wizard.cpp') diff --git a/wizard.cpp b/wizard.cpp new file mode 100644 index 0000000..ef4941f --- /dev/null +++ b/wizard.cpp @@ -0,0 +1,561 @@ + + +#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; +} + + + + + + + +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* titlePix = pixReadMemTiff( + reinterpret_cast(titleBlob.data()), + titleBlob.length(), + 0); + + tesseract::TessBaseAPI* api = new tesseract::TessBaseAPI(); + + if (api->Init(nullptr, "eng")) + { + throw std::runtime_error("Could not initialize tesseract"); + } + + api->SetImage(titlePix); + 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; + } + + + + + + + + + + + api->End(); + pixDestroy(&titlePix); + + 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"); + + + + + + +} + + -- cgit 1.4.1