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" 
42namespace tImage 
43
44 
45 
46// Verion information for the image loaders. This is all in the tImage namespace. 
47extern const char* Version_LibJpegTurbo
48extern const char* Version_OpenEXR
49extern const char* Version_ZLIB
50extern const char* Version_LibPNG
51extern const char* Version_LibTIFF
52extern const char* Version_ApngDis
53extern int Version_WEBP_Major
54extern 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). 
60class tPicture : public tLink<tPicture
61
62public
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 frameNum = 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 frameNum = 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 
228private
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 
244inline 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 
255inline bool tPicture::CanLoad(const tString& imageFile
256
257 return CanLoad( tSystem::tGetFileType(imageFile) ); 
258
259 
260 
261inline bool tPicture::CanSave(const tString& imageFile
262
263 return CanSave( tSystem::tGetFileType(imageFile) ); 
264
265 
266 
267inline 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 
279inline 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 
290inline 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 
302inline 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 
318inline bool tPicture::operator!=(const tPicture& src) const 
319
320 return !(*this == src); 
321
322 
323 
324
325