2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 #include "loader-png.h"
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"
34 using Integration::Bitmap;
35 using Dali::Integration::PixelBuffer;
43 // simple class to enforce clean-up of PNG structures
46 auto_png(png_structp& _png, png_infop& _info)
56 png_destroy_read_struct(&png, &info, NULL);
62 }; // struct auto_png;
64 bool LoadPngHeader(FILE *fp, unsigned int &width, unsigned int &height, png_structp &png, png_infop &info)
66 png_byte header[8] = { 0 };
68 // Check header to see if it is a PNG file
69 size_t size = fread(header, 1, 8, fp);
75 if(png_sig_cmp(header, 0, 8))
80 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
84 DALI_LOG_WARNING("Can't create PNG read structure\n");
88 info = png_create_info_struct(png);
91 DALI_LOG_WARNING("png_create_info_struct failed\n");
97 if(setjmp(png_jmpbuf(png)))
99 DALI_LOG_WARNING("error during png_init_io\n");
103 png_init_io(png, fp);
104 png_set_sig_bytes(png, 8);
107 png_read_info(png, info);
110 width = png_get_image_width(png, info);
111 height = png_get_image_height(png, info);
116 } // namespace - anonymous
118 bool LoadPngHeader(FILE *fp, const ImageAttributes& attributes, unsigned int &width, unsigned int &height )
120 png_structp png = NULL;
121 png_infop info = NULL;
122 auto_png autoPng(png, info);
124 bool success = LoadPngHeader(fp, width, height, png, info);
128 bool crop = (attributes.GetScalingMode() == Dali::ImageAttributes::ScaleToFill);
131 Size req((float) attributes.GetWidth(), (float) attributes.GetHeight());
132 const Size orig((float)width, (float)height);
134 // calculate actual width, height
135 req = FitScaleToFill(req, orig);
137 width = (int) req.width;
138 height = (int) req.height;
144 bool LoadBitmapFromPng( FILE *fp, Bitmap& bitmap, ImageAttributes& attributes, const ResourceLoadingClient& client )
146 png_structp png = NULL;
147 png_infop info = NULL;
148 auto_png autoPng(png, info);
150 /// @todo: consider parameters
152 unsigned int width, height;
153 unsigned char *pixels;
155 unsigned int bpp = 0; // bytes per pixel
158 // Load info from the header
159 if( !LoadPngHeader(fp, width, height, png, info) )
164 Pixel::Format pixelFormat = Pixel::RGBA8888;
166 // decide pixel format
167 unsigned int colordepth = png_get_bit_depth(png, info);
169 // Ask PNGLib to convert high precision images into something we can use:
170 if (colordepth == 16)
172 png_set_strip_16(png);
176 png_byte colortype = png_get_color_type(png, info);
178 if(colortype == PNG_COLOR_TYPE_GRAY)
184 pixelFormat = Pixel::L8;
194 else if(colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
200 pixelFormat = Pixel::LA88;
210 else if(colortype == PNG_COLOR_TYPE_RGB )
216 pixelFormat = Pixel::RGB888;
220 case 5: /// @todo is this correct for RGB16 5-6-5 ?
222 pixelFormat = Pixel::RGB565;
232 else if(colortype == PNG_COLOR_TYPE_RGBA)
238 pixelFormat = Pixel::RGBA8888;
248 else if(colortype == PNG_COLOR_TYPE_PALETTE)
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
259 if(png_get_valid(png, info, PNG_INFO_tRNS) == 0x10)
261 pixelFormat = Pixel::RGBA8888;
266 pixelFormat = Pixel::RGB888;
267 png_set_packing(png);
268 png_set_packswap(png);
269 png_set_palette_to_rgb(png);
283 DALI_LOG_WARNING( "Unsupported png format\n" );
288 bpp = Pixel::GetBytesPerPixel(pixelFormat);
290 png_read_update_info(png, info);
292 if(setjmp(png_jmpbuf(png)))
294 DALI_LOG_WARNING("error during png_read_image\n");
298 unsigned int rowBytes = png_get_rowbytes(png, info);
300 unsigned int bufferWidth = GetTextureDimension(width);
301 unsigned int bufferHeight = GetTextureDimension(height);
302 unsigned int stride = bufferWidth*bpp;
304 // not sure if this ever happens
305 if( rowBytes > stride )
307 stride = GetTextureDimension(rowBytes);
308 bufferWidth = stride / bpp;
311 /// @todo support more scaling types
312 bool crop = (attributes.GetScalingMode() == Dali::ImageAttributes::ScaleToFill);
316 pixels = new unsigned char[stride*bufferHeight];
320 // decode the whole image into bitmap buffer
321 pixels = bitmap.GetPackedPixelsProfile()->ReserveBuffer(pixelFormat, width, height, bufferWidth, bufferHeight);
324 DALI_ASSERT_DEBUG(pixels);
325 rows = (png_bytep*) malloc(sizeof(png_bytep) * height);
326 for(y=0; y<height; y++)
328 rows[y] = (png_byte*) (pixels + y * stride);
332 png_read_image(png, rows);
334 // copy part of the buffer to bitmap
337 Size req((float) attributes.GetWidth(), (float) attributes.GetHeight());
338 const Size orig((float)width, (float)height);
340 // calculate actual width, height
341 req = FitScaleToFill(req, orig);
343 // modify attributes with result
344 attributes.SetSize((int) req.width, (int) req.height);
345 attributes.SetPixelFormat(pixelFormat);
347 bufferWidth = GetTextureDimension(attributes.GetWidth());
348 bufferHeight = GetTextureDimension(attributes.GetHeight());
350 // cropped buffer's stride
351 int lstride = bufferWidth*bpp;
354 int x_offset = ((width - attributes.GetWidth()) / 2) * bpp;
355 int y_offset = ((height - attributes.GetHeight()) / 2) * stride;
357 // allocate bitmap buffer using requested size
358 unsigned char *bitmapBuffer = bitmap.GetPackedPixelsProfile()->ReserveBuffer(pixelFormat, attributes.GetWidth(), attributes.GetHeight(), bufferWidth, bufferHeight);
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)
365 memcpy(bufptr, lptr, attributes.GetWidth()*bpp);
374 // set the attributes
375 attributes.SetSize(width, height);
376 attributes.SetPixelFormat(pixelFormat);
379 bitmap.GetPackedPixelsProfile()->TestForTransparency();
386 // simple class to enforce clean-up of PNG structures
389 AutoPngWrite(png_structp& _png, png_infop& _info)
393 DALI_ASSERT_DEBUG(&_png != 0 && &_info != 0);
400 png_destroy_write_struct(&png, &info);
406 }; // struct AutoPngWrite;
410 // Custom libpng write callbacks that buffer to a vector instead of a file:
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
418 extern "C" void WriteData(png_structp png_ptr, png_bytep data, png_size_t length)
420 using Dali::SlpPlatform::PngBuffer;
421 DALI_ASSERT_DEBUG(png_ptr && data);
422 if(!png_ptr || !data)
426 // Make sure we don't try to propagate a C++ exception up the call stack of a pure C library:
428 // Recover our buffer for writing into:
429 PngBuffer* const encoded_img = (PngBuffer*) png_get_io_ptr(png_ptr);
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);
439 DALI_LOG_ERROR("PNG buffer for write to memory was passed from libpng as null.");
443 DALI_LOG_ERROR("C++ Exception caught");
447 /** Override the flush with a NOP to prevent libpng trying cstdlib file io. */
448 extern "C" void FlushData(png_structp png_ptr)
451 Debug::LogMessage(Debug::DebugInfo, "PNG Flush");
452 #endif // DEBUG_ENABLED
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.
468 bool EncodeToPng( const unsigned char* const pixelBuffer, PngBuffer& encodedPixels, std::size_t width, std::size_t height, Pixel::Format pixelFormat )
470 // Translate pixel format enum:
471 int pngPixelFormat = -1;
472 unsigned pixelBytes = 0;
473 bool rgbaOrder = true;
475 // Account for RGB versus BGR and presence of alpha in input pixels:
476 switch( pixelFormat )
480 pngPixelFormat = PNG_COLOR_TYPE_RGB;
484 case Pixel::BGRA8888:
487 ///! No break: fall through:
489 case Pixel::RGBA8888:
491 pngPixelFormat = PNG_COLOR_TYPE_RGB_ALPHA;
497 DALI_LOG_ERROR( "Unsupported pixel format for encoding to PNG." );
502 const int interlace = PNG_INTERLACE_NONE;
504 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
509 /* Allocate/initialize the image information data. REQUIRED */
510 png_infop info_ptr = png_create_info_struct( png_ptr );
513 png_destroy_write_struct(&png_ptr, NULL);
517 /* Set error handling. REQUIRED if you aren't supplying your own
518 * error handling functions in the png_create_write_struct() call.
520 if(setjmp(png_jmpbuf(png_ptr)))
522 png_destroy_write_struct(&png_ptr, &info_ptr);
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);
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.
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.
540 // PNG_FILTER_PAETH |
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)
550 // Let lib_png know if the pixel bytes are in BGR(A) order:
553 png_set_bgr( png_ptr );
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);
561 // Start to output the PNG data to our buffer:
562 png_write_info(png_ptr, info_ptr);
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)
570 png_write_row(png_ptr, row_ptr);
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);
580 } // namespace SlpPlatform