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