Merge branch 'devel/master' into devel/graphics
[platform/core/uifw/dali-adaptor.git] / dali / internal / imaging / common / loader-png.cpp
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 #include <dali/internal/imaging/common/loader-png.h>
19
20 #include <cstring>
21
22 #include <png.h>
23 #include <zlib.h>
24
25 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
26 #include <dali/integration-api/debug.h>
27 #include <dali/internal/legacy/tizen/platform-capabilities.h>
28
29 namespace Dali
30 {
31 namespace TizenPlatform
32 {
33 namespace
34 {
35 // simple class to enforce clean-up of PNG structures
36 struct auto_png
37 {
38   auto_png(png_structp& _png, png_infop& _info)
39   : png(_png),
40     info(_info)
41   {
42   }
43
44   ~auto_png()
45   {
46     if(NULL != png)
47     {
48       png_destroy_read_struct(&png, &info, NULL);
49     }
50   }
51
52   png_structp& png;
53   png_infop&   info;
54 }; // struct auto_png;
55
56 bool LoadPngHeader(FILE* fp, unsigned int& width, unsigned int& height, png_structp& png, png_infop& info)
57 {
58   png_byte header[8] = {0};
59
60   // Check header to see if it is a PNG file
61   size_t size = fread(header, 1, 8, fp);
62   if(size != 8)
63   {
64     return false;
65   }
66
67   if(png_sig_cmp(header, 0, 8))
68   {
69     return false;
70   }
71
72   png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
73
74   if(!png)
75   {
76     DALI_LOG_WARNING("Can't create PNG read structure\n");
77     return false;
78   }
79
80   info = png_create_info_struct(png);
81   if(!info)
82   {
83     DALI_LOG_WARNING("png_create_info_struct failed\n");
84     return false;
85   }
86
87   png_set_expand(png);
88
89   if(setjmp(png_jmpbuf(png)))
90   {
91     DALI_LOG_WARNING("error during png_init_io\n");
92     return false;
93   }
94
95   png_init_io(png, fp);
96   png_set_sig_bytes(png, 8);
97
98   // read image info
99   png_read_info(png, info);
100
101   // dimensions
102   width  = png_get_image_width(png, info);
103   height = png_get_image_height(png, info);
104
105   return true;
106 }
107
108 } // namespace
109
110 bool LoadPngHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
111 {
112   png_structp png  = NULL;
113   png_infop   info = NULL;
114   auto_png    autoPng(png, info);
115
116   bool success = LoadPngHeader(input.file, width, height, png, info);
117
118   return success;
119 }
120
121 bool LoadBitmapFromPng(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
122 {
123   png_structp png  = NULL;
124   png_infop   info = NULL;
125   auto_png    autoPng(png, info);
126
127   /// @todo: consider parameters
128   unsigned int y;
129   unsigned int width, height;
130   png_bytep*   rows;
131   unsigned int bpp   = 0; // bytes per pixel
132   bool         valid = false;
133
134   // Load info from the header
135   if(!LoadPngHeader(input.file, width, height, png, info))
136   {
137     return false;
138   }
139
140   Pixel::Format pixelFormat = Pixel::RGBA8888;
141
142   // decide pixel format
143   unsigned int colordepth = png_get_bit_depth(png, info);
144
145   // Ask PNGLib to convert high precision images into something we can use:
146   if(colordepth == 16)
147   {
148     png_set_strip_16(png);
149     colordepth = 8;
150   }
151
152   png_byte colortype = png_get_color_type(png, info);
153
154   if(colortype == PNG_COLOR_TYPE_GRAY ||
155      colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
156   {
157     if(colortype == PNG_COLOR_TYPE_GRAY)
158     {
159       pixelFormat = Pixel::L8;
160       if(png_get_valid(png, info, PNG_INFO_tRNS))
161       {
162         colortype = PNG_COLOR_TYPE_GRAY_ALPHA;
163         /* expand transparency entry -> alpha channel if present */
164         png_set_tRNS_to_alpha(png);
165         pixelFormat = Pixel::LA88;
166       }
167     }
168     else
169     {
170       pixelFormat = Pixel::LA88;
171     }
172
173     if(colordepth < 8)
174     {
175       /* expand gray (w/reduced bits) -> 8-bit RGB if necessary */
176       png_set_expand_gray_1_2_4_to_8(png);
177       /* pack all pixels to byte boundaries */
178       png_set_packing(png);
179     }
180     valid = true;
181   }
182   else if(colortype == PNG_COLOR_TYPE_RGB)
183   {
184     switch(colordepth)
185     {
186       case 8:
187       {
188         pixelFormat = Pixel::RGB888;
189         valid       = true;
190         break;
191       }
192       case 5: /// @todo is this correct for RGB16 5-6-5 ?
193       {
194         pixelFormat = Pixel::RGB565;
195         valid       = true;
196         break;
197       }
198       default:
199       {
200         break;
201       }
202     }
203   }
204   else if(colortype == PNG_COLOR_TYPE_RGBA)
205   {
206     switch(colordepth)
207     {
208       case 8:
209       {
210         pixelFormat = Pixel::RGBA8888;
211         valid       = true;
212         break;
213       }
214       default:
215       {
216         break;
217       }
218     }
219   }
220   else if(colortype == PNG_COLOR_TYPE_PALETTE)
221   {
222     switch(colordepth)
223     {
224       case 1:
225       {
226         pixelFormat = Pixel::LA88;
227         valid       = true;
228         break;
229       }
230
231       case 2:
232       case 4:
233       case 8:
234       {
235         /* Expand paletted or RGB images with transparency to full alpha channels
236          * so the data will be available as RGBA quartets. PNG_INFO_tRNS = 0x10
237          */
238         if(png_get_valid(png, info, PNG_INFO_tRNS) == 0x10)
239         {
240           pixelFormat = Pixel::RGBA8888;
241           valid       = true;
242         }
243         else
244         {
245           pixelFormat = Pixel::RGB888;
246           png_set_packing(png);
247           png_set_packswap(png);
248           png_set_palette_to_rgb(png);
249           valid = true;
250         }
251         break;
252       }
253       default:
254       {
255         break;
256       }
257     }
258   }
259
260   if(!valid)
261   {
262     DALI_LOG_WARNING("Unsupported png format\n");
263     return false;
264   }
265
266   // bytes per pixel
267   bpp = Pixel::GetBytesPerPixel(pixelFormat);
268
269   png_read_update_info(png, info);
270
271   if(setjmp(png_jmpbuf(png)))
272   {
273     DALI_LOG_WARNING("error during png_read_image\n");
274     return false;
275   }
276
277   unsigned int rowBytes = png_get_rowbytes(png, info);
278
279   unsigned int bufferWidth  = GetTextureDimension(width);
280   unsigned int bufferHeight = GetTextureDimension(height);
281   unsigned int stride       = bufferWidth * bpp;
282
283   // not sure if this ever happens
284   if(rowBytes > stride)
285   {
286     stride = GetTextureDimension(rowBytes);
287
288     bpp = stride / bufferWidth;
289     switch(bpp)
290     {
291       case 3:
292         pixelFormat = Pixel::RGB888;
293         break;
294       case 4:
295         pixelFormat = Pixel::RGBA8888;
296         break;
297       default:
298         break;
299     }
300   }
301
302   // decode the whole image into bitmap buffer
303   auto pixels = (bitmap = Dali::Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat)).GetBuffer();
304
305   DALI_ASSERT_DEBUG(pixels);
306   rows = reinterpret_cast<png_bytep*>(malloc(sizeof(png_bytep) * height));
307   if(!rows)
308   {
309     DALI_LOG_ERROR("malloc is failed\n");
310     return false;
311   }
312
313   for(y = 0; y < height; y++)
314   {
315     rows[y] = pixels + y * stride;
316   }
317
318   // decode image
319   png_read_image(png, rows);
320
321   free(rows);
322
323   return true;
324 }
325
326 // simple class to enforce clean-up of PNG structures
327 struct AutoPngWrite
328 {
329   AutoPngWrite(png_structp& _png, png_infop& _info)
330   : png(_png),
331     info(_info)
332   {
333   }
334
335   ~AutoPngWrite()
336   {
337     if(NULL != png)
338     {
339       png_destroy_write_struct(&png, &info);
340     }
341   }
342
343   png_structp& png;
344   png_infop&   info;
345 }; // struct AutoPngWrite;
346
347 namespace
348 {
349 // Custom libpng write callbacks that buffer to a vector instead of a file:
350
351 /**
352    * extern "C" linkage is used because this is a callback that we pass to a C
353    * library which is part of the underlying platform and so potentially compiled
354    * as C rather than C++.
355    * @see http://stackoverflow.com/a/2594222
356    * */
357 extern "C" void WriteData(png_structp png_ptr, png_bytep data, png_size_t length)
358 {
359   DALI_ASSERT_DEBUG(png_ptr && data);
360   if(!png_ptr || !data)
361   {
362     return;
363   }
364   // Make sure we don't try to propagate a C++ exception up the call stack of a pure C library:
365   try
366   {
367     // Recover our buffer for writing into:
368     Vector<unsigned char>* const encoded_img = static_cast<Vector<unsigned char>*>(png_get_io_ptr(png_ptr));
369     if(encoded_img)
370     {
371       const Vector<unsigned char>::SizeType bufferSize = encoded_img->Count();
372       encoded_img->Resize(bufferSize + length); //< Can throw OOM.
373       unsigned char* const bufferBack = encoded_img->Begin() + bufferSize;
374       memcpy(bufferBack, data, length);
375     }
376     else
377     {
378       DALI_LOG_ERROR("PNG buffer for write to memory was passed from libpng as null.\n");
379     }
380   }
381   catch(...)
382   {
383     DALI_LOG_ERROR("C++ Exception caught\n");
384   }
385 }
386
387 /** Override the flush with a NOP to prevent libpng trying cstdlib file io. */
388 extern "C" void FlushData(png_structp png_ptr)
389 {
390 #ifdef DEBUG_ENABLED
391   Debug::LogMessage(Debug::DebugInfo, "PNG Flush");
392 #endif // DEBUG_ENABLED
393 }
394 } // namespace
395
396 /**
397  * Potential improvements:
398  * 1. Detect <= 256 colours and write in palette mode.
399  * 2. Detect grayscale (will early-out quickly for colour images).
400  * 3. Store colour space / gamma correction info related to the device screen?
401  *    http://www.libpng.org/pub/png/book/chapter10.html
402  * 4. Refactor with callers to write straight through to disk and save keeping a big buffer around.
403  * 5. Prealloc buffer (reserve) to input size / <A number greater than 2 (expexcted few realloc but without using lots of memory) | 1 (expected zero reallocs but using a lot of memory)>.
404  * 6. Set the modification time with png_set_tIME(png_ptr, info_ptr, mod_time);
405  * 7. If caller asks for no compression, bypass libpng and blat raw data to
406  *    disk, topped and tailed with header/tail blocks.
407  */
408 bool EncodeToPng(const unsigned char* const pixelBuffer, Vector<unsigned char>& encodedPixels, std::size_t width, std::size_t height, Pixel::Format pixelFormat)
409 {
410   // Translate pixel format enum:
411   int      pngPixelFormat = -1;
412   unsigned pixelBytes     = 0;
413   bool     rgbaOrder      = true;
414
415   // Account for RGB versus BGR and presence of alpha in input pixels:
416   switch(pixelFormat)
417   {
418     case Pixel::RGB888:
419     {
420       pngPixelFormat = PNG_COLOR_TYPE_RGB;
421       pixelBytes     = 3;
422       break;
423     }
424     case Pixel::BGRA8888:
425     {
426       rgbaOrder = false;
427       ///! No break: fall through:
428     }
429     case Pixel::RGBA8888:
430     {
431       pngPixelFormat = PNG_COLOR_TYPE_RGB_ALPHA;
432       pixelBytes     = 4;
433       break;
434     }
435     default:
436     {
437       DALI_LOG_ERROR("Unsupported pixel format for encoding to PNG.\n");
438       return false;
439     }
440   }
441
442   const int interlace = PNG_INTERLACE_NONE;
443
444   png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
445   if(!png_ptr)
446   {
447     return false;
448   }
449   /* Allocate/initialize the image information data.  REQUIRED */
450   png_infop info_ptr = png_create_info_struct(png_ptr);
451   if(!info_ptr)
452   {
453     png_destroy_write_struct(&png_ptr, NULL);
454     return false;
455   }
456
457   /* Set error handling.  REQUIRED if you aren't supplying your own
458    * error handling functions in the png_create_write_struct() call.
459    */
460   if(setjmp(png_jmpbuf(png_ptr)))
461   {
462     png_destroy_write_struct(&png_ptr, &info_ptr);
463     return false;
464   }
465
466   // Since we are going to write to memory instead of a file, lets provide
467   // libpng with a custom write function and ask it to pass back our
468   // Vector buffer each time it calls back to flush data to "file":
469   png_set_write_fn(png_ptr, &encodedPixels, WriteData, FlushData);
470
471   // png_set_compression_level( png_ptr, Z_BEST_COMPRESSION);
472   png_set_compression_level(png_ptr, Z_BEST_SPEED);
473   // png_set_compression_level( png_ptr, Z_NO_COMPRESSION); //! We could just generate png directly without libpng in this case.
474
475   // Explicitly limit the number of filters used per scanline to speed us up:
476   // png_set_filter(png_ptr, 0, PNG_FILTER_NONE); ///!ToDo: Try this once baseline profile is in place.
477   // PNG_FILTER_SUB   |
478   // PNG_FILTER_UP    |
479   // PNG_FILTER_AVE   |
480   // PNG_FILTER_PAETH |
481   // PNG_ALL_FILTERS);
482   // Play with Zlib parameters in optimisation phase:
483   // png_set_compression_mem_level(png_ptr, 8);
484   // png_set_compression_strategy(png_ptr,
485   // Z_DEFAULT_STRATEGY);
486   // png_set_compression_window_bits(png_ptr, 15);
487   // png_set_compression_method(png_ptr, 8);
488   // png_set_compression_buffer_size(png_ptr, 8192)
489
490   // Let lib_png know if the pixel bytes are in BGR(A) order:
491   if(!rgbaOrder)
492   {
493     png_set_bgr(png_ptr);
494   }
495
496   // Set the image information:
497   png_set_IHDR(png_ptr, info_ptr, width, height, 8, pngPixelFormat, interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
498
499   // Start to output the PNG data to our buffer:
500   png_write_info(png_ptr, info_ptr);
501
502   // Walk the rows:
503   const unsigned  row_step = width * pixelBytes;
504   png_bytep       row_ptr  = const_cast<png_bytep>(pixelBuffer);
505   const png_bytep row_end  = row_ptr + height * row_step;
506   for(; row_ptr < row_end; row_ptr += row_step)
507   {
508     png_write_row(png_ptr, row_ptr);
509   }
510
511   /* It is REQUIRED to call this to finish writing the rest of the file */
512   png_write_end(png_ptr, info_ptr);
513   /* Clean up after the write, and free any memory allocated */
514   png_destroy_write_struct(&png_ptr, &info_ptr);
515   return true;
516 }
517
518 } // namespace TizenPlatform
519
520 } // namespace Dali