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