// Copyright 2016 Dolphin Emulator Project // Licensed under GPLv2+ // Refer to the license.txt file included. #include "Common/Image.h" // Versions of libpng older than 1.5 want us to not include setjmp.h before png.h, // so let's include png.h first of all headers #include #include #include #include #include #include #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" namespace Common { static int MultiplyAlpha(int alpha, int color) { const int temp = (alpha * color) + 0x80; return ((temp + (temp >> 8)) >> 8); } static void PremultiplyData(png_structp png, png_row_infop row_info, png_bytep data) { for (png_size_t i = 0; i < row_info->rowbytes; i += 4, data += 4) { const png_byte alpha = data[3]; u32 w; if (alpha == 0) { w = 0; } else { png_byte red = data[0]; png_byte green = data[1]; png_byte blue = data[2]; if (alpha != 0xff) { red = MultiplyAlpha(alpha, red); green = MultiplyAlpha(alpha, green); blue = MultiplyAlpha(alpha, blue); } w = (alpha << 24) | (red << 16) | (green << 8) | (blue << 0); } std::memcpy(data, &w, sizeof(w)); } } struct ReadProgress { const u8* current; const u8* end; }; static void ReadCallback(png_structp png, png_bytep data, png_size_t size) { ReadProgress* progress = static_cast(png_get_io_ptr(png)); for (size_t i = 0; i < size; ++i) { if (progress->current >= progress->end) png_error(png, "Read beyond end of file"); // This makes us longjmp back to LoadPNG data[i] = *(progress->current); ++(progress->current); } } static void PNGErrorCallback(png_structp png, png_const_charp error_msg) { ERROR_LOG(COMMON, "PNG loading error: %s", error_msg); longjmp(png_jmpbuf(png), 1); } #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4611) // VS shows the following warning even though no C++ objects are destroyed in LoadPNG: // "warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable" #endif bool LoadPNG(const std::vector& input, std::vector* data_out, u32* width_out, u32* height_out) { // The const_cast is only required for libpng versions older than 1.5 const bool is_png = !png_sig_cmp(const_cast(input.data()), 0, input.size()); if (!is_png) return false; png_struct* png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, PNGErrorCallback, nullptr); if (!png) return false; png_info* info = png_create_info_struct(png); if (!info) { png_destroy_read_struct(&png, &info, nullptr); return false; } // It would've been nice to use std::vector for this, but using setjmp safely // means that we have to use this manually managed volatile pointer garbage instead. png_byte** volatile row_pointers_volatile = nullptr; if (setjmp(png_jmpbuf(png))) { free(row_pointers_volatile); png_destroy_read_struct(&png, &info, nullptr); return false; } ReadProgress read_progress{input.data(), input.data() + input.size()}; png_set_read_fn(png, &read_progress, ReadCallback); png_read_info(png, info); png_uint_32 width, height; int depth, color_type, interlace; png_get_IHDR(png, info, &width, &height, &depth, &color_type, &interlace, nullptr, nullptr); if (color_type == PNG_COLOR_TYPE_PALETTE) png_set_palette_to_rgb(png); else if (color_type == PNG_COLOR_TYPE_GRAY) png_set_expand_gray_1_2_4_to_8(png); if (png_get_valid(png, info, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(png); if (depth == 16) png_set_strip_16(png); else if (depth < 8) png_set_packing(png); if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(png); if (interlace != PNG_INTERLACE_NONE) png_set_interlace_handling(png); png_set_bgr(png); png_set_filler(png, 0xff, PNG_FILLER_AFTER); png_set_read_user_transform_fn(png, PremultiplyData); png_read_update_info(png, info); png_get_IHDR(png, info, &width, &height, &depth, &color_type, &interlace, nullptr, nullptr); const size_t stride = width * 4; data_out->resize(stride * height); png_byte** row_pointers = static_cast(malloc(height * sizeof(png_byte*))); if (!row_pointers) { png_destroy_read_struct(&png, &info, nullptr); return false; } for (png_uint_32 i = 0; i < height; i++) row_pointers[i] = &(*data_out)[i * stride]; row_pointers_volatile = row_pointers; png_read_image(png, row_pointers); png_read_end(png, info); free(row_pointers); png_destroy_read_struct(&png, &info, nullptr); *width_out = width; *height_out = height; return true; } #ifdef _MSC_VER #pragma warning(pop) #endif } // namespace Common