rotate-me-fast/rotation.cpp
Fabien Freling e3e0d3c20a Fix first rotated tile on each row.
The first tile on each row seemed to be missing values.
This was due to the ‘continue’ statement that would prevent
src_rotated_point to be incremented properly.
2014-07-04 00:34:06 +02:00

1021 lines
27 KiB
C++

#include <string>
#include <fstream>
#include <iostream>
#include <sstream>
#include <cmath>
#include <cassert>
#include <cstring>
#include <chrono>
#include <cstdlib>
#include <xmmintrin.h>
#include <emmintrin.h>
using namespace std;
//
//
// Point
//
template <typename T>
struct TPoint {
T x;
T y;
TPoint(T a, T b)
: x(a)
, y(b)
{}
};
typedef TPoint<int> Point;
typedef TPoint<double> DPoint; // absolute point, can be negative
template<typename Elem, typename Traits, typename T>
std::basic_ostream<Elem, Traits>& operator << (std::basic_ostream<Elem, Traits>& o, TPoint<T> const& p)
{
o << "(" << p.x << ", " << p.y << ")";
return o;
}
//
//
// Image
//
struct Image {
unsigned int width;
unsigned int height;
uint8_t* buffer;
Image()
: width(0)
, height(0)
, buffer(NULL)
{}
virtual ~Image()
{
delete [] buffer;
}
Image(unsigned int w, unsigned int h)
{
this->width = w;
this->height = h;
buffer = new uint8_t[width * height * 3];
memset(buffer, 0, width * height * 3 * sizeof (uint8_t));
}
Image(string const& path)
: Image()
{
ifstream is(path);
if (!is.is_open())
{
cerr << "Cannot open file '" << path << "'" << endl;
abort();
}
if (!this->read_header(is))
{
cerr << "Invalid header." << endl;
abort();
}
if (!this->read_body(is))
{
delete buffer;
buffer = nullptr;
cerr << "Invalid header." << endl;
abort();
}
}
bool save(string const& path) const
{
ofstream os(path);
if (!os.is_open())
{
cerr << "Cannot open file '" << path << "'" << endl;
return false;
}
this->write_header(os);
this->write_body(os);
return true;
}
void set_pixel(unsigned int x, unsigned int y, uint8_t r, uint8_t g, uint8_t b)
{
if (x >= width || y >= height)
{
// cerr << __LINE__ << " | Point (" << x << ", " << y << ") out of bounds" << endl;
// cerr << " Image dimensions: " << width << " x " << height << endl;
// assert(false);
return;
}
int index = (y * width + x) * 3;
buffer[index++] = r;
buffer[index++] = g;
buffer[index++] = b;
}
void set_pixel(Point const& p, uint8_t r, uint8_t g, uint8_t b)
{
this->set_pixel(p.x, p.y, r, g, b);
}
protected:
bool read_header(std::ifstream& istr)
{
// check magic
if (istr.get() != 'P' )
{
return false;
}
char type = static_cast<char>(istr.get());
if (type != '6')
{
return false;
}
if (istr.get() != '\n')
{
return false;
}
// skip comments
while (istr.peek() == '#')
{
std::string line;
std::getline(istr, line);
}
// get size
istr >> width >> height;
if (width == 0 || height == 0)
{
return false;
}
// get maxvalue
if (istr.get() != '\n')
{
return false;
}
int max_value = -1;
istr >> max_value;
if (max_value > 255)
{
return false;
}
if (istr.get() != '\n')
{
return false;
}
// cout << "width: " << width << endl;
// cout << "height: " << height << endl;
return true;
}
bool write_header(std::ofstream& ostr) const
{
ostr << "P6" << endl;
ostr << width << " " << height << endl;
ostr << "255" << endl;
return true;
}
virtual bool read_body(std::ifstream& istr)
{
unsigned int const nb_pixels = width * height;
buffer = new uint8_t[nb_pixels * 3];
uint8_t* buf_index = buffer;
for (unsigned int i = 0; i < nb_pixels * 3; ++i)
{
*buf_index = istr.get();
++buf_index;
}
return true;
}
virtual bool write_body(std::ofstream& ostr) const
{
unsigned int const nb_pixels = width * height;
uint8_t* buf_index = buffer;
for (unsigned int i = 0; i < nb_pixels * 3; ++i)
{
ostr << (char) *buf_index;
++buf_index;
}
return true;
}
};
template<unsigned int W, unsigned int H>
struct TiledImage : public Image {
uint8_t** tiles;
unsigned int static const tile_w = W;
unsigned int static const tile_h = H;
unsigned int static const tile_size = W * H;
unsigned int nb_col_tile;
unsigned int nb_row_tile;
TiledImage()
: Image()
, tiles(NULL)
, nb_col_tile(0)
, nb_row_tile(0)
{}
~TiledImage()
{
unsigned int const nb_tiles = nb_col_tile * nb_row_tile;
for (unsigned int i = 0; i < nb_tiles; ++i)
{
delete [] tiles[i];
}
delete [] tiles;
}
TiledImage(unsigned int w, unsigned int h)
{
allocate_memory(w, h);
}
TiledImage(string const& path)
: TiledImage()
{
ifstream is(path);
if (!is.is_open())
{
cerr << "Cannot open file '" << path << "'" << endl;
abort();
}
if (!this->read_header(is))
{
cerr << "Invalid header." << endl;
abort();
}
if (!this->read_body(is))
{
// TODO: delete tiles
cerr << "Invalid header." << endl;
abort();
}
}
uint8_t*
access_pixel(unsigned int x, unsigned int y)
{
if (x >= width || y >= height)
return nullptr;
unsigned int const tile_width = tile_w * 3;
unsigned int const tile_index = (y / tile_h) * nb_col_tile + (x / tile_w);
uint8_t* tile = tiles[tile_index];
unsigned int const tile_j = y % tile_h;
unsigned int const tile_i = x % tile_w;
return tile + tile_j * tile_width + (tile_i * 3);
}
uint8_t const*
access_pixel(unsigned int x, unsigned int y) const
{
if (x >= width || y >= height)
return nullptr;
unsigned int const tile_width = tile_w * 3;
unsigned int const tile_index = (y / tile_h) * nb_col_tile + (x / tile_w);
//cout << "tile index: " << tile_index << endl;
uint8_t* tile = tiles[tile_index];
unsigned int const tile_j = y % tile_h;
unsigned int const tile_i = x % tile_w;
return tile + tile_j * tile_width + (tile_i * 3);
}
void
print_tile(unsigned int index) const
{
cout << "Tile[" << index << "]" << endl;
uint8_t const* tile = tiles[index];
unsigned int const tile_width = tile_w * 3;
for (unsigned int j = 0; j < tile_h; ++j)
{
for (unsigned int i = 0; i < tile_w; ++i)
{
if (i != 0)
cout << ", ";
uint8_t const* p = tile + j * tile_width + i * 3;
cout << (int) *p << " " << (int) *(p + 1) << " " << (int) *(p + 2);
}
cout << endl;
}
cout << endl;
}
protected:
void allocate_memory(unsigned int w, unsigned int h)
{
width = w;
height = h;
nb_col_tile = width / tile_w;
if (width % tile_w != 0)
++nb_col_tile;
nb_row_tile = height / tile_h;
if (height % tile_h != 0)
++nb_row_tile;
unsigned int const nb_tiles = nb_col_tile * nb_row_tile;
tiles = new uint8_t*[nb_tiles];
for (unsigned int i = 0; i < nb_tiles; ++i)
{
tiles[i] = new uint8_t[tile_w * tile_h * 3];
memset(tiles[i], 0, tile_w * tile_h * 3 * sizeof (uint8_t));
}
}
virtual bool read_body(std::ifstream& istr)
{
this->allocate_memory(width, height);
// Pixel loading
for (unsigned int j = 0; j < height; ++j)
for (unsigned int i = 0; i < width; ++i)
{
uint8_t* tile = this->access_pixel(i, j);
*(tile++) = istr.get();
*(tile++) = istr.get();
*(tile++) = istr.get();
}
return true;
}
virtual bool write_body(std::ofstream& ostr) const override
{
for (unsigned int j = 0; j < height; ++j)
for (unsigned int i = 0; i < width; ++i)
{
uint8_t const* tile = this->access_pixel(i, j);
ostr << (char) *(tile++);
ostr << (char) *(tile++);
ostr << (char) *(tile++);
}
return true;
}
};
//
//
// Trigonometry
//
DPoint convert_grid_coord(Image const& img, Point const& p)
{
return DPoint(p.x - img.width / 2.0f + 0.5, p.y - img.height / 2.0f + 0.5);
}
double convert_radian(Image const& img, Point const& p, double const ratio)
{
DPoint centered = convert_grid_coord(img, p);
double const cos_value = centered.x * ratio;
double const sin_value = - (centered.y * ratio);
double angle = acos(cos_value);
if (sin_value < 0)
{
angle = (2 * M_PI) - angle;
}
return angle;
}
DPoint convert_abs_coord(double const angle, double const ratio)
{
return DPoint(cos(angle) / ratio, - sin(angle) / ratio);
}
Point convert_img_coord(Image const& img, DPoint const& p)
{
int x = round(p.x + (img.width / 2.0f) - 0.5);
int y = round(p.y + (img.height / 2.0f) - 0.5);
return Point(x, y);
}
DPoint convert_img_coord_precision(Image const& img, DPoint const& p)
{
int x = p.x + (img.width / 2.0f) - 0.5;
int y = p.y + (img.height / 2.0f) - 0.5;
return DPoint(x, y);
}
void convert_abs_to_polar_coord(DPoint const& p, double& angle, double& dist)
{
angle = atan2(-p.y, p.x);
dist = sqrt(p.x * p.x + p.y * p.y);
}
DPoint convert_polar_to_grid_coord(double const angle, double const distance)
{
return DPoint(cos(angle) * distance, - (sin(angle) * distance));
}
double compute_ratio(Image const& img)
{
double const trigo_length = (sqrt(img.width * img.width + img.height * img.height) - 1) / 2;
return 1.0f / trigo_length;
}
void compute_output_size(Image const& src, double const rotation, unsigned int& width, unsigned int& height)
{
double const ratio = compute_ratio(src);
double min_w = 0;
double max_w = 0;
double min_h = 0;
double max_h = 0;
Point p(0, 0);
double angle = convert_radian(src, p, ratio);
DPoint tl = convert_abs_coord(angle + rotation, ratio);
min_w = min(min_w, tl.x);
max_w = max(max_w, tl.x);
min_h = min(min_h, tl.y);
max_h = max(max_h, tl.y);
p = Point(src.width - 1, 0);
angle = convert_radian(src, p, ratio);
DPoint tr = convert_abs_coord(angle + rotation, ratio);
min_w = min(min_w, tr.x);
max_w = max(max_w, tr.x);
min_h = min(min_h, tr.y);
max_h = max(max_h, tr.y);
p = Point(0, src.height - 1);
angle = convert_radian(src, p, ratio);
DPoint bl = convert_abs_coord(angle + rotation, ratio);
min_w = min(min_w, bl.x);
max_w = max(max_w, bl.x);
min_h = min(min_h, bl.y);
max_h = max(max_h, bl.y);
p = Point(src.width - 1, src.height - 1);
angle = convert_radian(src, p, ratio);
DPoint br = convert_abs_coord(angle + rotation, ratio);
min_w = min(min_w, br.x);
max_w = max(max_w, br.x);
min_h = min(min_h, br.y);
max_h = max(max_h, br.y);
width = (int) (max_w - min_w) + 1;
height = (int) (max_h - min_h) + 1;
}
//
//
// Math approximation
//
void round_if_very_small(double& d)
{
if (abs(d) < 1.0e-10)
d = 0.0;
if (abs(d - 1) < 1.0e-10)
d = 1.0;
}
inline
bool fequal(float a, float b, float sigma)
{
return abs(a - b) < sigma;
}
//
//
// Image rotation
//
DPoint get_mapped_point(Image const& src, Point const& p, double const rotation)
{
DPoint const d = convert_grid_coord(src, p);
double p_angle = 0;
double dist = 0;
convert_abs_to_polar_coord(d, p_angle, dist);
return convert_polar_to_grid_coord(p_angle + rotation, dist);
}
inline
void rotate_pixel(Image const& src, Image& rotated,
DPoint const& src_rotated_point, Point const& rot_point,
unsigned int const src_limit, unsigned int const rot_limit)
{
unsigned int src_index = ((int) src_rotated_point.y * src.width + (int) src_rotated_point.x) * 3;
unsigned int rot_index = (rot_point.y * rotated.width + rot_point.x) * 3;
// Out-of-bounds check
if (src_index >= src_limit
|| rot_index >= rot_limit)
return;
// Bilinear interpolation
unsigned int src_index_1 = src_index;
unsigned int src_index_2 = src_index_1 + 3;
unsigned int src_index_3 = src_index_1 + 3 * src.width;
unsigned int src_index_4 = src_index_3 + 3;
if (src_index_4 >= src_limit)
return;
double x_delta = src_rotated_point.x - floor(src_rotated_point.x);
round_if_very_small(x_delta);
double y_delta = src_rotated_point.y - floor(src_rotated_point.y);
round_if_very_small(y_delta);
// special case if we can directly map the src to the dest
if (x_delta == 0 && y_delta == 0)
{
memcpy(&rotated.buffer[rot_index], &src.buffer[src_index], 3 * sizeof (uint8_t));
return;
}
// SIMD
__m128 const x_d = _mm_set_ps1(x_delta);
__m128 const inv_x_d = _mm_set_ps1(1 - x_delta);
__m128 top_left = _mm_set_ps(src.buffer[src_index_1], src.buffer[src_index_1 + 1], src.buffer[src_index_1 + 2], 0.0);
__m128 top_right = _mm_set_ps(src.buffer[src_index_2], src.buffer[src_index_2 + 1], src.buffer[src_index_2 + 2], 0.0);
top_left = _mm_mul_ps(top_left, inv_x_d);
top_right = _mm_mul_ps(top_right, x_d);
top_left = _mm_add_ps(top_left, top_right);
__m128 bottom_left = _mm_set_ps(src.buffer[src_index_3], src.buffer[src_index_3 + 1], src.buffer[src_index_3 + 2], 0.0);
__m128 bottom_right = _mm_set_ps(src.buffer[src_index_4], src.buffer[src_index_4 + 1], src.buffer[src_index_4 + 2], 0.0);
bottom_left = _mm_mul_ps(bottom_left, inv_x_d);
bottom_right = _mm_mul_ps(bottom_right, x_d);
bottom_left = _mm_add_ps(bottom_left, bottom_right);
__m128 const y_d = _mm_set_ps1(y_delta);
__m128 const inv_y_d = _mm_set_ps1(1 - y_delta);
top_left = _mm_mul_ps(top_left, inv_y_d);
bottom_left = _mm_mul_ps(bottom_left, y_d);
top_left = _mm_add_ps(top_left, bottom_left);
// convert float values to uint8_t
rotated.buffer[rot_index] = top_left[3];
rotated.buffer[rot_index + 1] = top_left[2];
rotated.buffer[rot_index + 2] = top_left[1];
}
Image* rotate(Image const& src, double angle)
{
double const rotation = (angle / 180.0f) * M_PI;
unsigned int w = 0;
unsigned int h = 0;
compute_output_size(src, rotation, w, h);
Image* rotated = new Image(w, h);
// corner points in rotated image
// TODO: add one ligne for smooth border
DPoint const tl_grid = get_mapped_point(src, Point(0, 0), rotation);
Point const tl = convert_img_coord(*rotated, tl_grid);
DPoint const tr_grid = get_mapped_point(src, Point(src.width - 1, 0), rotation);
Point const tr = convert_img_coord(*rotated, tr_grid);
DPoint const bl_grid = get_mapped_point(src, Point(0, src.height - 1), rotation);
Point const bl = convert_img_coord(*rotated, bl_grid);
// corner points in source image
DPoint src_tl = get_mapped_point(*rotated, tl, -rotation);
src_tl = convert_img_coord_precision(src, src_tl);
DPoint const src_origin = get_mapped_point(*rotated, Point(0, 0), -rotation);
DPoint src_delta_x = get_mapped_point(*rotated, Point(1, 0), -rotation);
DPoint src_delta_y = get_mapped_point(*rotated, Point(0, 1), -rotation);
src_delta_x.x = src_delta_x.x - src_origin.x;
src_delta_x.y = src_delta_x.y - src_origin.y;
round_if_very_small(src_delta_x.x);
round_if_very_small(src_delta_x.y);
src_delta_y.x = src_delta_y.x - src_origin.x;
src_delta_y.y = src_delta_y.y - src_origin.y;
round_if_very_small(src_delta_y.x);
round_if_very_small(src_delta_y.y);
// // steps for first column in source image (y)
int origin_nb_steps = max(abs(bl.x - tl.x), abs(bl.y - tl.y));
// // steps for line in source image (x)
int line_nb_steps = max(abs(tr.x - tl.x), abs(tr.y - tl.y));
// steps for first column in rotated image (y)
DPoint const rotated_step((bl.x - tl.x) / (float) origin_nb_steps, (bl.y - tl.y) / (float) origin_nb_steps);
// steps for line in rotated image (x)
DPoint const bresenham((tr.x - tl.x) / (float) line_nb_steps, (tr.y - tl.y) / (float) line_nb_steps);
unsigned int const src_limit = src.width * src.height * 3;
unsigned int const rot_limit = rotated->width * rotated->height * 3;
for (int y_i = 0; y_i <= (int) origin_nb_steps; ++y_i)
{
// first column origin
Point const rot_origin(tl.x + y_i * rotated_step.x, tl.y + y_i * rotated_step.y);
Point rot_point(rot_origin.x, rot_origin.y);
DPoint rot_delta(0.0, 0.0);
Point previous = rot_origin;
for (int x_i = 0; x_i <= (int) line_nb_steps; ++x_i)
{
Point const delta(rot_point.x - tl.x, rot_point.y - tl.y);
DPoint src_rotated_point(src_tl.x + delta.x * src_delta_x.x + delta.y * src_delta_y.x,
src_tl.y + delta.x * src_delta_x.y + delta.y * src_delta_y.y);
rotate_pixel(src, *rotated, src_rotated_point, rot_point, src_limit, rot_limit);
if (previous.x != rot_point.x && previous.y != rot_point.y)
{
int y_slope = rot_point.y > previous.y ? 1 : -1;
int tmp_y = rot_point.y;
rot_point.y = previous.y;
src_rotated_point.x -= y_slope * src_delta_y.x;
src_rotated_point.y -= y_slope * src_delta_y.y;
rotate_pixel(src, *rotated, src_rotated_point, rot_point, src_limit, rot_limit);
rot_point.y = tmp_y;
}
previous = rot_point;
rot_delta.x += bresenham.x;
rot_point.x = rot_origin.x + (int) rot_delta.x;
rot_delta.y += bresenham.y;
rot_point.y = rot_origin.y + (int) rot_delta.y;
}
}
return rotated;
}
//
//
// Tile rotation
//
template<unsigned int W, unsigned int H>
void rotate_pixel(TiledImage<W, H> const& src,
DPoint const& src_rotated_point,
uint8_t* rot_tile, unsigned int rot_index)
{
uint8_t const* src_index_1 = src.access_pixel((int) src_rotated_point.x, (int) src_rotated_point.y);
double x_delta = src_rotated_point.x - (int) src_rotated_point.x;
round_if_very_small(x_delta);
double y_delta = src_rotated_point.y - (int) src_rotated_point.y;
round_if_very_small(y_delta);
// special case if we can directly map the src to the dest
if (x_delta == 0 && y_delta == 0)
{
memcpy(&rot_tile[rot_index], src_index_1, 3 * sizeof (uint8_t));
return;
}
uint8_t const* src_index_2 = src.access_pixel((int) src_rotated_point.x + 1, (int) src_rotated_point.y);
uint8_t const* src_index_3 = src.access_pixel((int) src_rotated_point.x, (int) src_rotated_point.y + 1);
uint8_t const* src_index_4 = src.access_pixel((int) src_rotated_point.x + 1, (int) src_rotated_point.y + 1);
// FIXME: deal with image border
if (!src_index_1 || !src_index_2 || !src_index_3 || !src_index_4)
return;
// SIMD
__m128 const x_d = _mm_set_ps1(x_delta);
__m128 const inv_x_d = _mm_set_ps1(1 - x_delta);
__m128 top_left = _mm_set_ps(*src_index_1, *(src_index_1 + 1), *(src_index_1 + 2), 0.0);
__m128 top_right = _mm_set_ps(*src_index_2, *(src_index_2 + 1), *(src_index_2 + 2), 0.0);
top_left = _mm_mul_ps(top_left, inv_x_d);
top_right = _mm_mul_ps(top_right, x_d);
top_left = _mm_add_ps(top_left, top_right);
__m128 bottom_left = _mm_set_ps(*src_index_3, *(src_index_3 + 1), *(src_index_3 + 2), 0.0);
__m128 bottom_right = _mm_set_ps(*src_index_4, *(src_index_4 + 1), *(src_index_4 + 2), 0.0);
bottom_left = _mm_mul_ps(bottom_left, inv_x_d);
bottom_right = _mm_mul_ps(bottom_right, x_d);
bottom_left = _mm_add_ps(bottom_left, bottom_right);
__m128 const y_d = _mm_set_ps1(y_delta);
__m128 const inv_y_d = _mm_set_ps1(1 - y_delta);
top_left = _mm_mul_ps(top_left, inv_y_d);
bottom_left = _mm_mul_ps(bottom_left, y_d);
top_left = _mm_add_ps(top_left, bottom_left);
// convert float values to uint8_t
rot_tile[rot_index] = top_left[3];
rot_tile[rot_index + 1] = top_left[2];
rot_tile[rot_index + 2] = top_left[1];
}
template<unsigned int W, unsigned int H>
TiledImage<W, H>*
rotate(TiledImage<W, H> const& src, double angle)
{
double const rotation = (angle / 180.0f) * M_PI;
unsigned int w = 0;
unsigned int h = 0;
compute_output_size(src, rotation, w, h);
auto rotated = new TiledImage<W, H>(w, h);
DPoint src_origin = get_mapped_point(*rotated, Point(0, 0), -rotation);
DPoint src_delta_x = get_mapped_point(*rotated, Point(1, 0), -rotation);
DPoint src_delta_y = get_mapped_point(*rotated, Point(0, 1), -rotation);
src_delta_x.x = src_delta_x.x - src_origin.x;
src_delta_x.y = src_delta_x.y - src_origin.y;
round_if_very_small(src_delta_x.x);
round_if_very_small(src_delta_x.y);
src_delta_y.x = src_delta_y.x - src_origin.x;
src_delta_y.y = src_delta_y.y - src_origin.y;
round_if_very_small(src_delta_y.x);
round_if_very_small(src_delta_y.y);
DPoint const rot_origin_in_src_grid = get_mapped_point(*rotated, Point(0, 0), -rotation);
DPoint const rot_origin_in_src = convert_img_coord_precision(src, rot_origin_in_src_grid);
for (unsigned int y = 0; y < rotated->nb_row_tile; ++y)
{
for (unsigned int x = 0; x < rotated->nb_col_tile; ++x)
{
unsigned int const rot_tile_index = y * rotated->nb_col_tile + x;
for (unsigned int j = 0; j < H; ++j)
{
int const y_index = y * H + j;
int x_index = x * W;
DPoint src_rotated_point(rot_origin_in_src.x + x_index * src_delta_x.x + y_index * src_delta_y.x,
rot_origin_in_src.y + x_index * src_delta_x.y + y_index * src_delta_y.y);
for (unsigned int i = 0; i < W; ++i)
{
unsigned int const rot_index = (j * W + i) * 3;
if (src_rotated_point.x >= 0 && src_rotated_point.x < src.width
&& src_rotated_point.y >= 0 && src_rotated_point.y < src.height)
{
rotate_pixel(src, src_rotated_point,
rotated->tiles[rot_tile_index], rot_index);
}
src_rotated_point.x += src_delta_x.x;
src_rotated_point.y += src_delta_x.y;
}
}
}
}
return rotated;
}
//
//
// Check
//
bool check_points()
{
Image five(5, 5);
Point origin(0, 0);
DPoint d1 = convert_grid_coord(five, origin);
assert(d1.x == -2);
assert(d1.y == -2);
return true;
}
bool check_trigo()
{
Image square(500, 500);
double const ratio = compute_ratio(square);
double const sigma = 1.0e-2;
if (!fequal(ratio, 1 / 707.106, sigma))
{
cerr << __LINE__ << " | Invalid ratio: " << ratio << " != " << 1 / 707.106 << endl;
return false;
}
// Check that the origin of a square image is at sqrt(2) / 2
double const angle = convert_radian(square, Point(0, 0), ratio);
if (!fequal(angle, 3 * M_PI / 4, sigma))
{
cerr << __LINE__ << " | Invalid angle value: " << angle << " != " << 3 * M_PI / 4 << endl;
return false;
}
// Check that we can reverse the origin point.
DPoint const abs_reverse_point = convert_abs_coord(angle, ratio);
Point const reverse_point = convert_img_coord(square, abs_reverse_point);
if (!fequal(0.0, reverse_point.x, sigma)
|| !fequal(0.0, reverse_point.y, sigma))
{
cerr << __LINE__ << "Reverse origin fail" << endl;
cerr << " " << reverse_point << " != (0, 0)" << endl;
cerr << " abs point " << abs_reverse_point << endl;
return false;
}
// Check that when rotating the origin by 45 degrees
double const rotation = M_PI / 4; // 45 degrees
unsigned int w = 0;
unsigned int h = 0;
compute_output_size(square, rotation, w, h);
if (!fequal(w, square.width * sqrt(2), sigma * square.width)
|| !fequal(h, square.height * sqrt(2), sigma * square.height))
{
cerr << "Invalid rotated image dimensions " << w << " x " << h << endl;
cerr << " expected " << (int) ceil(square.width * sqrt(2)) << " x " << (int) ceil(square.height * sqrt(2)) << endl;
return false;
}
Image rotated(w, h);
DPoint const a_p45 = convert_abs_coord(angle + rotation, ratio);
Point const p45 = convert_img_coord(rotated, a_p45);
if (!fequal(0, p45.x, sigma))
{
cerr << __LINE__ << " > Rotation origin by 45 degrees:" << endl;
cerr << " invalid x value: " << p45.x << " != " << 0 << endl;
cerr << " absolute point: " << a_p45 << endl;
cerr << " relative point: " << p45 << endl;
return false;
}
if (!fequal(p45.y, (h - 1) / 2.0f, sigma))
{
cerr << __LINE__ << " > Rotation origin by 45 degrees:" << endl;
cerr << "Invalid y value: " << p45.y << " != " << (h - 1) / 2.0f << endl;
cerr << " absolute point: " << a_p45 << endl;
cerr << " relative point: " << p45 << endl;
return false;
}
// Polar coordinates
{
DPoint const d(-42.5, 37.5);
double angle = 0;
double dist = 0;
convert_abs_to_polar_coord(d, angle, dist);
DPoint const reversed = convert_polar_to_grid_coord(angle, dist);
if (!fequal(d.x, reversed.x, sigma)
|| !fequal(d.y, reversed.y, sigma))
{
cerr << __LINE__ << " > Reverse polar coordinates:" << endl;
cerr << reversed << " != " << d << endl;
cerr << "polar (" << angle << ", " << dist << ")" << endl;
return false;
}
}
return true;
}
bool check_90(string const& path)
{
Image const src(path);
Image const* rotated = rotate(src, 90);
for (unsigned int y = 0; y < rotated->height; ++y)
{
for (unsigned int x = 0; x < rotated->width; ++x)
{
unsigned rot_index = (y * rotated->width + x) * 3;
unsigned src_index = (x * src.width + (src.width - 1 - y)) * 3;
if (memcmp(&rotated->buffer[rot_index], &src.buffer[src_index], 3 * sizeof (uint8_t)) != 0)
{
Point r(x, y);
Point s((src.width - 1 - y), x);
cerr << __LINE__ << " | R: " << r << " != S:" << s << endl;
cerr << "R dim: " << rotated->width << " x " << rotated->height << endl;
cerr << "S dim: " << src.width << " x " << src.height << endl;
return false;
}
}
}
delete rotated;
return true;
}
//
//
// Main
//
string get_save_path(string const& base, unsigned int i)
{
stringstream filename;
//filename << "/tmp/";
filename << base << "_";
if (i < 100)
filename << "0";
if (i < 10)
filename << "0";
filename << i << ".ppm";
return filename.str();
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
cout << "Usage: " << argv[0] << " image.ppm" << endl;
return 1;
}
bool perform_check = true;
if (perform_check)
{
if (!check_points())
return 1;
if (!check_trigo())
return 1;
if (!check_90(argv[1]))
{
cerr << __LINE__ << " | 90 degrees check failed" << endl << endl;
// return 1;
}
}
Image img(argv[1]);
TiledImage<32, 32> tiled_img(argv[1]);
for (double rotation = 0; rotation < 360; rotation += 45)
{
// No tile
auto const before = chrono::high_resolution_clock::now();
Image* const rotated = rotate(img, rotation);
auto const after = chrono::high_resolution_clock::now();
auto const duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
// Tile
auto const before_tiled = chrono::high_resolution_clock::now();
auto const rotated_tiled = rotate(tiled_img, rotation);
auto const after_tiled = chrono::high_resolution_clock::now();
auto const duration_ms_tiled = std::chrono::duration_cast<std::chrono::milliseconds>(after_tiled - before_tiled);
cout << "rotate(" << rotation << "): " << duration_ms.count() << " ms" << endl;
cout << "tiled: " << duration_ms_tiled.count() << " ms" << endl;
cout << "speedup: " << (int) (((float) duration_ms.count() / duration_ms_tiled.count() - 1) * 100) << "%" << endl << endl;
rotated->save(get_save_path("rotated", rotation));
rotated_tiled->save(get_save_path("rotated_tiled", rotation));
delete rotated;
delete rotated_tiled;
}
return 0;
}