Joshua Moerman
11 years ago
6 changed files with 295 additions and 98 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,116 +1,105 @@ |
|||||
#include <vector> |
#include <includes.hpp> |
||||
#include <iostream> |
|
||||
#include <iterator> |
|
||||
#include <numeric> |
|
||||
#include <cassert> |
|
||||
#include <cmath> |
|
||||
#include <boost/assign.hpp> |
|
||||
|
|
||||
|
#include <boost/filesystem.hpp> |
||||
|
#include <png.hpp> |
||||
|
#include <utilities.hpp> |
||||
|
|
||||
|
#include "compressed_image.hpp" |
||||
#include "striding_iterator.hpp" |
#include "striding_iterator.hpp" |
||||
#include "periodic_iterator.hpp" |
#include "periodic_iterator.hpp" |
||||
|
|
||||
#include "wavelet.hpp" |
#include "wavelet.hpp" |
||||
|
|
||||
bool is_pow_of_two(int n){ |
// note: we take a copy, because we will modify it in place
|
||||
return (n & (n - 1)) == 0; |
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> |
// wavelet transform in x-direction
|
||||
void shuffle(Iterator begin, Iterator end){ |
for(int i = 0; i < height; ++i){ |
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type; |
wavelet(image.begin() + i*width, image.begin() + (i+1)*width); |
||||
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> |
// wavelet transform in y-direction
|
||||
void unshuffle(Iterator begin, Iterator end){ |
for(int i = 0; i < width; ++i){ |
||||
typedef typename std::iterator_traits<Iterator>::value_type value_type; |
wavelet(strided(image.begin() + i, width), strided(image.end() + i, width)); |
||||
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> |
// save the principal coefficients
|
||||
void wavelet(Iterator begin, Iterator end){ |
std::vector<jcmp::coefficient> v; |
||||
int s = end - begin; |
for(int y = 0; y < height; ++y){ |
||||
for(int i = s; i >= 4; i >>= 1){ |
for(int x = 0; x < width; ++x){ |
||||
// half interval
|
auto&& el = image[x + width*y]; |
||||
end = begin + i; |
if(std::abs(el) > threshold) v.push_back({el, jcmp::uint(x), jcmp::uint(y)}); |
||||
assert(is_pow_of_two(end - begin)); |
else ++zeros; |
||||
|
|
||||
// multiply with Wn
|
|
||||
wavelet_mul(begin, end); |
|
||||
// then with Sn
|
|
||||
shuffle(begin, end); |
|
||||
} |
} |
||||
} |
} |
||||
|
|
||||
template <typename Iterator> |
return jcmp::image(width, height, std::move(v)); |
||||
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<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; |
||||
} |
} |
||||
|
|
||||
struct filter{ |
in.clear(); |
||||
filter(double threshold) |
|
||||
: threshold(threshold) |
// inverse wavelet transform in y-direction
|
||||
{} |
for(int i = 0; i < width; ++i){ |
||||
|
unwavelet(strided(image.begin() + i, width), strided(image.end() + i, width)); |
||||
|
} |
||||
|
|
||||
void operator()(double& x){ |
// inverse wavelet transform in x-direction
|
||||
if(std::abs(x) <= threshold) x = 0; |
for(int i = 0; i < height; ++i){ |
||||
|
unwavelet(image.begin() + i*width, image.begin() + (i+1)*width); |
||||
} |
} |
||||
|
|
||||
double threshold; |
return image; |
||||
}; |
} |
||||
|
|
||||
int main(){ |
int main(){ |
||||
using namespace boost::assign; |
namespace fs = boost::filesystem; |
||||
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; |
fs::path directory("images"); |
||||
|
fs::directory_iterator eod; |
||||
// print input
|
for(fs::directory_iterator it(directory); it != eod; ++it){ |
||||
std::copy(input.begin(), input.end(), std::ostream_iterator<double>(std::cout, "\n")); |
auto && path = it->path(); |
||||
std::cout << std::endl; |
if(path.extension() != ".png") continue; |
||||
|
|
||||
std::vector<double> thresholds; |
// open file
|
||||
thresholds += 0.0, 0.1, 0.2, 0.5; |
std::string filename = path.string(); |
||||
for(int i = 0; i < thresholds.size(); ++i){ |
std::cout << field("file") << filename << std::endl; |
||||
std::vector<double> v; |
png::istream image(filename); |
||||
v = input; |
|
||||
|
auto width = image.get_width(); |
||||
// transform to wavelet domain
|
auto height = image.get_height(); |
||||
wavelet(v.begin(), v.end()); |
|
||||
|
// read into vector
|
||||
// apply threshold
|
std::vector<double> image_vec; |
||||
std::for_each(v.begin(), v.end(), filter(thresholds[i])); |
image_vec.reserve(width * height); |
||||
int zeros = std::count(v.begin(), v.end(), 0.0); |
for(unsigned char c = 0; image >> c;) image_vec.push_back(c/255.0); |
||||
|
|
||||
// transform back to sample domain
|
// compress and decompress to see how we lost information
|
||||
unwavelet(v.begin(), v.end()); |
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; |
// output some information
|
||||
std::copy(v.begin(), v.end(), std::ostream_iterator<double>(std::cout, "\n")); |
std::cout << field("raw") << human_string(image_vec.size()) << std::endl; |
||||
std::cout << 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 new issue