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-2024 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/tBaseImage.h" 
29#include "Image/tPixelFormat.h" 
30#include "Image/tResample.h" 
31#include "Image/tLayer.h" 
32namespace tImage 
33
34 
35 
36// Verion information for the image loaders and libraries they may use. This is all in the tImage namespace. 
37extern const char* Version_LibJpegTurbo
38extern const char* Version_ASTCEncoder
39extern const char* Version_OpenEXR
40extern const char* Version_ZLIB
41extern const char* Version_LibPNG
42extern const char* Version_LibTIFF
43extern const char* Version_LibKTX
44extern const char* Version_ApngDis
45extern const char* Version_ApngAsm
46extern int Version_WEBP_Major
47extern int Version_WEBP_Minor
48extern int Version_BCDec_Major
49extern int Version_BCDec_Minor
50extern int Version_ETCDec_Major
51extern int Version_ETCDec_Minor
52extern int Version_LibSPNG_Major
53extern int Version_LibSPNG_Minor
54extern int Version_LibSPNG_Patch
55extern int Version_TinyXML2_Major
56extern int Version_TinyXML2_Minor
57extern int Version_TinyXML2_Patch
58extern int Version_TinyEXIF_Major
59extern int Version_TinyEXIF_Minor
60extern int Version_TinyEXIF_Patch
61 
62 
63// A tPicture is a single 2D image. A rectangular collection of R8G8B8A8 pixels (32bits per pixel). The origin is the 
64// lower left, and the rows are ordered from bottom to top in memory. @todo At some point we need to support HDR images and 
65// should represent the pixels as R32G32B32A32f. 
66// 
67// The main purpose of a tPicture is to allow manipulation of a single image. Things like setting pixel colours, 
68// rotation, flips, resampling and resizing are found here. 
69// 
70// There is no saving and loading directly from image files because some types may have multiple frames. For example 
71// a gif or webp may be animated. We could just choose a particular frame, but that would mean loading all frames only 
72// to keep a single one. There is the same complexity with saving. Different image formats have drastically different 
73// parameters that need to be specified for saving -- jpgs need a quality setting, astc files have a multitude of 
74// compression parameters in addition to the block size, targas can be RLE encoded, ktx files can be supercompressed... 
75// or not, etc. The purpose of the tImageNNN files is to deal with that complexity for each specific image type. From 
76// these loaders you can construct one or more tPictures by passing in the pixels, width, and height. 
77// 
78// There is some save/load functionality directly for a tPicture. It has it's own file format based of tChunks. It can 
79// save/load itself to/from a .tac file. 
80class tPicture : public tLink<tPicture
81
82public
83 // Constructs an empty picture that is invalid. You must call Load yourself later. 
84 tPicture() { } 
85 
86 // Constructs a picture that is width by height pixels. It will be all black pixels with an opaque alpha of 255. 
87 // That is, every pixel will be (0, 0, 0, 255). 
88 tPicture(int width, int height) { Set(width, height); } 
89 
90 // This constructor allows you to specify an external buffer of pixels to use. If copyPixels is true, it simply 
91 // copies the values from the buffer you supply. If copyPixels is false, it means you are giving the buffer to the 
92 // tPicture. In this case the tPicture will delete[] the buffer for you when appropriate. 
93 tPicture(int width, int height, tPixel4b* pixelBuffer, bool copyPixels = true) { Set(width, height, pixelBuffer, copyPixels); } 
94 
95 // Construct from a tFrame. If steal is true the tPicture will take ownership of the tFrame. If steal is false it 
96 // will copy the pixels out. The frame duration is also taken from the frame. 
97 tPicture(tFrame* frame, bool steal) { Set(frame, steal); } 
98 
99 // Constructs from any type derived from tImageBase (eg. tImageASTC). If steal is true the tImageBase MAY be 
100 // modified. In particular it may be invalid afterwards because the pixels may have been stolen from it. For 
101 // multiframe images it meay still be valid after but down a frame. On the other hand with steal false you are 
102 // guaranteed that image remains unmodified, but at the cost of duplicating memory for the pixels. 
103 tPicture(tBaseImage& image, bool steal = true) { Set(image, steal); } 
104 
105 // Copy constructor. 
106 tPicture(const tPicture& src) : tPicture() { Set(src); } 
107 
108 virtual ~tPicture() { Clear(); } 
109 bool IsValid() const { return Pixels ? true : false; } 
110 
111 // Invalidated the picture and frees memory associated with it. The tPicture will be invalid after this. 
112 void Clear(); 
113 
114 // Sets the image to the dimensions provided. Image will be opaque black after this call. Internally, if the 
115 // existing buffer is the right size, it is reused. In all cases, the entire image is cleared to black. 
116 void Set(int width, int height, const tPixel4b& colour = tPixel4b::black); 
117 
118 // Sets the image to the dimensions provided. Allows you to specify an external buffer of pixels to use. If 
119 // copyPixels is true, it simply copies the values from the buffer you supply. In this case it will attempt to 
120 // reuse it's existing buffer if it can. If copyPixels is false, it means you are giving the buffer to the 
121 // tPicture. In this case the tPicture will delete[] the buffer for you when appropriate. In all cases, existing 
122 // pixel data is lost. Other members of the tPicture are unmodified. 
123 void Set(int width, int height, tPixel4b* pixelBuffer, bool copyPixels = true); 
124 
125 // Sets from a tFrame. If steal is true the tPicture will take ownership of the tFrame. If steal is false it will 
126 // copy the pixels out. The frame duration is also taken from the frame. 
127 void Set(tFrame* frame, bool steal); 
128 
129 // Sets from any type derived from tImageBase (eg. tImageASTC). If steal is true the tImageBase MAY be 
130 // modified. In particular it may be invalid afterwards because the pixels may have been stolen from it. For 
131 // multiframe images it meay still be valid after but down a frame. On the other hand with steal false you are 
132 // guaranteed that image remains unmodified, but at the cost of duplicating memory for the pixels. 
133 void Set(tBaseImage& image, bool steal = true); 
134 
135 void Set(const tPicture& src); 
136 
137 // Save and Load to tChunk format. 
138 void Save(tChunkWriter&) const
139 void Load(const tChunk&); 
140 
141 // Returns true if all pixels are completely opaque (an alphas of 255). This function checks the entire pixel 
142 // buffer every time it is called. 
143 bool IsOpaque() const
144 
145 // These functions allow reading and writing pixels. 
146 tPixel4b& Pixel(int x, int y) { return Pixels[ GetIndex(x, y) ]; } 
147 tPixel4b* operator[](int i) /* Syntax: image[y][x] = colour; No bounds checking performed. */ { return Pixels + GetIndex(x: 0, y: i); } 
148 tPixel4b GetPixel(int x, int y) const { return Pixels[ GetIndex(x, y) ]; } 
149 tPixel4b* GetPixelPointer(int x = 0, int y = 0) const { return &Pixels[ GetIndex(x, y) ]; } 
150 tPixel4b* GetPixels() const { return Pixels; } 
151 tPixel4b* StealPixels() { tPixel4b* p = Pixels; Pixels = nullptr; return p; } 
152 
153 void SetPixel(int x, int y, const tColour4b& c) { Pixels[ GetIndex(x, y) ] = c; } 
154 void SetPixel(int x, int y, uint8 r, uint8 g, uint8 b, uint8 a = 0xFF) { Pixels[ GetIndex(x, y) ] = tColour4b(r, g, b, a); } 
155 void SetPixel(int x, int y, const tColour4b& c, comp_t channels); 
156 void SetAll(const tColour4b& = tColour4b(0, 0, 0), comp_t channels = tCompBit_RGBA); 
157 
158 // Spreads the specified single channel to all RGB channels. If channel is R, G, or B, it spreads to the remainder 
159 // of RGB (e.g. R will spread to GB). If channel is alpha, spreads to RGB. 
160 void Spread(tComp channel = tComp::R); 
161 
162 // Swizzle colour channels. You specify the RGBA destination channels in that order. For example, to swap R and B 
163 // channels you would call Swizzle(B,G,R,A). You can also use tComp::Zero and tComp::Full to set the channel to zero 
164 // or full values. The default swizzle is RGBA which does nothing. If tComp::Auto is set for any channel, it just 
165 // uses the default for that channel. 
166 void Swizzle(tComp = tComp::R, tComp = tComp::G, tComp = tComp::B, tComp = tComp::A); 
167 
168 // Computes RGB intensity and sets specified channels to that value. Any combination of RGBA allowed. 
169 void Intensity(comp_t channels = tCompBit_RGB); 
170 
171 // Blends blendColour (background) into the RGB channels specified (usually RGB, but any combination of the 3 is 
172 // allowed) using the pixel alpha to modulate. The new pixel colour is alpha*component + (1-alpha)*blend_component. 
173 // 
174 // Eg. If pixel alpha is 255, then none of the blend colour is used for that pixel. If alpha is 0, all of it is 
175 // used. If alpha is 64, then 1/4 of the current pixel colour and 3/4 of the supplied, 
176 // 
177 // FinalAlpha should be in [-1, 255]. If finalAlpha is >= 0 then the alpha will be set to finalAlpha after the blend 
178 // is complete. If finalAlpha is -1, the alpha is left unmodified. By default the finalAlpha is 255 (opaque) which 
179 // means the operation essentially creates a premultiplied-alpha opaque image. 
180 // Note that the alpha of the supplied colour is ignored (since we use finalAlpha). 
181 // Note that unspecified RGB channels are keft unmodified. 
182 void AlphaBlendColour(const tColour4b& blendColour, comp_t = tCompBit_RGB, int finalAlpha = 255); 
183 
184 int GetWidth() const { return Width; } 
185 int GetHeight() const { return Height; } 
186 int GetArea() const { return Width*Height; } 
187 int GetNumPixels() const { return GetArea(); } 
188 
189 void Rotate90(bool antiClockWise); 
190 
191 // Rotates image about center point. The resultant image size is always big enough to hold every source pixel. Call 
192 // one or more of the crop functions after if you need to change the canvas size or remove transparent sides. The 
193 // rotate algorithm first upscales the image x4, rotates, then downscales. That is what upFilter and downFilter are 
194 // for. If you want to rotate pixel-art (nearest neighbour, no up/dowuse upFilter = none. 
195 // 
196 // UpFilter DownFilter Description 
197 // None NA No up/down scaling. Preserves colours. Nearest Neighbour. Fast. Good for pixel art. 
198 // Valid Valid Up/down scaling. Smooth. Good results with up=bilinear, down=box. 
199 // Valid None Up/down scaling. Use alternate (sharper) downscaling scheme (pad + 2 X ScaleHalf). 
200 void RotateCenter 
201
202 float angle
203 const tPixel4b& fill = tPixel4b::transparent
204 tResampleFilter upFilter = tResampleFilter::None
205 tResampleFilter downFilter = tResampleFilter::None 
206 ); 
207 
208 void Flip(bool horizontal); 
209 
210 enum class Anchor 
211
212 LeftTop, MiddleTop, RightTop
213 LeftMiddle, MiddleMiddle, RightMiddle
214 LeftBottom, MiddleBottom, RightBottom 
215 }; 
216 
217 // Cropping. Can also perform a canvas enlargement. If width or height are smaller than the current size the image 
218 // is cropped. If larger, the fill colour is used. Fill defaults to transparent-zero-alpha black pixels. 
219 bool Crop(int newWidth, int newHeight, Anchor = Anchor::MiddleMiddle, const tColour4b& fill = tColour4b::transparent); 
220 bool Crop(int newWidth, int newHeight, int originX, int originY, const tColour4b& fill = tColour4b::transparent); 
221 
222 // Copies a supplied rectangle into the image at the specified position. 
223 bool CopyRegion(int regionW, int regionH, const tColour4b* regionPixels, int originX, int originY, comp_t channels = tCompBit_RGBA); 
224 bool CopyRegion(int regionW, int regionH, const tColour4b* regionPixels, Anchor = Anchor::LeftBottom, comp_t channels = tCompBit_RGBA); 
225 
226 // Crops sides that match the specified colour. Optionally select only some channels to be considered. 
227 // If this function wants to remove everything it returns false and leaves the image untouched. 
228 // If this function wants to remove nothing it returns false and leaves the image untouched. 
229 bool Deborder(const tColour4b& = tColour4b::transparent, comp_t channels = tCompBit_A); 
230 
231 // Same as above but only check if borders exist. Does not modify picture. 
232 bool HasBorders(const tColour4b& = tColour4b::transparent, comp_t channels = tCompBit_A) const
233 
234 // Quantize image colours based on a fixed palette. numColours must be 256 or less. checkExact means no change to 
235 // the image will be made if it already contains fewer colours than numColours already. This may or may not be 
236 // desireable as the computed or fixed palette would not be used. 
237 bool QuantizeFixed(int numColours, bool checkExact = true); 
238 
239 // Similar to above but uses spatial quantization to generate the palette. If ditherLevel is 0.0 it will compute a 
240 // good dither amount for you based on the image dimensions and number of colours. Filter size must be 1, 3, or 5. 
241 bool QuantizeSpatial(int numColours, bool checkExact = true, double ditherLevel = 0.0, int filterSize = 3); 
242 
243 // Similar to above but uses neuquant algorighm to generate the palette. With a sampling factor of 1 the entire 
244 // image is used in the learning phase. With a factor of 10, a pseudo-random subset of 1/10 of the pixels are used 
245 // in the learning phase. sampleFactor must be in [1, 30]. Bigger values are faster but lower quality. 
246 bool QuantizeNeu(int numColours, bool checkExact = true, int sampleFactor = 1); 
247 
248 // Similar to above but uses Wu algorighm to generate the palette. 
249 bool QuantizeWu(int numColours, bool checkExact = true); 
250 
251 // Ideally adjustments (brightness, contrast etc) would be done in a fragment shader and then 'committed' to the 
252 // tPicture with a simple adjust call. However currently the clients of tPicture don't have that ability so we're 
253 // going with a begin/adjust/end setup where a new 'original' pixel buffer is allocated on begin, and an adjustment 
254 // writes to the current buffer. End deletes the temporary original buffer. Adjustments are always based on the 
255 // original source pixels. It stops the issue, for example, of setting the brightness to full and losing all the 
256 // colour data when you move back down. This function also precomputes the internal min/max colour values 
257 // and histograms. Essentially this starts an adjustment session. Returns false if the image is invalid. 
258 bool AdjustmentBegin(); 
259 
260 // Adjust brightness based on the tPicture pixels and write them into the adjustment pixel buffer. Brightness is in 
261 // [0.0,1.0]. When brightness at 0.0 adjustment buffer will be completely black. When brightness at 1.0, pure white, 
262 // Note that the range of the brigtness is computed so that all values between [0,1] have an effect on the image. 
263 // This is possible because the min and max colour values were computed by inspecting every pixel when begin was 
264 // called. In other words the values the colours move up or down for a particular brightness are image dependent, 
265 // Returns success. 
266 // 
267 // The AdjustGetDefaultNNN functions get the parameters needed to have zero affect on the image. For brightness in 
268 // particular it is dependent on the image contents and may not be exactly 0.5. If the min/max colour values did not 
269 // reach 0 and full, the default brightness may be offset from 0.5. 
270 bool AdjustBrightness(float brightness, comp_t = tCompBit_RGB); 
271 bool AdjustGetDefaultBrightness(float& brightness); 
272 
273 // Adjust contrast based on the tPicture pixels and write them into the adjustment pixel buffer. Contrast is in 
274 // [0.0, 1.0]. When contrast is at 0.0, adjustment buffer will be lowest contrast. When contrast at 1.0, highest, 
275 // Returns success. 
276 bool AdjustContrast(float contrast, comp_t = tCompBit_RGB); 
277 bool AdjustGetDefaultContrast(float& contrast); 
278 
279 // Adjust levels. All values are E [0, 1]. Ensure blackPoint <= midPoint <= whitePoint and blackOut <= whiteOut. 
280 // If these conditions are not met they are silently enforced starting at black (unmodified). The powerMidGamma 
281 // option lets you decide between 2 algorithms to determine the curve on the gamma. If false it uses some code that 
282 // tries to mimic Photoshop. See https://stackoverflow.com/questions/39510072/algorithm-for-adjustment-of-image-levels 
283 // The curve for the above is C1 discontinuous at gamma 1, so I didn't like it. powerMidGamma, the default, uses 
284 // a continuous base-10 power curve that smoothly goes from gamma 0.1 to gamma 10. 
285 // For the power curve the gamma range is [0.1, 10.0] where 1.0 is linear. This approximates GIMP. 
286 // For the photo curve the gamma range is [0.01, 9.99] where 1.0 is linear. This approximates PS. 
287 // The defaults to result in no change are the same for both algorithms. 
288 bool AdjustLevels(float blackPoint, float midPoint, float whitePoint, float blackOut, float whiteOut, bool powerMidGamma = true, comp_t = tCompBit_RGB); 
289 bool AdjustGetDefaultLevels(float& blackPoint, float& midPoint, float& whitePoint, float& blackOut, float& whiteOut); 
290 
291 // Keeps the adjustment session open and restores the pixels to their original values. 
292 bool AdjustRestoreOriginal(); 
293 
294 // Ends adjustment session and deletes the temporary original pixel buffer. Returns success. 
295 bool AdjustmentEnd(); 
296 
297 // This function scales the image by half using a box filter. Useful for generating mipmaps. This function returns 
298 // false if the rescale could not be performed. For this function to succeed: 
299 // - The image needs to be valid AND 
300 // - The width must be divisible by two if it is not equal to 1 AND 
301 // - The height must be divisible by two if it is not equal to 1. 
302 // Dimensions of 1 are handled since it's handy for mipmap generation. If width=10 and height=1, we'd end up with a 
303 // 5x1 image. An 11x1 image would yield an error and return false. A 1x1 successfully yields the same 1x1 image. 
304 bool ScaleHalf(); 
305 
306 // Resizes the image using the specified filter. Returns success. If the resample fails the tPicture is unmodified. 
307 bool Resample 
308
309 int width, int height
310 tResampleFilter = tResampleFilter::Bilinear, tResampleEdgeMode = tResampleEdgeMode::Clamp 
311 ); 
312 bool Resize 
313
314 int width, int height
315 tResampleFilter filter = tResampleFilter::Bilinear, tResampleEdgeMode edgeMode = tResampleEdgeMode::Clamp 
316 ) { return Resample(width, height, filter, edgeMode); } 
317 
318 // A convenience. This is sort of light tTexture functionality -- generate layers that may be passed off to HW. 
319 // Unlike tTexture that compresses to a BC format, this function always uses R8G8B8A8 pixel format and does not 
320 // require power-of-2 dimensions. If generating mipmap layers, each layer is half (truncated) in width and height 
321 // until a 1x1 is reached. There is no restriction on starting dimensions (they may be odd for example). Populates 
322 // (appends) to the supplied tLayer list. If resampleFilter is None no mipmap layers are generated, only a single 
323 // layer will be appened. In this case edgeMode is ignored. If chainGeneration is true, the previous mip texture 
324 // is gused to generate the next -- this is faster but may not be as good quality. Returns number of appended 
325 // layers. 
326 int GenerateLayers 
327
328 tList<tLayer>&, tResampleFilter filter = tResampleFilter::Lanczos_Narrow
329 tResampleEdgeMode edgeMode = tResampleEdgeMode::Clamp
330 bool chainGeneration = true 
331 ); 
332 bool operator==(const tPicture&) const
333 bool operator!=(const tPicture&) const
334 
335 tString Filename
336 tPixelFormat PixelFormatSrc = tPixelFormat::Invalid
337 uint TextureID = 0
338 float Duration = 0.5f
339 
340 // Transient parameters. Only access between AdjustmentBegin/End. 
341 int BrightnessRGBMin = 0; // Used for brightness adjustments. 
342 int BrightnessRGBMax = 0; // Used for brightness adjustments. 
343 static const int NumGroups = 256; // Sure makes it easy choosing 256 groups. 
344 
345 // We use float counts since pixels with alpha are computed by multiplying by the alphs. This means we get 
346 // fractional counts -- but it makes the histogram more representive of the actual colours/intensity present. 
347 float HistogramR[NumGroups]; float MaxRCount; // Frequency of Red. Max R count in all groups. 
348 float HistogramG[NumGroups]; float MaxGCount; // Frequency of Green. Max G count in all groups. 
349 float HistogramB[NumGroups]; float MaxBCount; // Frequency of Blue. Max B count in all groups. 
350 float HistogramA[NumGroups]; float MaxACount; // Frequency of Alpha. Max A count in all groups. 
351 float HistogramI[NumGroups]; float MaxICount; // Frequency of Inten. Max I count in all groups (intensity). 
352 
353private
354 int GetIndex(int x, int y) const { tAssert((x >= 0) && (y >= 0) && (x < Width) && (y < Height)); return y * Width + x; } 
355 static int GetIndex(int x, int y, int w, int h) { tAssert((x >= 0) && (y >= 0) && (x < w) && (y < h)); return y * w + x; } 
356 
357 void RotateCenterNearest(const tMath::tMatrix2& rotMat, const tMath::tMatrix2& invRot, const tPixel4b& fill); 
358 void RotateCenterResampled 
359
360 const tMath::tMatrix2& rotMat, const tMath::tMatrix2& invRot, const tPixel4b& fill
361 tResampleFilter upFilter, tResampleFilter downFilter 
362 ); 
363 
364 // Returns false if either no borders or the borders overlap because the image in homogenous in the channels. 
365 bool GetBordersSizes 
366
367 const tColour4b&, comp_t channels
368 int& numBottomRows, int& numTopRows, int& numLeftCols, int& numRightCols 
369 ) const
370 
371 int Width = 0
372 int Height = 0
373 tPixel4b* Pixels = nullptr
374 tPixel4b* OriginalPixels = nullptr
375}; 
376 
377 
378// Implementation below this line. 
379 
380 
381inline void tPicture::Clear() 
382
383 Filename.Clear(); 
384 delete[] Pixels
385 Pixels = nullptr
386 delete[] OriginalPixels
387 OriginalPixels = nullptr
388 Width = 0
389 Height = 0
390 PixelFormatSrc = tPixelFormat::Invalid
391
392 
393 
394inline void tPicture::Set(int width, int height, const tPixel4b& colour
395
396 tAssert((width > 0) && (height > 0)); 
397 
398 // Reuse the existing buffer if possible. 
399 if (width*height != Width*Height
400
401 delete[] Pixels
402 Pixels = new tPixel4b[width*height]; 
403
404 Width = width
405 Height = height
406 for (int pixel = 0; pixel < (Width*Height); pixel++) 
407 Pixels[pixel] = colour
408 
409 PixelFormatSrc = tPixelFormat::R8G8B8A8
410
411 
412 
413inline void tPicture::Set(int width, int height, tPixel4b* pixelBuffer, bool copyPixels
414
415 tAssert((width > 0) && (height > 0) && pixelBuffer); 
416 
417 // If we're copying the pixels we may be able to reuse the existing buffer if it's the right size. If we're not 
418 // copying and the buffer is being handed to us, we just need to free our current buffer. 
419 if (copyPixels
420
421 if ((width*height != Width*Height) || !Pixels
422
423 delete[] Pixels
424 Pixels = new tPixel4b[width*height]; 
425
426
427 else 
428
429 delete[] Pixels
430 Pixels = pixelBuffer
431
432 Width = width
433 Height = height
434 
435 if (copyPixels
436 tStd::tMemcpy(dest: Pixels, src: pixelBuffer, numBytes: Width*Height*sizeof(tPixel4b)); 
437 
438 PixelFormatSrc = tPixelFormat::R8G8B8A8
439
440 
441 
442inline void tPicture::Set(tFrame* frame, bool steal
443
444 if (!frame || !frame->IsValid()) 
445 return
446 
447 Set(width: frame->Width, height: frame->Height, pixelBuffer: frame->GetPixels(steal), copyPixels: !steal); 
448 Duration = frame->Duration
449 if (steal
450 delete frame
451
452 
453 
454inline void tPicture::Set(tBaseImage& image, bool steal
455
456 if (!image.IsValid()) 
457 return
458 
459 tFrame* frame = image.GetFrame(steal); 
460 
461 // The true here is correct. Whether steal was true or not, we now have a frame that is under our 
462 // management and must be eventually deleted. 
463 Set(frame, steal: true); 
464
465 
466 
467inline void tPicture::Set(const tPicture& src
468
469 Clear(); 
470 if (!src.IsValid()) 
471 return
472 
473 Set(width: src.Width, height: src.Height, pixelBuffer: src.Pixels); 
474 Filename = src.Filename
475 PixelFormatSrc = src.PixelFormatSrc
476 Duration = src.Duration
477
478 
479 
480inline bool tPicture::IsOpaque() const 
481
482 for (int pixel = 0; pixel < (Width*Height); pixel++) 
483
484 if (Pixels[pixel].A < 255
485 return false
486
487 
488 return true
489
490 
491 
492inline void tPicture::SetPixel(int x, int y, const tColour4b& c, comp_t channels
493
494 tPixel4b& pixel = Pixels[ GetIndex(x, y) ]; 
495 
496 if (channels & tCompBit_R) pixel.R = c.R
497 if (channels & tCompBit_G) pixel.G = c.G
498 if (channels & tCompBit_B) pixel.B = c.B
499 if (channels & tCompBit_A) pixel.A = c.A
500
501 
502 
503inline void tPicture::SetAll(const tColour4b& clearColour, comp_t channels
504
505 if (!Pixels
506 return
507 
508 int numPixels = Width*Height
509 if (channels == tCompBit_RGBA
510
511 for (int p = 0; p < numPixels; p++) 
512 Pixels[p] = clearColour
513
514 else 
515
516 for (int p = 0; p < numPixels; p++) 
517
518 if (channels & tCompBit_R) Pixels[p].R = clearColour.R
519 if (channels & tCompBit_G) Pixels[p].G = clearColour.G
520 if (channels & tCompBit_B) Pixels[p].B = clearColour.B
521 if (channels & tCompBit_A) Pixels[p].A = clearColour.A
522
523
524
525 
526 
527inline void tPicture::Spread(tComp channel
528
529 int numPixels = Width*Height
530 for (int p = 0; p < numPixels; p++) 
531
532 tPixel4b& pixel = Pixels[p]; 
533 switch (channel
534
535 case tComp::R: pixel.G = pixel.B = pixel.R; break
536 case tComp::G: pixel.R = pixel.B = pixel.G; break
537 case tComp::B: pixel.R = pixel.G = pixel.B; break
538 case tComp::A: pixel.R = pixel.G = pixel.B = pixel.A; break
539
540
541
542 
543 
544inline void tPicture::Swizzle(tComp R, tComp G, tComp B, tComp A
545
546 if (!Pixels
547 return
548 
549 if (R == tComp::Auto) R = tComp::R
550 if (G == tComp::Auto) G = tComp::G
551 if (B == tComp::Auto) B = tComp::B
552 if (A == tComp::Auto) A = tComp::A
553 
554 if ((R == tComp::R) && (G == tComp::G) && (B == tComp::B) && (A == tComp::A)) 
555 return
556 
557 tPixel4b* newPixels = new tPixel4b[Width*Height]; 
558 for (int p = 0; p < Width*Height; p++) 
559
560 tColour4b& src = Pixels[p]; 
561 tColour4b& dst = newPixels[p]; 
562 uint8 r
563 (R==tComp::Zero ? 0
564 (R==tComp::Full ? 255
565 (R==tComp::R ? src.R
566 (R==tComp::G ? src.G
567 (R==tComp::B ? src.B
568 (R==tComp::A ? src.A : 0 
569 )))))); 
570 uint8 g
571 (G==tComp::Zero ? 0
572 (G==tComp::Full ? 255
573 (G==tComp::R ? src.R
574 (G==tComp::G ? src.G
575 (G==tComp::B ? src.B
576 (G==tComp::A ? src.A : 0 
577 )))))); 
578 uint8 b
579 (B==tComp::Zero ? 0
580 (B==tComp::Full ? 255
581 (B==tComp::R ? src.R
582 (B==tComp::G ? src.G
583 (B==tComp::B ? src.B
584 (B==tComp::A ? src.A : 0 
585 )))))); 
586 uint8 a
587 (A==tComp::Zero ? 0
588 (A==tComp::Full ? 255
589 (A==tComp::R ? src.R
590 (A==tComp::G ? src.G
591 (A==tComp::B ? src.B
592 (A==tComp::A ? src.A : 255 
593 )))))); 
594 dst.Set(r, g, b, a); 
595
596 delete[] Pixels
597 Pixels = newPixels
598
599 
600 
601inline void tPicture::Intensity(comp_t channels
602
603 if (!channels
604 return
605 
606 int numPixels = Width*Height
607 for (int p = 0; p < numPixels; p++) 
608
609 tPixel4b& pixel = Pixels[p]; 
610 int intensity = pixel.Intensity(); 
611 if (channels & tCompBit_R) pixel.R = intensity
612 if (channels & tCompBit_G) pixel.G = intensity
613 if (channels & tCompBit_B) pixel.B = intensity
614 if (channels & tCompBit_A) pixel.A = intensity
615
616
617 
618 
619inline void tPicture::AlphaBlendColour(const tColour4b& blend, comp_t channels, int finalAlpha
620
621 if (!Pixels
622 return
623 
624 tMath::tiClamp(val&: finalAlpha, min: -1, max: 255); 
625 int numPixels = Width*Height
626 for (int p = 0; p < numPixels; p++) 
627
628 tColour4f pixelCol(Pixels[p]); 
629 tColour4f blendCol(blend); 
630 tColour4f pixel = pixelCol
631 float alpha = pixelCol.A
632 float oneMinusAlpha = 1.0f - alpha
633 
634 if (channels & tCompBit_R) pixel.R = pixelCol.R*alpha + blendCol.R*oneMinusAlpha
635 if (channels & tCompBit_G) pixel.G = pixelCol.G*alpha + blendCol.G*oneMinusAlpha
636 if (channels & tCompBit_B) pixel.B = pixelCol.B*alpha + blendCol.B*oneMinusAlpha
637 if (finalAlpha >= 0
638 pixel.SetA(finalAlpha); 
639 
640 Pixels[p].Set(pixel); 
641
642
643 
644 
645inline bool tPicture::operator==(const tPicture& src) const 
646
647 if (!Pixels || !src.Pixels
648 return false
649 
650 if ((Width != src.Width) || (Height != src.Height)) 
651 return false
652 
653 for (int pixel = 0; pixel < (Width * Height); pixel++) 
654 if (Pixels[pixel] != src.Pixels[pixel]) 
655 return false
656 
657 return true
658
659 
660 
661inline bool tPicture::operator!=(const tPicture& src) const 
662
663 return !(*this == src); 
664
665 
666 
667
668