1 /** 2 * Copyright © The Bot Blog 2019 3 * License: MIT (https://github.com/TheBotBlog/thebotbloglib/blob/master/LICENSE) 4 * Author: Jacob Jensen (bausshf) 5 */ 6 module thebotbloglib.imagemanager; 7 8 import std.conv : to; 9 import std.string : strip, format; 10 import std.array : join, replace; 11 import std.file : exists, remove, copy; 12 13 /// Text options. 14 final class TextOptions 15 { 16 public: 17 /// The name of the font to use if no font-file is specified. 18 string fontName; 19 /// The font file to use if no font-name is specified. 20 string fontFile; 21 /// The font size. 22 float fontSize; 23 /// The color of the text. 24 Color color; 25 /// The rectangle to confine the text within. 26 Rectangle rect; 27 /// Boolean determining whether to center the text in the given rectangle. 28 bool centerText; 29 } 30 31 /// A color 32 final struct Color 33 { 34 public: 35 /// The red channel. 36 ubyte r; 37 /// The green channel. 38 ubyte g; 39 /// The blue channel. 40 ubyte b; 41 /// The alpha channel. 42 ubyte a; 43 44 static: 45 /** 46 * Creates a color from RGB. 47 * Params: 48 * r = the red channel. 49 * g = the green channel. 50 * b = The blue channel. 51 * Returns: 52 * The color. 53 */ 54 Color rgb(int r, int g, int b) 55 { 56 return Color(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)255); 57 } 58 59 /** 60 * Creates a color from RGBA. 61 * Params: 62 * r = the red channel. 63 * g = the green channel. 64 * b = The blue channel. 65 * a = The alpha channel. 66 * Returns: 67 * The color. 68 */ 69 Color rgba(int r, int g, int b, int a) 70 { 71 return Color(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, cast(ubyte)a); 72 } 73 } 74 75 /// A rectangle. 76 final class Rectangle 77 { 78 public: 79 /// The x coordinate. 80 ptrdiff_t x; 81 /// The y coordinate. 82 ptrdiff_t y; 83 /// The width. 84 ptrdiff_t width; 85 /// The height. 86 ptrdiff_t height; 87 /// Boolean determining whether the width is fixed and relative to the image width. 88 bool fixedWidth; 89 /// Boolean determining whether the height is fixed and relative to the image height. 90 bool fixedHeight; 91 } 92 93 /// An image manager. 94 final class ImageManager 95 { 96 private: 97 /// The base image path. 98 string _baseImagePath; 99 /// The final output path. 100 string _finalOutputPath; 101 /// The original output path. 102 string _originalOutputPath; 103 /// The output path. 104 string _outputPath; 105 /// The counter. 106 size_t _counter; 107 108 public: 109 final: 110 /** 111 * Creates a new image manager. 112 * Params: 113 * baseImagePath = The base image path. 114 * outputPath = The output image path. This must contain '%s' as a format in the filename. (The final image will not contain the format.) 115 */ 116 this(string baseImagePath, string outputPath) 117 { 118 _counter = 0; 119 120 _baseImagePath = baseImagePath; 121 122 _finalOutputPath = outputPath.replace("%s", ""); 123 _originalOutputPath = outputPath; 124 125 _outputPath = _originalOutputPath.format(_counter); 126 } 127 128 /** 129 * Merges another image on-top. 130 * Returns: 131 * True if the action was successfully executed, false otherwise. 132 */ 133 bool merge(string imagePath) 134 { 135 return runImageCmd("merge", imagePath); 136 } 137 138 /** 139 * Rotates the image 90 degrees. 140 * Returns: 141 * True if the action was successfully executed, false otherwise. 142 */ 143 bool rotate90() 144 { 145 return runImageCmd("rotate90"); 146 } 147 148 /** 149 * Rotate the image 180 degrees. 150 * Returns: 151 * True if the action was successfully executed, false otherwise. 152 */ 153 bool rotate180() 154 { 155 return runImageCmd("rotate180"); 156 } 157 158 /** 159 * Flips the image horizontal. 160 * Returns: 161 * True if the action was successfully executed, false otherwise. 162 */ 163 bool flipHorizontal() 164 { 165 return runImageCmd("flipH"); 166 } 167 168 /** 169 * Flips the image vertically. 170 * Returns: 171 * True if the action was successfully executed, false otherwise. 172 */ 173 bool flipVertical() 174 { 175 return runImageCmd("flipV"); 176 } 177 178 /** 179 * Inverses the colors. 180 * Returns: 181 * True if the action was successfully executed, false otherwise. 182 */ 183 bool inverseColors() 184 { 185 return runImageCmd("inverse"); 186 } 187 188 /** 189 * Turns the image black and white. 190 * Returns: 191 * True if the action was successfully executed, false otherwise. 192 */ 193 bool turnBlackAndWhite() 194 { 195 return runImageCmd("blackAndWhite"); 196 } 197 198 /** 199 * Draws text on the image. 200 * Params: 201 * text = The text to draw. 202 * options = The text drawing options. 203 * Returns: 204 * True if the action was successfully executed, false otherwise. 205 */ 206 bool drawText(string text, TextOptions options) 207 { 208 if (!options || !text || !text.strip.length) 209 { 210 return false; 211 } 212 213 if ((!options.fontName || !options.fontName.strip.length) && (!options.fontFile || !options.fontFile.strip.length)) 214 { 215 return false; 216 } 217 218 auto actions = ["drawText"]; 219 actions ~= "text::" ~ text; 220 221 if (options.fontName && options.fontName.strip.length) 222 { 223 actions ~= "font::" ~ options.fontName; 224 } 225 else 226 { 227 actions ~= "fontFile::" ~ options.fontFile; 228 } 229 230 if (options.fontSize >= 1) 231 { 232 actions ~= "fontSize::" ~ to!string(options.fontSize); 233 } 234 235 actions ~= "color::" ~ join([options.color.r.to!string, options.color.g.to!string, options.color.b.to!string, options.color.a.to!string], ","); 236 237 if (options.rect) 238 { 239 string width = options.rect.fixedWidth ? "*" : to!string(options.rect.width); 240 string height = options.rect.fixedHeight ? "*" : to!string(options.rect.height); 241 242 actions ~= "rect::" ~ to!string(options.rect.x) ~ "," ~ to!string(options.rect.y) ~ "," ~ width ~ "," ~ height; 243 } 244 245 if (options.centerText) 246 { 247 actions ~= "centerText::true"; 248 } 249 250 auto action = join(actions, "||"); 251 252 import std.stdio : writeln; 253 writeln(action); 254 255 return runImageCmd(action); 256 } 257 258 /** 259 * Draws a rectangle. 260 * Params: 261 * x = The x coordinate. 262 * y = The y coordinate. 263 * width = The width. 264 * height = The height. 265 * color = The color. 266 * fill = (optional) (default = false) Boolean determining whether the rectangle's colors should fill. 267 * Returns: 268 * True if the action was successfully executed, false otherwise. 269 */ 270 bool drawRectangle(ptrdiff_t x, ptrdiff_t y, ptrdiff_t width, ptrdiff_t height, Color color, bool fill = false) 271 { 272 auto action = "drawRect||rect::" ~ to!string(x) ~ "," ~ to!string(y) ~ "," ~ to!string(width) ~ "," ~ to!string(height); 273 action ~= "||color::" ~ join([color.r.to!string, color.g.to!string, color.b.to!string, color.a.to!string], ","); 274 275 if (fill) 276 { 277 action ~= "||fill::true"; 278 } 279 280 return runImageCmd(action); 281 } 282 283 /** 284 * Draws a line. 285 * Params: 286 * startX = The start x coordinate. 287 * startY = The start y coordinate. 288 * endX = The end x coordinate. 289 * endY = The end y coordinate. 290 * color = The color. 291 * Returns: 292 * True if the action was successfully executed, false otherwise. 293 */ 294 bool drawLine(ptrdiff_t startX, ptrdiff_t startY, ptrdiff_t endX, ptrdiff_t endY, Color color) 295 { 296 auto action = "drawLine||args::" ~ to!string(startX) ~ "," ~ to!string(startY) ~ "," ~ to!string(endX) ~ "," ~ to!string(endY); 297 action ~= "||color::" ~ join([color.r.to!string, color.g.to!string, color.b.to!string, color.a.to!string], ","); 298 299 return runImageCmd(action); 300 } 301 302 /** 303 * Draws an image. 304 * Params: 305 * imagePath = The image path. 306 * x = The x coordinate. 307 * y = The y coordinate. 308 * width = The width. 309 * height = The height. 310 * Returns: 311 * True if the action was successfully executed, false otherwise. 312 */ 313 bool drawImage(string imagePath, ptrdiff_t x, ptrdiff_t y, ptrdiff_t width, ptrdiff_t height) 314 { 315 return drawImage(imagePath, x, y, to!string(width), to!string(height)); 316 } 317 318 /** 319 * Draws an image. 320 * Params: 321 * imagePath = The image path. 322 * x = The x coordinate. 323 * y = The y coordinate. 324 * Returns: 325 * True if the action was successfully executed, false otherwise. 326 */ 327 bool drawImage(string imagePath, ptrdiff_t x, ptrdiff_t y) 328 { 329 return drawImage(imagePath, x, y, "*", "*"); 330 } 331 332 /** 333 * Draws an image with a custom width. 334 * Params: 335 * imagePath = The image path. 336 * x = The x coordinate. 337 * y = The y coordinate. 338 * width = The width. 339 * Returns: 340 * True if the action was successfully executed, false otherwise. 341 */ 342 bool drawImageStretchedHorizontal(string imagePath, ptrdiff_t x, ptrdiff_t y, ptrdiff_t width) 343 { 344 return drawImage(imagePath, x, y, to!string(width), "*"); 345 } 346 347 /** 348 * Draws an image with a custom height. 349 * Params: 350 * imagePath = The image path. 351 * x = The x coordinate. 352 * y = The y coordinate. 353 * height = The height. 354 * Returns: 355 * True if the action was successfully executed, false otherwise. 356 */ 357 bool drawImageStretchedVertical(string imagePath, ptrdiff_t x, ptrdiff_t y, ptrdiff_t height) 358 { 359 return drawImage(imagePath, x, y, "*", to!string(height)); 360 } 361 362 /// Finalizes the image. (Removes the last temp image and creates the final output image file.) 363 void finalize() 364 { 365 copy(_baseImagePath, _finalOutputPath); 366 367 if (exists(_baseImagePath)) 368 { 369 remove(_baseImagePath); 370 } 371 } 372 373 private: 374 /** 375 * Draws an image. 376 * Params: 377 * imagePath = The image path. 378 * x = The x coordinate. 379 * y = The y coordinate. 380 * width = The width. 381 * height = The height. 382 * Returns: 383 * True if the action was successfully executed, false otherwise. 384 */ 385 bool drawImage(string imagePath, ptrdiff_t x, ptrdiff_t y, string width, string height) 386 { 387 auto size = width ~ "," ~ height; 388 auto position = to!string(x) ~ "," ~ to!string(y); 389 390 return runImageCmd("drawImage||size::" ~ size ~ "||position::" ~ position, imagePath); 391 } 392 393 /** 394 * Runs the ImageCmd with the given parameters. 395 * Params: 396 * action = The action / parameters to pass. 397 * sourceImage = (optional) The source image to use. 398 * Returns: 399 * True if the action was successfully executed, false otherwise. 400 */ 401 bool runImageCmd(string action, string sourceImage = null) 402 { 403 import std.process : spawnProcess, wait, Pid; 404 import std.string : strip; 405 406 Pid imagePid; 407 if (!sourceImage || !sourceImage.strip.length) 408 { 409 imagePid = spawnProcess(["ImageCmd.exe", _baseImagePath, _outputPath, action]); 410 } 411 else 412 { 413 imagePid = spawnProcess(["ImageCmd.exe", _baseImagePath, sourceImage, action, _outputPath]); 414 } 415 416 auto result = wait(imagePid); 417 418 if (_counter) 419 { 420 if (exists(_baseImagePath)) 421 { 422 remove(_baseImagePath); 423 } 424 } 425 426 _baseImagePath = _outputPath; 427 _counter++; 428 _outputPath = _originalOutputPath.format(_counter); 429 430 return result == 0; 431 } 432 }