aboutsummaryrefslogtreecommitdiff
path: root/helium/quicksmooth.cc
blob: 346d0e74165123e9ac3846188b90103b3d077a94 (plain)
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
// Copyright 2006 Google Inc.
// All Rights Reserved.
// Author: <renn@google.com> (Marius Renn)
//
#include "color.h"
#include "debugging.h"
#include "image.h"
#include "quicksmooth.h"

using namespace helium;

Color QuickSmooth::Sum2(const Color& a, const Color& b) {
  return MakeColor(Red(a) + Red(b),
                   Green(a) + Green(b),
                   Blue(a) + Blue(b));
}

Color QuickSmooth::Sum3(const Color& a, const Color& b, const Color& c) {
  return MakeColor(Red(a) + Red(b) + Red(c),
                   Green(a) + Green(b) + Green(c),
                   Blue(a) + Blue(b) + Blue(c));
}

Color QuickSmooth::Sum4(const Color& a, 
                        const Color& b, 
                        const Color& c, 
                        const Color& d) {
  return MakeColor(Red(a) + Red(b) + Red(c) + Red(d),
                   Green(a) + Green(b) + Green(c) + Green(d),
                   Blue(a) + Blue(b) + Blue(c) + Blue(d));
}

// Smoothes the given image by using a convolution kernel of:
//
//  1/9  1/9  1/9
//  1/9  1/9  1/9
//  1/9  1/9  1/9
//
// This is actually done  in two steps. First all values are divided by
// 9, then in the second pass, all values in the kernel range are summed
// up. 
// The smoothing is done almost in-place, and old values are reused where
// possible. For any pixel, that is not on an edge of the image, the 
// following situation holds:
//
//  r1   r2    r3
//  c1   d1    x1
//  c2   d2    x2,
//
// where (r1 + r2 + r3), (c1 + c2), (d1 + d2) have already been calculated. 
// Only x1 and x2 need to be fetched from memory. (Actually, 3 values must be 
// fetched from memory: x1, x2, and d1. This is due to the algorithm 
// storing only the sums (r1 + r2 + r3), (c1 + c2) and (d1 + d2), not
// the operands themselves. Since we need to store (r1' + r2' + r3') for 
// the next row, we need to  calculate the sum (c1 + d1 + x1). We avoid 
// having to lookup c1 again by caching the last d1). 
// 
// In terms of memory usage, this means we require an extra 3 values for the
// three column sums (c1 + c2), (d1 + d2), (x1 + x2), and an extra n values
// for the row sums at each horizontal offset, where n is the width of the 
// image.
//
// Special cases apply for the edges, which are not discussed here, as they
// can be easily deduced from the standard case.
void QuickSmooth::SumPixels(Image& image) {
  Color* img_ptr = image.Access(1, 1);
  unsigned row = image.width();
  Color last_value = *img_ptr;
  Color row_value = 0;
  
  Color cur_col[3];
  Color cur_row[image.width()];
  
  // First pixel of image
  cur_col[0] = Sum3(*(img_ptr - row - 1),
                    *(img_ptr - 1),
                    *(img_ptr + row - 1));

  cur_col[1] = Sum3(*(img_ptr - row),
                    *(img_ptr),
                    *(img_ptr + row));
                   
  cur_col[2] = Sum3(*(img_ptr - row + 1),
                    *(img_ptr + 1),
                    *(img_ptr + row + 1));
                   
  cur_row[0] = Sum3(*(img_ptr - 1),
                    *(img_ptr),
                    *(img_ptr + 1));
             
  *img_ptr = Sum3(cur_col[0], cur_col[1], cur_col[2]);
  ++img_ptr;
  
  uint8 c = 0;
  
  // First row
  for (unsigned x = 2; x < image.width() - 1; x++) {
    cur_col[c] = Sum3(*(img_ptr - row + 1),
                          *(img_ptr + 1),
                          *(img_ptr + row + 1));
            
    cur_row[x - 1] = Sum3(last_value,
                          *(img_ptr),
                          *(img_ptr + 1));
                       
    last_value = *img_ptr;
    
    *img_ptr = Sum3(cur_col[0], cur_col[1], cur_col[2]);
    
    ++img_ptr;
    c = (c == 2) ? 0 : c + 1;
  }
  img_ptr += 2;
  
  // Rest of image
  for (unsigned y = 2; y < image.height() - 1; y++) {
    last_value = *img_ptr;
    
    // First pixel in row
    cur_col[0] = Sum2(*(img_ptr - 1),
                      *(img_ptr + row - 1));
    cur_col[1] = Sum2(*(img_ptr),
                      *(img_ptr + row));
    cur_col[2] = Sum2(*(img_ptr + 1),
                      *(img_ptr + row + 1));     
    row_value = Sum3(*(img_ptr - 1), 
                     *(img_ptr),
                     *(img_ptr + 1)); 
              
    *img_ptr = Sum4(cur_col[0], cur_col[1], cur_col[2], cur_row[0]);

    cur_row[0] = row_value;
    ++img_ptr;
    c = 0;
    
    // All other pixels in row
    Color d1, x1, x2;
    for (unsigned x = 2; x < image.width() - 1; x++) {
      d1 = *(img_ptr);
      x1 = *(img_ptr + 1);
      x2 = *(img_ptr + row + 1);
      
      cur_col[c] = Sum2(x1, x2);
      row_value = Sum3(last_value, d1, x2);
      
      last_value = d1;
      
      *img_ptr = Sum4(cur_col[0], cur_col[1], cur_col[2], cur_row[x - 1]);
      
      cur_row[x - 1] = row_value;
      ++img_ptr;
      c = (c == 2) ? 0 : c + 1;
    }
    img_ptr += 2;
  }
}

void QuickSmooth::DividePixels(Image& image) {
  Color pixel;
  for (Color* img_ptr = image.data(); img_ptr < image.DataEnd(); img_ptr++) {
    pixel = *img_ptr;
    *img_ptr = MakeColor(Red(pixel) / 9, Green(pixel) / 9, Blue(pixel) / 9);
  }
}

void QuickSmooth::Smooth(Image& image) {
  DividePixels(image);
  SumPixels(image);
}