Line data Source code
1 : // Copyright (c) 2018-2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/snapdev
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 : #pragma once
19 :
20 : /** \file
21 : * \brief A template used to handle matrix operations.
22 : *
23 : * This is an extension of the std::basic_string that allows one to use
24 : * case insensitive strings containers such as maps and sets.
25 : *
26 : * This implementation includes all the basic color computations in a 4x4
27 : * matrix. It also includes a function to output the matrix to your console.
28 : */
29 :
30 : // C++
31 : //
32 : #include <cctype>
33 : #include <cmath>
34 : #include <iostream>
35 : #include <sstream>
36 : #include <stdexcept>
37 : #include <vector>
38 :
39 :
40 :
41 : namespace snapdev
42 : {
43 :
44 : // Matrix additions, subtractions, and multiplications can be verified
45 : // using this web page:
46 : // http://www.calcul.com/show/calculator/matrix-multiplication
47 :
48 :
49 :
50 : template<typename T, typename SIZE = std::size_t>
51 : class matrix
52 : {
53 : public:
54 : typedef T value_type;
55 : typedef SIZE size_type;
56 :
57 : // The color weights are used to convert RGB to Luma.
58 : //
59 : // With a factor, it's possible to change the color toward or
60 : // away from the perfect Luma if the color is not already
61 : // a gray color (see the saturation() function.)
62 : //
63 : // Note that these are often referenced to as Luminance Weights.
64 : // The Luminance is what you get on your monitor itself, not with
65 : // linear RGB as we manage in software.
66 : //
67 : // The AVERAGE luma factors are present because they may be useful
68 : // in some situation, but they are definitely wrong and should
69 : // very rarely be used.
70 : //
71 : // see https://en.wikipedia.org/wiki/Luma_%28video%29
72 : // see https://www.opengl.org/archives/resources/code/samples/advanced/advanced97/notes/node140.html
73 : //
74 : static value_type constexpr HDTV_LUMA_RED = 0.2126;
75 : static value_type constexpr HDTV_LUMA_GREEN = 0.7152;
76 : static value_type constexpr HDTV_LUMA_BLUE = 0.0722;
77 :
78 : static value_type constexpr LED_LUMA_RED = 0.212;
79 : static value_type constexpr LED_LUMA_GREEN = 0.701;
80 : static value_type constexpr LED_LUMA_BLUE = 0.087;
81 :
82 : static value_type constexpr CRT_LUMA_RED = 0.3086;
83 : static value_type constexpr CRT_LUMA_GREEN = 0.6094;
84 : static value_type constexpr CRT_LUMA_BLUE = 0.0820;
85 :
86 : static value_type constexpr NTSC_LUMA_RED = 0.299;
87 : static value_type constexpr NTSC_LUMA_GREEN = 0.587;
88 : static value_type constexpr NTSC_LUMA_BLUE = 0.114;
89 :
90 : static value_type constexpr AVERAGE_LUMA_RED = 1.0 / 3.0;
91 : static value_type constexpr AVERAGE_LUMA_GREEN = 1.0 / 3.0;
92 : static value_type constexpr AVERAGE_LUMA_BLUE = 1.0 / 3.0;
93 :
94 : class element_ref
95 : {
96 : public:
97 555378 : element_ref(matrix<T, SIZE> & m, size_type j, size_type i)
98 555378 : : f_matrix(m)
99 : //, f_j(j)
100 : //, f_i(i)
101 555378 : , f_offset(i + j * m.columns())
102 : {
103 555378 : if(i >= m.f_columns)
104 : {
105 0 : throw std::out_of_range("used [] operator with too large a row number");
106 : }
107 555378 : }
108 :
109 221701 : element_ref & operator = (value_type const v)
110 : {
111 221701 : f_matrix.f_vector[f_offset] = v;
112 :
113 221701 : return *this;
114 : }
115 :
116 1588 : bool operator == (value_type const v) const
117 : {
118 1588 : return f_matrix.f_vector[f_offset] == v;
119 : }
120 :
121 : bool operator != (value_type const v) const
122 : {
123 : return f_matrix.f_vector[f_offset] != v;
124 : }
125 :
126 : bool operator < (value_type const v) const
127 : {
128 : return f_matrix.f_vector[f_offset] < v;
129 : }
130 :
131 : bool operator <= (value_type const v) const
132 : {
133 : return f_matrix.f_vector[f_offset] <= v;
134 : }
135 :
136 : bool operator > (value_type const v) const
137 : {
138 : return f_matrix.f_vector[f_offset] > v;
139 : }
140 :
141 : bool operator >= (value_type const v) const
142 : {
143 : return f_matrix.f_vector[f_offset] >= v;
144 : }
145 :
146 332089 : operator T () const
147 : {
148 332089 : return f_matrix.f_vector[f_offset];
149 : }
150 :
151 : private:
152 : matrix<T, SIZE> & f_matrix;
153 : size_type f_offset;
154 : };
155 :
156 : class row_ref
157 : {
158 : public:
159 555378 : row_ref(matrix<T, SIZE> & m, size_type j)
160 555378 : : f_matrix(m)
161 555378 : , f_j(j)
162 : {
163 555378 : if(j >= m.f_rows)
164 : {
165 0 : throw std::out_of_range("used [] operator with too large a row number");
166 : }
167 555378 : }
168 :
169 555378 : element_ref operator [] (size_type i)
170 : {
171 555378 : element_ref r(f_matrix, f_j, i);
172 555378 : return r;
173 : }
174 :
175 : value_type operator [] (size_type i) const
176 : {
177 : return f_matrix.f_vector[i + f_j * f_matrix.columns()];
178 : }
179 :
180 : private:
181 : matrix<T, SIZE> & f_matrix;
182 : size_type f_j; // row
183 : };
184 :
185 : class const_row_ref
186 : {
187 : public:
188 : const_row_ref(matrix<T, SIZE> const & m, size_type j)
189 : : f_matrix(m)
190 : , f_j(j)
191 : {
192 : if(j >= m.f_rows)
193 : {
194 : throw std::out_of_range("used [] operator with too large a row number");
195 : }
196 : }
197 :
198 : value_type operator [] (size_type i) const
199 : {
200 : if(i >= f_matrix.f_columns)
201 : {
202 : throw std::out_of_range("used [] operator with too large a row number");
203 : }
204 : return f_matrix.f_vector[i + f_j * f_matrix.columns()];
205 : }
206 :
207 : private:
208 : matrix<T, SIZE> const & f_matrix;
209 : size_type f_j; // row
210 : };
211 :
212 4 : matrix()
213 4 : {
214 4 : }
215 :
216 95299 : matrix(size_type rows, size_type columns)
217 95299 : : f_rows(rows)
218 95299 : , f_columns(columns)
219 95299 : , f_vector(rows * columns)
220 : {
221 95299 : initialize();
222 95299 : }
223 :
224 : template<typename V, typename SZ>
225 : matrix(matrix<V, SZ> const & rhs)
226 : : f_rows(rhs.f_rows)
227 : , f_columns(rhs.f_columns)
228 : , f_vector(rhs.f_vector.size())
229 : {
230 : initialize();
231 : }
232 :
233 : template<typename V, typename SZ>
234 : matrix<T, SIZE> & operator = (matrix<V, SZ> const & rhs) //= default;
235 : {
236 : f_rows = rhs.f_rows;
237 : f_columns = rhs.f_columns;
238 : f_vector = rhs.f_vector;
239 : f_luma_red = rhs.f_luma_red;
240 : f_luma_green = rhs.f_luma_green;
241 : f_luma_blue = rhs.f_luma_blue;
242 : #ifdef _DEBUG
243 : f_last_hue_matrix = rhs.f_last_hue_matrix;
244 : #endif
245 : }
246 :
247 : template<typename V, typename SZ>
248 : matrix<T, SIZE> & operator = (matrix<V, SZ> const && rhs) //= default;
249 : {
250 : f_rows = std::move(rhs.f_rows);
251 : f_columns = std::move(rhs.f_columns);
252 : f_vector = std::move(rhs.f_vector);
253 : f_luma_red = std::move(rhs.f_luma_red);
254 : f_luma_green = std::move(rhs.f_luma_green);
255 : f_luma_blue = std::move(rhs.f_luma_blue);
256 : #ifdef _DEBUG
257 : f_last_hue_matrix = std::move(rhs.f_last_hue_matrix);
258 : #endif
259 : }
260 :
261 : bool empty() const
262 : {
263 : return f_rows == 0 || f_columns == 0;
264 : }
265 :
266 : bool is_diagonal() const
267 : {
268 : for(size_type j(0); j < f_rows; ++j)
269 : {
270 : size_type const joffset(j * f_columns);
271 : for(size_type i(0); i < f_columns; ++i)
272 : {
273 : if(i != j
274 : && f_vector[i + joffset] != static_cast<value_type>(0)) // TODO: use a compare function with epsilon instead
275 : {
276 : return false;
277 : }
278 : }
279 : }
280 : return true;
281 : }
282 :
283 72 : size_type rows() const
284 : {
285 72 : return f_rows;
286 : }
287 :
288 555450 : size_type columns() const
289 : {
290 555450 : return f_columns;
291 : }
292 :
293 3 : void swap(matrix<T, SIZE> & rhs)
294 : {
295 3 : std::swap(f_rows, rhs.f_rows);
296 3 : std::swap(f_columns, rhs.f_columns);
297 3 : f_vector.swap(rhs.f_vector);
298 :
299 : // color related parameters
300 : //
301 3 : std::swap(f_luma_red, rhs.f_luma_red);
302 3 : std::swap(f_luma_green, rhs.f_luma_green);
303 3 : std::swap(f_luma_blue, rhs.f_luma_blue);
304 : #ifdef _DEBUG
305 3 : f_last_hue_matrix.swap(rhs.f_last_hue_matrix);
306 : #endif
307 3 : }
308 :
309 95299 : void initialize()
310 : {
311 95299 : if(f_rows == f_columns)
312 : {
313 81297 : identity();
314 : }
315 : else
316 : {
317 14002 : clear();
318 : }
319 95299 : }
320 :
321 14005 : void clear()
322 : {
323 14005 : std::fill(f_vector.begin(), f_vector.end(), value_type());
324 14005 : }
325 :
326 81297 : void identity()
327 : {
328 406061 : for(size_type j(0); j < f_rows; ++j)
329 : {
330 324764 : size_type const joffset(j * f_columns);
331 1622924 : for(size_type i(0); i < f_columns; ++i)
332 : {
333 1298160 : f_vector[i + joffset] =
334 : i == j
335 1298160 : ? static_cast<value_type>(1)
336 : : value_type();
337 : }
338 : }
339 81297 : }
340 :
341 555378 : row_ref operator [] (size_type row)
342 : {
343 555378 : row_ref r(*this, row);
344 555378 : return r;
345 : }
346 :
347 : const_row_ref operator [] (size_type row) const
348 : {
349 : const_row_ref r(*this, row);
350 : return r;
351 : }
352 :
353 : template<class S>
354 3 : matrix<T, SIZE> operator * (S const & scalar) const
355 : {
356 3 : matrix<T, SIZE> t(*this);
357 6 : return t *= scalar;
358 3 : }
359 :
360 : // At this point I don't know how to make that work...
361 : // template<class S, typename V, typename SZ> friend
362 : // matrix<V, SZ> operator * (S const & scalar, matrix<V, SZ> const & m)
363 : // {
364 : // return m * scalar;
365 : // }
366 :
367 : template<class S>
368 4 : matrix<T, SIZE> & operator *= (S const & scalar)
369 : {
370 20 : for(size_type j(0); j < f_rows; ++j)
371 : {
372 16 : size_type const joffset(j * f_columns);
373 80 : for(size_type i(0); i < f_columns; ++i)
374 : {
375 64 : f_vector[i + joffset] *= scalar;
376 : }
377 : }
378 :
379 4 : return *this;
380 : }
381 :
382 : template<typename V, typename SZ>
383 48007 : matrix<T, SIZE> operator * (matrix<V, SZ> const & m) const
384 : {
385 48007 : if(f_columns != m.f_rows)
386 : {
387 : // enhance that support with time
388 : // (i.e. the number of columns of one matrix has to match the
389 : // number of rows of the other, they don't need to be square
390 : // matrices of the same size)
391 : //
392 0 : std::cerr << "this " << f_rows << "x" << f_columns
393 0 : << " and rhs " << m.f_rows << "x" << m.f_columns << "\n";
394 0 : throw std::runtime_error("matrices of incompatible sizes for a multiplication");
395 : }
396 :
397 : // temporary buffer
398 : //
399 48007 : matrix<T, SIZE> t(f_rows, m.f_columns);
400 :
401 240035 : for(size_type j(0); j < f_rows; ++j)
402 : {
403 192028 : size_type const joffset(j * f_columns);
404 876140 : for(size_type i(0); i < m.f_columns; ++i)
405 : {
406 684112 : value_type sum = value_type();
407 :
408 : // k goes from 0 to (f_columns == m.f_rows)
409 : //
410 3420560 : for(size_type k(0); k < m.f_rows; ++k) // sometimes it's X and sometimes it's Y
411 : {
412 2736448 : sum += f_vector[k + joffset]
413 2736448 : * m.f_vector[i + k * m.f_columns];
414 : }
415 684112 : t.f_vector[i + j * t.f_columns] = sum;
416 : }
417 : }
418 :
419 48007 : return t;
420 : }
421 :
422 : template<class V>
423 7002 : matrix<T, SIZE> & operator *= (matrix<V> const & m)
424 : {
425 7002 : return *this = *this * m;
426 : }
427 :
428 : template<class S>
429 1 : matrix<T, SIZE> operator / (S const & scalar) const
430 : {
431 1 : matrix<T, SIZE> t(*this);
432 2 : return t /= scalar;
433 1 : }
434 :
435 : template<class S>
436 2 : matrix<T, SIZE> & operator /= (S const & scalar)
437 : {
438 10 : for(size_type y(0); y < f_rows; ++y)
439 : {
440 40 : for(size_type x(0); x < f_columns; ++x)
441 : {
442 32 : size_type const idx(x + y * f_columns);
443 32 : f_vector[idx] /= scalar;
444 : }
445 : }
446 :
447 2 : return *this;
448 : }
449 :
450 : template<typename V, typename SZ>
451 7001 : matrix<T, SIZE> operator / (matrix<V, SZ> const & m) const
452 : {
453 : // temporary buffer
454 : //
455 7001 : matrix<T, SIZE> t(m);
456 7001 : t.inverse();
457 14002 : return *this * t;
458 7001 : }
459 :
460 : template<typename V, typename SZ>
461 1 : matrix<T, SIZE> & operator /= (matrix<V, SZ> const & m)
462 : {
463 1 : matrix<T, SIZE> t(m);
464 1 : t.inverse();
465 2 : return *this *= t;
466 1 : }
467 :
468 : /** \brief Compute the inverse of `this` matrix if possible.
469 : *
470 : * This function computes the matrix determinant to see whether
471 : * it can be inverted. If so, it computes the inverse and becomes
472 : * that inverse.
473 : *
474 : * The function returns false if the inverse cannot be calculated
475 : * and the matrix remains unchanged.
476 : *
477 : * $$A^{-1} = {1 \over det(A)} adj(A)$$
478 : *
479 : * \return true if the inverse computation was successful.
480 : */
481 7003 : bool inverse()
482 : {
483 7003 : if(f_rows != 4
484 7003 : || f_columns != 4)
485 : {
486 0 : double const det(determinant());
487 : #pragma GCC diagnostic push
488 : #pragma GCC diagnostic ignored "-Wfloat-equal"
489 0 : if(det == static_cast<value_type>(0))
490 : {
491 0 : return false;
492 : }
493 : #pragma GCC diagnostic pop
494 0 : snapdev::matrix<double> adj(adjugate());
495 :
496 0 : *this = adj * (static_cast<value_type>(1) / det);
497 0 : return true;
498 0 : }
499 :
500 : // the following is very specific to a 4x4 matrix...
501 : //
502 7003 : value_type temp[4][8], *r[4], m[5];
503 :
504 7003 : r[0] = temp[0];
505 7003 : r[1] = temp[1];
506 7003 : r[2] = temp[2];
507 7003 : r[3] = temp[3];
508 :
509 7003 : temp[0][0] = f_vector[0 + 0 * 4];
510 7003 : temp[0][1] = f_vector[0 + 1 * 4];
511 7003 : temp[0][2] = f_vector[0 + 2 * 4];
512 7003 : temp[0][3] = f_vector[0 + 3 * 4];
513 :
514 7003 : temp[1][0] = f_vector[1 + 0 * 4];
515 7003 : temp[1][1] = f_vector[1 + 1 * 4];
516 7003 : temp[1][2] = f_vector[1 + 2 * 4];
517 7003 : temp[1][3] = f_vector[1 + 3 * 4];
518 :
519 7003 : temp[2][0] = f_vector[2 + 0 * 4];
520 7003 : temp[2][1] = f_vector[2 + 1 * 4];
521 7003 : temp[2][2] = f_vector[2 + 2 * 4];
522 7003 : temp[2][3] = f_vector[2 + 3 * 4];
523 :
524 7003 : temp[3][0] = f_vector[3 + 0 * 4];
525 7003 : temp[3][1] = f_vector[3 + 1 * 4];
526 7003 : temp[3][2] = f_vector[3 + 2 * 4];
527 7003 : temp[3][3] = f_vector[3 + 3 * 4];
528 :
529 : // can we do it?!
530 7003 : value_type abs_a = std::abs(r[3][0]);
531 7003 : value_type abs_b = std::abs(r[2][0]);
532 7003 : if(abs_a > abs_b) {
533 2 : value_type *swap = r[3];
534 2 : r[3] = r[2];
535 2 : r[2] = swap;
536 2 : abs_b = abs_a;
537 : }
538 :
539 7003 : abs_a = std::abs(r[1][0]);
540 7003 : if(abs_b > abs_a)
541 : {
542 7001 : value_type *swap = r[2];
543 7001 : r[2] = r[1];
544 7001 : r[1] = swap;
545 7001 : abs_a = abs_b;
546 : }
547 :
548 7003 : abs_b = std::abs(r[0][0]);
549 7003 : if(abs_a > abs_b)
550 : {
551 1002 : value_type *swap = r[1];
552 1002 : r[1] = r[0];
553 1002 : r[0] = swap;
554 1002 : abs_b = abs_a;
555 : }
556 :
557 : #pragma GCC diagnostic push
558 : #pragma GCC diagnostic ignored "-Wfloat-equal"
559 7003 : if(abs_b == 0.0f)
560 : {
561 0 : return false;
562 : }
563 :
564 7003 : temp[0][4] = temp[1][5] = temp[2][6] = temp[3][7] = 1.0f;
565 7003 : temp[0][5] =
566 7003 : temp[0][6] =
567 7003 : temp[0][7] =
568 7003 : temp[1][4] =
569 7003 : temp[1][6] =
570 7003 : temp[1][7] =
571 7003 : temp[2][4] =
572 7003 : temp[2][5] =
573 7003 : temp[2][7] =
574 7003 : temp[3][4] =
575 7003 : temp[3][5] =
576 7003 : temp[3][6] = 0.0f;
577 :
578 : // first elimination
579 7003 : m[4] = 1.0f / r[0][0];
580 7003 : m[1] = r[1][0] * m[4];
581 7003 : m[2] = r[2][0] * m[4];
582 7003 : m[3] = r[3][0] * m[4];
583 :
584 7003 : m[4] = r[0][1];
585 7003 : if(m[4] != 0.0)
586 : {
587 7003 : r[1][1] -= m[1] * m[4];
588 7003 : r[2][1] -= m[2] * m[4];
589 7003 : r[3][1] -= m[3] * m[4];
590 : }
591 :
592 7003 : m[4] = r[0][2];
593 7003 : if(m[4] != 0.0)
594 : {
595 7003 : r[1][2] -= m[1] * m[4];
596 7003 : r[2][2] -= m[2] * m[4];
597 7003 : r[3][2] -= m[3] * m[4];
598 : }
599 :
600 7003 : m[4] = r[0][3];
601 7003 : if(m[4] != 0.0)
602 : {
603 3 : r[1][3] -= m[1] * m[4];
604 3 : r[2][3] -= m[2] * m[4];
605 3 : r[3][3] -= m[3] * m[4];
606 : }
607 :
608 7003 : m[4] = r[0][4];
609 7003 : if(m[4] != 0.0)
610 : {
611 6001 : r[1][4] -= m[1] * m[4];
612 6001 : r[2][4] -= m[2] * m[4];
613 6001 : r[3][4] -= m[3] * m[4];
614 : }
615 :
616 7003 : m[4] = r[0][5];
617 7003 : if(m[4] != 0.0)
618 : {
619 2 : r[1][5] -= m[1] * m[4];
620 2 : r[2][5] -= m[2] * m[4];
621 2 : r[3][5] -= m[3] * m[4];
622 : }
623 :
624 7003 : m[4] = r[0][6];
625 7003 : if(m[4] != 0.0)
626 : {
627 1000 : r[1][6] -= m[1] * m[4];
628 1000 : r[2][6] -= m[2] * m[4];
629 1000 : r[3][6] -= m[3] * m[4];
630 : }
631 :
632 7003 : m[4] = r[0][7];
633 7003 : if(m[4] != 0.0)
634 : {
635 0 : r[1][7] -= m[1] * m[4];
636 0 : r[2][7] -= m[2] * m[4];
637 0 : r[3][7] -= m[3] * m[4];
638 : }
639 :
640 : // can we do it?!
641 7003 : abs_a = std::abs(r[3][1]);
642 7003 : abs_b = std::abs(r[2][1]);
643 7003 : if(abs_a > abs_b)
644 : {
645 0 : value_type *swap = r[3];
646 0 : r[3] = r[2];
647 0 : r[2] = swap;
648 0 : abs_b = abs_a;
649 : }
650 7003 : abs_a = std::abs(r[1][1]);
651 7003 : if(abs_b > abs_a)
652 : {
653 7003 : value_type *swap = r[2];
654 7003 : r[2] = r[1];
655 7003 : r[1] = swap;
656 7003 : abs_a = abs_b;
657 : }
658 :
659 7003 : if(abs_a == 0.0f)
660 : {
661 0 : return false;
662 : }
663 :
664 : // second elimination
665 7003 : m[4] = 1.0f / r[1][1];
666 :
667 7003 : m[2] = r[2][1] * m[4];
668 7003 : m[3] = r[3][1] * m[4];
669 :
670 7003 : if(m[2] != 0.0f)
671 : {
672 7003 : r[2][2] -= m[2] * r[1][2];
673 7003 : r[2][3] -= m[2] * r[1][3];
674 : }
675 :
676 7003 : if(m[3] != 0.0f)
677 : {
678 3 : r[3][2] -= m[3] * r[1][2];
679 3 : r[3][3] -= m[3] * r[1][3];
680 : }
681 :
682 7003 : m[4] = r[1][4];
683 7003 : if(m[4] != 0.0f)
684 : {
685 1 : r[2][4] -= m[2] * m[4];
686 1 : r[3][4] -= m[3] * m[4];
687 : }
688 :
689 7003 : m[4] = r[1][5];
690 7003 : if(m[4] != 0.0f)
691 : {
692 7003 : r[2][5] -= m[2] * m[4];
693 7003 : r[3][5] -= m[3] * m[4];
694 : }
695 :
696 7003 : m[4] = r[1][6];
697 7003 : if(m[4] != 0.0f)
698 : {
699 0 : r[2][6] -= m[2] * m[4];
700 0 : r[3][6] -= m[3] * m[4];
701 : }
702 :
703 7003 : m[4] = r[1][7];
704 7003 : if(m[4] != 0.0f)
705 : {
706 2 : r[2][7] -= m[2] * m[4];
707 2 : r[3][7] -= m[3] * m[4];
708 : }
709 :
710 : // can we do it?!
711 7003 : abs_a = std::abs(r[3][2]);
712 7003 : abs_b = std::abs(r[2][2]);
713 7003 : if(abs_a > abs_b)
714 : {
715 0 : value_type *swap = r[3];
716 0 : r[3] = r[2];
717 0 : r[2] = swap;
718 0 : abs_b = abs_a;
719 : }
720 :
721 7003 : if(abs_b == 0.0f)
722 : {
723 0 : return false;
724 : }
725 :
726 : // third elimination
727 7003 : m[3] = r[3][2] / r[2][2];
728 :
729 7003 : r[3][3] -= m[3] * r[2][3];
730 7003 : r[3][4] -= m[3] * r[2][4];
731 7003 : r[3][5] -= m[3] * r[2][5];
732 7003 : r[3][6] -= m[3] * r[2][6];
733 7003 : r[3][7] -= m[3] * r[2][7];
734 :
735 : // can we do it?!
736 7003 : if(r[3][3] == 0.0f) {
737 0 : return false;
738 : }
739 : #pragma GCC diagnostic pop
740 :
741 : // back substitute
742 : /* 3 */
743 7003 : m[4] = 1.0f / r[3][3];
744 7003 : r[3][4] *= m[4];
745 7003 : r[3][5] *= m[4];
746 7003 : r[3][6] *= m[4];
747 7003 : r[3][7] *= m[4];
748 :
749 : /* 2 */
750 7003 : m[2] = r[2][3];
751 7003 : m[4] = 1.0f / r[2][2];
752 7003 : r[2][4] = m[4] * (r[2][4] - r[3][4] * m[2]);
753 7003 : r[2][5] = m[4] * (r[2][5] - r[3][5] * m[2]);
754 7003 : r[2][6] = m[4] * (r[2][6] - r[3][6] * m[2]);
755 7003 : r[2][7] = m[4] * (r[2][7] - r[3][7] * m[2]);
756 :
757 7003 : m[1] = r[1][3];
758 7003 : r[1][4] -= r[3][4] * m[1];
759 7003 : r[1][5] -= r[3][5] * m[1];
760 7003 : r[1][6] -= r[3][6] * m[1];
761 7003 : r[1][7] -= r[3][7] * m[1];
762 :
763 7003 : m[0] = r[0][3];
764 7003 : r[0][4] -= r[3][4] * m[0];
765 7003 : r[0][5] -= r[3][5] * m[0];
766 7003 : r[0][6] -= r[3][6] * m[0];
767 7003 : r[0][7] -= r[3][7] * m[0];
768 :
769 : /* 1 */
770 7003 : m[1] = r[1][2];
771 7003 : m[4] = 1.0f / r[1][1];
772 7003 : r[1][4] = m[4] * (r[1][4] - r[2][4] * m[1]);
773 7003 : r[1][5] = m[4] * (r[1][5] - r[2][5] * m[1]);
774 7003 : r[1][6] = m[4] * (r[1][6] - r[2][6] * m[1]);
775 7003 : r[1][7] = m[4] * (r[1][7] - r[2][7] * m[1]);
776 :
777 7003 : m[0] = r[0][2];
778 7003 : r[0][4] -= r[2][4] * m[0];
779 7003 : r[0][5] -= r[2][5] * m[0];
780 7003 : r[0][6] -= r[2][6] * m[0];
781 7003 : r[0][7] -= r[2][7] * m[0];
782 :
783 : /* 0 */
784 7003 : m[0] = r[0][1];
785 7003 : m[4] = 1.0f / r[0][0];
786 7003 : r[0][4] = m[4] * (r[0][4] - r[1][4] * m[0]);
787 7003 : r[0][5] = m[4] * (r[0][5] - r[1][5] * m[0]);
788 7003 : r[0][6] = m[4] * (r[0][6] - r[1][6] * m[0]);
789 7003 : r[0][7] = m[4] * (r[0][7] - r[1][7] * m[0]);
790 :
791 : // save in destination (we need to put that in the back substitution)
792 7003 : f_vector[0 + 0 * 4] = r[0][4];
793 7003 : f_vector[0 + 1 * 4] = r[0][5];
794 7003 : f_vector[0 + 2 * 4] = r[0][6];
795 7003 : f_vector[0 + 3 * 4] = r[0][7];
796 :
797 7003 : f_vector[1 + 0 * 4] = r[1][4];
798 7003 : f_vector[1 + 1 * 4] = r[1][5];
799 7003 : f_vector[1 + 2 * 4] = r[1][6];
800 7003 : f_vector[1 + 3 * 4] = r[1][7];
801 :
802 7003 : f_vector[2 + 0 * 4] = r[2][4];
803 7003 : f_vector[2 + 1 * 4] = r[2][5];
804 7003 : f_vector[2 + 2 * 4] = r[2][6];
805 7003 : f_vector[2 + 3 * 4] = r[2][7];
806 :
807 7003 : f_vector[3 + 0 * 4] = r[3][4];
808 7003 : f_vector[3 + 1 * 4] = r[3][5];
809 7003 : f_vector[3 + 2 * 4] = r[3][6];
810 7003 : f_vector[3 + 3 * 4] = r[3][7];
811 :
812 7003 : return true;
813 : }
814 :
815 : /** \brief Reduce a matrix by removing one row and one column.
816 : *
817 : * This function creates a minor duplicate of this matrix with column i
818 : * and row j removed.
819 : *
820 : * The minor is denoted:
821 : *
822 : * $$M_{ij}$$
823 : *
824 : * It is a matrix built from $A$ without column `i` and row `j`.
825 : *
826 : * \note
827 : * The matrix must at least be a 2x2 matrix.
828 : *
829 : * \note
830 : * There is a "minor" macro so I named this function minor_matrix().
831 : *
832 : * \param[in] row The row to remove.
833 : * \param[in] column The column to remove.
834 : *
835 : * \return The requested minor matrix.
836 : */
837 220 : matrix<T, SIZE> minor_matrix(size_type row, size_type column) const
838 : {
839 220 : if(f_rows < 2
840 220 : || f_columns < 2)
841 : {
842 0 : throw std::runtime_error("minor_matrix() must be called with a matrix which is at least 2x2, although it does not need to be a square matrix");
843 : }
844 :
845 220 : matrix<T, SIZE> p(f_rows - 1, f_columns - 1);
846 :
847 : // we loop using p sizes
848 : // the code below ensures the correct input is retrieved
849 : //
850 : // di -- destination column
851 : // dj -- destination row
852 : // si -- source column
853 : // sj -- source row
854 : //
855 701 : for(size_type dj(0); dj < p.f_rows; ++dj)
856 : {
857 1574 : for(size_type di(0); di < p.f_columns; ++di)
858 : {
859 : // here we have 4 cases:
860 : //
861 : // a11 a12 | a13 | a14 a15
862 : // a21 a22 | a23 | a24 a25
863 : // --------+-----+--------
864 : // a31 a32 | a33 | a34 a35
865 : // --------+-----+--------
866 : // a41 a42 | a43 | a44 a45
867 : // a51 a52 | a53 | a54 a55
868 : //
869 : // assuming 'row' and 'column' are set to 3 and 3, then
870 : // the graph shows the 4 cases as the 4 corners, the
871 : // center lines will be removed so they are ignored in
872 : // the source
873 : //
874 1093 : size_type const si(di < column ? di : di + 1);
875 1093 : size_type const sj(dj < row ? dj : dj + 1);
876 :
877 1093 : p.f_vector[di + dj * p.f_columns] = f_vector[si + sj * f_columns];
878 : }
879 : }
880 :
881 220 : return p;
882 : }
883 :
884 : /** \brief Calculate the determinant of this matrix.
885 : *
886 : * This function calculates the determinant of this matrix:
887 : *
888 : * $$det(A) = \sum_{\sigma \in S_n} \Big( sgn(\sigma) \prod_{i=1}^{n} a_{i,\sigma_i} \Big)$$
889 : *
890 : * The function is implemented using a recursive set of calls. It
891 : * knows how to calculate a 2x2 matrix. All the others use recursive
892 : * calls to calculate the final result.
893 : *
894 : * Let's say you have a 3x3 matrix like this:
895 : *
896 : * \code
897 : * | a11 a12 a13 |
898 : * | a21 a22 a23 |
899 : * | a31 a32 a33 |
900 : * \endcode
901 : *
902 : * If first calculates the determinant of the 2x2 matrix:
903 : *
904 : * \code
905 : * | a22 a23 | = a22 x a33 - a23 x a32
906 : * | a32 a33 |
907 : * \endcode
908 : *
909 : * Then multiply that determinant by `a11`.
910 : *
911 : * Next it calculates the determinant of the 2x2 matrix:
912 : *
913 : * \code
914 : * | a21 a23 | = a21 x a33 - a23 x a31
915 : * | a31 a33 |
916 : * \endcode
917 : *
918 : * Then multiply that determinant by `a12` and multiply by -1.
919 : *
920 : * Finally, it calculates the last 2x2 matrix at the bottom left corner.
921 : *
922 : * \code
923 : * | a21 a22 | = a21 x a32 - a22 x a31
924 : * | a31 a32 |
925 : * \endcode
926 : *
927 : * Then multiply that last determinant by `a13`.
928 : *
929 : * Finally we sum all three results and that's our determinant for a
930 : * 3x3 matrix.
931 : *
932 : * The determinant of a 4x4 matrix will be calculated in a similar
933 : * way by also calculating the determinant of all the 3x3 matrices
934 : * defined under the first row.
935 : *
936 : * Source: https://en.wikipedia.org/wiki/Determinant
937 : *
938 : * \exception runtime_error
939 : * If the matrix is not a square matrix, raise a runtime_error exception.
940 : *
941 : * \return The determinant value.
942 : */
943 229 : value_type determinant() const
944 : {
945 229 : if(f_rows != f_columns)
946 : {
947 0 : throw std::runtime_error("determinant can only be calculated for square matrices");
948 : }
949 :
950 229 : if(f_columns == 1)
951 : {
952 4 : return f_vector[0];
953 : }
954 :
955 225 : if(f_columns == 2)
956 : {
957 172 : return f_vector[0 + 0 * 2] * f_vector[1 + 1 * 2]
958 172 : - f_vector[1 + 0 * 2] * f_vector[0 + 1 * 2];
959 : }
960 :
961 53 : value_type determinant = value_type();
962 :
963 53 : value_type sign = static_cast<value_type>(1);
964 214 : for(size_type c(0); c < f_columns; ++c)
965 : {
966 : // create a minor matrix
967 : //
968 161 : matrix<T, SIZE> p(minor_matrix(0, c));
969 :
970 : // add to the determinant for that column
971 : // (number of row 0 column 'c')
972 : //
973 161 : determinant += sign
974 161 : * f_vector[c + 0 * f_columns]
975 161 : * p.determinant();
976 :
977 : // swap the sign
978 : //
979 161 : sign *= static_cast<value_type>(-1);
980 : }
981 :
982 53 : return determinant;
983 : }
984 :
985 : /** \brief Swap the rows and columns of a matrix.
986 : *
987 : * This function returns the transpose of this matrix.
988 : *
989 : * Generally noted as:
990 : *
991 : * $$A^T$$
992 : *
993 : * The definition of the transpose is:
994 : *
995 : * $$[A^T]_{ij} = [A]_{ji}$$
996 : *
997 : * The resulting matrix has a number of columns equal to 'this' matrix
998 : * rows and vice versa.
999 : *
1000 : * \return A new matrix representing the transpose of 'this' matrix.
1001 : */
1002 6 : matrix<T, SIZE> transpose() const
1003 : {
1004 : // 'm' has its number of rows and columns swapped compared
1005 : // to 'this'
1006 6 : matrix<T, SIZE> m(f_columns, f_rows);
1007 :
1008 29 : for(size_type j(0); j < f_rows; ++j)
1009 : {
1010 96 : for(size_type i(0); i < f_columns; ++i)
1011 : {
1012 : // we could also have used "j + i * f_rows" on the left
1013 : // but I think it's more confusing
1014 : //
1015 73 : m.f_vector[j + i * m.f_columns] = f_vector[i + j * f_columns];
1016 : }
1017 : }
1018 :
1019 6 : return m;
1020 : }
1021 :
1022 : /** \brief This function calculates the adjugate of this matrix.
1023 : *
1024 : * \return The adjugate of this matrix.
1025 : */
1026 4 : matrix<T, SIZE> adjugate() const
1027 : {
1028 4 : if(f_rows != f_columns)
1029 : {
1030 : // is that true?
1031 : //
1032 0 : throw std::runtime_error("adjugate can only be calculated for square matrices");
1033 : }
1034 :
1035 4 : matrix<T, SIZE> r(f_rows, f_columns);
1036 :
1037 : // det(A) when A is 1x1 equals | 1 |
1038 : // which is the default 'r'
1039 : //
1040 4 : if(f_columns != 1)
1041 : {
1042 : //if(f_columns == 2)
1043 : //{
1044 : // // the 2x2 matrix is handled as a special case just so it goes
1045 : // // faster but not so much more than that
1046 : // //
1047 : // // adj | a b | = | d -b |
1048 : // // | c d | | -c a |
1049 : // //
1050 : // r.f_vector[0 + 0 * 2] = f_vector[1 + 1 * 2];
1051 : // r.f_vector[1 + 0 * 2] = -f_vector[1 + 0 * 2];
1052 : // r.f_vector[0 + 1 * 2] = -f_vector[0 + 1 * 2];
1053 : // r.f_vector[1 + 1 * 2] = f_vector[0 + 0 * 2];
1054 : //}
1055 : //else
1056 : {
1057 : // for larger matrices we use a loop and calculate the determinant
1058 : // for each new value with the "rest" of the matrix at that point
1059 : //
1060 17 : for(size_type j(0); j < f_rows; ++j)
1061 : {
1062 58 : for(size_type i(0); i < f_columns; ++i)
1063 : {
1064 45 : matrix<T, SIZE> p(minor_matrix(j, i));
1065 45 : r.f_vector[i + j * r.f_columns] = static_cast<value_type>(((i + j) & 1) == 0 ? 1 : -1) * p.determinant();
1066 : }
1067 : }
1068 :
1069 4 : return r.transpose();
1070 : }
1071 : }
1072 :
1073 0 : return r;
1074 4 : }
1075 :
1076 : /** \brief Add a scale to 'this' matrix.
1077 : *
1078 : * This function adds the specified scalar to the matrix. This adds
1079 : * the specified amount to all the elements of the matrix.
1080 : *
1081 : * $$[A]_{ij} \leftarrow [A]_{ij} + scalar$$
1082 : *
1083 : * \param[in] scalar The scalar to add to this matrix.
1084 : */
1085 : template<class S>
1086 1 : matrix<T, SIZE> operator + (S const & scalar) const
1087 : {
1088 1 : matrix<T, SIZE> t(*this);
1089 2 : return t += scalar;
1090 1 : }
1091 :
1092 : template<class S>
1093 2 : matrix<T, SIZE> & operator += (S const & scalar)
1094 : {
1095 10 : for(size_type y(0); y < f_rows; ++y)
1096 : {
1097 40 : for(size_type x(0); x < f_columns; ++x)
1098 : {
1099 32 : size_type const idx(x + y * f_columns);
1100 32 : f_vector[idx] += scalar;
1101 : }
1102 : }
1103 :
1104 2 : return *this;
1105 : }
1106 :
1107 : template<typename V, typename SZ>
1108 1 : matrix<T, SIZE> operator + (matrix<V, SZ> const & m) const
1109 : {
1110 1 : matrix<T, SIZE> t(*this);
1111 2 : return t += m;
1112 1 : }
1113 :
1114 : template<typename V, typename SZ>
1115 2 : matrix<T, SIZE> & operator += (matrix<V, SZ> const & m)
1116 : {
1117 2 : if(f_rows != m.f_rows
1118 2 : || f_columns != m.f_columns)
1119 : {
1120 0 : throw std::runtime_error("matrices of incompatible sizes for an addition");
1121 : }
1122 :
1123 10 : for(size_type y(0); y < f_rows; ++y)
1124 : {
1125 40 : for(size_type x(0); x < f_columns; ++x)
1126 : {
1127 32 : size_type const idx(x + y * f_columns);
1128 32 : f_vector[idx] += m.f_vector[idx];
1129 : }
1130 : }
1131 :
1132 2 : return *this;
1133 : }
1134 :
1135 : template<class S>
1136 1 : matrix<T, SIZE> operator - (S const & scalar) const
1137 : {
1138 1 : matrix<T, SIZE> t(*this);
1139 2 : return t -= scalar;
1140 1 : }
1141 :
1142 : template<class S>
1143 2 : matrix<T, SIZE> & operator -= (S const & scalar)
1144 : {
1145 2 : matrix<T, SIZE> t(f_rows, f_columns);
1146 :
1147 10 : for(size_type y(0); y < f_rows; ++y)
1148 : {
1149 40 : for(size_type x(0); x < f_columns; ++x)
1150 : {
1151 32 : size_type const idx(x + y * f_columns);
1152 32 : f_vector[idx] -= scalar;
1153 : }
1154 : }
1155 :
1156 2 : return *this;
1157 2 : }
1158 :
1159 : template<typename V, typename SZ>
1160 1 : matrix<T, SIZE> operator - (matrix<V, SZ> const & m) const
1161 : {
1162 1 : matrix<T, SIZE> t(*this);
1163 2 : return t -= m;
1164 1 : }
1165 :
1166 : template<class V, typename SZ>
1167 2 : matrix<T, SIZE> & operator -= (matrix<V, SZ> const & m)
1168 : {
1169 2 : if(f_rows != m.f_rows
1170 2 : || f_columns != m.f_columns)
1171 : {
1172 0 : throw std::runtime_error("matrices of incompatible sizes for a subtraction");
1173 : }
1174 :
1175 10 : for(size_type y(0); y < f_rows; ++y)
1176 : {
1177 40 : for(size_type x(0); x < f_columns; ++x)
1178 : {
1179 32 : size_type const idx(x + y * f_columns);
1180 32 : f_vector[idx] -= m.f_vector[idx];
1181 : }
1182 : }
1183 :
1184 2 : return *this;
1185 : }
1186 :
1187 :
1188 : static matrix<T, SIZE> rotation_matrix_4x4_x(double angle)
1189 : {
1190 : matrix<T, SIZE> result(4, 4);
1191 :
1192 : double const c(cos(angle));
1193 : double const s(sin(angle));
1194 :
1195 : result[1][1] = c;
1196 : result[1][2] = -s;
1197 : result[2][1] = s;
1198 : result[2][2] = c;
1199 :
1200 : return result;
1201 : }
1202 :
1203 :
1204 : static matrix<T, SIZE> rotation_matrix_4x4_y(double angle)
1205 : {
1206 : matrix<T, SIZE> result(4, 4);
1207 :
1208 : double const c(cos(angle));
1209 : double const s(sin(angle));
1210 :
1211 : result[0][0] = c;
1212 : result[0][2] = s;
1213 : result[2][0] = -s;
1214 : result[2][2] = c;
1215 :
1216 : return result;
1217 : }
1218 :
1219 :
1220 : static matrix<T, SIZE> rotation_matrix_4x4_z(double angle)
1221 : {
1222 : matrix<T, SIZE> result(4, 4);
1223 :
1224 : double const c(cos(angle));
1225 : double const s(sin(angle));
1226 :
1227 : result[0][0] = c;
1228 : result[0][1] = -s;
1229 : result[2][0] = s;
1230 : result[2][1] = c;
1231 :
1232 : return result;
1233 : }
1234 :
1235 :
1236 : /** \brief Apply a scaling factor to this matrix.
1237 : *
1238 : * This function multiplies 'this' matrix by a scaling factor and
1239 : * returns the result. 'this' does not get changed.
1240 : *
1241 : * The scale matrix looks like:
1242 : *
1243 : * $$
1244 : * \begin{bmatrix}
1245 : * brightness_r & 0 & 0 & 0
1246 : * \\ 0 & brightness_g & 0 & 0
1247 : * \\ 0 & 0 & brightness_b & 0
1248 : * \\ 0 & 0 & 0 & 1
1249 : * \end{bmatrix}
1250 : * $$
1251 : *
1252 : * The 'r', 'g', 'b' indices represent the RGB colors if one wants to
1253 : * scale just one color at a time although this function only offers
1254 : * to set all of the fields to the same value \p s.
1255 : *
1256 : * See:
1257 : * http://www.graficaobscura.com/matrix/index.html
1258 : *
1259 : * \param[in] b The scaling factor to apply to this matrix.
1260 : *
1261 : * \return 'this' x 'brightness'
1262 : */
1263 : template<typename S>
1264 1000 : matrix<T, SIZE> brightness(S const b) const
1265 : {
1266 1000 : if(f_rows != 4
1267 1000 : || f_columns != 4)
1268 : {
1269 0 : throw std::runtime_error("brightness() is only for 4x4 matrices at this time");
1270 : }
1271 :
1272 1000 : matrix<T, SIZE> m(4, 4);
1273 1000 : m[0][0] = b;
1274 1000 : m[1][1] = b;
1275 1000 : m[2][2] = b;
1276 :
1277 2000 : return *this * m;
1278 1000 : }
1279 :
1280 :
1281 : /** \brief Apply an RGB color saturation to this matrix.
1282 : *
1283 : * This function applies the saturation matrix defined below to
1284 : * 'this' matrix. When the saturation parameter is set to zero (0)
1285 : * the function transforms all the colors to gray. When the saturation
1286 : * is set to one (1), the color does not get changed.
1287 : *
1288 : * A saturation parameter outside of the [0 .. 1] range will have
1289 : * unexpected results.
1290 : *
1291 : * The saturation matrix:
1292 : *
1293 : * $$
1294 : * \begin{bmatrix}
1295 : * L_r \times ( 1 - saturation) + saturation & L_r \times ( 1 - saturation) & L_r \times ( 1 - saturation) & 0
1296 : * \\ L_g \times ( 1 - saturation) & L_g \times ( 1 - saturation) + saturation & L_g \times ( 1 - saturation) & 0
1297 : * \\ L_b \times ( 1 - saturation) & L_b \times ( 1 - saturation) & L_b \times ( 1 - saturation) + saturation & 0
1298 : * \\ 0 & 0 & 0 & 1
1299 : * \end{bmatrix}
1300 : * $$
1301 : *
1302 : * The weights used here come from the luma matrix. See the
1303 : * get_luma_vector() function.
1304 : *
1305 : * See:
1306 : * http://www.graficaobscura.com/matrix/index.html
1307 : *
1308 : * \param[in] s How close or far to correct the color toward saturation.
1309 : *
1310 : * \return 'this' x 'scale'
1311 : */
1312 : template<typename S>
1313 6000 : matrix<T, SIZE> saturation(S const s) const
1314 : {
1315 6000 : if(f_rows != 4
1316 6000 : || f_columns != 4)
1317 : {
1318 0 : throw std::runtime_error("saturation() is only for 4x4 matrices at this time");
1319 : }
1320 :
1321 6000 : matrix<T, SIZE> m(4, 4);
1322 :
1323 6000 : value_type const ns(static_cast<value_type>(s));
1324 6000 : value_type const os(static_cast<value_type>(1) - static_cast<value_type>(s));
1325 :
1326 6000 : m[0][0] = f_luma_red * os + ns;
1327 6000 : m[0][1] = f_luma_red * os;
1328 6000 : m[0][2] = f_luma_red * os;
1329 :
1330 6000 : m[1][0] = f_luma_green * os;
1331 6000 : m[1][1] = f_luma_green * os + ns;
1332 6000 : m[1][2] = f_luma_green * os;
1333 :
1334 6000 : m[2][0] = f_luma_blue * os;
1335 6000 : m[2][1] = f_luma_blue * os;
1336 6000 : m[2][2] = f_luma_blue * os + ns;
1337 :
1338 12000 : return *this * m;
1339 6000 : }
1340 :
1341 :
1342 : /** \brief Apply a hue correction.
1343 : *
1344 : * This function applies a hue correction to 'this' matrix.
1345 : *
1346 : * The hue correction makes use of the rotation around the red
1347 : * and green axis, then a skew to take luminance in account.
1348 : * At that point it rotates the color. Finally, the function
1349 : * reverses the skew and rotate back around the green and red
1350 : * axis.
1351 : *
1352 : * The following is the list of matrices used to rotate the hue:
1353 : *
1354 : * Rotation around the Red axis (Rr):
1355 : *
1356 : * $$
1357 : * R_r =
1358 : * \begin{bmatrix}
1359 : * 1 & 0 & 0 & 0
1360 : * \\ 0 & {1 \over \sqrt 2 } & {1 \over \sqrt 2 } & 0
1361 : * \\ 0 & -{1 \over \sqrt 2 } & {1 \over \sqrt 2 } & 0
1362 : * \\ 0 & 0 & 0 & 1
1363 : * \end{bmatrix}
1364 : * $$
1365 : *
1366 : * \note
1367 : * The $1 \over \sqrt 2$ is $sin ( \pi \over 4 )$ and
1368 : * $cos ( \pi \over 4 )$.
1369 : *
1370 : * Rotation around the Green axis (Rg):
1371 : *
1372 : * $$
1373 : * R_g =
1374 : * \begin{bmatrix}
1375 : * {\sqrt 2 \over \sqrt 3} & 0 & {1 \over \sqrt 3} & 0
1376 : * \\ 0 & 1 & 0 & 0
1377 : * \\ -{1 \over \sqrt 3} & 0 & {\sqrt 2 \over \sqrt 3} & 0
1378 : * \\ 0 & 0 & 0 & 1
1379 : * \end{bmatrix}
1380 : * $$
1381 : *
1382 : * \note
1383 : * The $\sqrt 2 \over \sqrt 3$ and $1 \over \sqrt 3$ are sin and
1384 : * cos as well. These take the first rotation in account (i.e.
1385 : * so it is again a 45° angle but applied after the 45° angle
1386 : * applied to the red axis.)
1387 : *
1388 : * Combine both rotations:
1389 : *
1390 : * $$
1391 : * R_{rg} = R_r R_g
1392 : * $$
1393 : *
1394 : * Since colors are linear but not at a similar angle, we want to
1395 : * unskew them for which we need to use the luminance. So here we
1396 : * compute the luminance of the color.
1397 : *
1398 : * $$
1399 : * \begin{bmatrix}
1400 : * L_r
1401 : * \\ L_g
1402 : * \\ L_b
1403 : * \\ 0
1404 : * \end{bmatrix}
1405 : * =
1406 : * R_{rg}
1407 : * \begin{bmatrix}
1408 : * W_r
1409 : * \\ W_g
1410 : * \\ W_b
1411 : * \\ 0
1412 : * \end{bmatrix}
1413 : * $$
1414 : *
1415 : * Now we define a rotation matrix for the blue axis. This one uses
1416 : * a variable as the angle. This is the actual rotation angle which
1417 : * can go from 0 to $2 \pi$:
1418 : *
1419 : * $$
1420 : * R_b =
1421 : * \begin{bmatrix}
1422 : * cos( \theta ) & sin( \theta ) & 0 & 0
1423 : * \\ -sin( \theta ) & cos( \theta ) & 0 & 0
1424 : * \\ 0 & 0 & 1 & 0
1425 : * \\ 0 & 0 & 0 & 1
1426 : * \end{bmatrix}
1427 : * $$
1428 : *
1429 : * Now we have all the matrices to calculate the hue rotation
1430 : * of all the components of an image:
1431 : *
1432 : * $$
1433 : * H = R_{rg} S R_b S^{-1} R_{rg}^{-1}
1434 : * $$
1435 : *
1436 : * $H$ can then be used as in:
1437 : *
1438 : * $$
1439 : * \begin{bmatrix}
1440 : * R'
1441 : * \\ G'
1442 : * \\ B'
1443 : * \end{bmatrix}
1444 : * =
1445 : * H
1446 : * \begin{bmatrix}
1447 : * R
1448 : * \\ G
1449 : * \\ B
1450 : * \end{bmatrix}
1451 : * $$
1452 : *
1453 : * See:
1454 : * http://www.graficaobscura.com/matrix/index.html
1455 : *
1456 : * We can also rewrite the hue matrix as follow:
1457 : *
1458 : * $$
1459 : * H = cos ( \theta ) C + sin ( \theta ) S + T
1460 : * $$
1461 : *
1462 : * Where C is the cosine matrix, S is the sine matrix and T is an
1463 : * additional translation.
1464 : *
1465 : * I found an example on Microsoft (see link below) which I suspect
1466 : * does not include the color skewing. In other word, it completely
1467 : * ignores the color luminance weights which gives invalid results
1468 : * (albeit in most cases relatively good.)
1469 : *
1470 : * $$
1471 : * H =
1472 : * cos ( \theta )
1473 : * \begin{bmatrix}
1474 : * 0.787 & -0.213 & -0.213
1475 : * \\ -0.715 & 0.285 & -0.715
1476 : * \\ -0.072 & -0.072 & 0.928
1477 : * \end{bmatrix}
1478 : * +
1479 : * sin ( \theta )
1480 : * \begin{bmatrix}
1481 : * -0.213 & 0.143 & -0.787
1482 : * \\ -0.715 & 0.140 & 0.715
1483 : * \\ 0.928 & -0.283 & 0.072
1484 : * \end{bmatrix}
1485 : * +
1486 : * \begin{bmatrix}
1487 : * 0.213 & 0.213 & 0.213
1488 : * \\ 0.715 & 0.715 & 0.715
1489 : * \\ 0.072 & 0.072 & 0.072
1490 : * \end{bmatrix}
1491 : * $$
1492 : *
1493 : * The correct version is the one we actually use and it goes like this
1494 : * with high precision (double floating points) and 4x4 matrices:
1495 : *
1496 : * IMPORTANT: the weights change depending on the selected luma.
1497 : * If the user makes use of a luma which is not one supported by
1498 : * default, the algorithm falls back to the fully dynamic version
1499 : * of the hue computation.
1500 : *
1501 : * $$
1502 : * H =
1503 : * cos ( \theta )
1504 : * \begin{bmatrix}
1505 : * 0.943571345820976240 & -0.056428654178995265 & -0.056428654178995265 & 0
1506 : * \\ -0.189552583569840000 & 0.810447416430107000 & -0.189552583569853000 & 0
1507 : * \\ -0.754018762251120000 & -0.754018762251113000 & 0.245981237748847000 & 0
1508 : * \\ 0 & 0 & 0 & 0
1509 : * \end{bmatrix}
1510 : * +
1511 : * sin ( \theta )
1512 : * \begin{bmatrix}
1513 : * 0.32589470021007605 & 0.90324496939967125 & -0.25145556897954369 & 0
1514 : * \\ -0.98010410586906000 & -0.40275383667945400 & 0.17459643251014600 & 0
1515 : * \\ 0.65420940565900000 & -0.50049113272020600 & 0.07685913646939400 & 0
1516 : * \\ 0 & 0 & 0 & 0
1517 : * \end{bmatrix}
1518 : * +
1519 : * \begin{bmatrix}
1520 : * 0.056428654178995 & 0.056428654178995 & 0.056428654178995 & 0
1521 : * \\ 0.189552583569860 & 0.189552583569860 & 0.189552583569860 & 0
1522 : * \\ 0.754018762251160 & 0.754018762251160 & 0.754018762251160 & 0
1523 : * \\ 0 & 0 & 0 & 1
1524 : * \end{bmatrix}
1525 : * $$
1526 : *
1527 : * This is exactly the matrix we use below, although we directly apply the
1528 : * matrix additions and multiplications to generate the matrix as quickly
1529 : * as possible (we calculate just the necessary component and avoid many
1530 : * copies so it ought to be much faster.)
1531 : *
1532 : * \note
1533 : * It looks like the Microsoft matrices are reversed, so affecting BGR
1534 : * instead of RGB. Our matrices expects RGBX, but we adjust the
1535 : * multiplication as required to handle BGR, XBGR, XRGB, etc. We will
1536 : * change our matrix if required by OpenGL but as far as I know it's
1537 : * already defined in the correct direction.
1538 : *
1539 : * See:
1540 : * https://msdn.microsoft.com/en-us/library/windows/desktop/hh706342(v=vs.85).aspx
1541 : *
1542 : * \note
1543 : * To verify that the angle is correct, one can use this Wikipedia
1544 : * page. As we can see, from Red, you add 30° to get yellow, 120°
1545 : * to get green, etc. Obviously you can go also backward with
1546 : * negative angles.
1547 : * https://en.wikipedia.org/wiki/Hue
1548 : *
1549 : * \note
1550 : * For test purposes, which version of the matrix is used is defined
1551 : * by a variable that can be retrieved using the get_last_hue_matrix().
1552 : * The function is only available in debug mode.
1553 : *
1554 : * \param[in] h The amount of rotation, an angle in radian.
1555 : *
1556 : * \return 'this' rotated around the hue axis by \p h
1557 : */
1558 : template<typename S>
1559 6000 : matrix<T, SIZE> hue(S const h) const
1560 : {
1561 6000 : if(f_rows != 4
1562 6000 : || f_columns != 4)
1563 : {
1564 0 : throw std::runtime_error("hue() is only for 4x4 matrices at this time");
1565 : }
1566 :
1567 6000 : value_type const rot_cos(std::cos(static_cast<value_type>(h)));
1568 6000 : value_type const rot_sin(std::sin(static_cast<value_type>(h)));
1569 :
1570 6000 : if(std::fabs(f_luma_red - HDTV_LUMA_RED ) < 0.0001
1571 1000 : && std::fabs(f_luma_green - HDTV_LUMA_GREEN) < 0.0001
1572 1000 : && std::fabs(f_luma_blue - HDTV_LUMA_BLUE ) < 0.0001)
1573 : {
1574 : // this matrix makes use of the HDTV luma
1575 : //
1576 1000 : matrix<T, SIZE> hue_matrix(4, 4);
1577 :
1578 1000 : hue_matrix[0][0] = 0.85089741314769186 * rot_cos + 0.39419567713872435 * rot_sin + 0.14910258685230815;
1579 1000 : hue_matrix[0][1] = -0.14910258685230816 * rot_cos + 0.97154594632835023 * rot_sin + 0.14910258685230815;
1580 1000 : hue_matrix[0][2] = -0.14910258685230816 * rot_cos - 0.18315459205090151 * rot_sin + 0.14910258685230815;
1581 :
1582 1000 : hue_matrix[1][0] = -0.08406523610970199 * rot_cos - 0.93399661436972614 * rot_sin + 0.08406523610970204;
1583 1000 : hue_matrix[1][1] = 0.91593476389029794 * rot_cos - 0.35664634518010058 * rot_sin + 0.08406523610970204;
1584 1000 : hue_matrix[1][2] = -0.08406523610970201 * rot_cos + 0.22070392400952521 * rot_sin + 0.08406523610970204;
1585 :
1586 1000 : hue_matrix[2][0] = -0.76683217703798975 * rot_cos + 0.53980093723100184 * rot_sin + 0.76683217703798978;
1587 1000 : hue_matrix[2][1] = -0.76683217703798980 * rot_cos - 0.61489960114824952 * rot_sin + 0.76683217703798978;
1588 1000 : hue_matrix[2][2] = 0.23316782296201015 * rot_cos - 0.03754933195862373 * rot_sin + 0.76683217703798978;
1589 :
1590 : #ifdef _DEBUG
1591 1000 : f_last_hue_matrix = "hdtv";
1592 : #endif
1593 :
1594 1000 : return *this * hue_matrix;
1595 1000 : }
1596 :
1597 5000 : if(std::fabs(f_luma_red - LED_LUMA_RED ) < 0.0001
1598 1000 : && std::fabs(f_luma_green - LED_LUMA_GREEN) < 0.0001
1599 1000 : && std::fabs(f_luma_blue - LED_LUMA_BLUE ) < 0.0001)
1600 : {
1601 : // this matrix make use of the LED luma
1602 : //
1603 1000 : matrix<T, SIZE> hue_matrix(4, 4);
1604 :
1605 1000 : hue_matrix[0][0] = 0.86455583487454547 * rot_cos + 0.40703991394281032 * rot_sin + 0.13544416512545457;
1606 1000 : hue_matrix[0][1] = -0.13544416512545459 * rot_cos + 0.98439018313243625 * rot_sin + 0.13544416512545460;
1607 1000 : hue_matrix[0][2] = -0.13544416512545459 * rot_cos - 0.17031035524681553 * rot_sin + 0.13544416512545460;
1608 :
1609 1000 : hue_matrix[1][0] = -0.07977101160856729 * rot_cos - 0.95224727296282565 * rot_sin + 0.07977101160856727;
1610 1000 : hue_matrix[1][1] = 0.92022898839143270 * rot_cos - 0.37489700377320009 * rot_sin + 0.07977101160856730;
1611 1000 : hue_matrix[1][2] = -0.07977101160856729 * rot_cos + 0.20245326541642572 * rot_sin + 0.07977101160856730;
1612 :
1613 1000 : hue_matrix[2][0] = -0.78478482326597805 * rot_cos + 0.54520735902001539 * rot_sin + 0.78478482326597820;
1614 1000 : hue_matrix[2][1] = -0.78478482326597812 * rot_cos - 0.60949317935923603 * rot_sin + 0.78478482326597832;
1615 1000 : hue_matrix[2][2] = 0.21521517673402187 * rot_cos - 0.03214291016961022 * rot_sin + 0.78478482326597832;
1616 :
1617 : #ifdef _DEBUG
1618 1000 : f_last_hue_matrix = "led";
1619 : #endif
1620 :
1621 1000 : return *this * hue_matrix;
1622 1000 : }
1623 :
1624 4000 : if(std::fabs(f_luma_red - CRT_LUMA_RED ) < 0.0001
1625 1000 : && std::fabs(f_luma_green - CRT_LUMA_GREEN) < 0.0001
1626 1000 : && std::fabs(f_luma_blue - CRT_LUMA_BLUE ) < 0.0001)
1627 : {
1628 : // this matrix make use of the CRT luma
1629 : //
1630 1000 : matrix<T, SIZE> hue_matrix(4, 4);
1631 :
1632 1000 : hue_matrix[0][0] = 0.943571345820976240 * rot_cos + 0.32589470021007605 * rot_sin + 0.056428654178995;
1633 1000 : hue_matrix[0][1] = -0.056428654178995265 * rot_cos + 0.90324496939967125 * rot_sin + 0.056428654178995;
1634 1000 : hue_matrix[0][2] = -0.056428654178995265 * rot_cos - 0.25145556897954369 * rot_sin + 0.056428654178995;
1635 :
1636 1000 : hue_matrix[1][0] = -0.189552583569840000 * rot_cos - 0.98010410586906000 * rot_sin + 0.189552583569860;
1637 1000 : hue_matrix[1][1] = 0.810447416430107000 * rot_cos - 0.40275383667945400 * rot_sin + 0.189552583569860;
1638 1000 : hue_matrix[1][2] = -0.189552583569853000 * rot_cos + 0.17459643251014600 * rot_sin + 0.189552583569860;
1639 :
1640 1000 : hue_matrix[2][0] = -0.754018762251120000 * rot_cos + 0.65420940565900000 * rot_sin + 0.754018762251160;
1641 1000 : hue_matrix[2][1] = -0.754018762251113000 * rot_cos - 0.50049113272020600 * rot_sin + 0.754018762251160;
1642 1000 : hue_matrix[2][2] = 0.245981237748847000 * rot_cos + 0.07685913646939400 * rot_sin + 0.754018762251160;
1643 :
1644 : #ifdef _DEBUG
1645 1000 : f_last_hue_matrix = "crt";
1646 : #endif
1647 :
1648 1000 : return *this * hue_matrix;
1649 1000 : }
1650 :
1651 3000 : if(std::fabs(f_luma_red - NTSC_LUMA_RED ) < 0.0001
1652 1000 : && std::fabs(f_luma_green - NTSC_LUMA_GREEN) < 0.0001
1653 1000 : && std::fabs(f_luma_blue - NTSC_LUMA_BLUE ) < 0.0001)
1654 : {
1655 : // this matrix make use of the NTSC luma
1656 : //
1657 1000 : matrix<T, SIZE> hue_matrix(4, 4);
1658 :
1659 1000 : hue_matrix[0][0] = 0.97667266520552899 * rot_cos + 0.35888772800180165 * rot_sin + 0.02332733479447109;
1660 1000 : hue_matrix[0][1] = -0.02332733479447109 * rot_cos + 0.93623799719142759 * rot_sin + 0.02332733479447109;
1661 1000 : hue_matrix[0][2] = -0.02332733479447108 * rot_cos - 0.21846254118782418 * rot_sin + 0.02332733479447109;
1662 :
1663 1000 : hue_matrix[1][0] = -0.17753044304672443 * rot_cos - 1.02526720325074270 * rot_sin + 0.17753044304672438;
1664 1000 : hue_matrix[1][1] = 0.82246955695327556 * rot_cos - 0.44791693406111712 * rot_sin + 0.17753044304672441;
1665 1000 : hue_matrix[1][2] = -0.17753044304672441 * rot_cos + 0.12943333512850867 * rot_sin + 0.17753044304672441;
1666 :
1667 1000 : hue_matrix[2][0] = -0.79914222215880441 * rot_cos + 0.66637947524894110 * rot_sin + 0.79914222215880448;
1668 1000 : hue_matrix[2][1] = -0.79914222215880448 * rot_cos - 0.48832106313031034 * rot_sin + 0.79914222215880459;
1669 1000 : hue_matrix[2][2] = 0.20085777784119549 * rot_cos + 0.08902920605931547 * rot_sin + 0.79914222215880459;
1670 :
1671 : #ifdef _DEBUG
1672 1000 : f_last_hue_matrix = "ntsc";
1673 : #endif
1674 :
1675 1000 : return *this * hue_matrix;
1676 1000 : }
1677 :
1678 2000 : if(std::fabs(f_luma_red - AVERAGE_LUMA_RED ) < 0.0001
1679 1000 : && std::fabs(f_luma_green - AVERAGE_LUMA_GREEN) < 0.0001
1680 1000 : && std::fabs(f_luma_blue - AVERAGE_LUMA_BLUE ) < 0.0001)
1681 : {
1682 : // this matrix make use of the average luma
1683 : // (in other words, it makes no luma correction at all)
1684 : //
1685 1000 : matrix<T, SIZE> hue_matrix(4, 4);
1686 :
1687 1000 : hue_matrix[0][0] = 1.88796748671567113 * rot_cos + 0.76774179094706859 * rot_sin - 0.88796748671567094;
1688 1000 : hue_matrix[0][1] = 0.88796748671567144 * rot_cos + 1.34509206013669466 * rot_sin - 0.88796748671567149;
1689 1000 : hue_matrix[0][2] = 0.88796748671567144 * rot_cos + 0.19039152175744295 * rot_sin - 0.88796748671567149;
1690 :
1691 1000 : hue_matrix[1][0] = -0.27909984885071244 * rot_cos - 2.01889870048836475 * rot_sin + 0.27909984885071226;
1692 1000 : hue_matrix[1][1] = 0.72090015114928749 * rot_cos - 1.44154843129873957 * rot_sin + 0.27909984885071243;
1693 1000 : hue_matrix[1][2] = -0.27909984885071243 * rot_cos - 0.86419816210911379 * rot_sin + 0.27909984885071243;
1694 :
1695 1000 : hue_matrix[2][0] = -1.60886763786495844 * rot_cos + 1.25115690954129627 * rot_sin + 1.60886763786495757;
1696 1000 : hue_matrix[2][1] = -1.60886763786495881 * rot_cos + 0.09645637116204509 * rot_sin + 1.60886763786495868;
1697 1000 : hue_matrix[2][2] = -0.60886763786495889 * rot_cos + 0.67380664035167087 * rot_sin + 1.60886763786495868;
1698 :
1699 : #ifdef _DEBUG
1700 1000 : f_last_hue_matrix = "average";
1701 : #endif
1702 :
1703 1000 : return *this * hue_matrix;
1704 1000 : }
1705 :
1706 : #ifdef _DEBUG
1707 1000 : f_last_hue_matrix = "dynamic";
1708 : #endif
1709 :
1710 : {
1711 :
1712 : // the full computation, it works, it's just slower than using
1713 : // a pre-calculated matrix
1714 :
1715 : // $R_r$ -- rotation around red axis (inverse rotation around X axis)
1716 : //
1717 1000 : matrix<T, SIZE> r_r(4, 4);
1718 1000 : value_type const inv_sqrt_2 = static_cast<value_type>(1) / std::sqrt(static_cast<value_type>(2));
1719 1000 : r_r[1][1] = inv_sqrt_2;
1720 1000 : r_r[1][2] = inv_sqrt_2;
1721 1000 : r_r[2][1] = -inv_sqrt_2;
1722 1000 : r_r[2][2] = inv_sqrt_2;
1723 :
1724 : //std::cerr << "R_r = " << r_r << "\n";
1725 :
1726 : // $R_g$ -- rotation around green axis (inverse rotation around Y axis)
1727 : //
1728 1000 : matrix<T, SIZE> r_g(4, 4);
1729 1000 : value_type const inv_sqrt_3 = static_cast<value_type>(1) / std::sqrt(static_cast<value_type>(3));
1730 1000 : value_type const sqrt_2_over_sqrt_3 = std::sqrt(static_cast<value_type>(2)) / std::sqrt(static_cast<value_type>(3));
1731 1000 : r_g[0][0] = sqrt_2_over_sqrt_3;
1732 1000 : r_g[0][2] = inv_sqrt_3;
1733 1000 : r_g[2][0] = -inv_sqrt_3;
1734 1000 : r_g[2][2] = sqrt_2_over_sqrt_3;
1735 :
1736 : //std::cerr << "R_g = " << r_g << "\n";
1737 :
1738 : // $R_{rg}$ -- the product or $R_r$ and $R_g$
1739 : //
1740 1000 : matrix<T, SIZE> r_rg(r_r * r_g);
1741 :
1742 : //std::cerr << "R_rg = " << r_rg << "\n";
1743 :
1744 : // Luminance Vector
1745 : //
1746 1000 : matrix<T, SIZE> w(get_luma_vector());
1747 : //w[0][0] = RED_WEIGHT;
1748 : //w[1][0] = GREEN_WEIGHT;
1749 : //w[2][0] = BLUE_WEIGHT;
1750 : //w[3][0] = 0;
1751 :
1752 : //std::cerr << "w = " << w << "\n";
1753 :
1754 1000 : matrix<T, SIZE> l(r_rg * w);
1755 :
1756 : //std::cerr << "l = " << l << "\n";
1757 :
1758 1000 : matrix<T, SIZE> s(4, 4);
1759 1000 : s[0][2] = l[0][0] / l[2][0];
1760 1000 : s[1][2] = l[1][0] / l[2][0];
1761 :
1762 : //std::cerr << "s = " << s << "\n";
1763 :
1764 : //std::cerr << "s / s = " << (s/s) << "\n";
1765 :
1766 : //std::cerr << "M_rg * s = " << (r_rg * s) << "\n";
1767 :
1768 1000 : matrix<T, SIZE> p(r_rg);
1769 1000 : p *= s;
1770 :
1771 : // Rotate blue (rotation around Z axis)
1772 : //
1773 1000 : matrix<T, SIZE> r_b(4, 4);
1774 1000 : r_b[0][0] = rot_cos;
1775 1000 : r_b[0][1] = rot_sin;
1776 1000 : r_b[1][0] = -rot_sin;
1777 1000 : r_b[1][1] = rot_cos;
1778 :
1779 : //std::cerr << "r_b = " << r_b << "\n";
1780 :
1781 : //matrix<T, SIZE> ident(4,4);
1782 : //std::cerr
1783 : // << "---------------------------------------\n"
1784 : // << "r_rg = " << r_rg
1785 : // << "\nr_g * r_r = " << (r_g * r_r)
1786 : // << "\nr_rg^-1 = " << (ident/r_rg)
1787 : // << "\n(1/r_g/r_r) = " << (ident/r_g/r_r) << "\n"
1788 : // << "\np = " << p << "\n"
1789 : // << "\np^-1 = " << (ident/p) << "\n"
1790 : // << "---------------------------------------\n";
1791 :
1792 : // $H = R_r R_g S R_b S^{-1} R_g^{-1} R_r^{-1}$
1793 : //
1794 1000 : return *this * p * r_b / p;
1795 :
1796 : // others to test further
1797 : //return r_rg * s * r_b / s / r_rg;
1798 : //return r_rg * s * r_b / s / r_g / r_r;
1799 : //return r_r * r_g * r_b / r_g / r_r;
1800 1000 : }
1801 : }
1802 :
1803 : #ifdef _DEBUG
1804 6000 : std::string get_last_hue_matrix()
1805 : {
1806 6000 : return f_last_hue_matrix;
1807 : }
1808 : #endif
1809 :
1810 : /** \brief Retrieve the current luma vector.
1811 : *
1812 : * By default the luma vector is set to the HDTV weights.
1813 : * This may not be desirable, though. If you would prefer to
1814 : * use NTSC (?!) or some other weights, call the
1815 : * set_luma_vector() function with the correct weights.
1816 : *
1817 : * \note
1818 : * This is often referenced as the luminance, which is not quite
1819 : * correct. What we offer here are luma weights. See:
1820 : * https://en.wikipedia.org/wiki/Luma_%28video%29 for additional
1821 : * information.
1822 : *
1823 : * You can access to the red, green, and blue weights as:
1824 : *
1825 : * \code
1826 : * matrix<T, SIZE> luma = m.get_luma_vector();
1827 : * matrix::value_type red = luma[0][0];
1828 : * matrix::value_type green = luma[1][0];
1829 : * matrix::value_type blue = luma[2][0];
1830 : * \endcode
1831 : *
1832 : * This matrix can directly be used against a 4x4 matrix.
1833 : *
1834 : * The 4th parameter is set to zero.
1835 : *
1836 : * \return A 4x1 matrix with the luma weights.
1837 : *
1838 : * \sa set_luma_vector()
1839 : */
1840 7000 : matrix<T, SIZE> get_luma_vector() const
1841 : {
1842 7000 : matrix<T, SIZE> luma(4, 1);
1843 7000 : luma[0][0] = f_luma_red;
1844 7000 : luma[1][0] = f_luma_green;
1845 7000 : luma[2][0] = f_luma_blue;
1846 7000 : return luma;
1847 0 : }
1848 :
1849 : /** \brief Change the luma vector.
1850 : *
1851 : * This function sets the luminance vector to the three weights
1852 : * you are passing here.
1853 : *
1854 : * The current luminance vector can be retrieved as a 4x1 matrix using
1855 : * the get_luma_vector().
1856 : *
1857 : * The recommended values are the following predefined weights:
1858 : *
1859 : * \li HDTV_RED_WEIGHT, HDTV_GREEN_WEIGHT, HDTV_BLUE_WEIGHT
1860 : * \li LED_RED_WEIGHT, LED_GREEN_WEIGHT, LED_BLUE_WEIGHT
1861 : * \li CRT_RED_WEIGHT, CRT_GREEN_WEIGHT, CRT_BLUE_WEIGHT
1862 : * \li NTSC_RED_WEIGHT, NTSC_GREEN_WEIGHT, NTSC_BLUE_WEIGHT
1863 : * \li AVERAGE_RED_WEIGHT, AVERAGE_GREEN_WEIGHT, AVERAGE_BLUE_WEIGHT
1864 : *
1865 : * The HDTV weights are those used by default if you do not call the
1866 : * set_luminance_vector() with different or even your own weights.
1867 : *
1868 : * \param[in] red_weight The luma red weight.
1869 : * \param[in] green_weight The luma green weight.
1870 : * \param[in] blue_weight The luma blue weight.
1871 : */
1872 12 : void set_luma_vector(value_type red_weight, value_type green_weight, value_type blue_weight)
1873 : {
1874 : // save the user defined weights
1875 : //
1876 12 : f_luma_red = red_weight;
1877 12 : f_luma_green = green_weight;
1878 12 : f_luma_blue = blue_weight;
1879 12 : }
1880 :
1881 : std::string to_string() const
1882 : {
1883 : std::stringstream s;
1884 :
1885 : s.precision(17);
1886 :
1887 : s << "[";
1888 : for(size_type j(0); j < f_rows; ++j)
1889 : {
1890 : s << std::endl << " [";
1891 : if(f_columns > 0)
1892 : {
1893 : s << f_vector[0 + j * f_columns];
1894 : for(size_type i(1); i < f_columns; ++i)
1895 : {
1896 : s << ", " << f_vector[i + j * f_columns];
1897 : }
1898 : }
1899 : s << "]";
1900 : }
1901 : s << std::endl << "]" << std::endl;
1902 :
1903 : return s.str();
1904 : }
1905 :
1906 : private:
1907 : friend element_ref;
1908 : friend row_ref;
1909 : friend const_row_ref;
1910 :
1911 : size_type f_rows = 0;
1912 : size_type f_columns = 0;
1913 : std::vector<T> f_vector = std::vector<T>();
1914 : value_type f_luma_red = HDTV_LUMA_RED;
1915 : value_type f_luma_green = HDTV_LUMA_GREEN;
1916 : value_type f_luma_blue = HDTV_LUMA_BLUE;
1917 : #ifdef _DEBUG
1918 : mutable std::string f_last_hue_matrix = std::string();
1919 : #endif
1920 : };
1921 :
1922 :
1923 : } // namespace snapdev
1924 :
1925 :
1926 :
1927 : /** \brief Output a matrix to a basic_ostream.
1928 : *
1929 : * This function allows one to print out a matrix. The function attempts
1930 : * to properly format the matrix in order to make it readable.
1931 : *
1932 : * \param[in] out The output stream where the matrix gets written.
1933 : * \param[in] m The actual matrix that is to be printed.
1934 : *
1935 : * \return A reference to the basic_ostream object.
1936 : */
1937 : template<class E, class S, class T, class SIZE>
1938 : std::basic_ostream<E, S> & operator << (std::basic_ostream<E, S> & out, snapdev::matrix<T, SIZE> const & m)
1939 : {
1940 : // write to a string buffer first
1941 : //
1942 : std::basic_ostringstream<E, S, std::allocator<E> > s;
1943 :
1944 : // setup the string output like the out stream
1945 : //
1946 : s.flags(out.flags());
1947 : s.imbue(out.getloc());
1948 : s.precision(out.precision());
1949 :
1950 : // output the matrix
1951 : //
1952 : s << "[";
1953 : for(typename snapdev::matrix<T, SIZE>::size_type j(0); j < m.rows(); ++j)
1954 : {
1955 : s << std::endl << " [";
1956 : if(m.columns() > 0)
1957 : {
1958 : s << static_cast<T>(m[j][0]);
1959 : for(typename snapdev::matrix<T, SIZE>::size_type i(1); i < m.columns(); ++i)
1960 : {
1961 : s << ", " << static_cast<T>(m[j][i]);
1962 : }
1963 : }
1964 : s << "]";
1965 : }
1966 : s << std::endl << "]" << std::endl;
1967 :
1968 : // buffer is ready, display in output in one go
1969 : //
1970 : return out << s.str();
1971 : }
1972 :
1973 :
1974 : // file in ve folder (matrix.c)
1975 : // http://www.graficaobscura.com/matrix/index.html
1976 : // https://ncalculators.com/matrix/3x3-matrix-multiplication-calculator.htm
1977 : // /home/alexis/m2osw/sources/freeware/libx3d/utilities/src/fast_matrix.c++
1978 :
1979 : // vim: ts=4 sw=4 et
|