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