2016-05-21 10:31:04 -05:00
|
|
|
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
|
|
|
|
/*
|
|
|
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
|
*/
|
|
|
|
|
|
2020-04-18 03:39:50 -05:00
|
|
|
|
#pragma once
|
|
|
|
|
|
2015-11-20 09:39:44 -06:00
|
|
|
|
/* cairo - a vector graphics library with display and print output
|
|
|
|
|
*
|
|
|
|
|
* Copyright © 2003 University of Southern California
|
|
|
|
|
*
|
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
|
* modify it either under the terms of the GNU Lesser General Public
|
|
|
|
|
* License version 2.1 as published by the Free Software Foundation
|
|
|
|
|
* (the "LGPL") or, at your option, under the terms of the Mozilla
|
|
|
|
|
* Public License Version 1.1 (the "MPL"). If you do not alter this
|
|
|
|
|
* notice, a recipient may use your version of this file under either
|
|
|
|
|
* the MPL or the LGPL.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the LGPL along with this library
|
|
|
|
|
* in the file COPYING-LGPL-2.1; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
|
|
|
|
|
* You should have received a copy of the MPL along with this library
|
|
|
|
|
* in the file COPYING-MPL-1.1
|
|
|
|
|
*
|
|
|
|
|
* The contents of this file are subject to the Mozilla Public License
|
|
|
|
|
* Version 1.1 (the "License"); you may not use this file except in
|
|
|
|
|
* compliance with the License. You may obtain a copy of the License at
|
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
|
*
|
|
|
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
|
|
|
|
|
* OF ANY KIND, either express or implied. See the LGPL or the MPL for
|
|
|
|
|
* the specific language governing rights and limitations.
|
|
|
|
|
*
|
|
|
|
|
* The Original Code is the cairo graphics library.
|
|
|
|
|
*
|
|
|
|
|
* The Initial Developer of the Original Code is University of Southern
|
|
|
|
|
* California.
|
|
|
|
|
*
|
|
|
|
|
* Contributor(s):
|
|
|
|
|
* Carl D. Worth <cworth@cworth.org>
|
|
|
|
|
* Kristian Høgsberg <krh@redhat.com>
|
|
|
|
|
* Chris Wilson <chris@chris-wilson.co.uk>
|
|
|
|
|
*/
|
|
|
|
|
|
2016-05-21 10:31:04 -05:00
|
|
|
|
#define PNG_SKIP_SETJMP_CHECK
|
|
|
|
|
#include <png.h>
|
2018-10-31 19:06:29 -05:00
|
|
|
|
#include <zlib.h>
|
2016-05-21 10:31:04 -05:00
|
|
|
|
|
2016-08-06 10:24:49 -05:00
|
|
|
|
#include <cassert>
|
2018-10-31 19:06:29 -05:00
|
|
|
|
#include <chrono>
|
2021-06-10 06:37:24 -05:00
|
|
|
|
#include <iomanip>
|
2016-08-06 10:24:49 -05:00
|
|
|
|
|
2019-03-22 08:49:47 -05:00
|
|
|
|
#ifdef IOS
|
|
|
|
|
#include <Foundation/Foundation.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
2018-10-31 19:06:29 -05:00
|
|
|
|
#include "Log.hpp"
|
2016-11-27 09:38:35 -06:00
|
|
|
|
#include "SpookyV2.h"
|
2021-06-22 07:40:48 -05:00
|
|
|
|
#include "TraceEvent.hpp"
|
2016-11-27 09:38:35 -06:00
|
|
|
|
|
2017-01-04 05:35:26 -06:00
|
|
|
|
namespace Png
|
2016-05-21 10:31:04 -05:00
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
// Callback functions for libpng
|
|
|
|
|
extern "C"
|
|
|
|
|
{
|
|
|
|
|
static void user_write_status_fn(png_structp, png_uint_32, int)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void user_write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
|
|
|
|
|
{
|
2016-12-23 01:31:35 -06:00
|
|
|
|
std::vector<char>* outputp = static_cast<std::vector<char>*>(png_get_io_ptr(png_ptr));
|
2016-05-21 10:31:04 -05:00
|
|
|
|
const size_t oldsize = outputp->size();
|
|
|
|
|
outputp->resize(oldsize + length);
|
|
|
|
|
std::memcpy(outputp->data() + oldsize, data, length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void user_flush_fn(png_structp)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2015-11-20 09:39:44 -06:00
|
|
|
|
/* Unpremultiplies data and converts native endian ARGB => RGBA bytes */
|
|
|
|
|
static void
|
|
|
|
|
unpremultiply_data (png_structp /*png*/, png_row_infop row_info, png_bytep data)
|
|
|
|
|
{
|
|
|
|
|
unsigned int i;
|
|
|
|
|
|
2015-11-24 02:41:59 -06:00
|
|
|
|
for (i = 0; i < row_info->rowbytes; i += 4)
|
|
|
|
|
{
|
2015-11-20 09:39:44 -06:00
|
|
|
|
uint8_t *b = &data[i];
|
2021-01-16 05:13:58 -06:00
|
|
|
|
uint32_t pix;
|
2015-11-20 09:39:44 -06:00
|
|
|
|
uint8_t alpha;
|
|
|
|
|
|
2021-01-16 05:13:58 -06:00
|
|
|
|
std::memcpy (&pix, b, sizeof (uint32_t));
|
|
|
|
|
|
|
|
|
|
alpha = (pix & 0xff000000) >> 24;
|
|
|
|
|
if (alpha == 255)
|
|
|
|
|
{
|
|
|
|
|
b[0] = ((pix & 0xff0000) >> 16);
|
|
|
|
|
b[1] = ((pix & 0x00ff00) >> 8);
|
|
|
|
|
b[2] = ((pix & 0x0000ff) >> 0);
|
|
|
|
|
b[3] = 255;
|
|
|
|
|
}
|
|
|
|
|
else if (alpha == 0)
|
2015-11-24 02:41:59 -06:00
|
|
|
|
{
|
2015-11-20 09:39:44 -06:00
|
|
|
|
b[0] = b[1] = b[2] = b[3] = 0;
|
2015-11-24 02:41:59 -06:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2021-01-16 05:13:58 -06:00
|
|
|
|
b[0] = (((pix & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
|
|
|
|
|
b[1] = (((pix & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
|
|
|
|
|
b[2] = (((pix & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
|
2015-11-20 09:39:44 -06:00
|
|
|
|
b[3] = alpha;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-11-24 02:41:59 -06:00
|
|
|
|
|
2019-09-21 20:10:18 -05:00
|
|
|
|
/// This function uses setjmp which may clobbers non-trivial objects.
|
|
|
|
|
/// So we can't use logging or create complex C++ objects in this frame.
|
|
|
|
|
/// Specifically, logging uses std::string objects, and GCC gives the following:
|
|
|
|
|
/// error: variable ‘__capacity’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered]
|
|
|
|
|
/// In practice, this is bogus, since __capacity is has a trivial type, but this error
|
|
|
|
|
/// shows up with the sanitizers. But technically we shouldn't mix C++ objects with setjmp.
|
|
|
|
|
/// Sadly, older libpng headers don't use const for the pixmap pointer parameter to
|
|
|
|
|
/// png_write_row(), so can't use const here for pixmap.
|
|
|
|
|
inline bool impl_encodeSubBufferToPNG(unsigned char* pixmap, size_t startX, size_t startY,
|
|
|
|
|
int width, int height, int bufferWidth, int bufferHeight,
|
|
|
|
|
std::vector<char>& output, LibreOfficeKitTileMode mode)
|
2016-05-21 10:31:04 -05:00
|
|
|
|
{
|
|
|
|
|
if (bufferWidth < width || bufferHeight < height)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
|
|
|
|
|
|
|
|
|
png_infop info_ptr = png_create_info_struct(png_ptr);
|
|
|
|
|
|
|
|
|
|
if (setjmp(png_jmpbuf(png_ptr)))
|
|
|
|
|
{
|
|
|
|
|
png_destroy_write_struct(&png_ptr, nullptr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-12 05:16:40 -06:00
|
|
|
|
#if MOBILEAPP
|
2018-10-31 19:06:29 -05:00
|
|
|
|
png_set_compression_level(png_ptr, Z_BEST_SPEED);
|
2019-04-19 19:09:22 -05:00
|
|
|
|
#else
|
|
|
|
|
// Level 4 gives virtually identical compression
|
|
|
|
|
// ratio to level 6, but is between 5-10% faster.
|
|
|
|
|
// Level 3 runs almost twice as fast, but the
|
|
|
|
|
// output is typically 2-3x larger.
|
|
|
|
|
png_set_compression_level(png_ptr, 4);
|
2018-10-31 19:06:29 -05:00
|
|
|
|
#endif
|
|
|
|
|
|
2019-03-22 08:49:47 -05:00
|
|
|
|
#ifdef IOS
|
|
|
|
|
auto initialSize = output.size();
|
|
|
|
|
#endif
|
|
|
|
|
|
2019-09-21 20:10:18 -05:00
|
|
|
|
png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE,
|
|
|
|
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
2016-05-21 10:31:04 -05:00
|
|
|
|
|
|
|
|
|
png_set_write_fn(png_ptr, &output, user_write_fn, user_flush_fn);
|
|
|
|
|
png_set_write_status_fn(png_ptr, user_write_status_fn);
|
|
|
|
|
|
|
|
|
|
png_write_info(png_ptr, info_ptr);
|
|
|
|
|
|
|
|
|
|
if (mode == LOK_TILEMODE_BGRA)
|
|
|
|
|
{
|
|
|
|
|
png_set_write_user_transform_fn (png_ptr, unpremultiply_data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int y = 0; y < height; ++y)
|
|
|
|
|
{
|
|
|
|
|
size_t position = ((startY + y) * bufferWidth * 4) + (startX * 4);
|
|
|
|
|
png_write_row(png_ptr, pixmap + position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
png_write_end(png_ptr, info_ptr);
|
|
|
|
|
|
|
|
|
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
|
|
|
|
|
2019-03-22 08:49:47 -05:00
|
|
|
|
#ifdef IOS
|
|
|
|
|
auto base64 = [[NSData dataWithBytesNoCopy:output.data() + initialSize length:(output.size() - initialSize) freeWhenDone:NO] base64EncodedDataWithOptions:0];
|
|
|
|
|
|
|
|
|
|
const char dataURLStart[] = "data:image/png;base64,";
|
|
|
|
|
|
|
|
|
|
output.resize(initialSize);
|
|
|
|
|
output.insert(output.end(), dataURLStart, dataURLStart + sizeof(dataURLStart)-1);
|
|
|
|
|
output.insert(output.end(), (char*)base64.bytes, (char*)base64.bytes + base64.length);
|
|
|
|
|
#endif
|
|
|
|
|
|
2016-05-21 10:31:04 -05:00
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-21 20:10:18 -05:00
|
|
|
|
/// Sadly, older libpng headers don't use const for the pixmap pointer parameter to
|
|
|
|
|
/// png_write_row(), so can't use const here for pixmap.
|
|
|
|
|
inline bool encodeSubBufferToPNG(unsigned char* pixmap, size_t startX, size_t startY, int width,
|
|
|
|
|
int height, int bufferWidth, int bufferHeight,
|
|
|
|
|
std::vector<char>& output, LibreOfficeKitTileMode mode)
|
|
|
|
|
{
|
2021-06-22 07:40:48 -05:00
|
|
|
|
ProfileZone pz("encodeSubBufferToPNG");
|
|
|
|
|
|
2019-09-21 20:10:18 -05:00
|
|
|
|
const auto start = std::chrono::steady_clock::now();
|
|
|
|
|
|
|
|
|
|
const bool res = impl_encodeSubBufferToPNG(pixmap, startX, startY, width, height, bufferWidth,
|
|
|
|
|
bufferHeight, output, mode);
|
|
|
|
|
if (Log::traceEnabled())
|
|
|
|
|
{
|
|
|
|
|
const auto end = std::chrono::steady_clock::now();
|
|
|
|
|
|
2020-12-06 21:45:46 -06:00
|
|
|
|
std::chrono::milliseconds duration
|
|
|
|
|
= std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
2019-09-21 20:10:18 -05:00
|
|
|
|
|
2020-12-06 21:45:46 -06:00
|
|
|
|
static std::chrono::milliseconds totalDuration;
|
2019-09-21 20:10:18 -05:00
|
|
|
|
static int nCalls = 0;
|
|
|
|
|
|
|
|
|
|
totalDuration += duration;
|
|
|
|
|
++nCalls;
|
2021-06-10 06:37:24 -05:00
|
|
|
|
|
|
|
|
|
static uint64_t totalPixelBytes = 0;
|
|
|
|
|
static uint64_t totalOutputBytes = 0;
|
|
|
|
|
|
|
|
|
|
totalPixelBytes += (width * height * 4);
|
|
|
|
|
totalOutputBytes += output.size();
|
|
|
|
|
|
2019-09-21 20:10:18 -05:00
|
|
|
|
LOG_TRC("PNG compression took "
|
2021-06-10 06:37:24 -05:00
|
|
|
|
<< duration << " (" << output.size() << " bytes from " << (width * height * 4) << "). Average after " << nCalls
|
|
|
|
|
<< " calls: " << (totalDuration.count() / static_cast<double>(nCalls)) << "ms, "
|
|
|
|
|
<< (totalOutputBytes / static_cast<double>(nCalls)) << " bytes, "
|
|
|
|
|
<< std::setprecision(2) << (100. * totalOutputBytes / totalPixelBytes) << "% compression.");
|
2019-09-21 20:10:18 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-21 10:31:04 -05:00
|
|
|
|
inline
|
|
|
|
|
bool encodeBufferToPNG(unsigned char* pixmap, int width, int height,
|
|
|
|
|
std::vector<char>& output, LibreOfficeKitTileMode mode)
|
|
|
|
|
{
|
|
|
|
|
return encodeSubBufferToPNG(pixmap, 0, 0, width, height, width, height, output, mode);
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-21 05:42:01 -06:00
|
|
|
|
inline
|
|
|
|
|
uint64_t hashSubBuffer(unsigned char* pixmap, size_t startX, size_t startY,
|
2016-12-05 04:56:12 -06:00
|
|
|
|
long width, long height, int bufferWidth, int bufferHeight)
|
2016-11-21 05:42:01 -06:00
|
|
|
|
{
|
|
|
|
|
if (bufferWidth < width || bufferHeight < height)
|
|
|
|
|
return 0; // magic invalid hash.
|
|
|
|
|
|
|
|
|
|
// assume a consistent mode - RGBA vs. BGRA for process
|
2016-11-27 09:38:35 -06:00
|
|
|
|
SpookyHash hash;
|
|
|
|
|
hash.Init(1073741789, 1073741789); // Seeds can be anything.
|
2016-12-23 00:03:08 -06:00
|
|
|
|
for (long y = 0; y < height; ++y)
|
2016-11-21 05:42:01 -06:00
|
|
|
|
{
|
2016-11-27 09:38:35 -06:00
|
|
|
|
const size_t position = ((startY + y) * bufferWidth * 4) + (startX * 4);
|
|
|
|
|
hash.Update(pixmap + position, width * 4);
|
2016-11-21 05:42:01 -06:00
|
|
|
|
}
|
2016-11-27 09:38:35 -06:00
|
|
|
|
|
2017-01-13 06:52:08 -06:00
|
|
|
|
uint64_t hash1;
|
|
|
|
|
uint64_t hash2;
|
|
|
|
|
hash.Final(&hash1, &hash2);
|
|
|
|
|
return hash1;
|
2016-11-21 05:42:01 -06:00
|
|
|
|
}
|
|
|
|
|
|
2016-08-06 10:24:49 -05:00
|
|
|
|
static
|
|
|
|
|
void readTileData(png_structp png_ptr, png_bytep data, png_size_t length)
|
|
|
|
|
{
|
|
|
|
|
png_voidp io_ptr = png_get_io_ptr(png_ptr);
|
|
|
|
|
assert(io_ptr);
|
2016-12-23 01:31:35 -06:00
|
|
|
|
std::stringstream& streamTile = *static_cast<std::stringstream*>(io_ptr);
|
|
|
|
|
streamTile.read(reinterpret_cast<char*>(data), length);
|
2016-08-06 10:24:49 -05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline
|
|
|
|
|
std::vector<png_bytep> decodePNG(std::stringstream& stream, png_uint_32& height, png_uint_32& width, png_uint_32& rowBytes)
|
|
|
|
|
{
|
|
|
|
|
png_byte signature[0x08];
|
2016-12-23 01:31:35 -06:00
|
|
|
|
stream.read(reinterpret_cast<char *>(signature), 0x08);
|
2016-08-06 10:24:49 -05:00
|
|
|
|
if (png_sig_cmp(signature, 0x00, 0x08))
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("Invalid PNG signature.");
|
|
|
|
|
}
|
|
|
|
|
|
2016-12-22 08:04:07 -06:00
|
|
|
|
png_structp ptrPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
2016-08-06 10:24:49 -05:00
|
|
|
|
if (ptrPNG == nullptr)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("png_create_read_struct failed.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
png_infop ptrInfo = png_create_info_struct(ptrPNG);
|
|
|
|
|
if (ptrInfo == nullptr)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("png_create_info_struct failed.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
png_infop ptrEnd = png_create_info_struct(ptrPNG);
|
|
|
|
|
if (ptrEnd == nullptr)
|
|
|
|
|
{
|
|
|
|
|
throw std::runtime_error("png_create_info_struct failed.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
png_set_read_fn(ptrPNG, &stream, readTileData);
|
|
|
|
|
png_set_sig_bytes(ptrPNG, 0x08);
|
|
|
|
|
|
|
|
|
|
png_read_info(ptrPNG, ptrInfo);
|
|
|
|
|
|
|
|
|
|
width = png_get_image_width(ptrPNG, ptrInfo);
|
|
|
|
|
height = png_get_image_height(ptrPNG, ptrInfo);
|
|
|
|
|
|
|
|
|
|
png_set_interlace_handling(ptrPNG);
|
|
|
|
|
png_read_update_info(ptrPNG, ptrInfo);
|
|
|
|
|
|
|
|
|
|
rowBytes = png_get_rowbytes(ptrPNG, ptrInfo);
|
|
|
|
|
assert(width == rowBytes / 4);
|
|
|
|
|
|
|
|
|
|
const size_t dataSize = (rowBytes + sizeof(png_bytep)) * height / sizeof(png_bytep);
|
|
|
|
|
const size_t size = dataSize + height + sizeof(png_bytep);
|
|
|
|
|
|
|
|
|
|
std::vector<png_bytep> rows;
|
|
|
|
|
rows.resize(size);
|
|
|
|
|
|
|
|
|
|
// rows
|
|
|
|
|
for (png_uint_32 itRow = 0; itRow < height; itRow++)
|
|
|
|
|
{
|
2018-02-07 03:17:19 -06:00
|
|
|
|
const size_t index = height + (itRow * rowBytes + sizeof(png_bytep) - 1) / sizeof(png_bytep);
|
2016-08-06 10:24:49 -05:00
|
|
|
|
rows[itRow] = reinterpret_cast<png_bytep>(&rows[index]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
png_read_image(ptrPNG, rows.data());
|
|
|
|
|
png_read_end(ptrPNG, ptrEnd);
|
|
|
|
|
png_destroy_read_struct(&ptrPNG, &ptrInfo, &ptrEnd);
|
|
|
|
|
|
|
|
|
|
return rows;
|
|
|
|
|
}
|
|
|
|
|
|
2016-05-21 10:31:04 -05:00
|
|
|
|
}
|
|
|
|
|
|
2015-11-24 02:41:59 -06:00
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|