1 module ithox.qrcode.renderer.image.abstractrender; 2 3 import ithox.qrcode.renderer.rendererinterface; 4 import ithox.qrcode.encoder.qrcode; 5 import ithox.qrcode.renderer.color.colorinterface; 6 import ithox.qrcode.renderer.image.decorator.decoratorinterface; 7 import ithox.qrcode.renderer.color.gray; 8 9 /** 10 * Image renderer, supporting multiple backends. 11 */ 12 abstract class AbstractRenderer : RendererInterface 13 { 14 /** 15 * Margin around the QR code, also known as quiet zone. 16 * 17 * @var integer 18 */ 19 protected int margin = 4; 20 /** 21 * Requested width of the rendered image. 22 * 23 * @var integer 24 */ 25 protected int width = 0; 26 /** 27 * Requested height of the rendered image. 28 * 29 * @var integer 30 */ 31 protected int height = 0; 32 /** 33 * Whether dimensions should be rounded down. 34 * 35 * @var boolean 36 */ 37 protected bool roundDimensions = true; 38 /** 39 * Final width of the image. 40 * 41 * @var integer 42 */ 43 protected int finalWidth; 44 /** 45 * Final height of the image. 46 * 47 * @var integer 48 */ 49 protected int finalHeight; 50 /** 51 * Size of each individual block. 52 * 53 * @var integer 54 */ 55 protected int blockSize; 56 /** 57 * Background color. 58 * 59 * @var Color\ColorInterface 60 */ 61 protected ColorInterface backgroundColor; 62 /** 63 * Whether dimensions should be rounded down 64 * 65 * @var boolean 66 */ 67 protected bool floorToClosestDimension; 68 /** 69 * Foreground color. 70 * 71 * @var Color\ColorInterface 72 */ 73 protected ColorInterface foregroundColor; 74 /** 75 * Decorators used on QR codes. 76 * 77 * @var array 78 */ 79 protected DecoratorInterface[] decorators; 80 81 /** 82 * Sets the margin around the QR code. 83 * 84 * @param integer margin 85 * @return AbstractRenderer 86 * @throws Exception\InvalidArgumentException 87 */ 88 public AbstractRenderer setMargin(int _margin) 89 { 90 if (margin < 0) { 91 throw new Exception("Margin must be equal to greater than 0"); 92 } 93 this.margin = _margin; 94 return this; 95 } 96 /** 97 * Gets the margin around the QR code. 98 * 99 * @return integer 100 */ 101 public int getMargin() 102 { 103 return this.margin; 104 } 105 /** 106 * Sets the height around the renderd image. 107 * 108 * If the width is smaller than the matrix width plus padding, the renderer 109 * will automatically use that as the width instead of the specified one. 110 * 111 * @param integer width 112 * @return AbstractRenderer 113 */ 114 public AbstractRenderer setWidth(int _width) 115 { 116 this.width = _width; 117 return this; 118 } 119 /** 120 * Gets the width of the rendered image. 121 * 122 * @return integer 123 */ 124 public int getWidth() 125 { 126 return this.width; 127 } 128 /** 129 * Sets the height around the renderd image. 130 * 131 * If the height is smaller than the matrix height plus padding, the 132 * renderer will automatically use that as the height instead of the 133 * specified one. 134 * 135 * @param integer height 136 * @return AbstractRenderer 137 */ 138 public AbstractRenderer setHeight(int _height) 139 { 140 this.height = _height; 141 return this; 142 } 143 /** 144 * Gets the height around the rendered image. 145 * 146 * @return integer 147 */ 148 public int getHeight() 149 { 150 return this.height; 151 } 152 /** 153 * Sets whether dimensions should be rounded down. 154 * 155 * @param boolean flag 156 * @return AbstractRenderer 157 */ 158 public AbstractRenderer setRoundDimensions(bool flag) 159 { 160 this.floorToClosestDimension = flag; 161 return this; 162 } 163 /** 164 * Gets whether dimensions should be rounded down. 165 * 166 * @return boolean 167 */ 168 public bool shouldRoundDimensions() 169 { 170 return this.floorToClosestDimension; 171 } 172 /** 173 * Sets background color. 174 * 175 * @param Color\ColorInterface color 176 * @return AbstractRenderer 177 */ 178 public AbstractRenderer setBackgroundColor(ColorInterface color) 179 { 180 this.backgroundColor = color; 181 return this; 182 } 183 /** 184 * Gets background color. 185 * 186 * @return Color\ColorInterface 187 */ 188 public ColorInterface getBackgroundColor() 189 { 190 if (this.backgroundColor is null) { 191 this.backgroundColor = new Gray(100); 192 } 193 return this.backgroundColor; 194 } 195 /** 196 * Sets foreground color. 197 * 198 * @param Color\ColorInterface color 199 * @return AbstractRenderer 200 */ 201 public AbstractRenderer setForegroundColor(ColorInterface color) 202 { 203 this.foregroundColor = color; 204 return this; 205 } 206 /** 207 * Gets foreground color. 208 * 209 * @return Color\ColorInterface 210 */ 211 public ColorInterface getForegroundColor() 212 { 213 if (this.foregroundColor is null) { 214 this.foregroundColor = new Gray(0); 215 } 216 return this.foregroundColor; 217 } 218 /** 219 * Adds a decorator to the renderer. 220 * 221 * @param DecoratorInterface decorator 222 * @return AbstractRenderer 223 */ 224 public AbstractRenderer addDecorator(DecoratorInterface decorator) 225 { 226 this.decorators[] = decorator; 227 return this; 228 } 229 230 /** 231 * render(): defined by RendererInterface. 232 * 233 * @see RendererInterface::render() 234 * @param QrCode qrCode 235 * @return string 236 */ 237 public string render(QrCode qrCode) 238 { 239 import std.algorithm; 240 auto input = qrCode.matrix(); 241 auto inputWidth = input.width(); 242 auto inputHeight = input.height(); 243 auto qrWidth = inputWidth + (this.getMargin() << 1); 244 auto qrHeight = inputHeight + (this.getMargin() << 1); 245 auto outputWidth = max(this.getWidth(), qrWidth); 246 auto outputHeight = max(this.getHeight(), qrHeight); 247 auto multiple = min(outputWidth / qrWidth, outputHeight / qrHeight); 248 if (this.shouldRoundDimensions()) { 249 outputWidth -= outputWidth % multiple; 250 outputHeight -= outputHeight % multiple; 251 } 252 // Padding includes both the quiet zone and the extra white pixels to 253 // accommodate the requested dimensions. For example, if input is 25x25 254 // the QR will be 33x33 including the quiet zone. If the requested size 255 // is 200x160, the multiple will be 4, for a QR of 132x132. These will 256 // handle all the padding from 100x100 (the actual QR) up to 200x160. 257 auto leftPadding = ((outputWidth - (inputWidth * multiple)) / 2); 258 auto topPadding = ((outputHeight - (inputHeight * multiple)) / 2); 259 // Store calculated parameters 260 this.finalWidth = outputWidth; 261 this.finalHeight = outputHeight; 262 this.blockSize = multiple; 263 this.initRender(); 264 this.addColor("background", this.getBackgroundColor()); 265 this.addColor("foreground", this.getForegroundColor()); 266 this.drawBackground("background"); 267 foreach (decorator;this.decorators) { 268 decorator.preProcess( 269 qrCode, 270 this, 271 outputWidth, 272 outputHeight, 273 leftPadding, 274 topPadding, 275 multiple 276 ); 277 } 278 for (auto inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { 279 for (auto inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { 280 if (input.get(inputX, inputY) == 1) { 281 this.drawBlock(outputX, outputY, "foreground"); 282 } 283 } 284 } 285 foreach (decorator;this.decorators){ 286 decorator.postProcess( 287 qrCode, 288 this, 289 outputWidth, 290 outputHeight, 291 leftPadding, 292 topPadding, 293 multiple 294 ); 295 } 296 return this.getByteStream(); 297 } 298 299 public void initRender(){}; 300 public void addColor(string id, ColorInterface color){} 301 public void drawBackground(string colorId){} 302 public void drawBlock(int x, int y, string colorId){} 303 public string getByteStream(){ 304 return string.init; 305 } 306 }