use bilinear rather then bicubic scaling because bicubic blurs too much.
[blender.git] / source / blender / compositor / operations / COM_ScaleOperation.cpp
1 /*
2  * Copyright 2011, Blender Foundation.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  *
18  * Contributor:
19  *              Jeroen Bakker
20  *              Monique Dewanchand
21  */
22
23 #include "COM_ScaleOperation.h"
24
25 #define USE_FORCE_BILINEAR
26 /* XXX - ignore input and use default from old compositor,
27  * could become an option like the transform node - campbell
28  *
29  * note: use bilinear because bicubic makes fuzzy even when not scaling at all (1:1)
30  */
31
32 ScaleOperation::ScaleOperation() : NodeOperation()
33 {
34         this->addInputSocket(COM_DT_COLOR);
35         this->addInputSocket(COM_DT_VALUE);
36         this->addInputSocket(COM_DT_VALUE);
37         this->addOutputSocket(COM_DT_COLOR);
38         this->setResolutionInputSocketIndex(0);
39         this->m_inputOperation = NULL;
40         this->m_inputXOperation = NULL;
41         this->m_inputYOperation = NULL;
42 }
43 void ScaleOperation::initExecution()
44 {
45         this->m_inputOperation = this->getInputSocketReader(0);
46         this->m_inputXOperation = this->getInputSocketReader(1);
47         this->m_inputYOperation = this->getInputSocketReader(2);
48         this->m_centerX = this->getWidth() / 2.0;
49         this->m_centerY = this->getHeight() / 2.0;
50 }
51
52 void ScaleOperation::deinitExecution()
53 {
54         this->m_inputOperation = NULL;
55         this->m_inputXOperation = NULL;
56         this->m_inputYOperation = NULL;
57 }
58
59
60 void ScaleOperation::executePixel(float *color, float x, float y, PixelSampler sampler)
61 {
62 #ifdef USE_FORCE_BILINEAR
63         sampler = COM_PS_BILINEAR;
64 #endif
65
66         float scaleX[4];
67         float scaleY[4];
68
69         this->m_inputXOperation->read(scaleX, x, y, sampler);
70         this->m_inputYOperation->read(scaleY, x, y, sampler);
71
72         const float scx = scaleX[0];
73         const float scy = scaleY[0];
74
75         float nx = this->m_centerX + (x - this->m_centerX) / scx;
76         float ny = this->m_centerY + (y - this->m_centerY) / scy;
77         this->m_inputOperation->read(color, nx, ny, sampler);
78 }
79
80 bool ScaleOperation::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output)
81 {
82         rcti newInput;
83         float scaleX[4];
84         float scaleY[4];
85
86         this->m_inputXOperation->read(scaleX, 0, 0, COM_PS_NEAREST);
87         this->m_inputYOperation->read(scaleY, 0, 0, COM_PS_NEAREST);
88
89         const float scx = scaleX[0];
90         const float scy = scaleY[0];
91
92         newInput.xmax = this->m_centerX + (input->xmax - this->m_centerX) / scx;
93         newInput.xmin = this->m_centerX + (input->xmin - this->m_centerX) / scx;
94         newInput.ymax = this->m_centerY + (input->ymax - this->m_centerY) / scy;
95         newInput.ymin = this->m_centerY + (input->ymin - this->m_centerY) / scy;
96
97         return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
98 }
99
100
101 // SCALE ABSOLUTE
102 ScaleAbsoluteOperation::ScaleAbsoluteOperation() : NodeOperation()
103 {
104         this->addInputSocket(COM_DT_COLOR);
105         this->addInputSocket(COM_DT_VALUE);
106         this->addInputSocket(COM_DT_VALUE);
107         this->addOutputSocket(COM_DT_COLOR);
108         this->setResolutionInputSocketIndex(0);
109         this->m_inputOperation = NULL;
110         this->m_inputXOperation = NULL;
111         this->m_inputYOperation = NULL;
112 }
113 void ScaleAbsoluteOperation::initExecution()
114 {
115         this->m_inputOperation = this->getInputSocketReader(0);
116         this->m_inputXOperation = this->getInputSocketReader(1);
117         this->m_inputYOperation = this->getInputSocketReader(2);
118         this->m_centerX = this->getWidth() / 2.0;
119         this->m_centerY = this->getHeight() / 2.0;
120 }
121
122 void ScaleAbsoluteOperation::deinitExecution()
123 {
124         this->m_inputOperation = NULL;
125         this->m_inputXOperation = NULL;
126         this->m_inputYOperation = NULL;
127 }
128
129
130 void ScaleAbsoluteOperation::executePixel(float *color, float x, float y, PixelSampler sampler)
131 {
132 #ifdef USE_FORCE_BILINEAR
133         sampler = COM_PS_BILINEAR;
134 #endif
135
136         float scaleX[4];
137         float scaleY[4];
138
139         this->m_inputXOperation->read(scaleX, x, y, sampler);
140         this->m_inputYOperation->read(scaleY, x, y, sampler);
141
142         const float scx = scaleX[0]; // target absolute scale
143         const float scy = scaleY[0]; // target absolute scale
144
145         const float width = this->getWidth();
146         const float height = this->getHeight();
147         //div
148         float relativeXScale = scx / width;
149         float relativeYScale = scy / height;
150
151         float nx = this->m_centerX + (x - this->m_centerX) / relativeXScale;
152         float ny = this->m_centerY + (y - this->m_centerY) / relativeYScale;
153
154         this->m_inputOperation->read(color, nx, ny, sampler);
155 }
156
157 bool ScaleAbsoluteOperation::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output)
158 {
159         rcti newInput;
160         float scaleX[4];
161         float scaleY[4];
162
163         this->m_inputXOperation->read(scaleX, 0, 0, COM_PS_NEAREST);
164         this->m_inputYOperation->read(scaleY, 0, 0, COM_PS_NEAREST);
165
166         const float scx = scaleX[0];
167         const float scy = scaleY[0];
168         const float width = this->getWidth();
169         const float height = this->getHeight();
170         //div
171         float relateveXScale = scx / width;
172         float relateveYScale = scy / height;
173
174         newInput.xmax = this->m_centerX + (input->xmax - this->m_centerX) / relateveXScale;
175         newInput.xmin = this->m_centerX + (input->xmin - this->m_centerX) / relateveXScale;
176         newInput.ymax = this->m_centerY + (input->ymax - this->m_centerY) / relateveYScale;
177         newInput.ymin = this->m_centerY + (input->ymin - this->m_centerY) / relateveYScale;
178
179         return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
180 }
181
182
183 // Absolute fixed siez
184 ScaleFixedSizeOperation::ScaleFixedSizeOperation() : NodeOperation()
185 {
186         this->addInputSocket(COM_DT_COLOR, COM_SC_NO_RESIZE);
187         this->addOutputSocket(COM_DT_COLOR);
188         this->setResolutionInputSocketIndex(0);
189         this->m_inputOperation = NULL;
190         this->m_is_offset = false;
191 }
192 void ScaleFixedSizeOperation::initExecution()
193 {
194         this->m_inputOperation = this->getInputSocketReader(0);
195         this->m_relX = this->m_inputOperation->getWidth() / (float)this->m_newWidth;
196         this->m_relY = this->m_inputOperation->getHeight() / (float)this->m_newHeight;
197
198
199         /* *** all the options below are for a fairly special case - camera framing *** */
200         if (this->m_offsetX != 0.0f || this->m_offsetY != 0.0f) {
201                 this->m_is_offset = true;
202
203                 if (this->m_newWidth > this->m_newHeight) {
204                         this->m_offsetX *= this->m_newWidth;
205                         this->m_offsetY *= this->m_newWidth;
206                 }
207                 else {
208                         this->m_offsetX *= this->m_newHeight;
209                         this->m_offsetY *= this->m_newHeight;
210                 }
211         }
212
213         if (this->m_is_aspect) {
214                 /* apply aspect from clip */
215                 const float w_src = this->m_inputOperation->getWidth();
216                 const float h_src = this->m_inputOperation->getHeight();
217
218                 /* destination aspect is already applied from the camera frame */
219                 const float w_dst = this->m_newWidth;
220                 const float h_dst = this->m_newHeight;
221
222                 const float asp_src = w_src / h_src;
223                 const float asp_dst = w_dst / h_dst;
224
225                 if (fabsf(asp_src - asp_dst) >= FLT_EPSILON) {
226                         if ((asp_src > asp_dst) == (this->m_is_crop == true)) {
227                                 /* fit X */
228                                 const float div = asp_src / asp_dst;
229                                 this->m_relX /= div;
230                                 this->m_offsetX += ((w_src - (w_src * div)) / (w_src / w_dst)) / 2.0f;
231                         }
232                         else {
233                                 /* fit Y */
234                                 const float div = asp_dst / asp_src;
235                                 this->m_relY /= div;
236                                 this->m_offsetY += ((h_src - (h_src * div)) / (h_src / h_dst)) / 2.0f;
237                         }
238
239                         this->m_is_offset = true;
240                 }
241         }
242         /* *** end framing options *** */
243 }
244
245 void ScaleFixedSizeOperation::deinitExecution()
246 {
247         this->m_inputOperation = NULL;
248 }
249
250
251 void ScaleFixedSizeOperation::executePixel(float *color, float x, float y, PixelSampler sampler)
252 {
253 #ifdef USE_FORCE_BILINEAR
254         sampler = COM_PS_BILINEAR;
255 #endif
256
257         if (this->m_is_offset) {
258                 float nx = ((x - this->m_offsetX) * this->m_relX);
259                 float ny = ((y - this->m_offsetY) * this->m_relY);
260                 this->m_inputOperation->read(color, nx, ny, sampler);
261         }
262         else {
263                 this->m_inputOperation->read(color, x * this->m_relX, y * this->m_relY, sampler);
264         }
265 }
266
267 bool ScaleFixedSizeOperation::determineDependingAreaOfInterest(rcti *input, ReadBufferOperation *readOperation, rcti *output)
268 {
269         rcti newInput;
270
271         newInput.xmax = input->xmax * this->m_relX;
272         newInput.xmin = input->xmin * this->m_relX;
273         newInput.ymax = input->ymax * this->m_relY;
274         newInput.ymin = input->ymin * this->m_relY;
275
276         return NodeOperation::determineDependingAreaOfInterest(&newInput, readOperation, output);
277 }
278
279 void ScaleFixedSizeOperation::determineResolution(unsigned int resolution[], unsigned int preferredResolution[])
280 {
281         unsigned int nr[2];
282         nr[0] = this->m_newWidth;
283         nr[1] = this->m_newHeight;
284         NodeOperation::determineResolution(resolution, nr);
285         resolution[0] = this->m_newWidth;
286         resolution[1] = this->m_newHeight;
287 }