1// tPicture.cpp 
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// layer, 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, 2019, 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#include "Foundation/tStandard.h" 
23#include "Image/tPicture.h" 
24#include "Math/tMatrix2.h" 
25#include <OpenEXR/loadImage.h> 
26#include <zlib.h> 
27#include <png.h> 
28#include <apngdis.h> 
29#include "LibTIFF/include/tiffvers.h" 
30#ifdef PLATFORM_WINDOWS 
31#include "TurboJpeg/Windows/jconfig.h" 
32#include "WebP/Windows/include/demux.h" 
33#elif defined(PLATFORM_LINUX) 
34#include "TurboJpeg/Linux/jconfig.h" 
35#include "WebP/Linux/include/demux.h" 
36#endif 
37#include "Image/tResample.h" 
38 
39 
40using namespace tMath
41using namespace tImage
42using namespace tSystem
43 
44 
45const char* tImage::Version_LibJpegTurbo = LIBJPEG_TURBO_VERSION
46const char* tImage::Version_OpenEXR = OPENEXR_VERSION_STRING
47const char* tImage::Version_ZLIB = ZLIB_VERSION
48const char* tImage::Version_LibPNG = PNG_LIBPNG_VER_STRING
49const char* tImage::Version_ApngDis = APNGDIS_VERSION_STRING
50const char* tImage::Version_LibTIFF = TIFFLIB_STANDARD_VERSION_STR
51int tImage::Version_WEBP_Major = WEBP_DECODER_ABI_VERSION >> 8
52int tImage::Version_WEBP_Minor = WEBP_DECODER_ABI_VERSION & 0xFF
53 
54 
55void tPicture::Set(int width, int height, const tPixel& colour
56
57 tAssert((width > 0) && (height > 0)); 
58 
59 // Reuse the existing buffer if possible. 
60 if (width*height != Width*Height
61
62 delete[] Pixels
63 Pixels = new tPixel[width*height]; 
64
65 Width = width
66 Height = height
67 for (int pixel = 0; pixel < (Width*Height); pixel++) 
68 Pixels[pixel] = colour
69 
70 SrcPixelFormat = tPixelFormat::R8G8B8A8
71
72 
73 
74void tPicture::Set(int width, int height, tPixel* pixelBuffer, bool copyPixels
75
76 tAssert((width > 0) && (height > 0) && pixelBuffer); 
77 
78 // 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 
79 // copying and the buffer is being handed to us, we just need to free our current buffer. 
80 if (copyPixels
81
82 if (width*height != Width*Height
83
84 delete[] Pixels
85 Pixels = new tPixel[width*height]; 
86
87
88 else 
89
90 delete[] Pixels
91 Pixels = pixelBuffer
92
93 Width = width
94 Height = height
95 
96 if (copyPixels
97 tStd::tMemcpy(Pixels, pixelBuffer, Width*Height*sizeof(tPixel)); 
98 
99 SrcPixelFormat = tPixelFormat::R8G8B8A8
100
101 
102 
103bool tPicture::CanSave(tFileType fileType
104
105 switch (fileType
106
107 case tFileType::TGA
108 case tFileType::BMP
109 case tFileType::JPG
110 case tFileType::PNG
111 return true
112
113 
114 return false
115
116 
117 
118bool tPicture::CanLoad(tFileType fileType
119
120 switch (fileType
121
122 case tFileType::APNG
123 case tFileType::BMP
124 case tFileType::EXR
125 case tFileType::GIF
126 case tFileType::HDR
127 case tFileType::ICO
128 case tFileType::JPG
129 case tFileType::PNG
130 case tFileType::TGA
131 case tFileType::TIFF
132 case tFileType::WEBP
133 case tFileType::XPM
134 return true
135
136 
137 return false
138
139 
140 
141bool tPicture::Save(const tString& imageFile, tPicture::tColourFormat colourFmt, int quality
142
143 if (!IsValid()) 
144 return false
145 
146 tFileType fileType = tGetFileType(imageFile); 
147 if (!CanSave(fileType)) 
148 return false
149 
150 // Native formats. 
151 switch (fileType
152
153 case tFileType::BMP
154 return SaveBMP(imageFile); 
155 
156 case tFileType::JPG
157 return SaveJPG(imageFile, quality); 
158 
159 case tFileType::PNG
160 return SavePNG(imageFile); 
161 
162 case tFileType::TGA
163 return SaveTGA(imageFile, tImage::tImageTGA::tFormat(colourFmt), tImage::tImageTGA::tCompression::None); 
164
165 
166 return false
167
168 
169 
170bool tPicture::SaveBMP(const tString& bmpFile) const 
171
172 tFileType fileType = tGetFileType(bmpFile); 
173 if (!IsValid() || (fileType != tFileType::BMP)) 
174 return false
175 
176 tImageBMP bmp(Pixels, Width, Height); 
177 tImageBMP::tFormat savedFormat = bmp.Save(bmpFile); 
178 return int(savedFormat) ? true : false
179
180 
181 
182bool tPicture::SaveJPG(const tString& jpgFile, int quality) const 
183
184 tFileType fileType = tGetFileType(jpgFile); 
185 if (!IsValid() || (fileType != tFileType::JPG)) 
186 return false
187 
188 tImageJPG jpeg(Pixels, Width, Height); 
189 bool success = jpeg.Save(jpgFile, quality); 
190 return success
191
192 
193 
194bool tPicture::SavePNG(const tString& pngFile) const 
195
196 tFileType fileType = tGetFileType(pngFile); 
197 if (!IsValid() || (fileType != tFileType::PNG)) 
198 return false
199 
200 tImagePNG png(Pixels, Width, Height); 
201 bool success = png.Save(pngFile); 
202 return success
203
204 
205 
206bool tPicture::SaveTGA(const tString& tgaFile, tImageTGA::tFormat format, tImageTGA::tCompression compression) const 
207
208 tFileType fileType = tGetFileType(tgaFile); 
209 if (!IsValid() || (fileType != tFileType::TGA)) 
210 return false
211 
212 tImageTGA targa(Pixels, Width, Height); 
213 tImageTGA::tFormat savedFormat = targa.Save(tgaFile, format, compression); 
214 if (savedFormat == tImageTGA::tFormat::Invalid
215 return false
216 
217 return true
218
219 
220 
221bool tPicture::Load(const tString& imageFile, int frameNum, LoadParams params
222
223 tMath::tiClampMin(frameNum, 0); 
224 
225 Clear(); 
226 if (!tFileExists(imageFile)) 
227 return false
228 
229 tFileType fileType = tGetFileType(imageFile); 
230 if (!CanLoad(fileType)) 
231 return false
232 
233 Filename = imageFile
234 
235 // Native handlers. 
236 // @todo It's looking like we should make an abstract tImageBase class at this point to get rid of all this duplication. 
237 switch (fileType
238
239 case tFileType::APNG
240
241 tImageAPNG apng
242 bool ok = apng.Load(imageFile); 
243 if (!ok || !apng.IsValid()) 
244 return false
245 
246 if (frameNum >= apng.GetNumFrames()) 
247 return false
248 
249 tImageAPNG::Frame* stolenFrame = apng.StealFrame(frameNum); 
250 Width = stolenFrame->Width
251 Height = stolenFrame->Height
252 SrcPixelFormat = stolenFrame->SrcPixelFormat
253  
254 Pixels = stolenFrame->Pixels
255 
256 // This is safe as the frame does not own/delete the pixels. 
257 delete stolenFrame
258 return true
259
260 
261 case tFileType::BMP
262
263 // BMPs can only have one frame. 
264 if (frameNum != 0
265 return false
266 tImageBMP bmp
267 bmp.Load(imageFile); 
268 if (!bmp.IsValid()) 
269 return false
270 Width = bmp.GetWidth(); 
271 Height = bmp.GetHeight(); 
272 Pixels = bmp.StealPixels(); 
273 SrcPixelFormat = bmp.SrcPixelFormat
274 return true
275
276 
277 case tFileType::EXR
278
279 tImageEXR exr
280 bool ok = exr.Load 
281
282 imageFile
283 frameNum
284 params.GammaValue
285 params.EXR_Exposure
286 params.EXR_Defog
287 params.EXR_KneeLow
288 params.EXR_KneeHigh 
289 ); 
290 if (!ok || !exr.IsValid()) 
291 return false
292 Width = exr.GetWidth(); 
293 Height = exr.GetHeight(); 
294 Pixels = exr.StealPixels(); 
295 SrcPixelFormat = exr.SrcPixelFormat
296 return true
297
298 
299 case tFileType::GIF
300
301 tImageGIF gif
302 bool ok = gif.Load(imageFile); 
303 if (!ok || !gif.IsValid()) 
304 return false
305 
306 if (frameNum >= gif.GetNumFrames()) 
307 return false
308 
309 Width = gif.GetWidth(); 
310 Height = gif.GetHeight(); 
311  
312 tImageGIF::Frame* stolenFrame = gif.StealFrame(frameNum); 
313 Pixels = stolenFrame->Pixels
314 
315 // This is safe as the frame does not own/delete the pixels. 
316 delete stolenFrame
317 
318 SrcPixelFormat = gif.SrcPixelFormat
319 return true
320
321 
322 case tFileType::HDR
323
324 // HDRs can only have one frame. 
325 if (frameNum != 0
326 return false
327 tImageHDR hdr
328 hdr.Load 
329
330 imageFile
331 params.GammaValue
332 params.HDR_Exposure 
333 ); 
334 if (!hdr.IsValid()) 
335 return false
336 Width = hdr.GetWidth(); 
337 Height = hdr.GetHeight(); 
338 Pixels = hdr.StealPixels(); 
339 SrcPixelFormat = hdr.SrcPixelFormat
340 return true
341
342 
343 case tFileType::ICO
344
345 tImageICO ico
346 bool ok = ico.Load(imageFile); 
347 if (!ok || !ico.IsValid()) 
348 return false
349 
350 if (frameNum >= ico.GetNumFrames()) 
351 return false
352 
353 tImageICO::Frame* stolenFrame = ico.StealFrame(frameNum); 
354 Pixels = stolenFrame->Pixels
355 
356 Width = stolenFrame->Width
357 Height = stolenFrame->Height;  
358 SrcPixelFormat = stolenFrame->SrcPixelFormat
359 
360 // This is safe as the frame does not own/delete the pixels. 
361 delete stolenFrame
362 return true
363
364 
365 case tFileType::JPG
366
367 // JPGs can only have one frame. 
368 if (frameNum != 0
369 return false
370 tImageJPG jpeg(imageFile); 
371 if (!jpeg.IsValid()) 
372 return false
373 Width = jpeg.GetWidth(); 
374 Height = jpeg.GetHeight(); 
375 Pixels = jpeg.StealPixels(); 
376 SrcPixelFormat = jpeg.SrcPixelFormat
377 return true
378
379 
380 case tFileType::PNG
381
382 // PNGs can only have one frame. 
383 if (frameNum != 0
384 return false
385 tImagePNG png(imageFile); 
386 if (!png.IsValid()) 
387 return false
388 Width = png.GetWidth(); 
389 Height = png.GetHeight(); 
390 Pixels = png.StealPixels(); 
391 SrcPixelFormat = png.SrcPixelFormat
392 return true
393
394 
395 case tFileType::TGA
396
397 // TGAs can only have one frame. 
398 if (frameNum != 0
399 return false
400 tImageTGA targa(imageFile); 
401 if (!targa.IsValid()) 
402 return false
403 Width = targa.GetWidth(); 
404 Height = targa.GetHeight(); 
405 Pixels = targa.StealPixels(); 
406 SrcPixelFormat = targa.SrcPixelFormat
407 return true
408
409 
410 case tFileType::TIFF
411
412 tImageTIFF tiff
413 bool ok = tiff.Load(imageFile); 
414 if (!ok || !tiff.IsValid()) 
415 return false
416 
417 if (frameNum >= tiff.GetNumFrames()) 
418 return false
419 
420 tImageTIFF::Frame* stolenFrame = tiff.StealFrame(frameNum); 
421 Width = stolenFrame->Width
422 Height = stolenFrame->Height
423 SrcPixelFormat = stolenFrame->SrcPixelFormat
424  
425 Pixels = stolenFrame->Pixels
426 
427 // This is safe as the frame does not own/delete the pixels. 
428 delete stolenFrame
429 return true
430
431 
432 case tFileType::WEBP
433
434 tImageWEBP webp
435 bool ok = webp.Load(imageFile); 
436 if (!ok || !webp.IsValid()) 
437 return false
438 
439 if (frameNum >= webp.GetNumFrames()) 
440 return false
441 
442 tImageWEBP::Frame* stolenFrame = webp.StealFrame(frameNum); 
443 Width = stolenFrame->Width
444 Height = stolenFrame->Height
445 SrcPixelFormat = stolenFrame->SrcPixelFormat
446  
447 Pixels = stolenFrame->Pixels
448 
449 // This is safe as the frame does not own/delete the pixels. 
450 delete stolenFrame
451 return true
452
453 
454 case tFileType::XPM
455
456 // XPMs can only have one frame. 
457 if (frameNum != 0
458 return false
459 tImageXPM xpm(imageFile); 
460 if (!xpm.IsValid()) 
461 return false
462 Width = xpm.GetWidth(); 
463 Height = xpm.GetHeight(); 
464 Pixels = xpm.StealPixels(); 
465 SrcPixelFormat = xpm.SrcPixelFormat
466 return true
467
468
469 
470 return false
471
472 
473 
474void tPicture::Save(tChunkWriter& chunk) const 
475
476 chunk.Begin(tChunkID::Image_Picture); 
477
478 chunk.Begin(tChunkID::Image_PictureProperties); 
479
480 chunk.Write(Width); 
481 chunk.Write(Height); 
482
483 chunk.End(); 
484 
485 chunk.Begin(tChunkID::Image_PicturePixels); 
486
487 chunk.Write(Pixels, GetNumPixels()); 
488
489 chunk.End(); 
490
491 chunk.End(); 
492
493 
494 
495void tPicture::Load(const tChunk& chunk
496
497 Clear(); 
498 if (chunk.ID() != tChunkID::Image_Picture
499 return
500 
501 for (tChunk ch = chunk.First(); ch.IsValid(); ch = ch.Next()) 
502
503 switch (ch.ID()) 
504
505 case tChunkID::Image_PictureProperties
506
507 ch.GetItem(Width); 
508 ch.GetItem(Height); 
509 break
510
511 
512 case tChunkID::Image_PicturePixels
513
514 tAssert(!Pixels && (GetNumPixels() > 0)); 
515 Pixels = new tPixel[GetNumPixels()]; 
516 ch.GetItems(Pixels, GetNumPixels()); 
517 break
518
519
520
521 SrcPixelFormat = tPixelFormat::R8G8B8A8
522
523 
524 
525void tPicture::Crop(int newW, int newH, Anchor anchor, const tColouri& fill
526
527 int originx = 0
528 int originy = 0
529 
530 switch (anchor
531
532 case Anchor::LeftTop: originx = 0; originy = Height-newH; break
533 case Anchor::MiddleTop: originx = Width/2 - newW/2; originy = Height-newH; break
534 case Anchor::RightTop: originx = Width - newW; originy = Height-newH; break
535 
536 case Anchor::LeftMiddle: originx = 0; originy = Height/2-newH/2; break
537 case Anchor::MiddleMiddle: originx = Width/2 - newW/2; originy = Height/2-newH/2; break
538 case Anchor::RightMiddle: originx = Width - newW; originy = Height/2-newH/2; break
539 
540 case Anchor::LeftBottom: originx = 0; originy = 0; break
541 case Anchor::MiddleBottom: originx = Width/2 - newW/2; originy = 0; break
542 case Anchor::RightBottom: originx = Width - newW; originy = 0; break
543
544 
545 Crop(newW, newH, originx, originy, fill); 
546
547 
548 
549void tPicture::Crop(int newW, int newH, int originX, int originY, const tColouri& fill
550
551 if ((newW <= 0) || (newH <= 0)) 
552
553 Clear(); 
554 return
555
556 
557 if ((newW == Width) && (newH == Height) && (originX == 0) && (originY == 0)) 
558 return
559 
560 tPixel* newPixels = new tPixel[newW * newH]; 
561 
562 // Set the new pixel colours. 
563 for (int y = 0; y < newH; y++) 
564
565 for (int x = 0; x < newW; x++) 
566
567 // If we're in range of the old picture we just copy the colour. If the old image is invalid no problem, as 
568 // we'll fall through to the else and the pixel will be set to black. 
569 if (tMath::tInIntervalIE(originX + x, 0, Width) && tMath::tInIntervalIE(originY + y, 0, Height)) 
570 newPixels[y * newW + x] = GetPixel(originX + x, originY + y); 
571 else 
572 newPixels[y * newW + x] = fill
573
574
575 
576 Clear(); 
577 Width = newW
578 Height = newH
579 Pixels = newPixels
580
581 
582 
583void tPicture::Crop(const tColouri& colour, uint32 channels
584
585 // Count bottom rows to crop. 
586 int numBottomRows = 0
587 for (int y = 0; y < Height; y++) 
588
589 bool allMatch = true
590 for (int x = 0; x < Width; x++) 
591
592 if (!colour.Equal(Pixels[ GetIndex(x, y) ], channels)) 
593
594 allMatch = false
595 break
596
597
598 if (allMatch
599 numBottomRows++; 
600 else 
601 break
602
603 
604 // Count top rows to crop. 
605 int numTopRows = 0
606 for (int y = Height-1; y >= 0; y--) 
607
608 bool allMatch = true
609 for (int x = 0; x < Width; x++) 
610
611 if (!colour.Equal(Pixels[ GetIndex(x, y) ], channels)) 
612
613 allMatch = false
614 break
615
616
617 if (allMatch
618 numTopRows++; 
619 else 
620 break
621
622 
623 // Count left columns to crop. 
624 int numLeftCols = 0
625 for (int x = 0; x < Width; x++) 
626
627 bool allMatch = true
628 for (int y = 0; y < Height; y++) 
629
630 if (!colour.Equal(Pixels[ GetIndex(x, y) ], channels)) 
631
632 allMatch = false
633 break
634
635
636 if (allMatch
637 numLeftCols++; 
638 else 
639 break
640
641 
642 // Count right columns to crop. 
643 int numRightCols = 0
644 for (int x = Width-1; x >= 0; x--) 
645
646 bool allMatch = true
647 for (int y = 0; y < Height; y++) 
648
649 if (!colour.Equal(Pixels[ GetIndex(x, y) ], channels)) 
650
651 allMatch = false
652 break
653
654
655 if (allMatch
656 numRightCols++; 
657 else 
658 break
659
660 
661 Crop(Width - numLeftCols - numRightCols, Height - numBottomRows - numTopRows, numLeftCols, numBottomRows); 
662
663 
664 
665void tPicture::Rotate90(bool antiClockwise
666
667 tAssert((Width > 0) && (Height > 0) && Pixels); 
668 int newW = Height
669 int newH = Width
670 tPixel* newPixels = new tPixel[newW * newH]; 
671 
672 for (int y = 0; y < Height; y++) 
673 for (int x = 0; x < Width; x++) 
674 newPixels[ GetIndex(y, x, newW, newH) ] = Pixels[ GetIndex(antiClockwise ? x : Width-1-x, antiClockwise ? Height-1-y : y) ]; 
675 
676 Clear(); 
677 Width = newW
678 Height = newH
679 Pixels = newPixels
680
681 
682 
683void tPicture::RotateCenter(float angle, const tPixel& fill, tResampleFilter filter
684
685 if (!IsValid()) 
686 return
687 
688 tMatrix2 rotMat
689 rotMat.MakeRotateZ(angle); 
690 
691 // Matrix is orthonormal so inverse is transpose. 
692 tMatrix2 invRot(rotMat); 
693 invRot.Transpose(); 
694 
695 if (filter != tResampleFilter::Invalid
696 RotateCenterResampled(rotMat, invRot, fill, filter); 
697 else 
698 RotateCenterNearest(rotMat, invRot, fill); 
699
700 
701 
702void tPicture::RotateCenterNearest(const tMatrix2& rotMat, const tMatrix2& invRot, const tPixel& fill
703
704 int srcW = Width
705 int srcH = Height
706 
707 // Rotate all corners to get new size. Memfill it with fill colour. Map from old to new. 
708 float srcHalfW = float(Width)/2.0f
709 float srcHalfH = float(Height)/2.0f
710 tPixel* srcPixels = Pixels
711 
712 tVector2 tl(-srcHalfW, srcHalfH); 
713 tVector2 tr( srcHalfW, srcHalfH); 
714 tVector2 bl(-srcHalfW, -srcHalfH); 
715 tVector2 br( srcHalfW, -srcHalfH); 
716 tl = rotMat*tl; tr = rotMat*tr; bl = rotMat*bl; br = rotMat*br
717 float epsilon = 0.0002f
718 int minx = int(tFloor(tRound(tMin(tl.x, tr.x, bl.x, br.x), epsilon))); 
719 int miny = int(tFloor(tRound(tMin(tl.y, tr.y, bl.y, br.y), epsilon))); 
720 int maxx = int(tCeiling(tRound(tMax(tl.x, tr.x, bl.x, br.x), epsilon))); 
721 int maxy = int(tCeiling(tRound(tMax(tl.y, tr.y, bl.y, br.y), epsilon))); 
722 Width = maxx - minx
723 Height = maxy - miny
724 
725 Pixels = new tPixel[Width*Height]; 
726 float halfW = float(Width)/2.0f
727 float halfH = float(Height)/2.0f
728 
729 // We now need to loop through every pixel in the new image and do a weighted sample of 
730 // the pixels it maps to in the original image. Actually weighted is not implemented yet 
731 // so do nearest. 
732 for (int y = 0; y < Height; y++) 
733
734 for (int x = 0; x < Width; x++) 
735
736 // Lets start with nearest pixel. We can get fancier after. 
737 // dstPos is the middle of the pixel we are writing to. srcPos is the original we are coming from. 
738 // The origin' of a pixel is the lower-left corner. The 0.5s get us to the center (and back). 
739 tVector2 dstPos(float(x)+0.5f - halfW, float(y)+0.5f - halfH); 
740 tVector2 srcPos = invRot*dstPos
741 srcPos += tVector2(srcHalfW, srcHalfH); 
742 srcPos -= tVector2(0.5f, 0.5f); 
743 
744 tPixel srcCol = tPixel::black
745 
746 int srcX = int(tRound(srcPos.x)); 
747 int srcY = int(tRound(srcPos.y)); 
748 bool useFill = (srcX < 0) || (srcX >= srcW) || (srcY < 0) || (srcY >= srcH); 
749 srcCol = useFill ? fill : srcPixels[ GetIndex(srcX, srcY, srcW, srcH) ]; 
750 Pixels[ GetIndex(x, y) ] = srcCol
751
752
753 
754 delete[] srcPixels
755
756 
757 
758void tPicture::RotateCenterResampled(const tMatrix2& rotMat, const tMatrix2& invRot, const tPixel& fill, tResampleFilter filter
759
760 tAssert(filter != tResampleFilter::Invalid); 
761 if (filter == tResampleFilter::Nearest
762
763 Resample(Width*2, Height*2, filter); 
764 Resample(Width*2, Height*2, filter); 
765
766 else 
767
768 Resample(Width*4, Height*4, filter); 
769
770  
771 RotateCenterNearest(rotMat, invRot, fill); 
772 ScaleHalf(); 
773 ScaleHalf(); 
774
775 
776 
777void tPicture::Flip(bool horizontal
778
779 tAssert((Width > 0) && (Height > 0) && Pixels); 
780 int newW = Width
781 int newH = Height
782 tPixel* newPixels = new tPixel[newW * newH]; 
783 
784 for (int y = 0; y < Height; y++) 
785 for (int x = 0; x < Width; x++) 
786 newPixels[ GetIndex(x, y) ] = Pixels[ GetIndex(horizontal ? Width-1-x : x, horizontal ? y : Height-1-y) ]; 
787 
788 Clear(); 
789 Width = newW
790 Height = newH
791 Pixels = newPixels
792
793 
794 
795bool tPicture::ScaleHalf() 
796
797 if (!IsValid()) 
798 return false
799 
800 // A 1x1 image is defined as already being rescaled. 
801 if ((Width == 1) && (Height == 1)) 
802 return true
803 
804 // We only allow non-divisible-by-2 dimensions if that dimension is exactly 1. 
805 if ( ((Width & 1) && (Width != 1)) || ((Height & 1) && (Height != 1)) ) 
806 return false
807 
808 int newWidth = Width >> 1
809 int newHeight = Height >> 1
810 if (newWidth == 0
811 newWidth = 1
812 if (newHeight == 0
813 newHeight = 1
814 
815 int numNewPixels = newWidth*newHeight
816 tPixel* newPixels = new tPixel[numNewPixels]; 
817 
818 // Deal with case where src height is 1 and src width is divisible by 2 OR where src width is 1 and src height is 
819 // divisible by 2. Image is either a row or column vector in this case. 
820 if ((Height == 1) || (Width == 1)) 
821
822 for (int p = 0; p < numNewPixels; p++) 
823
824 int p2 = 2*p
825 
826 int p0r = Pixels[p2].R
827 int p1r = Pixels[p2 + 1].R
828 newPixels[p].R = tMath::tClamp((p0r + p1r)>>1, 0, 255); 
829 
830 int p0g = Pixels[p2].G
831 int p1g = Pixels[p2 + 1].G
832 newPixels[p].G = tMath::tClamp((p0g + p1g)>>1, 0, 255); 
833 
834 int p0b = Pixels[p2].B
835 int p1b = Pixels[p2 + 1].B
836 newPixels[p].B = tMath::tClamp((p0b + p1b)>>1, 0, 255); 
837 
838 int p0a = Pixels[p2].A
839 int p1a = Pixels[p2 + 1].A
840 newPixels[p].A = tMath::tClamp((p0a + p1a)>>1, 0, 255); 
841
842
843 
844 // Handle the case where both width and height are both divisible by 2. 
845 else 
846
847 for (int x = 0; x < newWidth; x++) 
848
849 int x2 = 2*x
850 for (int y = 0; y < newHeight; y++) 
851
852 int y2 = 2*y
853 
854 // @todo Use SSE/SIMD here? 
855 int p0r = Pixels[y2*Width + x2].R
856 int p1r = Pixels[y2*Width + x2 + 1].R
857 int p2r = Pixels[(y2+1)*Width + x2].R
858 int p3r = Pixels[(y2+1)*Width + x2 + 1].R
859 newPixels[y*newWidth + x].R = tMath::tClamp((p0r + p1r + p2r + p3r)>>2, 0, 255); 
860 
861 int p0g = Pixels[y2*Width + x2].G
862 int p1g = Pixels[y2*Width + x2 + 1].G
863 int p2g = Pixels[(y2+1)*Width + x2].G
864 int p3g = Pixels[(y2+1)*Width + x2 + 1].G
865 newPixels[y*newWidth + x].G = tMath::tClamp((p0g + p1g + p2g + p3g)>>2, 0, 255); 
866 
867 int p0b = Pixels[y2*Width + x2].B
868 int p1b = Pixels[y2*Width + x2 + 1].B
869 int p2b = Pixels[(y2+1)*Width + x2].B
870 int p3b = Pixels[(y2+1)*Width + x2 + 1].B
871 newPixels[y*newWidth + x].B = tMath::tClamp((p0b + p1b + p2b + p3b)>>2, 0, 255); 
872 
873 int p0a = Pixels[y2*Width + x2].A
874 int p1a = Pixels[y2*Width + x2 + 1].A
875 int p2a = Pixels[(y2+1)*Width + x2].A
876 int p3a = Pixels[(y2+1)*Width + x2 + 1].A
877 newPixels[y*newWidth + x].A = tMath::tClamp((p0a + p1a + p2a + p3a)>>2, 0, 255); 
878
879
880
881 
882 Clear(); 
883 Pixels = newPixels
884 Width = newWidth
885 Height = newHeight
886 return true
887
888 
889 
890bool tPicture::Resample(int width, int height, tResampleFilter filter, tResampleEdgeMode edgeMode
891
892 if (!IsValid() || (width <= 0) || (height <= 0)) 
893 return false
894 
895 if ((width == Width) && (height == Height)) 
896 return true
897 
898 tPixel* newPixels = new tPixel[width*height]; 
899 bool success = tImage::Resample(Pixels, Width, Height, newPixels, width, height, filter, edgeMode); 
900 if (!success
901
902 delete newPixels
903 return false
904
905 
906 delete[] Pixels
907 Pixels = newPixels
908 Width = width
909 Height = height
910 
911 return true
912
913