@bhugueney/

rotate ppm in cxx

C++

No description

fork
loading
Files
  • main.cpp
  • Aerial.512-15.ppm
  • Aerial.512.ppm
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <array>
#include <cmath>
#include <tuple>
#include <functional>
#include <limits>
#include <type_traits>
#include <chrono>
#include <ctime>
#include <exception>
#include <stdexcept>

/*
  We will represent:
  - a bitmap of an image as a std::vector of rows to have any number of rows
  - a row as a std::vector of pixels to have any number of columns (in practice all lines will have the same size)
  - a pixel as a std::array of 3 color components
  - a color component as an unsigned char (values 0-255)

A more efficient memory layout would be a single chunk of memory of 
rows × cols pixels, obviously.
 */
typedef std::vector<std::vector<std::array<unsigned char, 3>>> img_t;

/*
  Overload to read a P6 ppm image. Comments are not handled.
  Max nb of color values are not used and assumed to be 255
 */
std::istream& operator >>(std::istream& is, img_t& img)
{
  std::string header;
  is >> header;
  if (header.compare("P6") != 0){ throw std::runtime_error("bad file format :"+ header);}
  // TODO should skip comments
  std::size_t w, h, max_col_val;
  is >> w >> h >> max_col_val; // we don't use max_col_Val, assuming 255
  is.get(); // skip the trailing white space
  img.resize(h);
  unsigned char pix[3];// R, G, B
  for(auto& row: img){
    row.resize(w);
    for(auto& pixel : row){
      is.read(reinterpret_cast<char *>(pix), 3);
      pixel= {pix[0], pix[1], pix[2]};
    }
  }
  return is;
}
/*
  Overload to output a PPM image in P6 file format
 */
std::ostream& operator <<(std::ostream& os, const img_t& img)
{
    os << "P6\n"
       << img[0].size() << " " << img.size() << "\n"
       << 255 << "\n";
    for(auto& row : img){
      for(auto& pixel : row){
      	for(auto& channel : pixel){
      	  os << channel;
	      }
      }
    }
    return os;
}

// using std::tuple instead of std::array<TC,2> to show the equivalent in python !
template<typename TA, typename TC>
std::tuple<TC, TC> rotate(TA angle, std::tuple<TC, TC> const& xy){
  TC x;
  TC y;
  std::tie(x,y)= xy;
  return std::tuple<TC, TC>(x*std::cos(angle) - y*std::sin(angle)
			    ,y*std::cos(angle) + x*std::sin(angle));
}

template<typename TC>
std::tuple<TC, TC> add(std::tuple<TC, TC> const& xy_1,std::tuple<TC, TC> const& xy_2){
  TC x_1;
  TC y_1;
  std::tie(x_1, y_1)= xy_1;
  TC x_2;
  TC y_2;
  std::tie(x_2, y_2)= xy_2;

  return std::make_tuple(x_1+x_2, y_1+y_2);
}

template<typename TS, typename TC>
std::tuple<TC, TC> scale(TS const k, std::tuple<TC, TC> const& xy){
  TC x;
  TC y;
  std::tie(x, y)= xy;
  return std::tuple<TC, TC>(k*x, k*y);
}

template<typename TA, typename TC>
std::tuple<TC, TC> rotate_around(TA angle, std::tuple<TC, TC> const& xy_c, std::tuple<TC, TC> const& xy){
  return add(xy_c, rotate(angle, add(scale(-1., xy_c), xy)));
}

template<typename TC>
std::tuple<TC, TC, TC, TC> update_min_max(std::tuple<TC, TC, TC, TC> xmin_xmax_ymin_y_max, std::tuple<TC, TC> xy){
  TC const x(std::get<0>(xy));
  TC const y(std::get<1>(xy));
  return std::make_tuple(std::min(std::get<0>(xmin_xmax_ymin_y_max), x)
			 ,std::max(std::get<1>(xmin_xmax_ymin_y_max), x)
			 ,std::min(std::get<2>(xmin_xmax_ymin_y_max), y)
			 ,std::max(std::get<3>(xmin_xmax_ymin_y_max), y));
}

