/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * The Original Code is Copyright (C) 2008 Blender Foundation. * All rights reserved. */ /** \file * \ingroup blendthumb * * Expose #blendthumb_create_png_data_from_thumb that creates the PNG data * but does not write it to a file. */ #include #include #include #include "blendthumb.hh" #include "BLI_endian_defines.h" #include "BLI_endian_switch.h" #include "BLI_vector.hh" static void png_extend_native_int32(blender::Vector &output, int32_t data) { if (ENDIAN_ORDER == L_ENDIAN) { BLI_endian_switch_int32(&data); } output.extend_unchecked(blender::Span((uint8_t *)&data, 4)); } /** The number of bytes each chunk uses on top of the data that's written. */ #define PNG_CHUNK_EXTRA 12 static void png_chunk_create(blender::Vector &output, const uint32_t tag, const blender::Vector &data) { uint32_t crc = crc32(0, nullptr, 0); crc = crc32(crc, (uint8_t *)&tag, sizeof(tag)); crc = crc32(crc, (uint8_t *)data.data(), data.size()); png_extend_native_int32(output, data.size()); output.extend_unchecked(blender::Span((uint8_t *)&tag, sizeof(tag))); output.extend_unchecked(data); png_extend_native_int32(output, crc); } static blender::Vector filtered_rows_from_thumb(const Thumbnail *thumb) { /* In the image data sent to the compression step, each scan-line is preceded by a filter type * byte containing the numeric code of the filter algorithm used for that scan-line. */ const size_t line_size = thumb->width * 4; blender::Vector filtered{}; size_t final_size = thumb->height * (line_size + 1); filtered.reserve(final_size); for (int i = 0; i < thumb->height; i++) { filtered.append_unchecked(0x00); filtered.extend_unchecked(blender::Span(&thumb->data[i * line_size], line_size)); } BLI_assert(final_size == filtered.size()); return filtered; } static std::optional> zlib_compress(const blender::Vector &data) { unsigned long uncompressed_size = data.size(); uLongf compressed_size = compressBound(uncompressed_size); blender::Vector compressed(compressed_size, 0x00); int return_value = compress2((uchar *)compressed.data(), &compressed_size, (uchar *)data.data(), uncompressed_size, Z_NO_COMPRESSION); if (return_value != Z_OK) { /* Something went wrong with compression of data. */ return std::nullopt; } compressed.resize(compressed_size); return compressed; } std::optional> blendthumb_create_png_data_from_thumb( const Thumbnail *thumb) { if (thumb->data.is_empty()) { return std::nullopt; } /* Create `IDAT` chunk data. */ blender::Vector image_data{}; { auto image_data_opt = zlib_compress(filtered_rows_from_thumb(thumb)); if (image_data_opt == std::nullopt) { return std::nullopt; } image_data = *image_data_opt; } /* Create the IHDR chunk data. */ blender::Vector ihdr_data{}; { const size_t ihdr_data_final_size = 4 + 4 + 5; ihdr_data.reserve(ihdr_data_final_size); png_extend_native_int32(ihdr_data, thumb->width); png_extend_native_int32(ihdr_data, thumb->height); ihdr_data.extend_unchecked({ 0x08, /* Bit Depth. */ 0x06, /* Color Type. */ 0x00, /* Compression method. */ 0x00, /* Filter method. */ 0x00, /* Interlace method. */ }); BLI_assert((size_t)ihdr_data.size() == ihdr_data_final_size); } /* Join it all together to create a PNG image. */ blender::Vector png_buf{}; { const size_t png_buf_final_size = ( /* Header. */ 8 + /* `IHDR` chunk. */ (ihdr_data.size() + PNG_CHUNK_EXTRA) + /* `IDAT` chunk. */ (image_data.size() + PNG_CHUNK_EXTRA) + /* `IEND` chunk. */ PNG_CHUNK_EXTRA); png_buf.reserve(png_buf_final_size); /* This is the standard PNG file header. Every PNG file starts with it. */ png_buf.extend_unchecked({0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}); png_chunk_create(png_buf, MAKE_ID('I', 'H', 'D', 'R'), ihdr_data); png_chunk_create(png_buf, MAKE_ID('I', 'D', 'A', 'T'), image_data); png_chunk_create(png_buf, MAKE_ID('I', 'E', 'N', 'D'), {}); BLI_assert((size_t)png_buf.size() == png_buf_final_size); } return png_buf; }