about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-03-02 18:01:31 -0500
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-03-02 18:01:31 -0500
commitbbe334b6c9249fea57dee53a0804693dab46f03c (patch)
tree292bd467f2f3bdee4d85106ec8ba08757705c05b
parentc1cc06fa5b3ae49b7cdadf0af439f5858d8483eb (diff)
downloadlunatic-bbe334b6c9249fea57dee53a0804693dab46f03c.tar.gz
lunatic-bbe334b6c9249fea57dee53a0804693dab46f03c.tar.bz2
lunatic-bbe334b6c9249fea57dee53a0804693dab46f03c.zip
Added overlay shadow, achievement title wrapping, and date
The canonical title font is Roboto Bold and the date font is Roboto Medium.
-rw-r--r--database.cpp54
-rw-r--r--database.h7
-rw-r--r--lunatic.cpp120
3 files changed, 166 insertions, 15 deletions
diff --git a/database.cpp b/database.cpp index 2885d1f..953c217 100644 --- a/database.cpp +++ b/database.cpp
@@ -1,6 +1,8 @@
1#include "database.h" 1#include "database.h"
2#include <sqlite3.h> 2#include <sqlite3.h>
3#include <stdexcept> 3#include <stdexcept>
4#include <sstream>
5#include <iomanip>
4 6
5database::database(std::string path) 7database::database(std::string path)
6{ 8{
@@ -120,3 +122,55 @@ std::string database::getRandomImageForGame(int gameId) const
120 122
121 return result; 123 return result;
122} 124}
125
126did database::getRandomDidForAchievement(int achievementId) const
127{
128 std::string queryString = "SELECT profile_id, DATETIME(achieved_at) FROM dids WHERE achievement_id = ? ORDER BY RANDOM() LIMIT 1";
129
130 sqlite3_stmt* ppstmt;
131 if (sqlite3_prepare_v2(
132 ppdb_,
133 queryString.c_str(),
134 queryString.length(),
135 &ppstmt,
136 NULL) != SQLITE_OK)
137 {
138 std::string errorMsg = sqlite3_errmsg(ppdb_);
139 sqlite3_finalize(ppstmt);
140
141 throw std::logic_error(errorMsg);
142 }
143
144 if (sqlite3_bind_int(ppstmt, 1, achievementId) != SQLITE_OK)
145 {
146 std::string errorMsg = sqlite3_errmsg(ppdb_);
147 sqlite3_finalize(ppstmt);
148
149 throw std::logic_error(errorMsg);
150 }
151
152 if (sqlite3_step(ppstmt) != SQLITE_ROW)
153 {
154 std::string errorMsg = sqlite3_errmsg(ppdb_);
155 sqlite3_finalize(ppstmt);
156
157 throw std::logic_error(errorMsg);
158 }
159
160 did result;
161 result.profileId = sqlite3_column_int(ppstmt, 0);
162
163 std::tm achievedAt = {};
164
165 std::stringstream dateParser;
166 dateParser << reinterpret_cast<const char*>(sqlite3_column_text(ppstmt, 1));
167 dateParser >> std::get_time(&achievedAt, "%Y-%m-%d %H:%M:%S");
168
169 std::stringstream dateFormatter;
170 dateFormatter << std::put_time(&achievedAt, "%m/%d/%Y");
171 result.date = dateFormatter.str();
172
173 sqlite3_finalize(ppstmt);
174
175 return result;
176}
diff --git a/database.h b/database.h index a616318..9dcb118 100644 --- a/database.h +++ b/database.h
@@ -12,6 +12,11 @@ struct achievement {
12 std::string color; 12 std::string color;
13}; 13};
14 14
15struct did {
16 int profileId;
17 std::string date;
18};
19
15class database { 20class database {
16public: 21public:
17 22
@@ -43,6 +48,8 @@ public:
43 48
44 std::string getRandomImageForGame(int gameId) const; 49 std::string getRandomImageForGame(int gameId) const;
45 50
51 did getRandomDidForAchievement(int achievementId) const;
52
46private: 53private:
47 54
48 database() = default; 55 database() = default;
diff --git a/lunatic.cpp b/lunatic.cpp index 497c648..291bd09 100644 --- a/lunatic.cpp +++ b/lunatic.cpp
@@ -6,8 +6,34 @@
6#include <chrono> 6#include <chrono>
7#include <thread> 7#include <thread>
8#include <Magick++.h> 8#include <Magick++.h>
9#include <list>
10#include <string>
11#include <sstream>
9#include "database.h" 12#include "database.h"
10 13
14template <class Container>
15Container split(std::string input, std::string delimiter)
16{
17 Container result;
18
19 while (!input.empty())
20 {
21 int divider = input.find(delimiter);
22 if (divider == std::string::npos)
23 {
24 result.push_back(input);
25
26 input = "";
27 } else {
28 result.push_back(input.substr(0, divider));
29
30 input = input.substr(divider+delimiter.length());
31 }
32 }
33
34 return result;
35}
36
11int main(int argc, char** argv) 37int main(int argc, char** argv)
12{ 38{
13 if (argc != 2) 39 if (argc != 2)
@@ -43,15 +69,88 @@ int main(int argc, char** argv)
43 std::string imagePath = config["images"].as<std::string>() 69 std::string imagePath = config["images"].as<std::string>()
44 + "/" + imageName; 70 + "/" + imageName;
45 71
46 Magick::Image overlay;
47 overlay.read("res/overlay.png");
48
49 Magick::Image moonColor; 72 Magick::Image moonColor;
50 moonColor.read("res/" + ach.color + ".png"); 73 moonColor.read("res/" + ach.color + ".png");
51 74
52 try 75 try
53 { 76 {
54 // Start with the game image 77 // Start with the Odyssey text overlay
78 Magick::Image overlay;
79 overlay.read("res/overlay.png");
80
81 // Add the moon image
82 overlay.composite(moonColor, 672, 85, Magick::OverCompositeOp);
83
84 // Add the name of the achievement
85 overlay.fontPointsize(54);
86 overlay.fillColor("white");
87 overlay.font("@" + config["title_font"].as<std::string>());
88
89 std::list<std::string> words = split<std::list<std::string>>(
90 ach.title,
91 " ");
92 std::ostringstream wrappingStream;
93 std::string curline;
94 int lines = 1;
95
96 Magick::TypeMetric metric;
97 while (!words.empty())
98 {
99 std::string temp = curline;
100
101 if (!curline.empty())
102 {
103 temp += " ";
104 }
105
106 temp += words.front();
107
108 overlay.fontTypeMetrics(temp, &metric);
109
110 if (metric.textWidth() > 1200)
111 {
112 wrappingStream << std::endl;
113 curline = words.front();
114
115 lines++;
116 } else {
117 if (!curline.empty())
118 {
119 wrappingStream << " ";
120 }
121
122 curline = temp;
123 }
124
125 wrappingStream << words.front();
126 words.pop_front();
127 }
128
129 std::string wrapped = wrappingStream.str();
130
131 overlay.annotate(
132 wrapped,
133 Magick::Geometry(1600, 228, 0, 710),
134 Magick::GravityType::NorthGravity);
135
136 // Add the achievement date
137 did theDid = db.getRandomDidForAchievement(ach.achievementId);
138
139 overlay.fontTypeMetrics(wrapped, &metric);
140
141 overlay.fontPointsize(20);
142 overlay.font("@" + config["date_font"].as<std::string>());
143 overlay.annotate(
144 theDid.date,
145 Magick::Geometry(1600, 228, 0, 710 + metric.textHeight() * lines - 22),
146 Magick::GravityType::NorthGravity);
147
148 // Make a shadow copy
149 Magick::Image shadow(overlay);
150 shadow.negate();
151 shadow.blur(0, 12);
152
153 // Read the game image
55 Magick::Image image; 154 Magick::Image image;
56 image.read(imagePath); 155 image.read(imagePath);
57 156
@@ -60,18 +159,9 @@ int main(int argc, char** argv)
60 image.scale("80x45"); 159 image.scale("80x45");
61 image.scale("1600x900"); 160 image.scale("1600x900");
62 161
63 // Add the text and moon image from Odyssey 162 // Add the generated overlay to it
163 image.composite(shadow, 0, 0, Magick::OverCompositeOp);
64 image.composite(overlay, 0, 0, Magick::OverCompositeOp); 164 image.composite(overlay, 0, 0, Magick::OverCompositeOp);
65 image.composite(moonColor, 672, 85, Magick::OverCompositeOp);
66
67 // Add the name of the achievement
68 image.fontPointsize(36);
69 image.fillColor("white");
70 image.font("@" + config["font"].as<std::string>());
71 image.annotate(
72 ach.title,
73 Magick::Geometry(0, 0, 0, 672),
74 Magick::GravityType::NorthGravity);
75 165
76 // Output for debug 166 // Output for debug
77 image.magick("png"); 167 image.magick("png");