summaryrefslogtreecommitdiffstats
path: root/src/filter/sopsat/sopsat.cpp
blob: dc02b380512aea3d41a0652b6ef7b7c264e36e24 (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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
/* 
 * Copyright (C) 2010 Simon Andreas Eugster (simon.eu@gmail.com)
 * This file is a Frei0r plugin.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <math.h>
#include <stdlib.h>
#include "frei0r.hpp"
#include "frei0r_math.h"

/**
  This filter implements a standard way of color correction proposed by
  the American Society of Cinematographers: The Color Decision List, also
  known as the

     ASC CDL

  More information about the ASC CDL can be found on Wikipedia[1], and
  the current revision of the specification, including example code, can
  be obtained by sending a mail to asc-cdl at theasc dot com. (Really works.)

  The ASC CDL is a standard format for basic primary color correction (primary
  meaning affecting the whole image and not only selected parts). Even big
  editing systems use it :) This filter only obtains the values; Importing and
  exporting to one of the possible ASC CDL exchange files must be done elsewhere.

  Basically there are two stages in the correction:
  1. SOP correction for each channel separately
  2. Overall saturation correction
  All corrections work on [0,1], so the RGB(A) values need to be transposed
  from {0,...,255} to [0,1].
  1. SOP correction
     * Slope:   out = in * slope;   0 <= slope < \infty
     * Offset:  out = in + offset;  -\infty < offset < \infty
     * Power:   out = in^power;     0 < power < \infty
  2. Saturation
     * Luma:    Y = 0.2126 R + 0.7152 G + 0.0722 B (according to Rec. 709)
     * Forall channels:
                out = luma + sat * (in-luma)
  As the values may exceed 1 (or 0), they need to be clamped where necessary.


  [1] http://en.wikipedia.org/wiki/Color_Decision_List
 */

class SOPSat : public frei0r::filter
{

public:

    f0r_param_double rSlope;
    f0r_param_double gSlope;
    f0r_param_double bSlope;
    f0r_param_double aSlope;
    f0r_param_double rOffset;
    f0r_param_double gOffset;
    f0r_param_double bOffset;
    f0r_param_double aOffset;
    f0r_param_double rPower;
    f0r_param_double gPower;
    f0r_param_double bPower;
    f0r_param_double aPower;
    f0r_param_double saturation;

    SOPSat(unsigned int, unsigned int)
    {

        register_param(rSlope, "rSlope", "Slope of the red color component");
        register_param(gSlope, "gSlope", "Slope of the green color component");
        register_param(bSlope, "bSlope", "Slope of the blue color component");
        register_param(aSlope, "aSlope", "Slope of the alpha component");
        register_param(rOffset, "rOffset", "Offset of the red color component");
        register_param(gOffset, "gOffset", "Offset of the green color component");
        register_param(bOffset, "bOffset", "Offset of the blue color component");
        register_param(aOffset, "aOffset", "Offset of the alpha component");
        register_param(rPower, "rPower", "Power (Gamma) of the red color component");
        register_param(gPower, "gPower", "Power (Gamma) of the green color component");
        register_param(bPower, "bPower", "Power (Gamma) of the blue color component");
        register_param(aPower, "aPower", "Power (Gamma) of the alpha component");
        register_param(saturation, "saturation", "Overall saturation");
        rSlope = 1 / 20.;
        gSlope = 1 / 20.;
        bSlope = 1 / 20.;
        aSlope = 1 / 20.;
        rOffset = (0 - (-4)) / (double)(4 - (-4));
        gOffset = (0 - (-4)) / (double)(4 - (-4));
        bOffset = (0 - (-4)) / (double)(4 - (-4));
        aOffset = (0 - (-4)) / (double)(4 - (-4));
        rPower = 1 / 20.;
        gPower = 1 / 20.;
        bPower = 1 / 20.;
        aPower = 1 / 20.;
        saturation = 1 / 10.;

        // Pre-build the lookup table.
        // For 1080p, rendering a 5-second video took
        // * 37 s without the LUT
        // * 7  s with the LUT
        // * 5  s without any effect applied (plain rendering).
        // So the LUT brings about 15x speedup.
        m_lutR = (unsigned char *) malloc(256*sizeof(char));
        m_lutG = (unsigned char *) malloc(256*sizeof(char));
        m_lutB = (unsigned char *) malloc(256*sizeof(char));
        m_lutA = (unsigned char *) malloc(256*sizeof(char));
        updateLUT();

    }
    
    ~SOPSat()
    {
        free(m_lutR);
        free(m_lutG);
        free(m_lutB);
        free(m_lutA);
    }

    virtual void update()
    {
        // Rebuild the lookup table in case the prarameters have changed.
        updateLUT();

        unsigned char *pixel = (unsigned char *) in;
        unsigned char *dest = (unsigned char *) out;

        if (fabs(m_sat-1) < 0.001) {
            // Calculating the saturation is expensive. So first check whether
            // we really need to do it.
            // Keeping the if/else outside of the loop gives a little speed gain.
            // Worth the duplicate code, as only 4 lines so far :)

            for (unsigned int i = 0; i < size; i++) {
                *dest++ = m_lutR[*pixel++];
                *dest++ = m_lutG[*pixel++];
                *dest++ = m_lutB[*pixel++];
                *dest++ = m_lutA[*pixel++];
            }
        } else {
            double luma;
            for (unsigned int i = 0; i < size; i++) {
                luma =   0.2126 * m_lutR[*(pixel+0)]
                       + 0.7152 * m_lutG[*(pixel+1)]
                       + 0.0722 * m_lutB[*(pixel+2)];
                *dest++ = CLAMP0255(luma + m_sat*(m_lutR[*pixel++]-luma));
                *dest++ = CLAMP0255(luma + m_sat*(m_lutG[*pixel++]-luma));
                *dest++ = CLAMP0255(luma + m_sat*(m_lutB[*pixel++]-luma));
                *dest++ = m_lutA[*pixel++];
            }
        }
    }

private:
    unsigned char *m_lutR;
    unsigned char *m_lutG;
    unsigned char *m_lutB;
    unsigned char *m_lutA;

    double m_sat;

    void updateLUT() {
        double rS = rSlope * 20;
        double gS = gSlope * 20;
        double bS = bSlope * 20;
        double aS = aSlope * 20;

        double rO = -4 + rOffset * (4 - (-4));
        double gO = -4 + gOffset * (4 - (-4));
        double bO = -4 + bOffset * (4 - (-4));
        double aO = -4 + aOffset * (4 - (-4));

        double rP = rPower * 20;
        double gP = gPower * 20;
        double bP = bPower * 20;
        double aP = aPower * 20;

        m_sat = saturation * 10;

        for (int i = 0; i < 256; i++) {
            // above0 avoids overflows for negative numbers.
            m_lutR[i] = CLAMP0255((int) (pow(above0((float)i/255 * rS + rO), rP)*255));
            m_lutG[i] = CLAMP0255((int) (pow(above0((float)i/255 * gS + gO), gP)*255));
            m_lutB[i] = CLAMP0255((int) (pow(above0((float)i/255 * bS + bO), bP)*255));
            m_lutA[i] = CLAMP0255((int) (pow(above0((float)i/255 * aS + aO), aP)*255));
        }
    }

    double above0(double f) {
        if (f < 0) {
            return 0;
        } else {
            return f;
        }
    }
    
};



frei0r::construct<SOPSat> plugin("SOP/Sat",
                "Slope/Offset/Power and Saturation color corrections according to the ASC CDL (Color Decision List)",
                "Simon A. Eugster (Granjow)",
                0,2,
                F0R_COLOR_MODEL_RGBA8888);