1 /**
2 * Module for picture manipulation.
3 *
4 * Authors:
5 *   Jacob Jensen
6 * License:
7 *   https://github.com/PoisonEngine/poison-ui/blob/master/LICENSE
8 */
9 module poison.ui.picture;
10 
11 import dsfml.graphics : Image, Texture, RenderWindow;
12 
13 import poison.ui.paint;
14 import poison.ui.sprite;
15 import poison.core : Size, Point;
16 
17 /// Paint graphics for a picture.
18 private class PictureGraphics {
19   /// The position.
20   Point _position;
21   /// The size.
22   Size _size;
23   /// The paint.
24   Paint _paint;
25 
26   /**
27   * Creates a new picture graphic.
28   * Params:
29   *   position =  The position of the graphic.
30   *   size =      The size of the graphic.
31   *   paint =     The paint of the graphic.
32   */
33   this(Point position, Size size, Paint paint) {
34     _position = position;
35     _size = size;
36     _paint = paint;
37   }
38 
39   @property {
40     /// Gets the position of the graphic.
41     Point position() { return _position; }
42 
43     /// Gets the size of the graphic.
44     Size size() { return _size; }
45 
46     /// Gets the paint of the graphic.
47     Paint paint() { return _paint; }
48   }
49 }
50 
51 /// A picture for image manipulation.
52 final class Picture {
53   private:
54   /// The background sprite.
55   TextureSprite _backgroundSprite;
56 
57   /// The drawing sprite.
58   TextureSprite _drawingSprite;
59 
60   /// The original filename.
61   string _fileName;
62 
63   /// The size.
64   Size _size;
65 
66   /// The fill paint.
67   Paint _fillPaint;
68 
69   /// The image buffer.
70   ubyte[] _imageBuffer;
71 
72   /// The graphics to render onto it.
73   PictureGraphics[] _graphics;
74 
75   /// The position.
76   Point _position;
77 
78   /// Renders the picture.
79   void render() {
80     assert(_fileName || _imageBuffer || _size);
81 
82     Image backgroundImage;
83     auto drawingImage = new Image();
84 
85     if (_fileName || _imageBuffer) {
86       backgroundImage = new Image();
87 
88       if (_fileName) {
89         backgroundImage.loadFromFile(_fileName);
90       }
91       else {
92         backgroundImage.loadFromMemory(_imageBuffer);
93       }
94 
95       auto imgSize = backgroundImage.getSize();
96       _size = new Size(cast(size_t)imgSize.x, cast(size_t)imgSize.y);
97 
98       drawingImage.create(_size.width, _size.height, transparent.sfmlColor);
99     }
100     else if (_size) {
101       drawingImage.create(_size.width, _size.height, _fillPaint.sfmlColor);
102     }
103 
104     if (_graphics) {
105       foreach (graphic; _graphics) {
106         auto xFrom = graphic.position.x;
107         auto xTo = graphic.position.x + graphic.size.width;
108         auto yFrom = graphic.position.y;
109         auto yTo = graphic.position.y + graphic.size.height;
110         auto color = graphic.paint.sfmlColor;
111 
112         foreach (x; xFrom .. xTo) {
113           foreach (y; yFrom .. yTo) {
114             drawingImage.setPixel(cast(uint)x, cast(uint)y, color);
115           }
116         }
117       }
118     }
119 
120     if (backgroundImage) {
121       auto texture = new Texture();
122   		texture.loadFromImage(backgroundImage);
123   		texture.setSmooth(true);
124 
125   		_backgroundSprite = new TextureSprite(texture);
126     }
127 
128     if (drawingImage) {
129       auto texture = new Texture();
130   		texture.loadFromImage(drawingImage);
131   		texture.setSmooth(true);
132 
133   		_drawingSprite = new TextureSprite(texture);
134     }
135 
136     position = _position;
137   }
138 
139   public:
140   final:
141   /**
142   * Creates a new picture.
143   * Params:
144   *   imageFile = The file for an image to load onto the picture.
145   */
146   this(string imageFile) {
147     _fileName = imageFile;
148   }
149 
150   /**
151   * Creates a new picture.
152   * Params:
153   *   size =      The size of the picture.
154   *   fillPaint = The initialization paint.
155   */
156   this(Size size, Paint fillPaint) {
157     _size = size;
158     _fillPaint = fillPaint;
159   }
160 
161   /**
162   * Creates a new picture.
163   * Params:
164   *   imageBuffer = The buffer to load the picture from.
165   */
166   this(ubyte[] imageBuffer) {
167     _imageBuffer = imageBuffer;
168   }
169 
170   /**
171   * Creates a new picture, copied from another.
172   * Params:
173   *   copyImage = The image to copy.
174   */
175   this(Picture copyImage) {
176     if (copyImage._imageBuffer) {
177       this(copyImage._imageBuffer.dup);
178     }
179     else if (copyImage._fileName) {
180       this(copyImage._fileName.dup);
181     }
182     else {
183       this(new Size(copyImage._size.width, copyImage._size.height), copyImage._fillPaint);
184     }
185 
186     if (copyImage._graphics) {
187       _graphics = copyImage._graphics.dup;
188     }
189   }
190 
191   /// Clears the graphics.
192   void clearGraphics() {
193     _graphics = null;
194   }
195 
196   /**
197   * Draws paint onto the picture.
198   * Params:
199   *   position =  The position within the picture to draw the paint.
200   *   size =      The size of the paint.
201   *   paint =     The paint to draw with.
202   */
203   void draw(Point position, Size size, Paint paint) {
204     _graphics ~= new PictureGraphics(position, size, paint);
205   }
206 
207   /**
208   * Paints a horizontal gradient on the picture.
209   * Params:
210   *   position =  The position within the picture to paint the gradient.
211   *   size =      The size of the gradient.
212   *   from =      The paint to gradient from.
213   *   to =        The paint to gradient to.
214   *   a =         The alpha channel of the gradient.
215   */
216   void gradientHorizontal(Point position, Size size, Paint from, Paint to, ubyte a = 0xff) {
217     bool decreaseR = from.r > to.r;
218     bool decreaseG = from.g > to.g;
219     bool decreaseB = from.b > to.b;
220 
221     auto block = new Size(1, size.height);
222 
223     ubyte rInc = cast(ubyte)(cast(float)(decreaseR ? from.r - to.r : to.r - from.r) / (cast(float)size.width / 1.5));
224     ubyte gInc = cast(ubyte)(cast(float)(decreaseG ? from.g - to.g : to.g - from.g) / (cast(float)size.width / 1.5));
225     ubyte bInc = cast(ubyte)(cast(float)(decreaseB ? from.b - to.b : to.b - from.b) / (cast(float)size.width / 1.5));
226 
227     for (auto x = position.x; x < (position.x + size.width); x++) {
228       draw(new Point(x, position.y), block, paintFromRGBA(from.r, from.g, from.b, a));
229 
230       if (decreaseR && from.r > (to.r + rInc)) {
231         from.r -= rInc;
232       }
233       else if (!decreaseR && from.r < (to.r - rInc)) {
234         from.r += rInc;
235       }
236 
237       if (decreaseG && from.g > (to.g + gInc)) {
238         from.g -= gInc;
239       }
240       else if (!decreaseG && from.g < (to.g - gInc)) {
241         from.g += gInc;
242       }
243 
244       if (decreaseB && from.b > (to.b + bInc)) {
245         from.b -= bInc;
246       }
247       else if (!decreaseB && from.b < (to.b - bInc)) {
248         from.b += bInc;
249       }
250     }
251   }
252 
253   /**
254   * Paints a vertical gradient on the picture.
255   * Params:
256   *   position =  The position within the picture to paint the gradient.
257   *   size =      The size of the gradient.
258   *   from =      The paint to gradient from.
259   *   to =        The paint to gradient to.
260   *   a =         The alpha channel of the gradient.
261   */
262   void gradientVertical(Point position, Size size, Paint from, Paint to, ubyte a = 0xff) {
263     bool decreaseR = from.r > to.r;
264     bool decreaseG = from.g > to.g;
265     bool decreaseB = from.b > to.b;
266 
267     auto block = new Size(size.width, 1);
268 
269     ubyte rInc = cast(ubyte)(cast(float)(decreaseR ? from.r - to.r : to.r - from.r) / (cast(float)size.height / 1.5));
270     ubyte gInc = cast(ubyte)(cast(float)(decreaseG ? from.g - to.g : to.g - from.g) / (cast(float)size.height / 1.5));
271     ubyte bInc = cast(ubyte)(cast(float)(decreaseB ? from.b - to.b : to.b - from.b) / (cast(float)size.height / 1.5));
272 
273     for (auto y = position.y; y < (position.y + size.height); y++) {
274       draw(new Point(position.x, y), block, paintFromRGBA(from.r, from.g, from.b, a));
275 
276       if (decreaseR && from.r > (to.r + rInc)) {
277         from.r -= rInc;
278       }
279       else if (!decreaseR && from.r < (to.r - rInc)) {
280         from.r += rInc;
281       }
282 
283       if (decreaseG && from.g > (to.g + gInc)) {
284         from.g -= gInc;
285       }
286       else if (!decreaseG && from.g < (to.g - gInc)) {
287         from.g += gInc;
288       }
289 
290       if (decreaseB && from.b > (to.b + bInc)) {
291         from.b -= bInc;
292       }
293       else if (!decreaseB && from.b < (to.b - bInc)) {
294         from.b += bInc;
295       }
296     }
297   }
298 
299   /**
300   * Resizes the picture.
301   * Params:
302   *   newSize = Resizes the picture.
303   * Bug:
304   *   Currently only work for pictures with no image file.
305   *   The fix is to use scale().
306   */
307   void resize(Size newSize) {
308     _size = newSize;
309   }
310 
311   /// Finalizes the picture and its graphics.
312   void finalize() {
313     render();
314   }
315 
316   @property {
317     /// Gets the size of the picture.
318     Size size() { return _size; }
319 
320     /// Gets the file name.
321     string fileName() { return _fileName; }
322 
323     /// Sets the file name.
324     void fileName(string newFileName) {
325       _fileName = newFileName;
326     }
327 
328     /// Gets the image buffer.
329     ubyte[] imageBuffer() { return _imageBuffer; }
330 
331     /// Sets the image buffer.
332     void imageBuffer(ubyte[] imageBuffer) {
333       _imageBuffer = imageBuffer;
334     }
335   }
336 
337   package(poison):
338   @property {
339     /// Gets the background sprite of the picture.
340     TextureSprite backgroundSprite() { return _backgroundSprite; }
341 
342     /// Gets the drawing sprite of the picture.
343     TextureSprite drawingSprite() { return _drawingSprite; }
344 
345     /// Sets the position of the picture's sprite.
346     void position(Point newPosition) {
347       _position = newPosition;
348 
349       if (_position) {
350         if (_backgroundSprite) {
351           _backgroundSprite.position = _position;
352         }
353 
354         if (_drawingSprite) {
355           _drawingSprite.position = _position;
356         }
357       }
358     }
359   }
360 }