1// tImagePNG.cpp 
2// 
3// This class knows how to load and save PNG files. It does zero processing of image data. It knows the details of the 
4// png file format and loads the data into a tPixel array. These tPixels may be 'stolen' by the tPicture's constructor 
5// if a png file is specified. After the array is stolen the tImagePNG is invalid. This is purely for performance. 
6// 
7// Copyright (c) 2020 Tristan Grimmer. 
8// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
9// granted, provided that the above copyright notice and this permission notice appear in all copies. 
10// 
11// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
12// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
13// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
14// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
15// PERFORMANCE OF THIS SOFTWARE. 
16// 
17// The loading and saving code in here is roughly based on the example code from the LibPNG library. The licence may be 
18// found in the file Licence_LibPNG.txt. 
19 
20#include <Foundation/tArray.h> 
21#include <System/tFile.h> 
22#include "png.h" 
23#include "Image/tImagePNG.h" 
24using namespace tSystem; 
25namespace tImage 
26{ 
27 
28 
29bool tImagePNG::Load(const tString& pngFile) 
30{ 
31 Clear(); 
32 
33 if (tSystem::tGetFileType(pngFile) != tSystem::tFileType::PNG) 
34 return false; 
35 
36 if (!tFileExists(pngFile)) 
37 return false; 
38 
39 int numBytes = 0; 
40 uint8* pngFileInMemory = tLoadFile(pngFile, nullptr, &numBytes); 
41 bool success = Set(pngFileInMemory, numBytes); 
42 delete[] pngFileInMemory; 
43 
44 return success; 
45} 
46 
47 
48bool tImagePNG::Set(const uint8* pngFileInMemory, int numBytes) 
49{ 
50 Clear(); 
51 if ((numBytes <= 0) || !pngFileInMemory) 
52 return false; 
53 
54 png_image pngImage; 
55 tStd::tMemset(&pngImage, 0, sizeof(pngImage)); 
56 pngImage.version = PNG_IMAGE_VERSION; 
57 int successCode = png_image_begin_read_from_memory(&pngImage, pngFileInMemory, numBytes); 
58 if (!successCode) 
59 { 
60 png_image_free(&pngImage); 
61 return false; 
62 } 
63 
64 SrcPixelFormat = (pngImage.format & PNG_FORMAT_FLAG_ALPHA) ? tPixelFormat::R8G8B8A8 : tPixelFormat::R8G8B8; 
65 
66 // We need to modify the format to match the type of the pixels we want to load into. 
67 pngImage.format = PNG_FORMAT_RGBA; 
68 Width = pngImage.width; 
69 Height = pngImage.height; 
70 
71 int numPixels = Width * Height; 
72 tPixel* reversedPixels = new tPixel[numPixels]; 
73 successCode = png_image_finish_read(&pngImage, nullptr, (uint8*)reversedPixels, 0, nullptr); 
74 if (!successCode) 
75 { 
76 png_image_free(&pngImage); 
77 delete[] reversedPixels; 
78 Clear(); 
79 return false; 
80 } 
81 
82 // Reverse rows. 
83 Pixels = new tPixel[numPixels]; 
84 int bytesPerRow = Width*4; 
85 for (int y = Height-1; y >= 0; y--) 
86 tStd::tMemcpy((uint8*)Pixels + ((Height-1)-y)*bytesPerRow, (uint8*)reversedPixels + y*bytesPerRow, bytesPerRow); 
87 delete[] reversedPixels; 
88 
89 return true; 
90} 
91 
92 
93bool tImagePNG::Set(tPixel* pixels, int width, int height, bool steal) 
94{ 
95 Clear(); 
96 if (!pixels || (width <= 0) || (height <= 0)) 
97 return false; 
98 
99 Width = width; 
100 Height = height; 
101 if (steal) 
102 { 
103 Pixels = pixels; 
104 } 
105 else 
106 { 
107 Pixels = new tPixel[Width*Height]; 
108 tStd::tMemcpy(Pixels, pixels, Width*Height*sizeof(tPixel)); 
109 } 
110 
111 SrcPixelFormat = tPixelFormat::R8G8B8A8; 
112 return true; 
113} 
114 
115 
116bool tImagePNG::Save(const tString& pngFile) const 
117{ 
118 if (!IsValid()) 
119 return false; 
120 
121 if (tSystem::tGetFileType(pngFile) != tSystem::tFileType::PNG) 
122 return false; 
123 
124 bool isOpaque = IsOpaque(); 
125 int srcBytesPerPixel = isOpaque ? 3 : 4; 
126 
127 // Guard against integer overflow. 
128 if (Height > PNG_SIZE_MAX / (Width * srcBytesPerPixel)) 
129 return false; 
130 
131 // If it's opaque we use the alternate no-alpha buffer. This should not be necessary 
132 // but I can't figure out how to get libpng reading 32bit and writing 24. 
133 uint8* srcPixels = (uint8*)Pixels; 
134 if (isOpaque) 
135 { 
136 srcPixels = new uint8[Width*Height*srcBytesPerPixel]; 
137 int dindex = 0; 
138 for (int p = 0; p < Width*Height; p++) 
139 { 
140 srcPixels[dindex++] = Pixels[p].R; 
141 srcPixels[dindex++] = Pixels[p].G; 
142 srcPixels[dindex++] = Pixels[p].B; 
143 } 
144 } 
145 
146 FILE* fp = fopen(pngFile, "wb"); 
147 if (!fp) 
148 return false; 
149 
150 // Create and initialize the png_struct with the desired error handler functions. 
151 png_structp pngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); 
152 if (!pngPtr) 
153 { 
154 fclose(fp); 
155 return false; 
156 } 
157 
158 png_infop infoPtr = png_create_info_struct(pngPtr); 
159 if (!infoPtr) 
160 { 
161 fclose(fp); 
162 png_destroy_write_struct(&pngPtr, 0); 
163 return false; 
164 } 
165 
166 // Set up default error handling. 
167 if (setjmp(png_jmpbuf(pngPtr))) 
168 { 
169 fclose(fp); 
170 png_destroy_write_struct(&pngPtr, &infoPtr); 
171 return false; 
172 } 
173 
174 png_init_io(pngPtr, fp); 
175 int bitDepth = 8; // Supported depths are 1, 2, 4, 8, 16. 
176 
177 // We write either 24 or 32 bit images depending on whether we have an alpha channel. 
178 uint32 pngColourType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; 
179 png_set_IHDR 
180 ( 
181 pngPtr, infoPtr, Width, Height, bitDepth, pngColourType, 
182 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE 
183 ); 
184 
185 // Optional significant bit (sBIT) chunk. 
186 png_color_8 sigBit; 
187 sigBit.red = 8; 
188 sigBit.green = 8; 
189 sigBit.blue = 8; 
190 sigBit.alpha = isOpaque ? 0 : 8; 
191 png_set_sBIT(pngPtr, infoPtr, &sigBit); 
192 
193 // Optional gamma chunk is strongly suggested if you have any guess as to the correct gamma of the image. 
194 // png_set_gAMA(pngPtr, infoPtr, 2.2f); 
195 
196 png_write_info(pngPtr, infoPtr); 
197 
198 // Shift the pixels up to a legal bit depth and fill in as appropriate to correctly scale the image. 
199 // png_set_shift(pngPtr, &sigBit); 
200 // 
201 // Pack pixels into bytes. 
202 // png_set_packing(pngPtr); 
203 // 
204 // Swap location of alpha bytes from ARGB to RGBA. 
205 // png_set_swap_alpha(pngPtr); 
206 // 
207 // Get rid of filler (OR ALPHA) bytes, pack XRGB/RGBX/ARGB/RGBA into RGB (4 channels -> 3 channels). The second parameter is not used. 
208 // png_set_filler(pngPtr, 0, PNG_FILLER_BEFORE); 
209 // 
210 // png_set_strip_alpha(pngPtr); 
211 // 
212 // Flip BGR pixels to RGB. 
213 // png_set_bgr(pngPtr); 
214 // 
215 // Swap bytes of 16-bit files to most significant byte first. 
216 // png_set_swap(pngPtr); 
217 tArray<png_bytep> rowPointers(Height); 
218 
219 // Set up pointers into the src data. 
220 for (int r = 0; r < Height; r++) 
221 rowPointers[Height-1-r] = srcPixels + r * Width * srcBytesPerPixel; 
222 
223 // tArray has an implicit cast operator. rowPointers is equivalient to rowPointers.GetElements(). 
224 png_write_image(pngPtr, rowPointers); 
225 
226 // Finish writing the rest of the file. 
227 png_write_end(pngPtr, infoPtr); 
228 
229 // Clear the srcPixels if we created the buffer. 
230 if (isOpaque) 
231 delete srcPixels; 
232 srcPixels = nullptr; 
233 
234 // Clean up. 
235 png_destroy_write_struct(&pngPtr, &infoPtr); 
236 fclose(fp); 
237 
238 return true; 
239} 
240 
241 
242bool tImagePNG::IsOpaque() const 
243{ 
244 for (int p = 0; p < (Width*Height); p++) 
245 { 
246 if (Pixels[p].A < 255) 
247 return false; 
248 } 
249 
250 return true; 
251} 
252 
253 
254tPixel* tImagePNG::StealPixels() 
255{ 
256 tPixel* pixels = Pixels; 
257 Pixels = nullptr; 
258 Width = 0; 
259 Height = 0; 
260 return pixels; 
261} 
262 
263 
264} 
265