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