From 4bf69e820467582456060c25aec5e07a9eebdbb8 Mon Sep 17 00:00:00 2001 From: Fabien Freling Date: Wed, 18 Jun 2014 08:47:10 +0200 Subject: [PATCH] Initial commit --- .gitignore | 5 + Makefile | 9 + TODO.md | 0 img/luigi.jpg | Bin 0 -> 33619 bytes rotation.cpp | 486 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 500 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 TODO.md create mode 100644 img/luigi.jpg create mode 100644 rotation.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b87d621 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.dSYM +*.swp + +*.ppm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0ecdb90 --- /dev/null +++ b/Makefile @@ -0,0 +1,9 @@ +CXX = clang++ +CXXFLAGS = -std=c++11 -W -Wall -Werror -O3 +BUILD_DIR=/tmp + +all: rotation.cpp + $(CXX) $(CXXFLAGS) $< -o $(BUILD_DIR)/rotation + +clean: + @rm -f *~ *.o .*.swp diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..e69de29 diff --git a/img/luigi.jpg b/img/luigi.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4bea30d96c6900f1dfecaa5ab4d37b052e2113d1 GIT binary patch literal 33619 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 aeXYPF(^Kjkd}dj+ +#include +#include +#include +#include +#include + +using namespace std; + +template +struct TPoint { + T x; + T y; + + TPoint(T a, T b) + : x(a) + , y(b) + {} +}; +typedef TPoint Point; +typedef TPoint APoint; // absolute point, can be negative +typedef TPoint DPoint; // absolute point, can be negative +template +std::basic_ostream& operator << (std::basic_ostream& o, TPoint const& p) +{ + o << "(" << p.x << ", " << p.y << ")"; + return o; +} + +struct Image { + unsigned int width; + unsigned int height; + uint8_t* r_chan; + uint8_t* g_chan; + uint8_t* b_chan; + + Image() + : width(0) + , height(0) + , r_chan(NULL) + , g_chan(NULL) + , b_chan(NULL) + {} + + Image(unsigned int w, unsigned int h) + { + this->width = w; + this->height = h; + r_chan = new uint8_t[width * height]; + memset(r_chan, 0, width * height * sizeof (uint8_t)); + g_chan = new uint8_t[width * height]; + memset(g_chan, 0, width * height * sizeof (uint8_t)); + b_chan = new uint8_t[width * height]; + memset(b_chan, 0, width * height * 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 r_chan; + r_chan = nullptr; + delete g_chan; + r_chan = nullptr; + delete b_chan; + r_chan = nullptr; + + cerr << "Invalid header." << endl; + abort(); + } + } + + bool save(string const& path) + { + 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) + { + int const index = y * width + x; + r_chan[index] = r; + g_chan[index] = g; + b_chan[index] = b; + } + + private: + bool read_header(std::ifstream& istr) + { + // check magic + if (istr.get() != 'P' ) + { + return false; + } + + char type = static_cast(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) + { + ostr << "P6" << endl; + ostr << width << " " << height << endl; + ostr << "255" << endl; + return true; + } + + bool read_body(std::ifstream& istr) + { + r_chan = new uint8_t[width * height]; + g_chan = new uint8_t[width * height]; + b_chan = new uint8_t[width * height]; + + for (unsigned int row = 0; row < height; ++row) + { + for (unsigned int col = 0; col < width; ++col) + { + int index = row * width + col; + r_chan[index] = istr.get(); + g_chan[index] = istr.get(); + b_chan[index] = istr.get(); + } + } + + return true; + } + + bool write_body(std::ofstream& ostr) + { + for (unsigned int row = 0; row < height; ++row) + { + for (unsigned int col = 0; col < width; ++col) + { + int index = row * width + col; + ostr << (char) r_chan[index]; + ostr << (char) g_chan[index]; + ostr << (char) b_chan[index]; + } + } + return true; + } +}; + + +// +// +// Drawing +// + +void draw_line(Image& img, unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) +{ + + assert(x1 < x2); + double const slope = (double) (y2 - y1) / (double) (x2 - x1); + for (unsigned int i = x1; i <= x2; ++i) + { + unsigned int y = slope * (i - x1) + y1; + img.set_pixel(i, y, 255, 0, 0); // set line to red + } +} + +void draw_line(Image& img, Point const& p1, Point const& p2) +{ + draw_line(img, p1.x, p1.y, p2.x, p2.y); +} + +Point rotate(Image const& img, Point const& src, double radian, double const ratio) +{ + cout << "rotate (" << src.x << ", " << src.y << ") x " << radian << endl; + double x = src.x - (img.width / 2.0f); + x *= ratio; + // double y = - src.y + (src.height / 2.0f); + // y *= ratio; + + double const angle_value = acos(x) + radian; + double const cos_x = cos(angle_value); + double const sin_x = sin(angle_value); + unsigned int const new_x = ceil(cos_x / ratio); + unsigned int const new_y = ceil(sin_x / ratio); + + cout << " = (" << new_x << ", " << new_y << ")" << endl; + return Point(new_x, new_y); +} + + +// +// +// Trigonometry +// + +double convert_radian(Image const& img, Point const& p, double const ratio) +{ + cout << "X: " << p.x << " - " << img.width / 2.0f << endl; + cout << "Y: " << - (int) p.y << " + " << img.height / 2.0f << endl; + double const centered_x = p.x - (img.width / 2.0f); + double const centered_y = (- (int) p.y) + (img.height / 2.0f); + cout << "centered point (" << centered_x << ", " << centered_y << ")" << endl; + 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; +} + +APoint convert_abs_coord(double const angle, double const ratio) +{ + cout << "Angle: " << angle << endl; + cout << "cos: " << cos(angle) << endl; + cout << "sin: " << sin(angle) << endl; + APoint tmp((int) ceil(cos(angle) / ratio), (int) ceil(sin(angle) / ratio)); + cout << "point: " << tmp << endl; + return APoint((int) ceil(cos(angle) / ratio), (int) ceil(sin(angle) / ratio)); +} + +Point convert_img_coord(Image const& img, APoint const& p) +{ + cout << "image: " << img.width << " x " << img.height << endl; + cout << "p: " << p << endl; + cout << "h / 2: " << img.height / 2.0f << endl; + return Point(p.x + img.width / 2.0f, - p.y + img.height / 2.0f); +} + +double compute_ratio(Image const& img) +{ + cout << "Compute ratio" << endl; + unsigned int const nb_points = img.width * img.height; + cout << " " << nb_points << " points" << endl; + double const square_side = sqrt(nb_points) - 1; + cout << " square side: " << square_side << endl; + double const half_side = square_side / 2; + cout << " half side: " << half_side << endl; + unsigned int const trigo_length = (unsigned int) ceil(half_side * sqrt(2)); + cout << " trigo length: " << trigo_length << endl; + return 1.0f / trigo_length; +} + +inline +bool fequal(float a, float b, float sigma) +{ + return abs(a - b) < sigma; +} + +void compute_output_size(Image const& src, double const rotation, unsigned int& width, unsigned int& height) +{ + double const ratio = compute_ratio(src); + int min_w = 0; + int max_w = 0; + int min_h = 0; + int max_h = 0; + + cout << "Image dimensions: " << src.width << " x " << src.height << endl; + Point p(0, 0); + double angle = convert_radian(src, p, ratio); + APoint 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); + cout << "Rotated " << p << " (" << angle << ") = " << tl << "(" << angle + rotation << ")" << endl << endl; + + p = Point(src.width - 1, 0); + angle = convert_radian(src, p, ratio); + APoint 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); + cout << "Rotated " << p << " (" << angle << ") = " << tr << "(" << angle + rotation << ")" << endl << endl; + + p = Point(0, src.height - 1); + angle = convert_radian(src, p, ratio); + APoint 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); + cout << "Rotated " << p << " (" << angle << ") = " << bl << "(" << angle + rotation << ")" << endl << endl; + + p = Point(src.width - 1, src.height - 1); + angle = convert_radian(src, p, ratio); + APoint 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); + cout << "Rotated " << p << " (" << angle << ") = " << br << "(" << angle + rotation << ")" << endl << endl; + + width = max_w - min_w + 1; + height = max_h - min_h + 1; +} + +bool check_trigo() +{ + Image square(500, 500); + double const ratio = compute_ratio(square); + + // Check that the origin of a square image is at sqrt(2) / 2 + double const angle = convert_radian(square, Point(0, 0), ratio); + + double const sigma = 1.0e-2; + if (abs(angle - (3 * M_PI / 4)) > sigma) + { + cout << "Invalid angle value: " << angle << " != " << 3 * M_PI / 4 << endl; + return false; + } + + // Check that we can reverse the origin point. + APoint const abs_reverse_point = convert_abs_coord(angle, ratio); + cout << "reversed abs origin: " << abs_reverse_point << endl; + Point const reverse_point = convert_img_coord(square, abs_reverse_point); + cout << "reversed origin in square: " << reverse_point << endl; + if (abs(0.0 - reverse_point.x) > sigma) + { + cerr << "Reverse origin:" << endl; + cout << "Invalid x value: " << reverse_point.x << " != " << 0 << endl; + return false; + } + if (abs(0.0 - reverse_point.y) > sigma) + { + cerr << "Reverse origin:" << endl; + cout << "Invalid y value: " << reverse_point.y << " != " << 0 << 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); + + // failed check: is precision an issue? +// if (!fequal(w, square.width * sqrt(2), sigma) +// || !fequal(h, square.height * sqrt(2), sigma)) +// { +// 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); + + APoint const a_p45 = convert_abs_coord(angle + rotation, ratio); + Point const p45 = convert_img_coord(rotated, a_p45); + if (abs((float) (-1) - p45.x) > sigma) + { + cerr << "Rotation origin by 45 degrees:" << endl; + cerr << "Invalid x value: " << p45.x << " != " << -1 << endl; + cerr << "Absolute point: (" << a_p45.x << ", " << a_p45.y << ")" << endl; + return false; + } + if (abs(0.0 - p45.y) > sigma) + { + cerr << "Rotation origin by 45 degrees:" << endl; + cerr << "Invalid y value: " << p45.y << " != " << 0 << endl; + return false; + } + + return true; +} + + +// +// +// Rotation +// + +Image rotate(Image const& src, double angle) +{ + unsigned int const nb_points = src.width * src.height; + double const square_side = sqrt(nb_points); + double const half_side = square_side / 2; + unsigned int const trigo_length = (unsigned int) ceil(half_side * sqrt(2)); + double const ratio = 1.0f / trigo_length; + + // top left + double const cos_value = - (src.width / 2.0f) * ratio; +// double const sin_value = (src.height / 2.0f) * ratio; + double const angle_value = acos(cos_value); + cout << "top left angle: " << angle_value << endl; + + double const radian = (angle / 360.0f) * (2 * M_PI); + + + // FIXME + Image rot(trigo_length, trigo_length); + Point tl = rotate(src, Point(0, 0), radian, ratio); + Point tr = rotate(src, Point(src.width - 1, 0), radian, ratio); + draw_line(rot, tl, tr); + // draw_line(rot, 0, 0, rot.width - 1, 0); + // draw_line(rot, 0, rot.height - 1, rot.width - 1, rot.height - 1); + // draw_line(rot, 0, 0, rot.width - 1, rot.height - 1); + // rot.set_pixel(rot.width - 1, rot.height - 1, 0, 255, 0); +// draw_line(rot, 0, 0, 0, rot.height - 1); +// draw_line(rot, rot.width - 1, 0, rot.width - 1, rot.height - 1); + return rot; +} + + +// +// +// Main +// + +int main() +{ + if (!check_trigo()) + return 1; + + return 0; + + Image img("img/luigi.ppm"); + + Image rotated = rotate(img, 30); + rotated.save("rotated.ppm"); + + return 0; +}