/*
img_t could nearly be a template parameter
 */
img_t rotate(double angle, img_t const & src){
  auto start = std::chrono::system_clock::now();
  long w_src(src[0].size());
  long h_src(src.size());
  auto xy_c_src(scale(0.5, std::make_tuple(w_src, h_src)));
  auto src_to_dest= [angle, xy_c_src](auto xy){return rotate_around(-angle, xy_c_src, xy);};
  long x_min_dest= std::numeric_limits<long>::max(), x_max_dest=std::numeric_limits<long>::min()
    , y_min_dest=std::numeric_limits<long>::max(), y_max_dest= std::numeric_limits<long>::min();
  
  std::tie(x_min_dest, x_max_dest, y_min_dest, y_max_dest)= update_min_max(std::make_tuple(x_min_dest, x_max_dest, y_min_dest, y_max_dest)
									   , src_to_dest(std::make_tuple(0L,0L)));
  std::tie(x_min_dest, x_max_dest, y_min_dest, y_max_dest)= update_min_max(std::make_tuple(x_min_dest, x_max_dest, y_min_dest, y_max_dest)
									   , src_to_dest(std::make_tuple(w_src,0L)));
  std::tie(x_min_dest, x_max_dest, y_min_dest, y_max_dest)= update_min_max(std::make_tuple(x_min_dest, x_max_dest, y_min_dest, y_max_dest)
									   , src_to_dest(std::make_tuple(w_src, h_src)));
  std::tie(x_min_dest, x_max_dest, y_min_dest, y_max_dest)= update_min_max(std::make_tuple(x_min_dest, x_max_dest, y_min_dest, y_max_dest)
									   , src_to_dest(std::make_tuple(0L, h_src)));

  long  w_dest= x_max_dest - x_min_dest;
  long  h_dest= y_max_dest - y_min_dest;

  auto is_in_src=[h_src, w_src](auto r, auto c){return (r >= 0) && (r < h_src) && ( c >= 0) && (c < w_src);};
  auto default_color= decltype(src[0][0]){0,0,0};

  img_t dest;
  for(long r_dest=0; r_dest != h_dest; ++r_dest){
    //dest.push_back(std::decay<decltype(dest[0])>::type{});
    dest.push_back(std::vector<std::array<unsigned char, 3>>());
    for(long c_dest=0; c_dest != w_dest; ++c_dest){
      	long r_src, c_src;
	std::tie(c_src, r_src)= rotate_around(angle, xy_c_src, std::make_tuple(c_dest + x_min_dest
										,r_dest + y_min_dest));
      dest.back().push_back(is_in_src(r_src, c_src) ? src[r_src][c_src] : default_color);
    }
  }
  auto end = std::chrono::system_clock::now();
  std::chrono::duration<double> elapsed_seconds = end-start;
  std::cerr<< "rotation of "<< w_src << "x"<< h_src<<" to " << w_dest << "x" << h_dest << " in " << elapsed_seconds.count() << "s.\n";
  return dest;
}

constexpr double const_pi() { return std::acos(-1.); }

int main(int argc, char* argv[]){
  // no arguments on repl.i ?
  char const*const argv_repl[]={"main", "15","Aerial.512.ppm","Aerial.512-15.ppm"};
  argc= 4;
  argv= const_cast<char**>(argv_repl);
  try {
    double const angle= (argc >1)
      ? const_pi()*std::stod(argv[1])/180.
      : const_pi()/4.;
    
    std::istream* input = &std::cin;
    std::ifstream file_input;
    if (argc > 2) {
      file_input.open(argv[2], std::ios::binary);
      input = &file_input;
    }
    
    std::ostream* output = &std::cout;
    std::ofstream file_output;
    if (argc > 3) {
      file_output.open(argv[3], std::ios::binary);
      output = &file_output;
    }
    
    img_t img;
    *input >> img;
    *output << rotate(angle, img);
  }catch (std::exception& e){
    std::cerr<<  "exception " << typeid(e).name() <<" caught: " << e.what() << '\n';
  }
  return 0;
}