1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2010-2013 Francois Beaune, Jupiter Jazz Limited
9 // Copyright (c) 2014-2018 Francois Beaune, The appleseedhq Organization
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a copy
12 // of this software and associated documentation files (the "Software"), to deal
13 // in the Software without restriction, including without limitation the rights
14 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 // copies of the Software, and to permit persons to whom the Software is
16 // furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included in
19 // all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 // THE SOFTWARE.
28 //
29 
30 // Interface header.
31 #include "analysis.h"
32 
33 // appleseed.foundation headers.
34 #include "foundation/image/canvasproperties.h"
35 #include "foundation/image/color.h"
36 #include "foundation/image/colorspace.h"
37 #include "foundation/image/image.h"
38 #include "foundation/image/tile.h"
39 #include "foundation/math/scalar.h"
40 
41 // Standard headers.
42 #include <cassert>
43 #include <cmath>
44 #include <cstddef>
45 
46 using namespace std;
47 
48 namespace foundation
49 {
50 
51 namespace
52 {
accumulate_luminance(const Tile & tile,double & accumulated_luminance,size_t & relevant_pixel_count)53     void accumulate_luminance(
54         const Tile&     tile,
55         double&         accumulated_luminance,
56         size_t&         relevant_pixel_count)
57     {
58         assert(tile.get_channel_count() == 4);
59 
60         const size_t tile_width = tile.get_width();
61         const size_t tile_height = tile.get_height();
62 
63         for (size_t y = 0; y < tile_height; ++y)
64         {
65             for (size_t x = 0; x < tile_width; ++x)
66             {
67                 // Fetch the pixel color; assume linear RGBA.
68                 Color4f linear_rgba;
69                 tile.get_pixel(x, y, linear_rgba);
70 
71                 // Extract the RGB part (ignore the alpha channel).
72                 const Color3f linear_rgb = linear_rgba.rgb();
73 
74                 // Skip pixels containing NaN values.
75                 if (has_nan(linear_rgb))
76                     continue;
77 
78                 // Compute the Rec. 709 relative luminance of this pixel.
79                 const float lum = luminance(clamp_low(linear_rgb, 0.0f));
80 
81                 // It should no longer be possible to have NaN at this point.
82                 assert(lum == lum);
83 
84                 accumulated_luminance += static_cast<double>(lum);
85                 ++relevant_pixel_count;
86             }
87         }
88     }
89 
accumulate_luminance(const Image & image,double & accumulated_luminance,size_t & relevant_pixel_count)90     void accumulate_luminance(
91         const Image&    image,
92         double&         accumulated_luminance,
93         size_t&         relevant_pixel_count)
94     {
95         accumulated_luminance = 0.0;
96         relevant_pixel_count = 0;
97 
98         const CanvasProperties& props = image.properties();
99 
100         for (size_t ty = 0; ty < props.m_tile_count_y; ++ty)
101         {
102             for (size_t tx = 0; tx < props.m_tile_count_x; ++tx)
103             {
104                 const Tile& tile = image.tile(tx, ty);
105                 accumulate_luminance(tile, accumulated_luminance, relevant_pixel_count);
106             }
107         }
108     }
109 }
110 
compute_average_luminance(const Image & image)111 double compute_average_luminance(const Image& image)
112 {
113     double accumulated_luminance;
114     size_t relevant_pixel_count;
115 
116     accumulate_luminance(image, accumulated_luminance, relevant_pixel_count);
117 
118     return relevant_pixel_count > 0
119         ? accumulated_luminance / relevant_pixel_count
120         : 0.0;
121 }
122 
are_images_compatible(const Image & image1,const Image & image2)123 bool are_images_compatible(const Image& image1, const Image& image2)
124 {
125     const CanvasProperties& props1 = image1.properties();
126     const CanvasProperties& props2 = image2.properties();
127 
128     return
129         props1.m_canvas_width == props2.m_canvas_width &&
130         props1.m_canvas_height == props2.m_canvas_height;
131 }
132 
compute_rms_deviation(const Image & image1,const Image & image2)133 double compute_rms_deviation(const Image& image1, const Image& image2)
134 {
135     if (!are_images_compatible(image1, image2))
136         throw ExceptionIncompatibleImages();
137 
138     const CanvasProperties& props = image1.properties();
139     double mse = 0.0;   // mean square error
140 
141     for (size_t y = 0; y < props.m_canvas_height; ++y)
142     {
143         for (size_t x = 0; x < props.m_canvas_width; ++x)
144         {
145             Color3f color1;
146             image1.get_pixel(x, y, color1);
147 
148             Color3f color2;
149             image2.get_pixel(x, y, color2);
150 
151             mse += square_distance(color1, color2);
152         }
153     }
154 
155     mse /= props.m_pixel_count * 3.0;
156 
157     return sqrt(mse);
158 }
159 
160 }   // namespace foundation
161