rotate-me-fast/rotation.cpp
Fabien Freling 7c6cfd9046 Create small overlap in tiles.
Tiles overlap on the bottom line and the right column.
Having an overlap allows us to get neighbor points for each point in a
tile, regardless of its position.
2014-07-10 22:01:55 +02:00

1075 lines
27 KiB
C++

#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>
#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;
}
struct PackedPixel {
uint32_t r;
uint32_t g;
uint32_t b;
PackedPixel()
: r(0)
, g(0)
, b(0)
{}
};
uint8_t interpolate_packed(uint32_t pack, double x, double x_inv, double y, double y_inv)
{
return (((pack >> 24) & 0x000f) * x_inv + ((pack >> 16) & 0x000f) * x) * y_inv
+ (((pack >> 8) & 0x000f) * x_inv + (pack & 0x000f) * x) * y;
}
//
//
// 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;
// two lines overlap, bottom + right
unsigned int static const tile_size = (W + 1) * (H + 1);
unsigned int nb_col_tile;
unsigned int nb_row_tile;
TiledImage()
: Image()
, tiles(NULL)
, nb_col_tile(0)
, nb_row_tile(0)
{}
~TiledImage()
{
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 const*
get_tile(unsigned int index) const
{
if (index >= nb_col_tile * nb_row_tile)
return nullptr;
return tiles + index * tile_size * 3;
}
uint8_t*
get_tile(unsigned int index)
{
if (index >= nb_col_tile * nb_row_tile)
return nullptr;
return tiles + index * tile_size * 3;
}
uint8_t*
access_pixel(unsigned int x, unsigned int y)
{
if (x >= width || y >= height)
return nullptr;
unsigned int const tile_width = (tile_w + 1) * 3;
unsigned int const tile_index = (y / tile_h) * nb_col_tile + (x / tile_w);
uint8_t* tile = this->get_tile(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 + 1) * 3;
unsigned int const tile_index = (y / tile_h) * nb_col_tile + (x / tile_w);
const uint8_t* tile = this->get_tile(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);
}
PackedPixel
get_packed_pixels(unsigned int x, unsigned int y) const
{
PackedPixel pack;
this->insert_pixel(pack, x, y);
pack.r = pack.r << 8;
pack.g = pack.g << 8;
pack.b = pack.b << 8;
this->insert_pixel(pack, x + 1, y);
pack.r = pack.r << 8;
pack.g = pack.g << 8;
pack.b = pack.b << 8;
this->insert_pixel(pack, x, y + 1);
pack.r = pack.r << 8;
pack.g = pack.g << 8;
pack.b = pack.b << 8;
this->insert_pixel(pack, x + 1, y + 1);
return pack;
}
void
print_tile(unsigned int index) const
{
cout << "Tile[" << index << "]" << endl;
uint8_t const* tile = this->get_tile(index);
unsigned int const tile_width = (tile_w + 1) * 3;
for (unsigned int j = 0; j < tile_h + 1; ++j)
{
for (unsigned int i = 0; i < tile_w + 1; ++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;
}
void fill_overlap()
{
unsigned int const tile_width = (W + 1) * 3;
for (int j = nb_row_tile - 1; j >= 0; --j)
for (unsigned int i = 0; i < nb_col_tile; ++i)
{
// copy last line overlap
if (j != (int) nb_row_tile - 1)
{
uint8_t const* tile_src = this->access_pixel(i * W, (j + 1) * H);
uint8_t* tile_dst = this->access_pixel(i * W, j * H);
tile_dst += H * tile_width;
memcpy(tile_dst, tile_src, tile_width * sizeof (uint8_t));
}
// copy last col overlap
if (i != nb_col_tile - 1)
{
uint8_t* tile_src = this->get_tile(i + 1 + j * nb_col_tile);
uint8_t* tile_dst = this->get_tile(i + j * nb_col_tile);
tile_dst += W * 3;
for (unsigned int y = 0; y < H; ++y)
{
memcpy(tile_dst, tile_src, 3 * sizeof (uint8_t));
tile_src += tile_width;
tile_dst += tile_width;
}
}
}
}
protected:
void insert_pixel(PackedPixel& pack, unsigned int x, unsigned int y) const
{
unsigned int const tile_width = tile_w * 3;
unsigned int const tile_index = (y / tile_h) * nb_col_tile + (x / tile_w);
if (tile_index >= nb_col_tile * nb_row_tile)
return;
uint8_t const* tile = tiles[tile_index];
unsigned int const tile_j = y % tile_h;
unsigned int const tile_i = x % tile_w;
unsigned int index = tile_j * tile_width + (tile_i * 3);
pack.r += tile[index];
pack.g += tile[index + 1] + 1;
pack.b += tile[index + 2] + 1;
}
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 * tile_size * 3];
memset(tiles, 0, nb_tiles * tile_size * 3 * sizeof (uint8_t));
}
virtual bool read_body(std::ifstream& istr) override
{
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();
}
this->fill_overlap();
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)
{
double x = p.x + (img.width / 2.0f) - 0.5;
double 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,
Point const& src_rotated_point,
unsigned int const src_limit,
uint8_t* rotate_buffer, unsigned int rot_index)
{
unsigned int const quantize = 8;
int const src_x = src_rotated_point.x >> 3;
int const src_y = src_rotated_point.y >> 3;
unsigned int src_index = (src_y * src.width + src_x) * 3;
// 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;
// Out-of-bounds check
if (src_index_4 >= src_limit)
return;
unsigned int x_delta = src_rotated_point.x & 0x07;;
unsigned int y_delta = src_rotated_point.y & 0x07;
unsigned int const inv_x = quantize - x_delta;
unsigned int const inv_y = quantize - y_delta;
// No SIMD
rotate_buffer[rot_index] = ((src.buffer[src_index_1] * inv_x + src.buffer[src_index_2] * x_delta) * inv_y
+ (src.buffer[src_index_3] * inv_x + src.buffer[src_index_4] * x_delta) * y_delta) >> 6;
rotate_buffer[rot_index + 1] = ((src.buffer[src_index_1 + 1] * inv_x + src.buffer[src_index_2 + 1] * x_delta) * inv_y
+ (src.buffer[src_index_3 + 1] * inv_x + src.buffer[src_index_4 + 1] * x_delta) * y_delta) >> 6;
rotate_buffer[rot_index + 2] = ((src.buffer[src_index_1 + 2] * inv_x + src.buffer[src_index_2 + 2] * x_delta) * inv_y
+ (src.buffer[src_index_3 + 2] * inv_x + src.buffer[src_index_4 + 2] * x_delta) * y_delta) >> 6;
}
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);
// 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);
unsigned int const src_limit = src.width * src.height * 3;
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);
unsigned int buffer_index = 0;
uint8_t* buffer = rotated->buffer;
unsigned int const quantize = 8;
int const& src_qwidth = src.width * quantize;
int const& src_qheight = src.height * quantize;
for (unsigned int y = 0; y < rotated->height; ++y)
{
Point const src_rotated_point((rot_origin_in_src.x + y * src_delta_y.x) * quantize,
(rot_origin_in_src.y + y * src_delta_y.y) * quantize);
for (unsigned int x = 0; x < rotated->width; ++x)
{
Point const src_runner(src_rotated_point.x + x * src_delta_x.x * quantize,
src_rotated_point.y + x * src_delta_x.y * quantize);
if (src_runner.x >= 0 && src_runner.x < src_qwidth
&& src_runner.y >= 0 && src_runner.y < src_qheight)
{
rotate_pixel(src, src_runner,
src_limit,
buffer, buffer_index * 3);
}
++buffer_index;
}
}
return rotated;
}
//
//
// Tile rotation
//
template<unsigned int W, unsigned int H>
void rotate_pixel(TiledImage<W, H> const& src,
Point const& src_rotated_point,
uint8_t* rot_tile)
{
unsigned int const quantize = 8;
int const src_x = src_rotated_point.x >> 3;
int const src_y = src_rotated_point.y >> 3;
uint8_t const* src_index_1 = src.access_pixel(src_x, src_y);
uint8_t const* src_index_2 = src_index_1 + 3;
uint8_t const* src_index_3 = src_index_1 + (W + 1) * 3;
uint8_t const* src_index_4 = src_index_3 + 3;
// FIXME: deal with image border
if (!src_index_4)
return;
unsigned int x_delta = src_rotated_point.x & 0x07;;
unsigned int y_delta = src_rotated_point.y & 0x07;
unsigned int const inv_x = quantize - x_delta;
unsigned int const inv_y = quantize - y_delta;
// No SIMD
rot_tile[0] = ((src_index_1[0] * inv_x + src_index_2[0] * x_delta) * inv_y
+ (src_index_3[0] * inv_x + src_index_4[0] * x_delta) * y_delta) >> 6;
rot_tile[1] = ((src_index_1[1] * inv_x + src_index_2[1] * x_delta) * inv_y
+ (src_index_3[1] * inv_x + src_index_4[1] * x_delta) * y_delta) >> 6;
rot_tile[2] = ((src_index_1[2] * inv_x + src_index_2[2] * x_delta) * inv_y
+ (src_index_3[2] * inv_x + src_index_4[2] * x_delta) * y_delta) >> 6;
}
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);
unsigned int const quantize = 8;
int const& src_qwidth = src.width * quantize;
int const& src_qheight = src.height * quantize;
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;
uint8_t* runner = rotated->get_tile(rot_tile_index);
for (unsigned int j = 0; j < H; ++j)
{
int const y_index = y * H + j;
int x_index = x * W;
DPoint const src_rotated_point((rot_origin_in_src.x + x_index * src_delta_x.x + y_index * src_delta_y.x) * quantize,
(rot_origin_in_src.y + x_index * src_delta_x.y + y_index * src_delta_y.y) * quantize);
for (unsigned int i = 0; i < W; ++i)
{
Point const src_runner(src_rotated_point.x + i * src_delta_x.x * quantize,
src_rotated_point.y + i * src_delta_x.y * quantize);
if (src_runner.x >= 0 && src_runner.x < src_qwidth
&& src_runner.y >= 0 && src_runner.y < src_qheight)
{
rotate_pixel(src, src_runner, runner);
}
runner += 3;
}
// Jump overlapping pixel
runner += 3;
}
}
}
// rotated->fill_overlap();
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 = false;
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;
}
}
// No tile
Image img(argv[1]);
float average = 0.0;
int i = 0;
for (double rotation = 0; rotation < 360; rotation += 45)
{
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);
average += duration_ms.count();
cout << "rotate(" << rotation << "): " << duration_ms.count() << " ms" << endl;
rotated->save(get_save_path("rotated", rotation));
delete rotated;
++i;
}
cout << "---------" << endl;
cout << " average: " << average / i << "ms" << endl << endl;
// Tile
TiledImage<16, 16> tiled_img(argv[1]);
average = 0.0;
i = 0;
for (double rotation = 0; rotation < 360; rotation += 45)
{
auto const before = chrono::high_resolution_clock::now();
auto const rotated = rotate(tiled_img, rotation);
auto const after = chrono::high_resolution_clock::now();
auto const duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>(after - before);
average += duration_ms.count();
cout << "rotate tiled(" << rotation << "): " << duration_ms.count() << " ms" << endl;
rotated->save(get_save_path("rotated_tiled", rotation));
delete rotated;
++i;
}
cout << "---------" << endl;
cout << " average: " << average / i << "ms" << endl;
return 0;
}