Made file format. Restructures some stuff.
This commit is contained in:
parent
f206ad4c77
commit
8103b7fbf4
6 changed files with 297 additions and 100 deletions
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
// I got sick of all those includes I need everywhere -.-
|
||||
// So here is a list I'll always include
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
inline bool is_pow_of_two(int n){
|
||||
return (n & (n - 1)) == 0;
|
||||
}
|
||||
|
||||
inline std::string human_string(int n){
|
||||
static const std::string names [] = {"", "K", "M", "G"};
|
||||
int i = 0;
|
||||
while(n > 1000 && i < sizeof(names)){
|
||||
n /= 1000;
|
||||
++i;
|
||||
}
|
||||
return std::to_string(n) + names[i];
|
||||
}
|
||||
|
||||
inline std::string field(std::string const & str){
|
||||
const int length = 12;
|
||||
if(str.size() > length) return str + ":\t";
|
||||
auto add = length - str.size();
|
||||
return str + ":" + std::string(add, ' ') + "\t";
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include <includes.hpp>
|
||||
|
||||
// joshua's compression :D
|
||||
namespace jcmp {
|
||||
typedef uint32_t uint;
|
||||
|
||||
struct __attribute__ ((__packed__)) header {
|
||||
const char signature[4];
|
||||
uint width;
|
||||
uint height;
|
||||
uint length;
|
||||
|
||||
header(uint width = 0, uint height = 0, uint length = 0)
|
||||
: signature{'J', 'C', 'M', 'P'}
|
||||
, width(width)
|
||||
, height(height)
|
||||
, length(length)
|
||||
{}
|
||||
};
|
||||
|
||||
struct __attribute__ ((__packed__)) coefficient {
|
||||
double c;
|
||||
uint x;
|
||||
uint y;
|
||||
};
|
||||
|
||||
struct image {
|
||||
header header;
|
||||
std::vector<coefficient> data;
|
||||
|
||||
image() = default;
|
||||
|
||||
image(uint width, uint height, std::vector<coefficient> const & data_in)
|
||||
: header(width, height, data_in.size())
|
||||
, data(data_in)
|
||||
{}
|
||||
|
||||
void clear(){
|
||||
header.length = header.height = header.width = 0;
|
||||
data.clear();
|
||||
}
|
||||
};
|
||||
|
||||
inline void write_to_file(image const & image, std::string filename){
|
||||
std::filebuf file;
|
||||
file.open(filename, std::ios_base::out|std::ios_base::trunc);
|
||||
file.sputn(reinterpret_cast<const char*>(&image.header), sizeof(header));
|
||||
file.sputn(reinterpret_cast<const char*>(image.data.data()), image.data.size() * sizeof(coefficient));
|
||||
}
|
||||
|
||||
inline image read_from_file(std::string const& filename){
|
||||
std::filebuf file;
|
||||
file.open(filename, std::ios_base::in);
|
||||
image image;
|
||||
file.sgetn(reinterpret_cast<char*>(&image.header), sizeof(header));
|
||||
assert(strncmp("JCMP", image.header.signature, 4) == 0);
|
||||
image.data.resize(image.header.length);
|
||||
file.sgetn(reinterpret_cast<char*>(image.data.data()), image.data.size() * sizeof(coefficient));
|
||||
return image;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
#include <includes.hpp>
|
||||
|
||||
#include "compressed_image.hpp"
|
||||
namespace jcmp{
|
||||
bool operator==(coefficient const & lhs, coefficient const & rhs){
|
||||
return lhs.c == rhs.c && lhs.x == rhs.x && lhs.y == rhs.y;
|
||||
}
|
||||
}
|
||||
|
||||
struct gen{
|
||||
jcmp::uint width, x, y;
|
||||
|
||||
jcmp::coefficient operator()(){
|
||||
++x;
|
||||
if(x > width){
|
||||
x = 0;
|
||||
++y;
|
||||
}
|
||||
// only for testing purpose :D
|
||||
return {rand() / double(RAND_MAX), x, y};
|
||||
}
|
||||
};
|
||||
|
||||
int main(){
|
||||
const int w = 1024, h = 512;
|
||||
srand(time(0));
|
||||
|
||||
std::vector<jcmp::coefficient> v(w*h / 100);
|
||||
std::generate(v.begin(), v.end(), gen{w, 0, 0});
|
||||
|
||||
jcmp::image i(w, h, std::move(v));
|
||||
|
||||
jcmp::write_to_file(i, "test.jcmp");
|
||||
auto j = jcmp::read_from_file("test.jcmp");
|
||||
|
||||
assert(w == j.header.width);
|
||||
assert(h == j.header.height);
|
||||
assert(i.data.size() == j.data.size());
|
||||
assert(i.data == j.data);
|
||||
|
||||
std::cout << "Test succeeded" << std::endl;
|
||||
}
|
|
@ -1,5 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <includes.hpp>
|
||||
|
||||
#include "striding_iterator.hpp"
|
||||
#include "periodic_iterator.hpp"
|
||||
|
||||
static double const evn_coef[] = {
|
||||
(1.0 + std::sqrt(3.0))/(std::sqrt(32.0)),
|
||||
(3.0 + std::sqrt(3.0))/(std::sqrt(32.0)),
|
||||
|
@ -14,9 +19,11 @@ static double const odd_coef[] = {
|
|||
-evn_coef[0]
|
||||
};
|
||||
|
||||
// Apply the matrix Wn with the DAUB4 coefficients
|
||||
// Assumes input to be periodic
|
||||
template <typename Iterator>
|
||||
void wavelet_mul(Iterator begin, Iterator end){
|
||||
int mul = end - begin;
|
||||
auto mul = end - begin;
|
||||
std::vector<double> out(mul, 0.0);
|
||||
for(int i = 0; i < mul; i += 2){
|
||||
out[i] = std::inner_product(evn_coef, evn_coef+4, periodic(begin, end) + i, 0.0);
|
||||
|
@ -29,7 +36,7 @@ void wavelet_mul(Iterator begin, Iterator end){
|
|||
|
||||
template <typename Iterator>
|
||||
void wavelet_inv(Iterator begin, Iterator end){
|
||||
int mul = end - begin;
|
||||
auto mul = end - begin;
|
||||
std::vector<double> out(mul, 0.0);
|
||||
Iterator bc = begin;
|
||||
for(int i = 0; i < mul; i += 2, begin += 2){
|
||||
|
@ -42,3 +49,63 @@ void wavelet_inv(Iterator begin, Iterator end){
|
|||
*bc++ = out[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Shuffle works with an extra copy, might be inefficient, but it is at least correct ;)
|
||||
// It applies the even-odd-sort matrix Sn
|
||||
template <typename Iterator>
|
||||
void shuffle(Iterator begin, Iterator end){
|
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type;
|
||||
auto s = end - begin;
|
||||
assert(s % 2 == 0);
|
||||
|
||||
std::vector<value_type> v(s);
|
||||
std::copy(strided(begin , 2), strided(end , 2), v.begin());
|
||||
std::copy(strided(begin+1, 2), strided(end+1, 2), v.begin() + s/2);
|
||||
std::copy(v.begin(), v.end(), begin);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
void unshuffle(Iterator begin, Iterator end){
|
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type;
|
||||
auto s = end - begin;
|
||||
assert(s % 2 == 0);
|
||||
|
||||
std::vector<value_type> v(s);
|
||||
std::copy(begin, begin + s/2, strided(v.begin(), 2));
|
||||
std::copy(begin + s/2, end, strided(v.begin()+1, 2));
|
||||
std::copy(v.begin(), v.end(), begin);
|
||||
}
|
||||
|
||||
// Combines the matrix Wn and Sn recusrively
|
||||
// Only works for inputs of size 2^m
|
||||
template <typename Iterator>
|
||||
void wavelet(Iterator begin, Iterator end){
|
||||
auto s = end - begin;
|
||||
assert(s >= 4);
|
||||
for(int i = s; i >= 4; i >>= 1){
|
||||
// half interval
|
||||
end = begin + i;
|
||||
assert(is_pow_of_two(end - begin));
|
||||
|
||||
// multiply with Wn
|
||||
wavelet_mul(begin, end);
|
||||
// then with Sn
|
||||
shuffle(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
void unwavelet(Iterator begin, Iterator end){
|
||||
auto s = end - begin;
|
||||
assert(s >= 4);
|
||||
for(int i = 4; i <= s; i <<= 1){
|
||||
// double interval
|
||||
end = begin + i;
|
||||
assert(is_pow_of_two(end - begin));
|
||||
|
||||
// unshuffle: Sn^-1
|
||||
unshuffle(begin, end);
|
||||
// then Wn^-1
|
||||
wavelet_inv(begin, end);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,116 +1,105 @@
|
|||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <numeric>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <boost/assign.hpp>
|
||||
#include <includes.hpp>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <png.hpp>
|
||||
#include <utilities.hpp>
|
||||
|
||||
#include "compressed_image.hpp"
|
||||
#include "striding_iterator.hpp"
|
||||
#include "periodic_iterator.hpp"
|
||||
|
||||
#include "wavelet.hpp"
|
||||
|
||||
bool is_pow_of_two(int n){
|
||||
return (n & (n - 1)) == 0;
|
||||
}
|
||||
// note: we take a copy, because we will modify it in place
|
||||
jcmp::image compress(std::vector<double> image, int width, double threshold, int& zeros){
|
||||
auto height = image.size() / width;
|
||||
assert(is_pow_of_two(width));
|
||||
assert(is_pow_of_two(height));
|
||||
|
||||
template <typename Iterator>
|
||||
void shuffle(Iterator begin, Iterator end){
|
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type;
|
||||
typedef typename std::iterator_traits<Iterator>::difference_type diff_type;
|
||||
diff_type s = end - begin;
|
||||
assert(s % 2 == 0);
|
||||
|
||||
std::vector<value_type> v(s);
|
||||
std::copy(strided(begin , 2), strided(end , 2), v.begin());
|
||||
std::copy(strided(begin+1, 2), strided(end+1, 2), v.begin() + s/2);
|
||||
std::copy(v.begin(), v.end(), begin);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
void unshuffle(Iterator begin, Iterator end){
|
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type;
|
||||
typedef typename std::iterator_traits<Iterator>::difference_type diff_type;
|
||||
diff_type s = end - begin;
|
||||
assert(s % 2 == 0);
|
||||
|
||||
std::vector<value_type> v(s);
|
||||
std::copy(begin, begin + s/2, strided(v.begin(), 2));
|
||||
std::copy(begin + s/2, end, strided(v.begin()+1, 2));
|
||||
std::copy(v.begin(), v.end(), begin);
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
void wavelet(Iterator begin, Iterator end){
|
||||
int s = end - begin;
|
||||
for(int i = s; i >= 4; i >>= 1){
|
||||
// half interval
|
||||
end = begin + i;
|
||||
assert(is_pow_of_two(end - begin));
|
||||
|
||||
// multiply with Wn
|
||||
wavelet_mul(begin, end);
|
||||
// then with Sn
|
||||
shuffle(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Iterator>
|
||||
void unwavelet(Iterator begin, Iterator end){
|
||||
int s = end - begin;
|
||||
for(int i = 4; i <= s; i <<= 1){
|
||||
// double interval
|
||||
end = begin + i;
|
||||
assert(is_pow_of_two(end - begin));
|
||||
|
||||
// unshuffle: Sn^-1
|
||||
unshuffle(begin, end);
|
||||
// then Wn^-1
|
||||
wavelet_inv(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
struct filter{
|
||||
filter(double threshold)
|
||||
: threshold(threshold)
|
||||
{}
|
||||
|
||||
void operator()(double& x){
|
||||
if(std::abs(x) <= threshold) x = 0;
|
||||
// wavelet transform in x-direction
|
||||
for(int i = 0; i < height; ++i){
|
||||
wavelet(image.begin() + i*width, image.begin() + (i+1)*width);
|
||||
}
|
||||
|
||||
double threshold;
|
||||
};
|
||||
// wavelet transform in y-direction
|
||||
for(int i = 0; i < width; ++i){
|
||||
wavelet(strided(image.begin() + i, width), strided(image.end() + i, width));
|
||||
}
|
||||
|
||||
// save the principal coefficients
|
||||
std::vector<jcmp::coefficient> v;
|
||||
for(int y = 0; y < height; ++y){
|
||||
for(int x = 0; x < width; ++x){
|
||||
auto&& el = image[x + width*y];
|
||||
if(std::abs(el) > threshold) v.push_back({el, jcmp::uint(x), jcmp::uint(y)});
|
||||
else ++zeros;
|
||||
}
|
||||
}
|
||||
|
||||
return jcmp::image(width, height, std::move(v));
|
||||
}
|
||||
|
||||
std::vector<double> decompress(jcmp::image in){
|
||||
auto width = in.header.width;
|
||||
auto height = in.header.height;
|
||||
assert(is_pow_of_two(width));
|
||||
assert(is_pow_of_two(height));
|
||||
|
||||
std::vector<double> image(width * height, 0.0);
|
||||
|
||||
// read in coefficient on coordinates
|
||||
for(auto it = in.data.begin(); it != in.data.end(); ++it){
|
||||
auto&& x = *it;
|
||||
image[x.x + width*x.y] = x.c;
|
||||
}
|
||||
|
||||
in.clear();
|
||||
|
||||
// inverse wavelet transform in y-direction
|
||||
for(int i = 0; i < width; ++i){
|
||||
unwavelet(strided(image.begin() + i, width), strided(image.end() + i, width));
|
||||
}
|
||||
|
||||
// inverse wavelet transform in x-direction
|
||||
for(int i = 0; i < height; ++i){
|
||||
unwavelet(image.begin() + i*width, image.begin() + (i+1)*width);
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
int main(){
|
||||
using namespace boost::assign;
|
||||
std::vector<double> input;
|
||||
input += 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0;
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// print input
|
||||
std::copy(input.begin(), input.end(), std::ostream_iterator<double>(std::cout, "\n"));
|
||||
std::cout << std::endl;
|
||||
fs::path directory("images");
|
||||
fs::directory_iterator eod;
|
||||
for(fs::directory_iterator it(directory); it != eod; ++it){
|
||||
auto && path = it->path();
|
||||
if(path.extension() != ".png") continue;
|
||||
|
||||
std::vector<double> thresholds;
|
||||
thresholds += 0.0, 0.1, 0.2, 0.5;
|
||||
for(int i = 0; i < thresholds.size(); ++i){
|
||||
std::vector<double> v;
|
||||
v = input;
|
||||
// open file
|
||||
std::string filename = path.string();
|
||||
std::cout << field("file") << filename << std::endl;
|
||||
png::istream image(filename);
|
||||
|
||||
// transform to wavelet domain
|
||||
wavelet(v.begin(), v.end());
|
||||
auto width = image.get_width();
|
||||
auto height = image.get_height();
|
||||
|
||||
// apply threshold
|
||||
std::for_each(v.begin(), v.end(), filter(thresholds[i]));
|
||||
int zeros = std::count(v.begin(), v.end(), 0.0);
|
||||
// read into vector
|
||||
std::vector<double> image_vec;
|
||||
image_vec.reserve(width * height);
|
||||
for(unsigned char c = 0; image >> c;) image_vec.push_back(c/255.0);
|
||||
|
||||
// transform back to sample domain
|
||||
unwavelet(v.begin(), v.end());
|
||||
// compress and decompress to see how we lost information
|
||||
int zeros = 0;
|
||||
auto compressed_vec = decompress(compress(image_vec, width, 0.5, zeros));
|
||||
|
||||
// print compressed
|
||||
std::cout << "\ncp: " << zeros / double(v.size()) << std::endl;
|
||||
std::copy(v.begin(), v.end(), std::ostream_iterator<double>(std::cout, "\n"));
|
||||
std::cout << std::endl;
|
||||
// output some information
|
||||
std::cout << field("raw") << human_string(image_vec.size()) << std::endl;
|
||||
std::cout << field("compressed") << human_string(image_vec.size() - zeros) << std::endl;
|
||||
|
||||
// save to output file
|
||||
std::string cfilename = "compressed/" + path.filename().string();
|
||||
png::gray_ostream compressed_image(width, height, cfilename);
|
||||
for(int i = 0; i < compressed_vec.size(); ++i) compressed_image << compressed_vec[i];
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue