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