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