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 }