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"  |
24 | using namespace tSystem;  |
25 | namespace tImage  |
26 | {  |
27 |   |
28 |   |
29 | bool 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 |   |
48 | bool 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 |   |
93 | bool 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 |   |
116 | bool 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 |   |
242 | bool 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 |   |
254 | tPixel* tImagePNG::StealPixels()  |
255 | {  |
256 | tPixel* pixels = Pixels;  |
257 | Pixels = nullptr;  |
258 | Width = 0;  |
259 | Height = 0;  |
260 | return pixels;  |
261 | }  |
262 |   |
263 |   |
264 | }  |
265 | |