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