Blame synfig-core/src/synfig/rendering/software/function/packedsurface.cpp

1febca
/* === S Y N F I G ========================================================= */
1febca
/*!	\file synfig/rendering/software/function/packedsurface.cpp
1febca
**	\brief PackedSurface
1febca
**
1febca
**	$Id$
1febca
**
1febca
**	\legal
1febca
**	......... ... 2016 Ivan Mahonin
1febca
**
1febca
**	This package is free software; you can redistribute it and/or
1febca
**	modify it under the terms of the GNU General Public License as
1febca
**	published by the Free Software Foundation; either version 2 of
1febca
**	the License, or (at your option) any later version.
1febca
**
1febca
**	This package is distributed in the hope that it will be useful,
1febca
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
1febca
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1febca
**	General Public License for more details.
1febca
**	\endlegal
1febca
*/
1febca
/* ========================================================================= */
1febca
1febca
/* === H E A D E R S ======================================================= */
1febca
1febca
#ifdef USING_PCH
1febca
#	include "pch.h"
1febca
#else
1febca
#ifdef HAVE_CONFIG_H
1febca
#	include <config.h></config.h>
1febca
#endif
1febca
2b738b
#include <cstdlib></cstdlib>
1febca
#include <cstring></cstring>
1febca
1febca
#include <vector></vector>
1febca
#include <map></map>
1febca
1febca
#include "packedsurface.h"
1febca
1febca
#include <synfig real.h=""></synfig>
1febca
#include <synfig zstreambuf.h=""></synfig>
1febca
1febca
#endif
1febca
1febca
using namespace synfig;
1febca
using namespace rendering;
1febca
using namespace software;
1febca
1febca
/* === M A C R O S ========================================================= */
1febca
1febca
/* === G L O B A L S ======================================================= */
1febca
1febca
/* === P R O C E D U R E S ================================================= */
1febca
1febca
/* === M E T H O D S ======================================================= */
1febca
1febca
1febca
PackedSurface::Reader::Reader():
1febca
	surface(NULL),
1febca
	first(NULL),
1febca
	last(NULL),
1febca
	cache(NULL)
1febca
{ }
1febca
1febca
PackedSurface::Reader::Reader(const PackedSurface &surface):
1febca
	surface(NULL),
1febca
	first(NULL),
1febca
	last(NULL),
1febca
	cache(NULL)
