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