1 /*
2  * DSFML - The Simple and Fast Multimedia Library for D
3  *
4  * Copyright (c) 2013 - 2017 Jeremy DeHaan (dehaan.jeremiah@gmail.com)
5  *
6  * This software is provided 'as-is', without any express or implied warranty.
7  * In no event will the authors be held liable for any damages arising from the
8  * use of this software.
9  *
10  * Permission is granted to anyone to use this software for any purpose,
11  * including commercial applications, and to alter it and redistribute it
12  * freely, subject to the following restrictions:
13  *
14  * 1. The origin of this software must not be misrepresented; you must not claim
15  * that you wrote the original software. If you use this software in a product,
16  * an acknowledgment in the product documentation would be appreciated but is
17  * not required.
18  *
19  * 2. Altered source versions must be plainly marked as such, and must not be
20  * misrepresented as being the original software.
21  *
22  * 3. This notice may not be removed or altered from any source distribution
23  */
24 
25 /**
26  * $(U Image) is an abstraction to manipulate images as bidimensional arrays of
27  * pixels. The class provides functions to load, read, write and save pixels, as
28  * well as many other useful functions.
29  *
30  * $(U Image) can handle a unique internal representation of pixels, which is
31  * RGBA 32 bits. This means that a pixel must be composed of 8 bits red, green,
32  * blue and alpha channels – just like a $(COLOR_LINK). All the functions that
33  * return an array of pixels follow this rule, and all parameters that you pass
34  * to $(U Image) functions (such as `loadFromPixels`) must use this
35  * representation as well.
36  *
37  * An $(U Image) can be copied, but it is a heavy resource and if possible you
38  * should always use `const` references to pass or return them to avoid useless
39  * copies.
40  *
41  * Example:
42  * ---
43  * // Load an image file from a file
44  * auto background = new Image();
45  * if (!background.loadFromFile("background.jpg"))
46  *     return -1;
47  *
48  * // Create a 20x20 image filled with black color
49  * auto image = new Image();
50  * image.create(20, 20, Color.Black);
51  *
52  * // Copy image1 on image2 at position (10, 10)
53  * image.copy(background, 10, 10);
54  *
55  * // Make the top-left pixel transparent
56  * auto color = image.getPixel(0, 0);
57  * color.a = 0;
58  * image.setPixel(0, 0, color);
59  *
60  * // Save the image to a file
61  * if (!image.saveToFile("result.png"))
62  *     return -1;
63  * ---
64  *
65  * See_Also:
66  * $(TEXTURE_LINK)
67  */
68 module dsfml.graphics.image;
69 
70 import dsfml.system.vector2;
71 
72 import dsfml.graphics.color;
73 import dsfml.system.inputstream;
74 
75 import dsfml.graphics.rect;
76 
77 import dsfml.system.err;
78 
79 /**
80  * Class for loading, manipulating and saving images.
81  */
82 class Image
83 {
84     package sfImage* sfPtr;
85 
86     /// Default constructor.
87     this()
88     {
89         sfPtr = sfImage_construct();
90     }
91 
92     package this(sfImage* image)
93     {
94         sfPtr = image;
95     }
96 
97     /// Destructor.
98     ~this()
99     {
100         import dsfml.system.config;
101         mixin(destructorOutput);
102         sfImage_destroy(sfPtr);
103     }
104 
105     /**
106      * Create the image and fill it with a unique color.
107      *
108      * Params:
109      * 		width	= Width of the image
110      * 		height	= Height of the image
111      * 		color	= Fill color
112      *
113      */
114     void create(uint width, uint height, Color color)
115     {
116 
117         sfImage_createFromColor(sfPtr, width, height,color.r, color.b, color.g, color.a);
118     }
119 
120     /**
121      * Create the image from an array of pixels.
122      *
123      * The pixel array is assumed to contain 32-bits RGBA pixels, and have the
124      * given width and height. If not, this is an undefined behaviour. If pixels
125      * is null, an empty image is created.
126      *
127      * Params:
128      * 		width	= Width of the image
129      * 		height	= Height of the image
130      * 		pixels	= Array of pixels to copy to the image
131      *
132      */
133     void create(uint width, uint height, const(ubyte)[] pixels)
134     {
135         sfImage_createFromPixels(sfPtr, width, height,pixels.ptr);
136     }
137 
138     /**
139      * Load the image from a file on disk.
140      *
141      * The supported image formats are bmp, png, tga, jpg, gif, psd, hdr and
142      * pic. Some format options are not supported, like progressive jpeg. If
143      * this function fails, the image is left unchanged.
144      *
145      * Params:
146      * 		filename	= Path of the image file to load
147      *
148      * Returns: true if loading succeeded, false if it failed
149      */
150     bool loadFromFile(const(char)[] filename)
151     {
152         import dsfml.system..string;
153 
154         bool ret = sfImage_loadFromFile(sfPtr, filename.ptr, filename.length);
155 
156         if(!ret)
157         {
158             err.write(dsfml.system..string.toString(sfErr_getOutput()));
159         }
160 
161         return ret;
162     }
163 
164     /**
165      * Load the image from a file in memory.
166      *
167      * The supported image formats are bmp, png, tga, jpg, gif, psd, hdr and
168      * pic. Some format options are not supported, like progressive jpeg. If
169      * this function fails, the image is left unchanged.
170      *
171      * Params:
172      * 		data	= Data file in memory to load
173      *
174      * Returns: true if loading succeeded, false if it failed
175      */
176     bool loadFromMemory(const(void)[] data)
177     {
178         import dsfml.system..string;
179 
180         bool ret = sfImage_loadFromMemory(sfPtr, data.ptr, data.length);
181         if(!ret)
182         {
183             err.write(dsfml.system..string.toString(sfErr_getOutput()));
184         }
185 
186         return ret;
187     }
188 
189     /**
190      * Load the image from a custom stream.
191      *
192      * The supported image formats are bmp, png, tga, jpg, gif, psd, hdr and
193      * pic. Some format options are not supported, like progressive jpeg. If
194      * this function fails, the image is left unchanged.
195      *
196      * Params:
197      * 		stream	= Source stream to read from
198      *
199      * Returns: true if loading succeeded, false if it failed
200      */
201     bool loadFromStream(InputStream stream)
202     {
203         import dsfml.system..string;
204 
205         bool ret = sfImage_loadFromStream(sfPtr, new imageStream(stream));
206         if(!ret)
207         {
208             err.write(dsfml.system..string.toString(sfErr_getOutput()));
209         }
210 
211         return ret;
212     }
213 
214     /**
215      * Get the color of a pixel
216      *
217      * This function doesn't check the validity of the pixel coordinates; using
218      * out-of-range values will result in an undefined behaviour.
219      *
220      * Params:
221      * 		x	= X coordinate of the pixel to get
222      * 		y	= Y coordinate of the pixel to get
223      *
224      * Returns: Color of the pixel at coordinates (x, y)
225      */
226     Color getPixel(uint x, uint y)
227     {
228         Color temp;
229         sfImage_getPixel(sfPtr, x,y, &temp.r, &temp.b, &temp.g, &temp.a);
230         return temp;
231     }
232 
233     /**
234      * Get the read-only array of pixels that make up the image.
235      *
236      * The returned value points to an array of RGBA pixels made of 8 bits
237      * integers components. The size of the array is:
238      * `width * height * 4 (getSize().x * getSize().y * 4)`.
239      *
240      * Warning: the returned slice may become invalid if you modify the image,
241      * so you should never store it for too long.
242      *
243      * Returns: Read-only array of pixels that make up the image.
244      */
245     const(ubyte)[] getPixelArray()
246     {
247         Vector2u size = getSize();
248         int length = size.x * size.y * 4;
249 
250         if(length!=0)
251         {
252             return sfImage_getPixelsPtr(sfPtr)[0..length];
253         }
254         else
255         {
256             err.writeln("Trying to access the pixels of an empty image");
257             return [];
258         }
259     }
260 
261     /**
262      * Return the size (width and height) of the image.
263      *
264      * Returns: Size of the image, in pixels.
265      */
266     Vector2u getSize()
267     {
268         Vector2u temp;
269         sfImage_getSize(sfPtr,&temp.x, &temp.y);
270         return temp;
271     }
272 
273     /**
274      * Change the color of a pixel.
275      *
276      * This function doesn't check the validity of the pixel coordinates, using
277      * out-of-range values will result in an undefined behaviour.
278      *
279      * Params:
280      * 		x		= X coordinate of pixel to change
281      * 		y		= Y coordinate of pixel to change
282      * 		color	= New color of the pixel
283      */
284     void setPixel(uint x, uint y, Color color)
285     {
286         sfImage_setPixel(sfPtr, x,y,color.r, color.b,color.g, color.a);
287     }
288 
289     /**
290      * Copy pixels from another image onto this one.
291      *
292      * This function does a slow pixel copy and should not be used intensively.
293      * It can be used to prepare a complex static image from several others, but
294      * if you need this kind of feature in real-time you'd better use
295      * RenderTexture.
296      *
297      * If sourceRect is empty, the whole image is copied. If applyAlpha is set
298      * to true, the transparency of source pixels is applied. If it is false,
299      * the pixels are copied unchanged with their alpha value.
300      *
301      * Params:
302      * 	source		= Source image to copy
303      * 	destX		= X coordinate of the destination position
304      * 	destY		= Y coordinate of the destination position
305      * 	sourceRect	= Sub-rectangle of the source image to copy
306      * 	applyAlpha	= Should the copy take the source transparency into account?
307      */
308     void copyImage(const(Image) source, uint destX, uint destY, IntRect sourceRect = IntRect(0,0,0,0), bool applyAlpha = false)
309     {
310         sfImage_copyImage(sfPtr, source.sfPtr, destX, destY,sourceRect.left, sourceRect.top, sourceRect.width, sourceRect.height, applyAlpha);//:sfImage_copyImage(sfPtr, source.sfPtr, destX, destY, temp, sfFalse);
311     }
312 
313     /**
314      * Create a transparency mask from a specified color-key.
315      *
316      * This function sets the alpha value of every pixel matching the given
317      * color to alpha (0 by default) so that they become transparent.
318      *
319      * Params:
320      * 		color	= Color to make transparent
321      * 		alpha	= Alpha value to assign to transparent pixels
322      */
323     void createMaskFromColor(Color maskColor, ubyte alpha = 0)
324     {
325         sfImage_createMaskFromColor(sfPtr,maskColor.r,maskColor.b, maskColor.g, maskColor.a, alpha);
326     }
327 
328     /// Create a copy of the Image.
329     @property Image dup() const
330     {
331         return new Image(sfImage_copy(sfPtr));
332     }
333 
334     /// Flip the image horizontally (left <-> right)
335     void flipHorizontally()
336     {
337         sfImage_flipHorizontally(sfPtr);
338     }
339 
340     /// Flip the image vertically (top <-> bottom)
341     void flipVertically()
342     {
343         sfImage_flipVertically(sfPtr);
344     }
345 
346     /**
347      * Save the image to a file on disk.
348      *
349      * The format of the image is automatically deduced from the extension. The
350      * supported image formats are bmp, png, tga and jpg. The destination file
351      * is overwritten if it already exists. This function fails if the image is
352      * empty.
353      *
354      * Params:
355      * 		filename	= Path of the file to save
356      *
357      * Returns: true if saving was successful
358      */
359     bool saveToFile(const(char)[] filename)
360     {
361         import dsfml.system..string;
362         bool toReturn = sfImage_saveToFile(sfPtr, filename.ptr, filename.length);
363         err.write(dsfml.system..string.toString(sfErr_getOutput()));
364         return toReturn;
365     }
366 }
367 
368 unittest
369 {
370     version(DSFML_Unittest_Graphics)
371     {
372         import std.stdio;
373 
374         writeln("Unit test for Image");
375 
376         auto image = new Image();
377 
378         image.create(100,100,Color.Blue);
379 
380         assert(image.getPixel(0,0) == Color.Blue);
381 
382         image.setPixel(0,0,Color.Green);
383 
384         assert(image.getPixel(0,0) == Color.Green);
385 
386 
387         image.flipHorizontally();
388 
389         assert(image.getPixel(99,0) == Color.Green);
390 
391         image.flipVertically();
392 
393         assert(image.getPixel(99,99) == Color.Green);
394 
395         assert(image.getSize() == Vector2u(100,100));
396 
397         writeln();
398     }
399 }
400 
401 
402 private extern(C++) interface imageInputStream
403 {
404     long read(void* data, long size);
405 
406     long seek(long position);
407 
408     long tell();
409 
410     long getSize();
411 }
412 
413 
414 private class imageStream:imageInputStream
415 {
416     private InputStream myStream;
417 
418     this(InputStream stream)
419     {
420         myStream = stream;
421     }
422 
423     extern(C++)long read(void* data, long size)
424     {
425         return myStream.read(data[0..cast(size_t)size]);
426     }
427 
428     extern(C++)long seek(long position)
429     {
430         return myStream.seek(position);
431     }
432 
433     extern(C++)long tell()
434     {
435         return myStream.tell();
436     }
437 
438     extern(C++)long getSize()
439     {
440         return myStream.getSize();
441     }
442 }
443 
444 
445 package extern(C) struct sfImage;
446 
447 private extern(C):
448 
449 //Construct a new image
450 sfImage* sfImage_construct();
451 
452 void sfImage_create(sfImage* image, uint width, uint height);
453 
454 /// \brief Create an image and fill it with a unique color
455 void sfImage_createFromColor(sfImage* image, uint width, uint height, ubyte r, ubyte b, ubyte g, ubyte a);
456 
457 /// \brief Create an image from an array of pixels
458 void sfImage_createFromPixels(sfImage* image, uint width, uint height, const(ubyte)* pixels);
459 
460 /// \brief Create an image from a file on disk
461 bool sfImage_loadFromFile(sfImage* image, const(char)* filename, size_t length);
462 
463 /// \brief Create an image from a file in memory
464 bool sfImage_loadFromMemory(sfImage* image, const(void)* data, size_t size);
465 
466 /// \brief Create an image from a custom stream
467 bool sfImage_loadFromStream(sfImage* image, imageInputStream stream);
468 
469 /// \brief Copy an existing image
470 sfImage* sfImage_copy(const(sfImage)* image);
471 
472 /// \brief Destroy an existing image
473 void sfImage_destroy(sfImage* image);
474 
475 /// \brief Save an image to a file on disk
476 bool sfImage_saveToFile(const sfImage* image, const char* filename, size_t length);
477 
478 /// \brief Return the size of an image
479 void sfImage_getSize(const sfImage* image, uint* width, uint* height);
480 
481 /// \brief Create a transparency mask from a specified color-key
482 void sfImage_createMaskFromColor(sfImage* image, ubyte r, ubyte b, ubyte g, ubyte a, ubyte alpha);
483 
484 /// \brief Copy pixels from an image onto another
485 void sfImage_copyImage(sfImage* image, const(sfImage)* source, uint destX, uint destY, int sourceRectLeft, int sourceRectTop, int sourceRectWidth, int sourceRectHeight, bool applyAlpha);
486 
487 /// \brief Change the color of a pixel in an image
488 void sfImage_setPixel(sfImage* image, uint x, uint y, ubyte r, ubyte b, ubyte g, ubyte a);
489 
490 /// \brief Get the color of a pixel in an image
491 void sfImage_getPixel(const sfImage* image, uint x, uint y, ubyte* r, ubyte* b, ubyte* g, ubyte* a);
492 
493 /// \brief Get a read-only pointer to the array of pixels of an image
494 const(ubyte)* sfImage_getPixelsPtr(const sfImage* image);
495 
496 /// \brief Flip an image horizontally (left <-> right)
497 void sfImage_flipHorizontally(sfImage* image);
498 
499 /// \brief Flip an image vertically (top <-> bottom)
500 void sfImage_flipVertically(sfImage* image);
501 
502 const(char)* sfErr_getOutput();