Skip to content

Instantly share code, notes, and snippets.

@YaLTeR
Last active December 17, 2015 16:19
Show Gist options
  • Save YaLTeR/174dd504843d7e5c6268 to your computer and use it in GitHub Desktop.
Save YaLTeR/174dd504843d7e5c6268 to your computer and use it in GitHub Desktop.
Implementation of the fractal image compression algorithm. Can compress using fixed block size (4x4, 8x8, 16x16) as well as using a quadtree with a quality setting (1 to 100 inclusive). This is not the complete source code of a program, however the algorithm is here completely.
#include "stdafx.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <utility>
#include "colorspaces.hpp"
#include "psnr.hpp"
#include "compress\compress.h"
#include "bitarr.hpp"
enum transformation
{
NONE = 0,
ROT_90,
ROT_180,
ROT_270,
FLIP,
FLIP_90,
FLIP_180,
FLIP_270
};
struct square
{
std::vector<double> data;
square flipped() const
{
size_t s = static_cast<size_t>(sqrt(data.size()));
square res;
res.data.resize(data.size());
for (size_t i = 0; i < s; ++i) {
for (size_t j = 0; j < s; ++j) {
res.data[i * s + j] = data[i * s + s - j - 1];
}
}
return res;
}
square transformed(transformation t) const
{
size_t s = static_cast<size_t>(sqrt(data.size()));
auto tr = static_cast<unsigned char>(t);
square temp;
if (tr >= FLIP) {
temp = flipped();
tr -= FLIP;
} else {
temp = *this;
}
if (tr == NONE)
return temp;
square res;
res.data.resize(data.size());
for (size_t i = 0; i < s; ++i) {
for (size_t j = 0; j < s; ++j) {
if (tr == ROT_90)
res.data[j * s + s - i - 1] = temp.data[i * s + j];
else if (tr == ROT_180)
res.data[(s - i - 1) * s + s - j - 1] = temp.data[i * s + j];
else if (tr == ROT_270)
res.data[(s - j - 1) * s + i] = temp.data[i * s + j];
}
}
return res;
}
};
struct mapping
{
mapping(int idx_b, transformation tr, double q_, double p_, double dist_ = 0.0)
: idx_big(idx_b)
, t(tr)
, q(q_)
, p(p_)
, dist(dist_)
{}
int idx_big;
transformation t;
double q;
double p;
double dist;
};
struct quadtree_node
{
quadtree_node(size_t bs)
: my_block_size(bs)
{}
bool leftup_is_quadtree;
bool rightup_is_quadtree;
bool leftdown_is_quadtree;
bool rightdown_is_quadtree;
size_t my_block_size;
int x;
int y;
union {
quadtree_node *node;
mapping *map;
} lu;
union {
quadtree_node *node;
mapping *map;
} ru;
union {
quadtree_node *node;
mapping *map;
} ld;
union {
quadtree_node *node;
mapping *map;
} rd;
};
struct mapping_parent
{
quadtree_node *parent;
enum pos {
LU, RU, LD, RD
} position;
mapping_parent(quadtree_node* par_, pos pos_)
: parent(par_)
, position(pos_)
{}
};
std::vector<square> partition(const std::vector<double>& a, int w, int block_size)
{
assert(w % block_size == 0);
int w_res = w / block_size;
std::vector<square> res(w_res * w_res);
for (int y = 0; y < w; ++y) {
for (int x = 0; x < w; ++x) {
res[(y / block_size) * w_res + x / block_size].data.push_back(a[y * w + x]);
}
}
return res;
}
void add_transforms(std::vector<square>& a)
{
size_t s = a.size();
a.reserve(a.size() * 8);
for (int i = 1; i < 8; ++i) {
for (size_t j = 0; j < s; ++j) {
a.push_back(a[j].transformed(static_cast<transformation>(i)));
}
}
}
static const constexpr auto Q_MAX = 0.9, Q_MIN = -1.0;
void compute_best_qp(const square& small, const square& big, size_t block_size, size_t block_size_big_mul, double& q, double& p)
{
size_t s_small = block_size;
size_t s_big = block_size * block_size_big_mul;
double dij = 0.0, dijsq = 0.0, rij = 0.0, rijdij = 0.0;
for (size_t j = 0; j < s_small; ++j) {
for (size_t i = 0; i < s_small; ++i) {
auto idx = j * s_small + i;
auto temp = big.data[j * block_size_big_mul * s_big + i * block_size_big_mul];
dij += temp;
dijsq += temp * temp;
rij += small.data[idx];
rijdij += small.data[idx] * temp;
}
}
q = (rijdij - dij * rij) / (dijsq - dij * dij);
//q = 0.75;
q = (q > Q_MAX) ? Q_MAX : q;
q = (q < Q_MIN) ? Q_MIN : q;
// Account for the size reduction we're gonna do later.
auto temp = (q - Q_MIN) * (255 / (Q_MAX - Q_MIN));
if (temp > 255.0)
temp = 255.0;
else if (temp < 0.0)
temp = 0.0;
auto temp2 = static_cast<uint8_t>(temp);
q = temp2 * ((Q_MAX - Q_MIN) / 255) + Q_MIN;
// Now compute p.
p = (rij - q * dij) / (block_size * block_size);
//p = (rij - dij * (rijdij / dijsq)) / (1 - dij * (dij / dijsq));
//p = (p <= -1) ? -1 : p;
//p = (p >= 1) ? 1 : p;
//q = (rijdij - p * dij) / dijsq;
//printf("q = %.8f; p = %.8f\n", q, p);
}
std::vector<mapping> make_mapping(const std::vector<square>& par_small, const std::vector<square>& par_big, size_t block_size, size_t block_size_big_mul)
{
int s = par_small.size();
size_t s_b = par_big.size() / 8;
std::vector<mapping> res(s, mapping(0, NONE, 0.0, 0.0));
#pragma omp parallel for
for (int i = 0; i < s; ++i) {
//printf("%d\n", i);
auto sq_small = par_small[i];
// Find best mapping.
int best_idx;
transformation best_t;
double best_q, best_p;
double best_dist = DBL_MAX;
for (size_t j = 0; j < par_big.size(); ++j) {
auto sq_big = par_big[j];
double q, p;
compute_best_qp(sq_small, sq_big, block_size, block_size_big_mul, q, p);
double dist = 0.0;
for (size_t a = 0; a < block_size; ++a) {
for (size_t b = 0; b < block_size; ++b) {
auto t = sq_big.data[a * block_size_big_mul * (block_size * block_size_big_mul) + b * block_size_big_mul];
auto o = sq_small.data[a * block_size + b] - q * t - p;
dist += o * o;
}
}
if (dist < best_dist) {
size_t j_transform = j % s_b;
best_dist = dist;
best_idx = j_transform;
best_t = static_cast<transformation>(j / s_b);
best_q = q;
best_p = p;
}
}
//printf("best_idx = %d\n", best_idx);
res[i] = mapping(best_idx, best_t, best_q, best_p, best_dist);
}
return res;
}
auto fill_with_mappings(
const std::vector<square>& par_16x16,
const std::vector<square>& par_8x8,
const std::vector<square>& par_4x4,
const std::vector<square>& parbig_32x32,
const std::vector<square>& parbig_16x16,
const std::vector<square>& parbig_8x8,
quadtree_node& node)
{
assert(node.my_block_size <= 16);
assert(node.my_block_size >= 4);
node.leftup_is_quadtree = false;
node.rightup_is_quadtree = false;
node.leftdown_is_quadtree = false;
node.rightdown_is_quadtree = false;
auto& par_small = (node.my_block_size == 16) ? par_16x16 : ((node.my_block_size == 8) ? par_8x8 : par_4x4);
auto& par_big = (node.my_block_size == 16) ? parbig_32x32 : ((node.my_block_size == 8) ? parbig_16x16 : parbig_8x8);
auto par_small_width = static_cast<size_t>(sqrt(par_small.size()));
std::vector<square> par{
par_small[node.y * 2 * par_small_width + node.x * 2],
par_small[node.y * 2 * par_small_width + node.x * 2 + 1],
par_small[node.y * 2 * par_small_width + par_small_width + node.x * 2],
par_small[node.y * 2 * par_small_width + par_small_width + node.x * 2 + 1]
};
auto m = make_mapping(par, par_big, node.my_block_size, 2);
node.lu.map = new mapping(std::move(m[0]));
node.ru.map = new mapping(std::move(m[1]));
node.ld.map = new mapping(std::move(m[2]));
node.rd.map = new mapping(std::move(m[3]));
}
void fill(
const std::vector<square>& par_16x16,
const std::vector<square>& par_8x8,
const std::vector<square>& par_4x4,
const std::vector<square>& parbig_32x32,
const std::vector<square>& parbig_16x16,
const std::vector<square>& parbig_8x8,
quadtree_node& node,
std::vector<std::pair<mapping*, mapping_parent>>& bottom_level_nodes)
{
assert(node.my_block_size > 16);
node.leftup_is_quadtree = true;
node.rightup_is_quadtree = true;
node.leftdown_is_quadtree = true;
node.rightdown_is_quadtree = true;
node.lu.node = new quadtree_node(node.my_block_size / 2);
node.ru.node = new quadtree_node(node.my_block_size / 2);
node.ld.node = new quadtree_node(node.my_block_size / 2);
node.rd.node = new quadtree_node(node.my_block_size / 2);
node.lu.node->x = node.x * 2;
node.lu.node->y = node.y * 2;
node.ru.node->x = node.x * 2 + 1;
node.ru.node->y = node.y * 2;
node.ld.node->x = node.x * 2;
node.ld.node->y = node.y * 2 + 1;
node.rd.node->x = node.x * 2 + 1;
node.rd.node->y = node.y * 2 + 1;
if (node.my_block_size > 32) {
fill(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.lu.node,
bottom_level_nodes);
fill(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.ru.node,
bottom_level_nodes);
fill(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.ld.node,
bottom_level_nodes);
fill(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.rd.node,
bottom_level_nodes);
} else {
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.lu.node);
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.ru.node);
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.ld.node);
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node.rd.node);
auto f = [&](quadtree_node* n) {
bottom_level_nodes.emplace_back(n->lu.map, mapping_parent(n, mapping_parent::LU));
bottom_level_nodes.emplace_back(n->ru.map, mapping_parent(n, mapping_parent::RU));
bottom_level_nodes.emplace_back(n->ld.map, mapping_parent(n, mapping_parent::LD));
bottom_level_nodes.emplace_back(n->rd.map, mapping_parent(n, mapping_parent::RD));
};
f(node.lu.node);
f(node.ru.node);
f(node.ld.node);
f(node.rd.node);
}
}
auto make_quadtree(
const std::vector<square>& par_16x16,
const std::vector<square>& par_8x8,
const std::vector<square>& par_4x4,
const std::vector<square>& parbig_32x32,
const std::vector<square>& parbig_16x16,
const std::vector<square>& parbig_8x8,
size_t w,
int quality)
{
quadtree_node top(w / 2);
top.x = 0;
top.y = 0;
std::vector<std::pair<mapping*, mapping_parent>> bottom_nodes;
fill(par_16x16, par_8x8, par_4x4, parbig_32x32, parbig_16x16, parbig_8x8, top, bottom_nodes);
std::sort(bottom_nodes.begin(), bottom_nodes.end(), [](auto a, auto b) {
return a.first->dist > b.first->dist;
});
std::vector<std::pair<mapping*, mapping_parent>> bottom_nodes_2;
for (size_t i = 0; i < (bottom_nodes.size() * quality) / 100; ++i) {
auto p = bottom_nodes[i];
delete p.first;
auto mappar = p.second;
auto node = mappar.parent;
switch (mappar.position) {
case mapping_parent::LU:
node->leftup_is_quadtree = true;
node->lu.node = new quadtree_node(node->my_block_size / 2);
node->lu.node->x = node->x * 2;
node->lu.node->y = node->y * 2;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->lu.node);
bottom_nodes_2.emplace_back(node->lu.node->lu.map, mapping_parent(node->lu.node, mapping_parent::LU));
bottom_nodes_2.emplace_back(node->lu.node->ru.map, mapping_parent(node->lu.node, mapping_parent::RU));
bottom_nodes_2.emplace_back(node->lu.node->ld.map, mapping_parent(node->lu.node, mapping_parent::LD));
bottom_nodes_2.emplace_back(node->lu.node->rd.map, mapping_parent(node->lu.node, mapping_parent::RD));
break;
case mapping_parent::RU:
node->rightup_is_quadtree = true;
node->ru.node = new quadtree_node(node->my_block_size / 2);
node->ru.node->x = node->x * 2 + 1;
node->ru.node->y = node->y * 2;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->ru.node);
bottom_nodes_2.emplace_back(node->ru.node->lu.map, mapping_parent(node->ru.node, mapping_parent::LU));
bottom_nodes_2.emplace_back(node->ru.node->ru.map, mapping_parent(node->ru.node, mapping_parent::RU));
bottom_nodes_2.emplace_back(node->ru.node->ld.map, mapping_parent(node->ru.node, mapping_parent::LD));
bottom_nodes_2.emplace_back(node->ru.node->rd.map, mapping_parent(node->ru.node, mapping_parent::RD));
break;
case mapping_parent::LD:
node->leftdown_is_quadtree = true;
node->ld.node = new quadtree_node(node->my_block_size / 2);
node->ld.node->x = node->x * 2;
node->ld.node->y = node->y * 2 + 1;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->ld.node);
bottom_nodes_2.emplace_back(node->ld.node->lu.map, mapping_parent(node->ld.node, mapping_parent::LU));
bottom_nodes_2.emplace_back(node->ld.node->ru.map, mapping_parent(node->ld.node, mapping_parent::RU));
bottom_nodes_2.emplace_back(node->ld.node->ld.map, mapping_parent(node->ld.node, mapping_parent::LD));
bottom_nodes_2.emplace_back(node->ld.node->rd.map, mapping_parent(node->ld.node, mapping_parent::RD));
break;
case mapping_parent::RD:
node->rightdown_is_quadtree = true;
node->rd.node = new quadtree_node(node->my_block_size / 2);
node->rd.node->x = node->x * 2 + 1;
node->rd.node->y = node->y * 2 + 1;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->rd.node);
bottom_nodes_2.emplace_back(node->rd.node->lu.map, mapping_parent(node->rd.node, mapping_parent::LU));
bottom_nodes_2.emplace_back(node->rd.node->ru.map, mapping_parent(node->rd.node, mapping_parent::RU));
bottom_nodes_2.emplace_back(node->rd.node->ld.map, mapping_parent(node->rd.node, mapping_parent::LD));
bottom_nodes_2.emplace_back(node->rd.node->rd.map, mapping_parent(node->rd.node, mapping_parent::RD));
break;
}
}
std::sort(bottom_nodes_2.begin(), bottom_nodes_2.end(), [](auto a, auto b) {
return a.first->dist > b.first->dist;
});
for (size_t i = 0; i < (bottom_nodes_2.size() * quality) / 100; ++i) {
auto p = bottom_nodes_2[i];
delete p.first;
auto mappar = p.second;
auto node = mappar.parent;
switch (mappar.position) {
case mapping_parent::LU:
node->leftup_is_quadtree = true;
node->lu.node = new quadtree_node(node->my_block_size / 2);
node->lu.node->x = node->x * 2;
node->lu.node->y = node->y * 2;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->lu.node);
break;
case mapping_parent::RU:
node->rightup_is_quadtree = true;
node->ru.node = new quadtree_node(node->my_block_size / 2);
node->ru.node->x = node->x * 2 + 1;
node->ru.node->y = node->y * 2;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->ru.node);
break;
case mapping_parent::LD:
node->leftdown_is_quadtree = true;
node->ld.node = new quadtree_node(node->my_block_size / 2);
node->ld.node->x = node->x * 2;
node->ld.node->y = node->y * 2 + 1;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->ld.node);
break;
case mapping_parent::RD:
node->rightdown_is_quadtree = true;
node->rd.node = new quadtree_node(node->my_block_size / 2);
node->rd.node->x = node->x * 2 + 1;
node->rd.node->y = node->y * 2 + 1;
fill_with_mappings(
par_16x16,
par_8x8,
par_4x4,
parbig_32x32,
parbig_16x16,
parbig_8x8,
*node->rd.node);
break;
}
}
return top;
}
void del_quadtree(quadtree_node& top)
{
if (top.leftup_is_quadtree) {
del_quadtree(*top.lu.node);
delete top.lu.node;
} else {
delete top.lu.map;
}
if (top.rightup_is_quadtree) {
del_quadtree(*top.ru.node);
delete top.ru.node;
} else {
delete top.ru.map;
}
if (top.leftdown_is_quadtree) {
del_quadtree(*top.ld.node);
delete top.ld.node;
} else {
delete top.ld.map;
}
if (top.rightdown_is_quadtree) {
del_quadtree(*top.rd.node);
delete top.rd.node;
} else {
delete top.rd.map;
}
}
void iterate(std::vector<double>& y, const std::vector<mapping>& map, size_t w, size_t block_size, size_t block_size_big_mul, double minval, double maxval)
{
auto y_copy = y;
auto par = partition(y_copy, w, block_size * block_size_big_mul);
size_t s_m = w / block_size;
#pragma omp parallel for
for (int j = 0; j < static_cast<int>(s_m); ++j) { // omp needs a signed int.
for (size_t i = 0; i < s_m; ++i) {
auto m = map[j * s_m + i];
//printf("Block %d, %d matched from block %d, %d with transformation = %d\n", i, j, m.idx_big % (s_m / 2), m.idx_big / (s_m / 2), m.t);
auto sq = par[m.idx_big].transformed(m.t);
//printf("x_c = %d;\ty_c = %d;\tt = %d\n", m.x_big, m.y_big, static_cast<int>(m.t));
for (size_t y_ = 0; y_ < block_size; ++y_) {
for (size_t x_ = 0; x_ < block_size; ++x_) {
auto temp = m.q * sq.data[y_ * block_size_big_mul * (block_size * block_size_big_mul) + x_ * block_size_big_mul] + m.p;
temp = (temp > maxval) ? maxval : temp;
temp = (temp < minval) ? minval : temp;
y[(j * block_size + y_) * w + (i * block_size + x_)] = temp;
}
}
}
}
}
auto iterate(const std::vector<double>& y, const std::vector<quadtree_node*> nodes, size_t w, size_t w_out, double minval, double maxval)
{
std::vector<double> output(w_out * w_out, 0.0);
auto par_32x32 = partition(y, w, 32);
auto par_16x16 = partition(y, w, 16);
auto par_8x8 = partition(y, w, 8);
#pragma omp parallel for
for (int i = 0; i < static_cast<int>(nodes.size()); ++i) { // omp needs a signed int.
auto node = nodes[i];
auto& par = (node->my_block_size == 16) ? par_32x32 : ((node->my_block_size == 8) ? par_16x16 : par_8x8);
if (!node->leftup_is_quadtree) {
auto m = node->lu.map;
auto sq = par[m->idx_big].transformed(m->t);
for (size_t y_ = 0; y_ < node->my_block_size; ++y_) {
for (size_t x_ = 0; x_ < node->my_block_size; ++x_) {
auto temp = m->q * sq.data[y_ * 2 * (node->my_block_size * 2) + x_ * 2] + m->p;
temp = (temp > maxval) ? maxval : temp;
temp = (temp < minval) ? minval : temp;
output[(node->y * 2 * node->my_block_size + y_) * w_out + (node->x * 2 * node->my_block_size + x_)] = temp;
}
}
}
if (!node->rightup_is_quadtree) {
auto m = node->ru.map;
auto sq = par[m->idx_big].transformed(m->t);
for (size_t y_ = 0; y_ < node->my_block_size; ++y_) {
for (size_t x_ = 0; x_ < node->my_block_size; ++x_) {
auto temp = m->q * sq.data[y_ * 2 * (node->my_block_size * 2) + x_ * 2] + m->p;
temp = (temp > maxval) ? maxval : temp;
temp = (temp < minval) ? minval : temp;
output[(node->y * 2 * node->my_block_size + y_) * w_out + (node->x * 2 * node->my_block_size + node->my_block_size + x_)] = temp;
}
}
}
if (!node->leftdown_is_quadtree) {
auto m = node->ld.map;
auto sq = par[m->idx_big].transformed(m->t);
for (size_t y_ = 0; y_ < node->my_block_size; ++y_) {
for (size_t x_ = 0; x_ < node->my_block_size; ++x_) {
auto temp = m->q * sq.data[y_ * 2 * (node->my_block_size * 2) + x_ * 2] + m->p;
temp = (temp > maxval) ? maxval : temp;
temp = (temp < minval) ? minval : temp;
output[(node->y * 2 * node->my_block_size + node->my_block_size + y_) * w_out + (node->x * 2 * node->my_block_size + x_)] = temp;
}
}
}
if (!node->rightdown_is_quadtree) {
auto m = node->rd.map;
auto sq = par[m->idx_big].transformed(m->t);
for (size_t y_ = 0; y_ < node->my_block_size; ++y_) {
for (size_t x_ = 0; x_ < node->my_block_size; ++x_) {
auto temp = m->q * sq.data[y_ * 2 * (node->my_block_size * 2) + x_ * 2] + m->p;
temp = (temp > maxval) ? maxval : temp;
temp = (temp < minval) ? minval : temp;
output[(node->y * 2 * node->my_block_size + node->my_block_size + y_) * w_out + (node->x * 2 * node->my_block_size + node->my_block_size + x_)] = temp;
}
}
}
}
return output;
}
void write_mapping(BitVec& data, const mapping& m, size_t w, size_t block_size)
{
size_t max_idx = (w / (block_size / 2)) * (w / (block_size / 2));
size_t bits_for_idx = std::lround(std::log2(static_cast<double>(max_idx)));
for (size_t i = 0; i < bits_for_idx; ++i)
data.add_bit((m.idx_big & (1 << i)) > 0);
data.add_bit((m.t & 1) > 0);
data.add_bit((m.t & 2) > 0);
data.add_bit((m.t & 4) > 0);
// 1 byte for q.
auto temp = (m.q - Q_MIN) * (255 / (Q_MAX - Q_MIN));
if (temp > 255.0)
temp = 255.0;
else if (temp < 0.0)
temp = 0.0;
auto q = static_cast<uint8_t>(temp);
data.add_byte(q);
// 1 byte for p.
double p;
if (m.p >= 0)
p = sqrt(m.p);
else
p = -sqrt(-m.p);
temp = (p + 1.0) * (255 / 2.0);
if (temp > 255.0)
temp = 255.0;
else if (temp < 0.0)
temp = 0.0;
data.add_byte(static_cast<uint8_t>(temp));
}
void read_mapping(BitArr& data, mapping& m, size_t w, size_t block_size)
{
size_t max_idx = (w / (block_size / 2)) * (w / (block_size / 2));
size_t bits_for_idx = std::lround(std::log2(static_cast<double>(max_idx)));
m.idx_big = 0;
for (size_t i = 0; i < bits_for_idx; ++i)
m.idx_big |= data.get_bit() << i;
int t = data.get_bit();
t |= data.get_bit() << 1;
t |= data.get_bit() << 2;
m.t = static_cast<transformation>(t);
uint8_t q = data.get_byte();
m.q = q * ((Q_MAX - Q_MIN) / 255) + Q_MIN;
uint8_t p = data.get_byte();
auto p_ = p * (2.0 / 255) - 1.0;
if (p_ >= 0)
m.p = p_ * p_;
else
m.p = -(p_ * p_);
//printf("%.8f\n", m.p);
}
void write_quadtree(BitVec& data, quadtree_node& top, size_t w)
{
data.add_bit(top.leftup_is_quadtree);
data.add_bit(top.rightup_is_quadtree);
data.add_bit(top.leftdown_is_quadtree);
data.add_bit(top.rightdown_is_quadtree);
if (top.leftup_is_quadtree) {
write_quadtree(data, *top.lu.node, w);
} else {
write_mapping(data, *top.lu.map, w, top.my_block_size);
}
if (top.rightup_is_quadtree) {
write_quadtree(data, *top.ru.node, w);
} else {
write_mapping(data, *top.ru.map, w, top.my_block_size);
}
if (top.leftdown_is_quadtree) {
write_quadtree(data, *top.ld.node, w);
} else {
write_mapping(data, *top.ld.map, w, top.my_block_size);
}
if (top.rightdown_is_quadtree) {
write_quadtree(data, *top.rd.node, w);
} else {
write_mapping(data, *top.rd.map, w, top.my_block_size);
}
}
void read_quadtree(BitArr& data, size_t w, size_t cur_block_size, int cur_x, int cur_y, quadtree_node*& out, std::vector<quadtree_node*>& bottom_level_nodes)
{
out = new quadtree_node(cur_block_size);
out->x = cur_x;
out->y = cur_y;
out->leftup_is_quadtree = data.get_bit();
out->rightup_is_quadtree = data.get_bit();
out->leftdown_is_quadtree = data.get_bit();
out->rightdown_is_quadtree = data.get_bit();
if (!out->leftup_is_quadtree
|| !out->rightup_is_quadtree
|| !out->leftdown_is_quadtree
|| !out->rightdown_is_quadtree) {
bottom_level_nodes.push_back(out);
}
if (out->leftup_is_quadtree) {
read_quadtree(data, w, cur_block_size / 2, cur_x * 2, cur_y * 2, out->lu.node, bottom_level_nodes);
} else {
mapping m(0, NONE, 0.0, 0.0);
read_mapping(data, m, w, cur_block_size);
out->lu.map = new mapping(std::move(m));
}
if (out->rightup_is_quadtree) {
read_quadtree(data, w, cur_block_size / 2, cur_x * 2 + 1, cur_y * 2, out->ru.node, bottom_level_nodes);
} else {
mapping m(0, NONE, 0.0, 0.0);
read_mapping(data, m, w, cur_block_size);
out->ru.map = new mapping(std::move(m));
}
if (out->leftdown_is_quadtree) {
read_quadtree(data, w, cur_block_size / 2, cur_x * 2, cur_y * 2 + 1, out->ld.node, bottom_level_nodes);
} else {
mapping m(0, NONE, 0.0, 0.0);
read_mapping(data, m, w, cur_block_size);
out->ld.map = new mapping(std::move(m));
}
if (out->rightdown_is_quadtree) {
read_quadtree(data, w, cur_block_size / 2, cur_x * 2 + 1, cur_y * 2 + 1, out->rd.node, bottom_level_nodes);
} else {
mapping m(0, NONE, 0.0, 0.0);
read_mapping(data, m, w, cur_block_size);
out->rd.map = new mapping(std::move(m));
}
}
/*
This function should compress image data.
Arguments:
image - pointer to raw rgb image data. To access pixel at point x,y use image[y*w + x]. Pixel format 0x00RRGGBB
compressed_file_name - open this file to write your output
w - width of input image
h - height of input image
block_size - size of grid block
quality - compresion quality. Range [1,100]
*/
void Compress(unsigned long* image, const char* compressed_file_name, size_t w, size_t block_size, int quality)
{
std::vector<double> y, u, v;
bool grayscale = colorspaces::yuv_from_rgb_discr(w, w, image, y, u, v);
if (quality == 100) {
quality = 0;
block_size = 4;
}
std::vector<unsigned char> output_vec;
BitVec bits(output_vec);
bits.add_bit(w == 512);
bits.add_bit(quality != 0);
bits.add_bit(grayscale);
if (quality == 0) {
bits.add_bit(block_size == 4 || block_size == 16);
bits.add_bit(block_size == 16);
auto par_small = partition(y, w, block_size);
auto par_large = partition(y, w, block_size * 2);
add_transforms(par_large);
auto m = make_mapping(par_small, par_large, block_size, 2);
size_t s = m.size();
for (size_t i = 0; i < s; ++i) {
write_mapping(bits, m[i], w, block_size);
}
if (!grayscale) {
par_small = partition(u, w / 2, block_size);
m = make_mapping(par_small, par_large, block_size, 2);
s = s / 4;
for (size_t i = 0; i < s; ++i) {
write_mapping(bits, m[i], w, block_size);
}
par_small = partition(v, w / 2, block_size);
m = make_mapping(par_small, par_large, block_size, 2);
for (size_t i = 0; i < s; ++i) {
write_mapping(bits, m[i], w, block_size);
}
}
} else {
auto par_16x16 = partition(y, w, 16);
auto par_8x8 = partition(y, w, 8);
auto par_4x4 = partition(y, w, 4);
auto parbig_32x32 = partition(y, w, 32);
auto parbig_16x16 = par_16x16;
auto parbig_8x8 = par_8x8;
add_transforms(parbig_32x32);
add_transforms(parbig_16x16);
add_transforms(parbig_8x8);
auto tree = make_quadtree(par_16x16, par_8x8, par_4x4, parbig_32x32, parbig_16x16, parbig_8x8, w, quality);
write_quadtree(bits, tree, w);
del_quadtree(tree);
if (!grayscale) {
par_16x16 = partition(u, w / 2, 16);
par_8x8 = partition(u, w / 2, 8);
par_4x4 = partition(u, w / 2, 4);
tree = make_quadtree(par_16x16, par_8x8, par_4x4, parbig_32x32, parbig_16x16, parbig_8x8, w / 2, quality);
write_quadtree(bits, tree, w);
del_quadtree(tree);
par_16x16 = partition(v, w / 2, 16);
par_8x8 = partition(v, w / 2, 8);
par_4x4 = partition(v, w / 2, 4);
tree = make_quadtree(par_16x16, par_8x8, par_4x4, parbig_32x32, parbig_16x16, parbig_8x8, w / 2, quality);
write_quadtree(bits, tree, w);
del_quadtree(tree);
}
}
bits.align();
FILE *fp = fopen(compressed_file_name, "wb");
printf("Starting arithmetic compression\n");
compress::compress(output_vec.data(), output_vec.size(), fp, false, false);
fclose(fp);
}
/*
This function should decompress image data
Arguments:
compressed_file_name - open this file to read your compressed data from it
w - set this variable to the actual width of output image
h - set this variable to the actual height of output image
Return value:
Pointer to raw output image data (Pixel format 0x00RRGGBB)
*/
unsigned long* Decompress(const char* compressed_file_name, size_t& w)
{
FILE *fp = fopen(compressed_file_name, "rb");
std::vector<uint8_t> in;
compress::decompress(fp, in);
printf("Arithmetic decompression complete\n");
fclose(fp);
BitArr bits(in.data());
w = bits.get_bit() ? 512 : 256;
bool quadtree = bits.get_bit();
bool grayscale = bits.get_bit();
unsigned long *image = new unsigned long[w * w];
std::vector<double> y(w * w, 0.5);
if (!quadtree) {
size_t s;
size_t block_size;
if (bits.get_bit()) {
block_size = bits.get_bit() ? 16 : 4;
} else {
bits.get_bit();
block_size = 8;
}
s = (w / block_size) * (w / block_size);
std::vector<mapping> m(s, mapping(0, NONE, 0.0, 0.0));
for (size_t i = 0; i < s; ++i) {
read_mapping(bits, m[i], w, block_size);
}
for (int i = 0; i < 100; ++i) {
iterate(y, m, w, block_size, 2, 0.0, 1.0);
}
if (grayscale) {
colorspaces::rgb_from_y(w, w, y, image);
} else {
s = s / 4;
std::vector<mapping> m_u(s, mapping(0, NONE, 0.0, 0.0));
for (size_t i = 0; i < s; ++i) {
read_mapping(bits, m_u[i], w, block_size);
}
std::vector<mapping> m_v(s, mapping(0, NONE, 0.0, 0.0));
for (size_t i = 0; i < s; ++i) {
read_mapping(bits, m_v[i], w, block_size);
}
//auto u = y, v = y;
//iterate(u, m_u, w, block_size, 2, -0.436, 0.436);
//iterate(v, m_v, w, block_size, 2, -0.615, 0.615);
std::vector<double> u(w * w / 4);
std::vector<double> v(w * w / 4);
auto y_copy = y;
auto par = partition(y_copy, w, block_size * 2);
size_t s_m = (w / 2) / block_size;
#pragma omp parallel for
for (int j = 0; j < static_cast<int>(s_m); ++j) { // omp needs a signed int.
for (size_t i = 0; i < s_m; ++i) {
auto m = m_u[j * s_m + i];
auto m2 = m_v[j * s_m + i];
auto sq = par[m.idx_big].transformed(m.t);
auto sq2 = par[m2.idx_big].transformed(m2.t);
//printf("x_c = %d;\ty_c = %d;\tt = %d\n", m.x_big, m.y_big, static_cast<int>(m.t));
for (size_t y_ = 0; y_ < block_size; ++y_) {
for (size_t x_ = 0; x_ < block_size; ++x_) {
auto temp = m.q * sq.data[y_ * 2 * (block_size * 2) + x_ * 2] + m.p;
auto temp2 = m2.q * sq2.data[y_ * 2 * (block_size * 2) + x_ * 2] + m2.p;
temp = (temp > 0.436) ? 0.436 : temp;
temp = (temp < -0.436) ? -0.436 : temp;
temp2 = (temp2 > 0.615) ? 0.615 : temp2;
temp2 = (temp2 < -0.615) ? -0.615 : temp2;
u[(j * block_size + y_) * (w / 2) + (i * block_size + x_)] = temp;
v[(j * block_size + y_) * (w / 2) + (i * block_size + x_)] = temp2;
}
}
}
}
colorspaces::rgb_from_yuv_discr(w, w, y, u, v, image);
}
} else {
quadtree_node *tree;
std::vector<quadtree_node*> bottom_level_nodes;
read_quadtree(bits, w, w / 2, 0, 0, tree, bottom_level_nodes);
for (int i = 0; i < 100; ++i) {
y = iterate(y, bottom_level_nodes, w, w, 0.0, 1.0);
}
del_quadtree(*tree);
delete tree;
if (grayscale) {
colorspaces::rgb_from_y(w, w, y, image);
} else {
bottom_level_nodes.clear();
read_quadtree(bits, w, w / 4, 0, 0, tree, bottom_level_nodes);
auto u = iterate(y, bottom_level_nodes, w, w / 2, -0.436, 0.436);
del_quadtree(*tree);
delete tree;
bottom_level_nodes.clear();
read_quadtree(bits, w, w / 4, 0, 0, tree, bottom_level_nodes);
auto v = iterate(y, bottom_level_nodes, w, w / 2, -0.615, 0.615);
del_quadtree(*tree);
delete tree;
colorspaces::rgb_from_yuv_discr(w, w, y, u, v, image);
}
}
return image;
}
void PSNR(unsigned long *image, unsigned long *image2, int w, int h)
{
std::vector<double> y1;
std::vector<double> y2;
colorspaces::y_from_rgb(w, h, image, y1);
colorspaces::y_from_rgb(w, h, image2, y2);
auto y_psnr = psnr::psnr(y1, y2);
printf("%.8f\n", y_psnr);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment