1 | // tPicture.h  |
2 | //  |
3 | // This class represents a simple one-frame image. It is a collection of raw uncompressed 32-bit tPixels. It can load  |
4 | // various formats from disk such as jpg, tga, png, etc. It intentionally _cannot_ load a dds file. More on that later.  |
5 | // Image manipulation (excluding compression) is supported in a tPicture, so there are crop, scale, rotate, etc  |
6 | // functions in this class.  |
7 | //  |
8 | // Some image disk formats have more than one 'frame' or image inside them. For example, tiff files can have more than  |
9 | // page, and gif/webp images may be animated and have more than one frame. A tPicture can only prepresent _one_ of   |
10 | // these frames.  |
11 | //  |
12 | // Copyright (c) 2006, 2016, 2017, 2020 Tristan Grimmer.  |
13 | // Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby  |
14 | // granted, provided that the above copyright notice and this permission notice appear in all copies.  |
15 | //  |
16 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL  |
17 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,  |
18 | // INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN  |
19 | // AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR  |
20 | // PERFORMANCE OF THIS SOFTWARE.  |
21 |   |
22 | #pragma once  |
23 | #include <Foundation/tList.h>  |
24 | #include <Foundation/tConstants.h>  |
25 | #include <Math/tColour.h>  |
26 | #include <System/tFile.h>  |
27 | #include <System/tChunk.h>  |
28 | #include "Image/tImageAPNG.h"  |
29 | #include "Image/tImageBMP.h"  |
30 | #include "Image/tImageEXR.h"  |
31 | #include "Image/tImageGIF.h"  |
32 | #include "Image/tImageHDR.h"  |
33 | #include "Image/tImageICO.h"  |
34 | #include "Image/tImageJPG.h"  |
35 | #include "Image/tImagePNG.h"  |
36 | #include "Image/tImageTGA.h"  |
37 | #include "Image/tImageTIFF.h"  |
38 | #include "Image/tImageWEBP.h"  |
39 | #include "Image/tImageXPM.h"  |
40 | #include "Image/tPixelFormat.h"  |
41 | #include "Image/tResample.h"  |
42 | namespace tImage  |
43 | {  |
44 |   |
45 |   |
46 | // Verion information for the image loaders. This is all in the tImage namespace.  |
47 | extern const char* Version_LibJpegTurbo;  |
48 | extern const char* Version_OpenEXR;  |
49 | extern const char* Version_ZLIB;  |
50 | extern const char* Version_LibPNG;  |
51 | extern const char* Version_LibTIFF;  |
52 | extern const char* Version_ApngDis;  |
53 | extern int Version_WEBP_Major;  |
54 | extern int Version_WEBP_Minor;  |
55 |   |
56 |   |
57 | // A tPicture is a single 2D image. A rectangular collection of RGBA pixels (32bits per pixel). The origin is the lower  |
58 | // left, and the rows are ordered from bottom to top in memory. This matches the expectation of OpenGL texture  |
59 | // manipulation functions for the most part (there are cases when it is inconsistent with itself).  |
60 | class tPicture : public tLink<tPicture>  |
61 | {  |
62 | public:  |
63 | // Constructs an empty picture that is invalid. You must call Load yourself later.  |
64 | tPicture() { }  |
65 |   |
66 | // Constructs a picture that is width by height pixels. It will be all black pixels with an opaque alpha of 255.  |
67 | // That is, every pixel will be (0, 0, 0, 255).  |
68 | tPicture(int width, int height) { Set(width, height); }  |
69 |   |
70 | // This constructor allows you to specify an external buffer of pixels to use. If copyPixels is true, it simply  |
71 | // copies the values from the buffer you supply. If copyPixels is false, it means you are giving the buffer to the  |
72 | // tPicture. In this case the tPicture will delete[] the buffer for you when appropriate.  |
73 | tPicture(int width, int height, tPixel* pixelBuffer, bool copyPixels = true) { Set(width, height, pixelBuffer, copyPixels); }  |
74 |   |
75 | struct LoadParams  |
76 | {  |
77 | LoadParams() { }  |
78 | float GammaValue = 2.2f;//::tMath::DefaultGamma;  |
79 | int HDR_Exposure = tImageHDR::DefaultExposure;  |
80 | float EXR_Exposure = tImageEXR::DefaultExposure;  |
81 | float EXR_Defog = tImageEXR::DefaultDefog;  |
82 | float EXR_KneeLow = tImageEXR::DefaultKneeLow;  |
83 | float EXR_KneeHigh = tImageEXR::DefaultKneeHigh;  |
84 | };  |
85 |   |
86 | // Loads the supplied image file. If the image couldn't be loaded, IsValid will return false afterwards. Uses the  |
87 | // filename extension to determine what file type it is loading. dds files may _not_ be loaded into a tPicture.  |
88 | // Use a tTexture if you want to load a dds. For images with more than one frame (animated gif, tiff, etc) the  |
89 | // frameNum specifies which one to load and will result in an invalid tPicture if you go too high.  |
90 | tPicture(const tString& imageFile, int = 0, LoadParams params = LoadParams()) { Load(imageFile, frameNum, params); }  |
91 |   |
92 | // Copy constructor.  |
93 | tPicture(const tPicture& src) : tPicture() { Set(src); }  |
94 |   |
95 | virtual ~tPicture() { Clear(); }  |
96 | bool IsValid() const { return Pixels ? true : false; }  |
97 |   |
98 | // Invalidated the picture and frees memory associated with it. The tPicture will be invalid after this.  |
99 | void Clear();  |
100 |   |
101 | // Sets the image to the dimensions provided. Image will be opaque black after this call. Internally, if the  |
102 | // existing buffer is the right size, it is reused. In all cases, the entire image is cleared to black.  |
103 | void Set(int width, int height, const tPixel& colour = tPixel::black);  |
104 |   |
105 | // Sets the image to the dimensions provided. Allows you to specify an external buffer of pixels to use. If  |
106 | // copyPixels is true, it simply copies the values from the buffer you supply. In this case it will attempt to  |
107 | // reuse it's existing buffer if it can. If copyPixels is false, it means you are giving the buffer to the  |
108 | // tPicture. In this case the tPicture will delete[] the buffer for you when appropriate. In all cases, existing  |
109 | // pixel data is lost.  |
110 | void Set(int width, int height, tPixel* pixelBuffer, bool copyPixels = true);  |
111 | void Set(const tPicture& src);  |
112 |   |
113 | // Can this class save the the filetype supplied?  |
114 | static bool CanSave(const tString& imageFile);  |
115 | static bool CanSave(tSystem::tFileType);  |
116 |   |
117 | // Can this class load the the filetype supplied?  |
118 | static bool CanLoad(const tString& imageFile);  |
119 | static bool CanLoad(tSystem::tFileType);  |
120 |   |
121 | enum class tColourFormat  |
122 | {  |
123 | Invalid, // Invalid must be 0.  |
124 | Auto, // Save function will decide format. Colour if all image pixels are opaque and ColourAndAlpha otherwise.  |
125 | Colour,  |
126 | ColourAndAlpha  |
127 | };  |
128 |   |
129 | // Saves to the image file you specify and examines the extension to determine filetype. Supports tga, png, bmp, jpg.  |
130 | // If tColourFormat is set to auto, the opacity/alpha channel will be excluded if all pixels are opaque.  |
131 | // Alpha channels are not supported for gif and jpg files. Quality (used for jpg) is in [1, 100].  |
132 | bool Save(const tString& imageFile, tColourFormat = tColourFormat::Auto, int quality = 95);  |
133 |   |
134 | bool SaveBMP(const tString& bmpFile) const;  |
135 | bool SaveJPG(const tString& jpgFile, int quality = 95) const;  |
136 | bool SavePNG(const tString& pngFile) const;  |
137 | bool SaveTGA  |
138 | (  |
139 | const tString& tgaFile, tImageTGA::tFormat = tImageTGA::tFormat::Auto,  |
140 | tImageTGA::tCompression = tImageTGA::tCompression::RLE  |
141 | ) const;  |
142 |   |
143 | // Always clears the current image before loading. If false returned, you will have an invalid tPicture.  |
144 | bool Load(const tString& imageFile, int = 0, LoadParams params = LoadParams());  |
145 |   |
146 | // Save and Load to tChunk format.  |
147 | void Save(tChunkWriter&) const;  |
148 | void Load(const tChunk&);  |
149 |   |
150 | // Returns true if all pixels are completely opaque (an alphas of 255). This function checks the entire pixel  |
151 | // buffer every time it is called.  |
152 | bool IsOpaque() const;  |
153 |   |
154 | // These functions allow reading and writing pixels.  |
155 | tPixel& Pixel(int x, int y) { return Pixels[ GetIndex(x, y) ]; }  |
156 | tPixel* operator[](int i) /* Syntax: image[y][x] = colour; No bounds checking performed. */ { return Pixels + GetIndex(0, i); }  |
157 | tPixel GetPixel(int x, int y) const { return Pixels[ GetIndex(x, y) ]; }  |
158 | tPixel* GetPixelPointer(int x = 0, int y = 0) { return &Pixels[ GetIndex(x, y) ]; }  |
159 |   |
160 | void SetPixel(int x, int y, const tColouri& c) { Pixels[ GetIndex(x, y) ] = c; }  |
161 | void SetPixel(int x, int y, uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) { Pixels[ GetIndex(x, y) ] = tColouri(r, g, b, a); }  |
162 | void SetAll(const tColouri& = tColouri(0, 0, 0));  |
163 |   |
164 | int GetWidth() const { return Width; }  |
165 | int GetHeight() const { return Height; }  |
166 | int GetNumPixels() const { return Width*Height; }  |
167 |   |
168 | void Rotate90(bool antiClockWise);  |
169 |   |
170 | // Rotates image about center point. Might add another call for choosing the center point. The resultant image size  |
171 | // is always big enough to hold every possible source pixel. Call one or more of the crop functions after if you  |
172 | // need to change the canvas size or remove transparent sides. If rotate filter is set to something other than  |
173 | // None, this function scales up the image 4 times using the supplied filter. It then rotates and scales back  |
174 | // down using a box filter which is fast/hi-quality for multiples of 2. If filter is set to Invalid, no upsampling  |
175 | // occurs and a straight nearest-neighbour rotation is performed (useful for pixel art and preserves colours).  |
176 | void RotateCenter  |
177 | (  |
178 | float angle,  |
179 | const tPixel& fill = tPixel::transparent,  |
180 | tResampleFilter = tResampleFilter::Invalid  |
181 | );  |
182 |   |
183 | void Flip(bool horizontal);  |
184 |   |
185 | // Cropping. Can also perform a canvas enlargement. If width or height are smaller than the current size the image  |
186 | // is cropped. If larger, the fill colour is used. Fill defaults to transparent-zero-alpha black pixels.  |
187 | enum class Anchor  |
188 | {  |
189 | LeftTop, MiddleTop, RightTop,  |
190 | LeftMiddle, MiddleMiddle, RightMiddle,  |
191 | LeftBottom, MiddleBottom, RightBottom  |
192 | };  |
193 | void Crop(int newWidth, int newHeight, Anchor = Anchor::MiddleMiddle, const tColouri& fill = tColouri::transparent);  |
194 | void Crop(int newWidth, int newHeight, int originX, int originY, const tColouri& fill = tColouri::transparent);  |
195 |   |
196 | // Crops sides that match the specified colour. Optionally select only some channels to be considered.  |
197 | void Crop(const tColouri& = tColouri::transparent, uint32 channels = tMath::ColourChannel_A);  |
198 |   |
199 | // This function scales the image by half using a box filter. Useful for generating mipmaps. This function returns  |
200 | // false if the rescale could not be performed. For this function to succeed:  |
201 | // - The image needs to be valid AND  |
202 | // - The width must be divisible by two if it is not equal to 1 AND  |
203 | // - The height must be divisible by two if it is not equal to 1.  |
204 | // Dimensions of 1 are handled since it's handy for mipmap generation. If width=10 and height=1, we'd end up with a  |
205 | // 5x1 image. An 11x1 image would yield an error and return false. A 1x1 successfully yields the same 1x1 image.  |
206 | bool ScaleHalf();  |
207 |   |
208 | // Resizes the image using the specified filter. Returns success. If the resample fails the tPicture is unmodified.  |
209 | bool Resample  |
210 | (  |
211 | int width, int height,  |
212 | tResampleFilter = tResampleFilter::Bilinear, tResampleEdgeMode = tResampleEdgeMode::Clamp  |
213 | );  |
214 | bool Resize  |
215 | (  |
216 | int width, int height,  |
217 | tResampleFilter filter = tResampleFilter::Bilinear, tResampleEdgeMode edgeMode = tResampleEdgeMode::Clamp  |
218 | ) { return Resample(width, height, filter, edgeMode); }  |
219 |   |
220 | bool operator==(const tPicture&) const;  |
221 | bool operator!=(const tPicture&) const;  |
222 |   |
223 | tString Filename;  |
224 | tPixelFormat SrcPixelFormat = tPixelFormat::Invalid;  |
225 | uint TextureID = 0;  |
226 | float Duration = 0.5f;  |
227 |   |
228 | private:  |
229 | int GetIndex(int x, int y) const { tAssert((x >= 0) && (y >= 0) && (x < Width) && (y < Height)); return y * Width + x; }  |
230 | static int GetIndex(int x, int y, int w, int h) { tAssert((x >= 0) && (y >= 0) && (x < w) && (y < h)); return y * w + x; }  |
231 |   |
232 | void RotateCenterNearest(const tMath::tMatrix2& rotMat, const tMath::tMatrix2& invRot, const tPixel& fill);  |
233 | void RotateCenterResampled(const tMath::tMatrix2& rotMat, const tMath::tMatrix2& invRot, const tPixel& fill, tResampleFilter);  |
234 |   |
235 | int Width = 0;  |
236 | int Height = 0;  |
237 | tPixel* Pixels = nullptr;  |
238 | };  |
239 |   |
240 |   |
241 | // Implementation below this line.  |
242 |   |
243 |   |
244 | inline void tPicture::Clear()  |
245 | {  |
246 | Filename.Clear();  |
247 | delete[] Pixels;  |
248 | Pixels = nullptr;  |
249 | Width = 0;  |
250 | Height = 0;  |
251 | SrcPixelFormat = tPixelFormat::Invalid;  |
252 | }  |
253 |   |
254 |   |
255 | inline bool tPicture::CanLoad(const tString& imageFile)  |
256 | {  |
257 | return CanLoad( tSystem::tGetFileType(imageFile) );  |
258 | }  |
259 |   |
260 |   |
261 | inline bool tPicture::CanSave(const tString& imageFile)  |
262 | {  |
263 | return CanSave( tSystem::tGetFileType(imageFile) );  |
264 | }  |
265 |   |
266 |   |
267 | inline bool tPicture::IsOpaque() const  |
268 | {  |
269 | for (int pixel = 0; pixel < (Width*Height); pixel++)  |
270 | {  |
271 | if (Pixels[pixel].A < 255)  |
272 | return false;  |
273 | }  |
274 |   |
275 | return true;  |
276 | }  |
277 |   |
278 |   |
279 | inline void tPicture::SetAll(const tColouri& clearColour)  |
280 | {  |
281 | if (!Pixels)  |
282 | return;  |
283 |   |
284 | int numPixels = Width*Height;  |
285 | for (int p = 0; p < numPixels; p++)  |
286 | Pixels[p] = clearColour;  |
287 | }  |
288 |   |
289 |   |
290 | inline void tPicture::Set(const tPicture& src)  |
291 | {  |
292 | Clear();  |
293 | if (src.Pixels)  |
294 | {  |
295 | Set(src.Width, src.Height, src.Pixels);  |
296 | Filename = src.Filename;  |
297 | }  |
298 | SrcPixelFormat = src.SrcPixelFormat;  |
299 | }  |
300 |   |
301 |   |
302 | inline bool tPicture::operator==(const tPicture& src) const  |
303 | {  |
304 | if (!Pixels || !src.Pixels)  |
305 | return false;  |
306 |   |
307 | if ((Width != src.Width) || (Height != src.Height))  |
308 | return false;  |
309 |   |
310 | for (int pixel = 0; pixel < (Width * Height); pixel++)  |
311 | if (Pixels[pixel] != src.Pixels[pixel])  |
312 | return false;  |
313 |   |
314 | return true;  |
315 | }  |
316 |   |
317 |   |
318 | inline bool tPicture::operator!=(const tPicture& src) const  |
319 | {  |
320 | return !(*this == src);  |
321 | }  |
322 |   |
323 |   |
324 | }  |
325 | |