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