| 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 | |