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