From 8103b7fbf4f0686badaf93cb18a4c3bcab89562c Mon Sep 17 00:00:00 2001 From: Joshua Moerman Date: Wed, 6 Nov 2013 22:22:20 +0100 Subject: [PATCH] Made file format. Restructures some stuff. --- include/includes.hpp | 11 ++ include/utilities.hpp | 24 ++++ wavelet/compressed_image.hpp | 64 +++++++++++ wavelet/compressed_image_test.cpp | 42 +++++++ wavelet/wavelet.hpp | 71 +++++++++++- wavelet/wavelet2.cpp | 181 ++++++++++++++---------------- 6 files changed, 295 insertions(+), 98 deletions(-) diff --git a/include/includes.hpp b/include/includes.hpp index e69de29..03a6b82 100644 --- a/include/includes.hpp +++ b/include/includes.hpp @@ -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 +#include +#include +#include +#include +#include +#include diff --git a/include/utilities.hpp b/include/utilities.hpp index e69de29..2156fcc 100644 --- a/include/utilities.hpp +++ b/include/utilities.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include + +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"; +} diff --git a/wavelet/compressed_image.hpp b/wavelet/compressed_image.hpp index e69de29..3bbc4d4 100644 --- a/wavelet/compressed_image.hpp +++ b/wavelet/compressed_image.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include + +// 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 data; + + image() = default; + + image(uint width, uint height, std::vector 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(&image.header), sizeof(header)); + file.sputn(reinterpret_cast(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(&image.header), sizeof(header)); + assert(strncmp("JCMP", image.header.signature, 4) == 0); + image.data.resize(image.header.length); + file.sgetn(reinterpret_cast(image.data.data()), image.data.size() * sizeof(coefficient)); + return image; + } + +} diff --git a/wavelet/compressed_image_test.cpp b/wavelet/compressed_image_test.cpp index e69de29..fad089d 100644 --- a/wavelet/compressed_image_test.cpp +++ b/wavelet/compressed_image_test.cpp @@ -0,0 +1,42 @@ +#include + +#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 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; +} diff --git a/wavelet/wavelet.hpp b/wavelet/wavelet.hpp index f60487c..1bea72d 100644 --- a/wavelet/wavelet.hpp +++ b/wavelet/wavelet.hpp @@ -1,5 +1,10 @@ #pragma once +#include + +#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 void wavelet_mul(Iterator begin, Iterator end){ - int mul = end - begin; + auto mul = end - begin; std::vector 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 void wavelet_inv(Iterator begin, Iterator end){ - int mul = end - begin; + auto mul = end - begin; std::vector 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 +void shuffle(Iterator begin, Iterator end){ + typedef typename std::iterator_traits::value_type value_type; + auto s = end - begin; + assert(s % 2 == 0); + + std::vector 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 +void unshuffle(Iterator begin, Iterator end){ + typedef typename std::iterator_traits::value_type value_type; + auto s = end - begin; + assert(s % 2 == 0); + + std::vector 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 +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 +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); + } +} diff --git a/wavelet/wavelet2.cpp b/wavelet/wavelet2.cpp index f998746..4433ada 100644 --- a/wavelet/wavelet2.cpp +++ b/wavelet/wavelet2.cpp @@ -1,116 +1,105 @@ -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include + +#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 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 -void shuffle(Iterator begin, Iterator end){ - typedef typename std::iterator_traits::value_type value_type; - typedef typename std::iterator_traits::difference_type diff_type; - diff_type s = end - begin; - assert(s % 2 == 0); - - std::vector 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); -} + // wavelet transform in x-direction + for(int i = 0; i < height; ++i){ + wavelet(image.begin() + i*width, image.begin() + (i+1)*width); + } -template -void unshuffle(Iterator begin, Iterator end){ - typedef typename std::iterator_traits::value_type value_type; - typedef typename std::iterator_traits::difference_type diff_type; - diff_type s = end - begin; - assert(s % 2 == 0); - - std::vector 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); -} + // wavelet transform in y-direction + for(int i = 0; i < width; ++i){ + wavelet(strided(image.begin() + i, width), strided(image.end() + i, width)); + } -template -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); + // save the principal coefficients + std::vector 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)); } -template -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); +std::vector 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 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; } -} -struct filter{ - filter(double threshold) - : threshold(threshold) - {} + in.clear(); - void operator()(double& x){ - if(std::abs(x) <= threshold) x = 0; + // inverse wavelet transform in y-direction + for(int i = 0; i < width; ++i){ + unwavelet(strided(image.begin() + i, width), strided(image.end() + i, width)); } - double threshold; -}; + // 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 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; - - // print input - std::copy(input.begin(), input.end(), std::ostream_iterator(std::cout, "\n")); - std::cout << std::endl; - - std::vector thresholds; - thresholds += 0.0, 0.1, 0.2, 0.5; - for(int i = 0; i < thresholds.size(); ++i){ - std::vector v; - v = input; - - // transform to wavelet domain - wavelet(v.begin(), v.end()); - - // apply threshold - std::for_each(v.begin(), v.end(), filter(thresholds[i])); - int zeros = std::count(v.begin(), v.end(), 0.0); - - // transform back to sample domain - unwavelet(v.begin(), v.end()); - - // print compressed - std::cout << "\ncp: " << zeros / double(v.size()) << std::endl; - std::copy(v.begin(), v.end(), std::ostream_iterator(std::cout, "\n")); - std::cout << std::endl; + namespace fs = boost::filesystem; + + 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; + + // open file + std::string filename = path.string(); + std::cout << field("file") << filename << std::endl; + png::istream image(filename); + + auto width = image.get_width(); + auto height = image.get_height(); + + // read into vector + std::vector image_vec; + image_vec.reserve(width * height); + for(unsigned char c = 0; image >> c;) image_vec.push_back(c/255.0); + + // compress and decompress to see how we lost information + int zeros = 0; + auto compressed_vec = decompress(compress(image_vec, width, 0.5, zeros)); + + // 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]; } }