1febca
{
1febca
	open(surface);
1febca
}
1febca
1febca
PackedSurface::Reader::~Reader()
1febca
{
1febca
	close();
1febca
}
1febca
1febca
void
1febca
PackedSurface::Reader::open(const PackedSurface &surface)
1febca
{
1febca
	if (this->surface == &surface)
1febca
		return;
1febca
1febca
	close();
1febca
1febca
	if (surface.width <= 0 && surface.height <= 0)
1febca
		return;
1febca
1febca
	{
1febca
		this->surface = &surface;
Rodolfo Ribeiro Gomes cfe072
		std::lock_guard<std::mutex> lock(surface.mutex);</std::mutex>
1febca
		surface.readers.insert(this);
1febca
	}
1febca
1febca
	if (surface.chunk_size)
1febca
	{
1febca
		chunks.resize(surface.chunks_width*surface.chunks_height, NULL);
1febca
1febca
		int cacheCount = std::max(surface.chunks_width, surface.chunks_height)*CacheRows;
1febca
		assert(cacheCount > 1);
1febca
		int cacheEntrySize = sizeof(CacheEntry) + surface.chunk_size;
1febca
		cache = new char[cacheCount*cacheEntrySize];
1febca
		first = (CacheEntry*)cache;
1febca
		for(int i = 0; i < cacheCount; ++i)
1febca
		{
1febca
			CacheEntry *entry = (CacheEntry*)(void*)(cache + i*cacheEntrySize);
1febca
			entry->chunk_index = -1;
1febca
			entry->next = NULL;
1febca
			entry->prev = last;
1febca
			if (last) last->next = entry;
1febca
			last = entry;
1febca
		}
1febca
	}
1febca
}
1febca
1febca
void
1febca
PackedSurface::Reader::close()
1febca
{
1febca
	if (is_opened())
1febca
	{
1febca
		{
Rodolfo Ribeiro Gomes cfe072
			std::lock_guard<std::mutex> lock(surface->mutex);</std::mutex>
1febca
			surface->readers.erase(this);
1febca
		}
1febca
		if (cache) delete[] cache;
1febca
		first = NULL;
1febca
		last = NULL;
1febca
		cache = NULL;
1febca
		surface = NULL;
1febca
	}
1febca
}
1febca
1febca
Color
1febca
PackedSurface::Reader::get_pixel(int x, int y) const
1febca
{
1febca
	if (!is_opened())
1febca
		return Color();
1febca
1febca
	if (x < 0)
1febca
		x = 0;
1febca
	if (x >= surface->width)
1febca
		x = surface->width-1;
1febca
	if (y < 0)
1febca
		y = 0;
1febca
	if (y >= surface->height)
1febca
		y = surface->height-1;
1febca
1febca
	if (cache)
1febca
	{
1febca
		int chunk_index = x/ChunkSize + y/ChunkSize*surface->chunks_width;
1febca
		x %= ChunkSize;
1febca
		y %= ChunkSize;
1febca
		CacheEntry *entry = chunks[chunk_index];
1febca
		if (!entry)
1febca
		{
1febca
			const void *data;
1febca
			int size;
1febca
			bool compressed;
1febca
			surface->get_compressed_chunk(chunk_index, data, size, compressed);
1febca
			if (!compressed)
1febca
				return surface->get_pixel(&((const char*)data)[x*surface->pixel_size + y*surface->chunk_row_size]);
1febca
1febca
			entry = last;
1febca
			if (entry->chunk_index >= 0)
1febca
				chunks[entry->chunk_index] = NULL;
1febca
			entry->chunk_index = chunk_index;
1febca
			chunks[chunk_index] = entry;
1febca
			zstreambuf::unpack(entry->data(), surface->chunk_size, data, size);
1febca
		}
1febca
		if (first != entry)
1febca
		{
1febca
			entry->prev->next = entry->next;
1febca
			(entry->next ? entry->next->prev : last) = entry->prev;
1febca
1febca
			first->prev = entry;
1febca
			entry->prev = NULL;
1febca
			entry->next = first;
1febca
			first = entry;
1febca
		}
1febca
		return surface->get_pixel(entry->data(x*surface->pixel_size + y*surface->chunk_row_size));
1febca
	}
1febca
	else
1febca
	if (surface->pixel_size)
1febca
	{
1febca
		return surface->get_pixel(&surface->data[x*surface->pixel_size + y*surface->row_size]);
1febca
	}
1febca
	return surface->constant;
1febca
}
1febca
1febca
1febca
PackedSurface::PackedSurface():
1febca
	width(0),
1febca
	height(0),
1febca
	channel_type(),
1febca
	pixel_size(0),
1febca
	row_size(0),
1febca
	chunk_size(0),
1febca
	chunk_row_size(0),
1febca
	chunks_width(0),
1febca
	chunks_height(0)
1febca
{
1febca
	memset(channels, 0, sizeof(channels));
1febca
	memset(discrete_to_float, 0, sizeof(discrete_to_float));
1febca
}
1febca
1febca
PackedSurface::~PackedSurface()
1febca
{
1febca
	clear();
1febca
}
1febca
1febca
1febca
void
1febca
PackedSurface::clear() {
1febca
	while(!readers.empty())
1febca
		(*readers.begin())->close();
1febca
	width = 0;
1febca
	height = 0;
1febca
	channel_type = ChannelUInt8;
1febca
	memset(channels, 0, sizeof(channels));
1febca
	memset(discrete_to_float, 0, sizeof(discrete_to_float));
1febca
	constant = Color();
1febca
	pixel_size = 0;
1febca
	row_size = 0;
1febca
	chunk_size = 0;
1febca
	chunk_row_size = 0;
1febca
	chunks_width = 0;
1febca
	chunks_height = 0;
1febca
	data.clear();
1febca
}
1febca
1febca
Color::value_type
1febca
PackedSurface::get_channel(const void *pixel, int offset, ChannelType type, Color::value_type constant, const Color::value_type *discrete_to_float)
1febca
{
1febca
	if (offset < 0)
1febca
		return constant;
1febca
	if (type == ChannelUInt8)
1febca
		return discrete_to_float[((const unsigned char*)pixel)[offset]];
1febca
	return *(const Color::value_type*)((const char*)pixel + offset);
1febca
}
1febca
1febca
void
1febca
PackedSurface::set_channel(void *pixel, int offset, ChannelType type, Color::value_type color, const Color::value_type *discrete_to_float)
1febca
{
1febca
	if (offset < 0)
1febca
		return;
1febca
	if (type == ChannelUInt8) {
1febca
		int i = 0;
1febca
		int j = 255;
1febca
		while(true)
1febca
		{
1febca
			int k = (i+j)/2;
1febca
			if (k == i) break;
1febca
			if (color < discrete_to_float[k]) j = k; else i = k;
1febca
		}
1febca
		int best_key = fabs(discrete_to_float[i] - color) < fabs(discrete_to_float[j] - color) ? i : j;
1febca
		((unsigned char*)pixel)[offset] = (unsigned char)best_key;
1febca
		return;
1febca
	}
1febca
	*(Color::value_type*)((char*)pixel + offset) = color;
1febca
}
1febca
1febca
Color
1febca
PackedSurface::get_pixel(const void *pixel) const
1febca
{
1febca
	return Color(
1febca
		get_channel(pixel, channels[0], channel_type, constant.get_r(), discrete_to_float),
1febca
		get_channel(pixel, channels[1], channel_type, constant.get_g(), discrete_to_float),
1febca
		get_channel(pixel, channels[2], channel_type, constant.get_b(), discrete_to_float),
1febca
		get_channel(pixel, channels[3], channel_type, constant.get_a(), discrete_to_float) );
1febca
}
1febca
1febca
void
1febca
PackedSurface::set_pixel(void *pixel, const Color &color)
1febca
{
1febca
	set_channel(pixel, channels[0], channel_type, color.get_r(), discrete_to_float);
1febca
	set_channel(pixel, channels[1], channel_type, color.get_g(), discrete_to_float);
1febca
	set_channel(pixel, channels[2], channel_type, color.get_b(), discrete_to_float);
1febca
	set_channel(pixel, channels[3], channel_type, color.get_a(), discrete_to_float);
1febca
}
1febca
1febca
void
1febca
PackedSurface::get_compressed_chunk(int index, const void *&data, int &size, bool &compressed) const
1febca
{
1febca
	assert(chunk_size);
1febca
	const int *chunks = (const int*)(const void*)&this->data.front();
1febca
	int begin = chunks[index];
1febca
	int end = chunks[index+1];
1febca
	data = &this->data[begin];
1febca
	size = end - begin;
1febca
	compressed = size != chunk_size;
1febca
}
1febca
1febca
void
1febca
PackedSurface::set_pixels(const Color *pixels, int width, int height, int pitch) {
1febca
	clear();
1febca
	if (pixels == NULL || width <= 0 || height <= 0)
1febca
		return;
1febca
1febca
	if (pitch == 0) pitch = sizeof(Color)*width;
1febca
1febca
	// check format
1febca
	Color constant = *pixels;
1febca
	Color::value_type *constant_channels = (Color::value_type*)(void*)&constant;
1febca
	bool discrete = true;
1febca
	std::vector<discretehelper> discrete_values;</discretehelper>
1febca
	bool channels_equality[4][4];
1febca
	bool constant_equality[4];
1febca
	for(int i = 0; i < 4; ++i)
1febca
	{
1febca
		for(int j = 0; j < 4; ++j)
1febca
			channels_equality[i][j] = true;
1febca
		constant_equality[i] = true;
1febca
	}
1febca
1febca
	for(int row = 0; row < height; ++row) {
1febca
		for(const Color *color = (const Color*)((const char*)pixels + row*pitch), *end = color + width; color < end; ++color)
1febca
		{
1febca
			const Color::value_type *color_channels = (const Color::value_type*)(const void*)color;
1febca
			for(int i = 0; i < 4; ++i)
1febca
			{
1febca
				// compare channels
1febca
				for(int j = 0; j < i; ++j)
1febca
					if (channels_equality[i][j] && !approximate_equal_lp(color_channels[i], color_channels[j]))
1febca
						channels_equality[i][j] = false;
1febca
1febca
				// compare with constant
1febca
				if (constant_equality[i] && !approximate_equal_lp(color_channels[i], constant_channels[i]))
1febca
					constant_equality[i] = false;
1febca
1febca
				// check discrete
1febca
				if (discrete)
1febca
				{
1febca
					Color::value_type c = color_channels[i];
1febca
					if (discrete_values.empty()) {
1febca
						discrete_values.push_back(DiscreteHelper(c));
1febca
					} else {
1febca
						int i = 0;
1febca
						int j = discrete_values.size() - 1;
1febca
						while(true) {
1febca
							int k = (i+j)/2;
1febca
							if (k == i) break;
1febca
							if (c < discrete_values[k].min) j = k; else i = k;
1febca
						}
1febca
1febca
						if (discrete_values[i].in_range(c))
1febca
							++discrete_values[i].count;
1febca
						else
1febca
						if (discrete_values[j].in_range(c))
1febca
							++discrete_values[j].count;
1febca
						else
1febca
						{
1febca
							if (c < discrete_values[j].value)
1febca
								discrete_values.insert(discrete_values.begin() + j, DiscreteHelper(c));
1febca
							else
1febca
								discrete_values.push_back(DiscreteHelper(c));
1febca
							if (discrete_values.size() > 260) discrete = false;
1febca
						}
1febca
					}
1febca
				}
1febca
			}
1febca
		}
1febca
	}
1febca
1febca
	this->channel_type = discrete ? ChannelUInt8 : ChannelFloat32;
1febca
	int channel_size = this->channel_type == ChannelUInt8 ? sizeof(unsigned char) : sizeof(ColorReal);
1febca
1febca
	if (discrete) {
1febca
		while(discrete_values.size() > 256) {
1febca
			std::vector<discretehelper>::iterator min_i = discrete_values.begin();</discretehelper>
1febca
			for(std::vector<discretehelper>::iterator i = discrete_values.begin(); i != discrete_values.end(); ++i)</discretehelper>
1febca
				if (min_i->count > i->count)
1febca
					min_i = i;
1febca
			discrete_values.erase(min_i);
1febca
		}
1febca
		int index = 0;
1febca
		for(std::vector<discretehelper>::const_iterator i = discrete_values.begin(); i != discrete_values.end(); ++i, ++index)</discretehelper>
1febca
			discrete_to_float[index] = i->value;
1febca
		if (index > 0)
1febca
			for(; index < 256; ++index)
1febca
				discrete_to_float[index] = discrete_to_float[index - 1];
1febca
	}
1febca
1febca
	pixel_size = 0;
1febca
	for(int i = 0; i < 4; ++i) {
1febca
		channels[i] = i*channel_size;
1febca
		for(int j = 0; j < i; ++j)
1febca
			if (channels_equality[i][j])
1febca
				{ channels[i] = channels[j]; break; }
1febca
		if (constant_equality[i])
1febca
			channels[i] = -1;
e47a8f
		else
e47a8f
			constant_channels[i] = 0;
1febca
		if (channels[i] >= 0 && channels[i] + channel_size > pixel_size)
1febca
			pixel_size = channels[i] + channel_size;
1febca
	}
e47a8f
	this->constant = constant;
1febca
	this->width = width;
1febca
	this->height = height;
1febca
	row_size = width * pixel_size;
1febca
2b738b
	const char *s;
2b738b
	bool gzip = (s = getenv("SYNFIG_PACK_IMAGES_GZIP")) && atoi(s) != 0;
2b738b
	bool split = (s = getenv("SYNFIG_PACK_IMAGES_SPLIT")) && atoi(s) != 0;
2b738b
1febca
	if (pixel_size == 0) {
1febca
		// do nothing
1febca
	}
1febca
	else
2b738b
	if ((!gzip && !split) || std::max((width-1)/ChunkSize + 1, (height-1)/ChunkSize + 1)*CacheRows*ChunkSize*ChunkSize*16 > width*height)
1febca
	{
1febca
		// no compression
1febca
		data.resize(row_size*height);
1febca
		char *pixel = &data.front();
1febca
		for(int row = 0; row < height; ++row)
1febca
			for(const Color *color = (const Color*)((const char*)pixels + row*pitch), *end = color + width; color < end; ++color, pixel += pixel_size)
1febca
				set_pixel(pixel, *color);
1febca
	}
1febca
	else
1febca
	{
1febca
		// make chunks
1febca
		chunk_row_size = pixel_size*ChunkSize;
1febca
		chunk_size = chunk_row_size*ChunkSize;
1febca
		chunks_width = (width-1)/ChunkSize + 1;
1febca
		chunks_height = (height-1)/ChunkSize + 1;
1febca
1febca
		int count = chunks_width*chunks_height;
1febca
		std::vector<char> data((count + 1)*sizeof(int), 0);</char>
1febca
		std::vector<char> chunk(chunk_size);</char>
1febca
		std::vector<char> compressed_chunk(2*chunk.size());</char>
1febca
		for(int i = 0; i < count; ++i) {
1febca
			char *pixel = &chunk.front();
1febca
			for(int r = 0; r < ChunkSize; ++r) {
1febca
				int x0 = i%chunks_width*ChunkSize;
1febca
				int y0 = i/chunks_width*ChunkSize;
1febca
				const Color *color = (const Color*)((const char*)pixels + (y0 + r)*pitch) + x0;
1febca
				for(int c = 0; c < ChunkSize; ++c, pixel += pixel_size, ++color)
2b738b
					if (x0+c < width && y0+r < height)
1febca
						set_pixel(pixel, *color);
1febca
					else
1febca
						set_pixel(pixel, Color());
1febca
			}
1febca
2b738b
			const void* current_data = &chunk.front();
2b738b
			int size = (int)chunk.size();
2b738b
2b738b
			if (gzip) {
2b738b
				int gzip_size = (int)zstreambuf::pack(&compressed_chunk.front(), compressed_chunk.size(), &chunk.front(), chunk.size(), true);
2b738b
				if (gzip_size <= (int)chunk.size()/4)
2b738b
				{
2b738b
					current_data = &compressed_chunk.front();
2b738b
					size = gzip_size;
2b738b
				}
1febca
			}
1febca
1febca
			((int*)(void*)&data.front())[i] = data.size();
1febca
			data.resize(data.size() + size);
1febca
			memcpy(&data[data.size() - size], current_data, size);
1febca
		}
1febca
		((int*)(void*)&data.front())[count] = data.size();
1febca
1febca
		this->data = data;
1febca
	}
1febca
}
1febca
1febca
void
1febca
PackedSurface::get_pixels(Color *target) const {
1febca
	if (target == NULL || width <= 0 || height <= 0)
1febca
		return;
1febca
	Reader reader(*this);
1febca
	Color *color = target;
1febca
	for(int y = 0; y < height; ++y)
1febca
		for(int x = 0; x < width; ++x, ++color)
1febca
			*color = reader.get_pixel(x, y);
1febca
}
1febca
1febca
1febca
1febca
/* === E N T R Y P O I N T ================================================= */