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 | |
9 | struct 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 | |
24 | static 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 | |
30 | static 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 | |
37 | static 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 | |
56 | static 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 | |
74 | static 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 | |
81 | static 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 | |
127 | bool LoadPng(SImageByteBuffer &ByteLoader, const char *pFileName, int &PngliteIncompatible, size_t &Width, size_t &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(size_t 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 size_t ColorChannelCount = png_get_channels(png_ptr: pPNGStruct, info_ptr: pPNGInfo); |
224 | const size_t 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(size_t 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: Height * Width * ColorChannelCount * sizeof(uint8_t)); |
237 | |
238 | for(size_t 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 | |
262 | static 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 | |
278 | static void FlushPNGWrite(png_structp png_ptr) {} |
279 | |
280 | static size_t 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 | |
298 | bool SavePng(EImageFormat ImageFormat, const uint8_t *pRawBuffer, SImageByteBuffer &WrittenBytes, size_t Width, size_t 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 | size_t 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 | size_t WidthBytes = Width * WriteBytesPerPixel; |
339 | ptrdiff_t BufferOffset = 0; |
340 | for(size_t 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(size_t 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 | |