1#include "image_loader.h"
2#include <base/log.h>
3#include <base/system.h>
4#include <csetjmp>
5#include <cstdlib>
6
7#include <png.h>
8
9struct SLibPNGWarningItem
10{
11 SImageByteBuffer *m_pByteLoader;
12 const char *m_pFileName;
13 std::jmp_buf m_Buf;
14};
15
16[[noreturn]] static void LibPNGError(png_structp png_ptr, png_const_charp error_msg)
17{
18 SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
19 pUserStruct->m_pByteLoader->m_Err = -1;
20 dbg_msg(sys: "png", fmt: "error for file \"%s\": %s", pUserStruct->m_pFileName, error_msg);
21 std::longjmp(env: pUserStruct->m_Buf, val: 1);
22}
23
24static void LibPNGWarning(png_structp png_ptr, png_const_charp warning_msg)
25{
26 SLibPNGWarningItem *pUserStruct = (SLibPNGWarningItem *)png_get_error_ptr(png_ptr);
27 dbg_msg(sys: "png", fmt: "warning for file \"%s\": %s", pUserStruct->m_pFileName, warning_msg);
28}
29
30static bool FileMatchesImageType(SImageByteBuffer &ByteLoader)
31{
32 if(ByteLoader.m_pvLoadedImageBytes->size() >= 8)
33 return png_sig_cmp(sig: (png_bytep)ByteLoader.m_pvLoadedImageBytes->data(), start: 0, num_to_check: 8) == 0;
34 return false;
35}
36
37static void ReadDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToRead)
38{
39 png_voidp pIO_Ptr = png_get_io_ptr(png_ptr: pPNGStruct);
40
41 SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr;
42
43 if(pByteLoader->m_pvLoadedImageBytes->size() >= pByteLoader->m_LoadOffset + (size_t)ByteCountToRead)
44 {
45 mem_copy(dest: pOutBytes, source: &(*pByteLoader->m_pvLoadedImageBytes)[pByteLoader->m_LoadOffset], size: (size_t)ByteCountToRead);
46
47 pByteLoader->m_LoadOffset += (size_t)ByteCountToRead;
48 }
49 else
50 {
51 pByteLoader->m_Err = -1;
52 dbg_msg(sys: "png", fmt: "could not read bytes, file was too small.");
53 }
54}
55
56static EImageFormat LibPNGGetImageFormat(int ColorChannelCount)
57{
58 switch(ColorChannelCount)
59 {
60 case 1:
61 return IMAGE_FORMAT_R;
62 case 2:
63 return IMAGE_FORMAT_RA;
64 case 3:
65 return IMAGE_FORMAT_RGB;
66 case 4:
67 return IMAGE_FORMAT_RGBA;
68 default:
69 dbg_assert(false, "ColorChannelCount invalid");
70 dbg_break();
71 }
72}
73
74static void LibPNGDeleteReadStruct(png_structp pPNGStruct, png_infop pPNGInfo)
75{
76 if(pPNGInfo != nullptr)
77 png_destroy_info_struct(png_ptr: pPNGStruct, info_ptr_ptr: &pPNGInfo);
78 png_destroy_read_struct(png_ptr_ptr: &pPNGStruct, info_ptr_ptr: nullptr, end_info_ptr_ptr: nullptr);
79}
80
81static int PngliteIncompatibility(png_structp pPNGStruct, png_infop pPNGInfo)
82{
83 int ColorType = png_get_color_type(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
84 int BitDepth = png_get_bit_depth(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
85 int InterlaceType = png_get_interlace_type(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
86 int Result = 0;
87 switch(ColorType)
88 {
89 case PNG_COLOR_TYPE_GRAY:
90 case PNG_COLOR_TYPE_RGB:
91 case PNG_COLOR_TYPE_RGB_ALPHA:
92 case PNG_COLOR_TYPE_GRAY_ALPHA:
93 break;
94 default:
95 log_debug("png", "color type %d unsupported by pnglite", ColorType);
96 Result |= PNGLITE_COLOR_TYPE;
97 }
98
99 switch(BitDepth)
100 {
101 case 8:
102 case 16:
103 break;
104 default:
105 log_debug("png", "bit depth %d unsupported by pnglite", BitDepth);
106 Result |= PNGLITE_BIT_DEPTH;
107 }
108
109 if(InterlaceType != PNG_INTERLACE_NONE)
110 {
111 log_debug("png", "interlace type %d unsupported by pnglite", InterlaceType);
112 Result |= PNGLITE_INTERLACE_TYPE;
113 }
114 if(png_get_compression_type(png_ptr: pPNGStruct, info_ptr: pPNGInfo) != PNG_COMPRESSION_TYPE_BASE)
115 {
116 log_debug("png", "non-default compression type unsupported by pnglite");
117 Result |= PNGLITE_COMPRESSION_TYPE;
118 }
119 if(png_get_filter_type(png_ptr: pPNGStruct, info_ptr: pPNGInfo) != PNG_FILTER_TYPE_BASE)
120 {
121 log_debug("png", "non-default filter type unsupported by pnglite");
122 Result |= PNGLITE_FILTER_TYPE;
123 }
124 return Result;
125}
126
127bool LoadPng(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, int &Width, int &Height, uint8_t *&pImageBuff, EImageFormat &ImageFormat)
128{
129 SLibPNGWarningItem UserErrorStruct = {.m_pByteLoader: &ByteLoader, .m_pFileName: pFileName, .m_Buf: {}};
130
131 png_structp pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING, error_ptr: nullptr, error_fn: nullptr, warn_fn: nullptr);
132
133 if(pPNGStruct == nullptr)
134 {
135 dbg_msg(sys: "png", fmt: "libpng internal failure: png_create_read_struct failed.");
136 return false;
137 }
138
139 png_infop pPNGInfo = nullptr;
140 png_bytepp pRowPointers = nullptr;
141 Height = 0; // ensure this is not undefined for the error handler
142 if(setjmp(UserErrorStruct.m_Buf))
143 {
144 if(pRowPointers != nullptr)
145 {
146 for(int i = 0; i < Height; ++i)
147 {
148 delete[] pRowPointers[i];
149 }
150 }
151 delete[] pRowPointers;
152 LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
153 return false;
154 }
155 png_set_error_fn(png_ptr: pPNGStruct, error_ptr: &UserErrorStruct, error_fn: LibPNGError, warning_fn: LibPNGWarning);
156
157 pPNGInfo = png_create_info_struct(png_ptr: pPNGStruct);
158
159 if(pPNGInfo == nullptr)
160 {
161 png_destroy_read_struct(png_ptr_ptr: &pPNGStruct, info_ptr_ptr: nullptr, end_info_ptr_ptr: nullptr);
162 dbg_msg(sys: "png", fmt: "libpng internal failure: png_create_info_struct failed.");
163 return false;
164 }
165
166 if(!FileMatchesImageType(ByteLoader))
167 {
168 LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
169 dbg_msg(sys: "png", fmt: "file does not match image type.");
170 return false;
171 }
172
173 ByteLoader.m_LoadOffset = 8;
174
175 png_set_read_fn(png_ptr: pPNGStruct, io_ptr: (png_bytep)&ByteLoader, read_data_fn: ReadDataFromLoadedBytes);
176
177 png_set_sig_bytes(png_ptr: pPNGStruct, num_bytes: 8);
178
179 png_read_info(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
180
181 if(ByteLoader.m_Err != 0)
182 {
183 LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
184 dbg_msg(sys: "png", fmt: "byte loader error.");
185 return false;
186 }
187
188 Width = png_get_image_width(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
189 Height = png_get_image_height(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
190 const int ColorType = png_get_color_type(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
191 const png_byte BitDepth = png_get_bit_depth(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
192 PngliteIncompatible = PngliteIncompatibility(pPNGStruct, pPNGInfo);
193
194 if(BitDepth == 16)
195 {
196 png_set_strip_16(png_ptr: pPNGStruct);
197 }
198 else if(BitDepth > 8)
199 {
200 dbg_msg(sys: "png", fmt: "non supported bit depth.");
201 LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
202 return false;
203 }
204
205 if(Width == 0 || Height == 0 || BitDepth == 0)
206 {
207 dbg_msg(sys: "png", fmt: "image had width, height or bit depth of 0.");
208 LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
209 return false;
210 }
211
212 if(ColorType == PNG_COLOR_TYPE_PALETTE)
213 png_set_palette_to_rgb(png_ptr: pPNGStruct);
214
215 if(ColorType == PNG_COLOR_TYPE_GRAY && BitDepth < 8)
216 png_set_expand_gray_1_2_4_to_8(png_ptr: pPNGStruct);
217
218 if(png_get_valid(png_ptr: pPNGStruct, info_ptr: pPNGInfo, PNG_INFO_tRNS))
219 png_set_tRNS_to_alpha(png_ptr: pPNGStruct);
220
221 png_read_update_info(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
222
223 const int ColorChannelCount = png_get_channels(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
224 const int BytesInRow = png_get_rowbytes(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
225 dbg_assert(BytesInRow == Width * ColorChannelCount, "bytes in row incorrect.");
226
227 pRowPointers = new png_bytep[Height];
228 for(int y = 0; y < Height; ++y)
229 {
230 pRowPointers[y] = new png_byte[BytesInRow];
231 }
232
233 png_read_image(png_ptr: pPNGStruct, image: pRowPointers);
234
235 if(ByteLoader.m_Err == 0)
236 pImageBuff = (uint8_t *)malloc(size: (size_t)Height * (size_t)Width * (size_t)ColorChannelCount * sizeof(uint8_t));
237
238 for(int i = 0; i < Height; ++i)
239 {
240 if(ByteLoader.m_Err == 0)
241 mem_copy(dest: &pImageBuff[i * BytesInRow], source: pRowPointers[i], size: BytesInRow);
242 delete[] pRowPointers[i];
243 }
244 delete[] pRowPointers;
245 pRowPointers = nullptr;
246
247 if(ByteLoader.m_Err != 0)
248 {
249 LibPNGDeleteReadStruct(pPNGStruct, pPNGInfo);
250 dbg_msg(sys: "png", fmt: "byte loader error.");
251 return false;
252 }
253
254 ImageFormat = LibPNGGetImageFormat(ColorChannelCount);
255
256 png_destroy_info_struct(png_ptr: pPNGStruct, info_ptr_ptr: &pPNGInfo);
257 png_destroy_read_struct(png_ptr_ptr: &pPNGStruct, info_ptr_ptr: nullptr, end_info_ptr_ptr: nullptr);
258
259 return true;
260}
261
262static void WriteDataFromLoadedBytes(png_structp pPNGStruct, png_bytep pOutBytes, png_size_t ByteCountToWrite)
263{
264 if(ByteCountToWrite > 0)
265 {
266 png_voidp pIO_Ptr = png_get_io_ptr(png_ptr: pPNGStruct);
267
268 SImageByteBuffer *pByteLoader = (SImageByteBuffer *)pIO_Ptr;
269
270 size_t NewSize = pByteLoader->m_LoadOffset + (size_t)ByteCountToWrite;
271 pByteLoader->m_pvLoadedImageBytes->resize(new_size: NewSize);
272
273 mem_copy(dest: &(*pByteLoader->m_pvLoadedImageBytes)[pByteLoader->m_LoadOffset], source: pOutBytes, size: (size_t)ByteCountToWrite);
274 pByteLoader->m_LoadOffset = NewSize;
275 }
276}
277
278static void FlushPNGWrite(png_structp png_ptr) {}
279
280static int ImageLoaderHelperFormatToColorChannel(EImageFormat Format)
281{
282 switch(Format)
283 {
284 case IMAGE_FORMAT_R:
285 return 1;
286 case IMAGE_FORMAT_RA:
287 return 2;
288 case IMAGE_FORMAT_RGB:
289 return 3;
290 case IMAGE_FORMAT_RGBA:
291 return 4;
292 default:
293 dbg_assert(false, "Format invalid");
294 dbg_break();
295 }
296}
297
298bool SavePng(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, int Width, int Height)
299{
300 png_structp pPNGStruct = png_create_write_struct(PNG_LIBPNG_VER_STRING, error_ptr: nullptr, error_fn: nullptr, warn_fn: nullptr);
301
302 if(pPNGStruct == nullptr)
303 {
304 dbg_msg(sys: "png", fmt: "libpng internal failure: png_create_write_struct failed.");
305 return false;
306 }
307
308 png_infop pPNGInfo = png_create_info_struct(png_ptr: pPNGStruct);
309
310 if(pPNGInfo == nullptr)
311 {
312 png_destroy_read_struct(png_ptr_ptr: &pPNGStruct, info_ptr_ptr: nullptr, end_info_ptr_ptr: nullptr);
313 dbg_msg(sys: "png", fmt: "libpng internal failure: png_create_info_struct failed.");
314 return false;
315 }
316
317 WrittenBytes.m_LoadOffset = 0;
318 WrittenBytes.m_pvLoadedImageBytes->clear();
319
320 png_set_write_fn(png_ptr: pPNGStruct, io_ptr: (png_bytep)&WrittenBytes, write_data_fn: WriteDataFromLoadedBytes, output_flush_fn: FlushPNGWrite);
321
322 int ColorType = PNG_COLOR_TYPE_RGB;
323 int WriteBytesPerPixel = ImageLoaderHelperFormatToColorChannel(Format: ImageFormat);
324 if(ImageFormat == IMAGE_FORMAT_R)
325 {
326 ColorType = PNG_COLOR_TYPE_GRAY;
327 }
328 else if(ImageFormat == IMAGE_FORMAT_RGBA)
329 {
330 ColorType = PNG_COLOR_TYPE_RGBA;
331 }
332
333 png_set_IHDR(png_ptr: pPNGStruct, info_ptr: pPNGInfo, width: Width, height: Height, bit_depth: 8, color_type: ColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
334
335 png_write_info(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
336
337 png_bytepp pRowPointers = new png_bytep[Height];
338 int WidthBytes = Width * WriteBytesPerPixel;
339 ptrdiff_t BufferOffset = 0;
340 for(int y = 0; y < Height; ++y)
341 {
342 pRowPointers[y] = new png_byte[WidthBytes];
343 mem_copy(dest: pRowPointers[y], source: pRawBuffer + BufferOffset, size: WidthBytes);
344 BufferOffset += (ptrdiff_t)WidthBytes;
345 }
346 png_write_image(png_ptr: pPNGStruct, image: pRowPointers);
347
348 png_write_end(png_ptr: pPNGStruct, info_ptr: pPNGInfo);
349
350 for(int y = 0; y < Height; ++y)
351 {
352 delete[](pRowPointers[y]);
353 }
354 delete[](pRowPointers);
355
356 png_destroy_info_struct(png_ptr: pPNGStruct, info_ptr_ptr: &pPNGInfo);
357 png_destroy_write_struct(png_ptr_ptr: &pPNGStruct, info_ptr_ptr: nullptr);
358
359 return true;
360}
361