2 * Copyright (c) 2024 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 <dali/internal/imaging/common/loader-png.h>
25 #include <dali/devel-api/adaptor-framework/pixel-buffer.h>
26 #include <dali/integration-api/debug.h>
27 #include <dali/internal/legacy/tizen/platform-capabilities.h>
31 namespace TizenPlatform
35 // simple class to enforce clean-up of PNG structures
38 auto_png(png_structp& _png, png_infop& _info)
48 png_destroy_read_struct(&png, &info, NULL);
54 }; // struct auto_png;
56 bool LoadPngHeader(FILE* fp, unsigned int& width, unsigned int& height, png_structp& png, png_infop& info)
58 png_byte header[8] = {0};
60 // Check header to see if it is a PNG file
61 size_t size = fread(header, 1, 8, fp);
62 if(DALI_UNLIKELY(size != 8))
64 DALI_LOG_ERROR("fread failed\n");
68 if(DALI_UNLIKELY(png_sig_cmp(header, 0, 8)))
70 DALI_LOG_ERROR("png_sig_cmp failed\n");
74 png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
76 if(DALI_UNLIKELY(!png))
78 DALI_LOG_ERROR("Can't create PNG read structure\n");
82 info = png_create_info_struct(png);
83 if(DALI_UNLIKELY(!info))
85 DALI_LOG_ERROR("png_create_info_struct failed\n");
91 if(DALI_UNLIKELY(setjmp(png_jmpbuf(png))))
93 DALI_LOG_ERROR("error during png_init_io\n");
98 png_set_sig_bytes(png, 8);
101 png_read_info(png, info);
104 width = png_get_image_width(png, info);
105 height = png_get_image_height(png, info);
112 bool LoadPngHeader(const Dali::ImageLoader::Input& input, unsigned int& width, unsigned int& height)
114 png_structp png = NULL;
115 png_infop info = NULL;
116 auto_png autoPng(png, info);
118 bool success = LoadPngHeader(input.file, width, height, png, info);
123 bool LoadBitmapFromPng(const Dali::ImageLoader::Input& input, Dali::Devel::PixelBuffer& bitmap)
125 png_structp png = NULL;
126 png_infop info = NULL;
127 auto_png autoPng(png, info);
129 /// @todo: consider parameters
131 unsigned int width, height;
133 unsigned int bpp = 0; // bytes per pixel
136 // Load info from the header
137 if(DALI_UNLIKELY(!LoadPngHeader(input.file, width, height, png, info)))
142 Pixel::Format pixelFormat = Pixel::RGBA8888;
144 // decide pixel format
145 unsigned int colordepth = png_get_bit_depth(png, info);
147 // Ask PNGLib to convert high precision images into something we can use:
150 png_set_strip_16(png);
154 png_byte colortype = png_get_color_type(png, info);
156 if(colortype == PNG_COLOR_TYPE_GRAY ||
157 colortype == PNG_COLOR_TYPE_GRAY_ALPHA)
159 if(colortype == PNG_COLOR_TYPE_GRAY)
161 pixelFormat = Pixel::L8;
162 if(png_get_valid(png, info, PNG_INFO_tRNS))
164 colortype = PNG_COLOR_TYPE_GRAY_ALPHA;
165 /* expand transparency entry -> alpha channel if present */
166 png_set_tRNS_to_alpha(png);
167 pixelFormat = Pixel::LA88;
172 pixelFormat = Pixel::LA88;
177 /* expand gray (w/reduced bits) -> 8-bit RGB if necessary */
178 png_set_expand_gray_1_2_4_to_8(png);
179 /* pack all pixels to byte boundaries */
180 png_set_packing(png);
184 else if(colortype == PNG_COLOR_TYPE_RGB)
190 pixelFormat = Pixel::RGB888;
194 case 5: /// @todo is this correct for RGB16 5-6-5 ?
196 pixelFormat = Pixel::RGB565;
206 else if(colortype == PNG_COLOR_TYPE_RGBA)
212 pixelFormat = Pixel::RGBA8888;
222 else if(colortype == PNG_COLOR_TYPE_PALETTE)
228 pixelFormat = Pixel::LA88;
237 /* Expand paletted or RGB images with transparency to full alpha channels
238 * so the data will be available as RGBA quartets. PNG_INFO_tRNS = 0x10
240 if(png_get_valid(png, info, PNG_INFO_tRNS) == 0x10)
242 pixelFormat = Pixel::RGBA8888;
247 pixelFormat = Pixel::RGB888;
248 png_set_packing(png);
249 png_set_packswap(png);
250 png_set_palette_to_rgb(png);
262 if(DALI_UNLIKELY(!valid))
264 DALI_LOG_ERROR("Unsupported png format\n");
269 bpp = Pixel::GetBytesPerPixel(pixelFormat);
271 png_read_update_info(png, info);
273 if(DALI_UNLIKELY(setjmp(png_jmpbuf(png))))
275 DALI_LOG_ERROR("error during png_read_image\n");
279 unsigned int rowBytes = png_get_rowbytes(png, info);
281 unsigned int bufferWidth = GetTextureDimension(width);
282 unsigned int bufferHeight = GetTextureDimension(height);
283 unsigned int stride = bufferWidth * bpp;
285 // not sure if this ever happens
286 if(rowBytes > stride)
288 stride = GetTextureDimension(rowBytes);
290 bpp = stride / bufferWidth;
294 pixelFormat = Pixel::RGB888;
297 pixelFormat = Pixel::RGBA8888;
304 // decode the whole image into bitmap buffer
305 auto pixels = (bitmap = Dali::Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat)).GetBuffer();
307 DALI_ASSERT_DEBUG(pixels);
311 DALI_LOG_ERROR("PixelBuffer couldn't be created\n");
315 rows = reinterpret_cast<png_bytep*>(malloc(sizeof(png_bytep) * height));
316 if(DALI_UNLIKELY(!rows))
318 DALI_LOG_ERROR("malloc is failed. request malloc size : %zu\n", sizeof(png_bytep) * height);
322 for(y = 0; y < height; y++)
324 rows[y] = pixels + y * stride;
328 png_read_image(png, rows);
335 // simple class to enforce clean-up of PNG structures
338 AutoPngWrite(png_structp& _png, png_infop& _info)
348 png_destroy_write_struct(&png, &info);
354 }; // struct AutoPngWrite;
358 // Custom libpng write callbacks that buffer to a vector instead of a file:
361 * extern "C" linkage is used because this is a callback that we pass to a C
362 * library which is part of the underlying platform and so potentially compiled
363 * as C rather than C++.
364 * @see http://stackoverflow.com/a/2594222
366 extern "C" void WriteData(png_structp png_ptr, png_bytep data, png_size_t length)
368 DALI_ASSERT_DEBUG(png_ptr && data);
369 if(!png_ptr || !data)
373 // Make sure we don't try to propagate a C++ exception up the call stack of a pure C library:
376 // Recover our buffer for writing into:
377 Vector<unsigned char>* const encoded_img = static_cast<Vector<unsigned char>*>(png_get_io_ptr(png_ptr));
380 const Vector<unsigned char>::SizeType bufferSize = encoded_img->Count();
381 encoded_img->ResizeUninitialized(bufferSize + length); //< Can throw OOM.
382 unsigned char* const bufferBack = encoded_img->Begin() + bufferSize;
383 memcpy(bufferBack, data, length);
387 DALI_LOG_ERROR("PNG buffer for write to memory was passed from libpng as null.\n");
392 DALI_LOG_ERROR("C++ Exception caught\n");
396 /** Override the flush with a NOP to prevent libpng trying cstdlib file io. */
397 extern "C" void FlushData(png_structp png_ptr)
400 Debug::LogMessageWithFunctionLine(Debug::INFO, "PNG Flush");
401 #endif // DEBUG_ENABLED
406 * Potential improvements:
407 * 1. Detect <= 256 colours and write in palette mode.
408 * 2. Detect grayscale (will early-out quickly for colour images).
409 * 3. Store colour space / gamma correction info related to the device screen?
410 * http://www.libpng.org/pub/png/book/chapter10.html
411 * 4. Refactor with callers to write straight through to disk and save keeping a big buffer around.
412 * 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)>.
413 * 6. Set the modification time with png_set_tIME(png_ptr, info_ptr, mod_time);
414 * 7. If caller asks for no compression, bypass libpng and blat raw data to
415 * disk, topped and tailed with header/tail blocks.
417 bool EncodeToPng(const unsigned char* const pixelBuffer, Vector<unsigned char>& encodedPixels, std::size_t width, std::size_t height, Pixel::Format pixelFormat)
419 // Translate pixel format enum:
420 int pngPixelFormat = -1;
421 unsigned pixelBytes = 0;
422 bool rgbaOrder = true;
424 // Account for RGB versus BGR and presence of alpha in input pixels:
429 pngPixelFormat = PNG_COLOR_TYPE_GRAY;
435 pngPixelFormat = PNG_COLOR_TYPE_GRAY_ALPHA;
441 pngPixelFormat = PNG_COLOR_TYPE_RGB;
445 case Pixel::BGRA8888:
448 ///! No break: fall through:
450 case Pixel::RGBA8888:
452 pngPixelFormat = PNG_COLOR_TYPE_RGB_ALPHA;
458 DALI_LOG_ERROR("Unsupported pixel format for encoding to PNG. Format enum : [%d]\n", pixelFormat);
463 const int interlace = PNG_INTERLACE_NONE;
465 png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
466 if(DALI_UNLIKELY(!png_ptr))
468 DALI_LOG_ERROR("Fail to create png_structp for png version" PNG_LIBPNG_VER_STRING "\n");
471 /* Allocate/initialize the image information data. REQUIRED */
472 png_infop info_ptr = png_create_info_struct(png_ptr);
473 if(DALI_UNLIKELY(!info_ptr))
475 DALI_LOG_ERROR("Fail to create png_infop\n");
476 png_destroy_write_struct(&png_ptr, NULL);
480 /* Set error handling. REQUIRED if you aren't supplying your own
481 * error handling functions in the png_create_write_struct() call.
483 if(DALI_UNLIKELY(setjmp(png_jmpbuf(png_ptr))))
485 DALI_LOG_ERROR("Fail to png_jmpbuf\n");
486 png_destroy_write_struct(&png_ptr, &info_ptr);
490 // Since we are going to write to memory instead of a file, lets provide
491 // libpng with a custom write function and ask it to pass back our
492 // Vector buffer each time it calls back to flush data to "file":
493 png_set_write_fn(png_ptr, &encodedPixels, WriteData, FlushData);
495 // png_set_compression_level( png_ptr, Z_BEST_COMPRESSION);
496 png_set_compression_level(png_ptr, Z_BEST_SPEED);
497 // png_set_compression_level( png_ptr, Z_NO_COMPRESSION); //! We could just generate png directly without libpng in this case.
499 // Explicitly limit the number of filters used per scanline to speed us up:
500 // png_set_filter(png_ptr, 0, PNG_FILTER_NONE); ///!ToDo: Try this once baseline profile is in place.
504 // PNG_FILTER_PAETH |
506 // Play with Zlib parameters in optimisation phase:
507 // png_set_compression_mem_level(png_ptr, 8);
508 // png_set_compression_strategy(png_ptr,
509 // Z_DEFAULT_STRATEGY);
510 // png_set_compression_window_bits(png_ptr, 15);
511 // png_set_compression_method(png_ptr, 8);
512 // png_set_compression_buffer_size(png_ptr, 8192)
514 // Let lib_png know if the pixel bytes are in BGR(A) order:
517 png_set_bgr(png_ptr);
520 // Set the image information:
521 png_set_IHDR(png_ptr, info_ptr, width, height, 8, pngPixelFormat, interlace, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
523 // Start to output the PNG data to our buffer:
524 png_write_info(png_ptr, info_ptr);
527 const unsigned row_step = width * pixelBytes;
528 png_bytep row_ptr = const_cast<png_bytep>(pixelBuffer);
529 const png_bytep row_end = row_ptr + height * row_step;
530 for(; row_ptr < row_end; row_ptr += row_step)
532 png_write_row(png_ptr, row_ptr);
535 /* It is REQUIRED to call this to finish writing the rest of the file */
536 png_write_end(png_ptr, info_ptr);
537 /* Clean up after the write, and free any memory allocated */
538 png_destroy_write_struct(&png_ptr, &info_ptr);
542 } // namespace TizenPlatform