4 * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
5 * Copyright (C) 2012 Monty <monty@xiph.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 #include "overlayframe.h"
30 * New resampler code; replace the original somehwat blurry engine
31 * with a fairly standard kernel resampling core. This could be used
32 * for full affine transformation but only implements scale/translate.
33 * Mostly reuses the old blending macro code.
37 * 1) Pixels are points, not areas or squares.
39 * 2) To maintain the usual edge and scaling conventions, pixels are
40 * set inward from the image edge, eg, the left edge of an image is
41 * at pixel location x=-.5, not x=0. Although pixels are not
42 * squares, the usual way of stating this is 'the pixel is located
43 * at the center of its square'.
45 * 3) Because of 1 and 2, we must truncate and weight the kernel
46 * convolution at the edge of the input area. Otherwise, all
47 * resampled areas would be bordered by a transparency halo. E.g.
48 * in the old engine, upsampling HDV to 1920x1080 results in the
49 * left and right edges being partially transparent and underlying
50 * layers shining through.
52 * 4) The contribution of fractional pixels at the edges of input
53 * ranges are weighted according to the fraction. Note that the
54 * kernel weighting is adjusted, not the opacity. This is one
55 * exception to 'pixels have no area'.
57 * 5) The opacity of fractional pixels at the edges of the output
58 * range is adjusted according to the fraction. This is the other
59 * exception to 'pixels have no area'.
61 * Fractional alpha blending has been modified across the board from:
62 * output_alpha = input_alpha > output_alpha ? input_alpha : output_alpha;
64 * output_alpha = output_alpha + ((max - output_alpha) * input_alpha) / max;
67 /* Sinc needed for Lanczos kernel */
68 static float sinc(const float x)
70 if(fabsf(x) < TRANSFORM_MIN) return 1.0f;
76 * All resampling (except Nearest Neighbor) is performed via
77 * transformed 2D resampling kernels bult from 1D lookups.
79 OverlayKernel::OverlayKernel(int interpolation_type)
82 this->type = interpolation_type;
84 switch(interpolation_type)
88 lookup = new float[(n = TRANSFORM_SPP) + 1];
89 for (i = 0; i <= TRANSFORM_SPP; i++)
90 lookup[i] = (float)(TRANSFORM_SPP - i) / TRANSFORM_SPP;
93 /* Use a Catmull-Rom filter (not b-spline) */
96 lookup = new float[(n = 2 * TRANSFORM_SPP) + 1];
97 for(i = 0; i <= TRANSFORM_SPP; i++) {
98 float x = i / (float)TRANSFORM_SPP;
99 lookup[i] = 1.f - 2.5f * x * x + 1.5f * x * x * x;
101 for(; i <= 2 * TRANSFORM_SPP; i++) {
102 float x = i / (float)TRANSFORM_SPP;
103 lookup[i] = 2.f - 4.f * x + 2.5f * x * x - .5f * x * x * x;
109 lookup = new float[(n = 3 * TRANSFORM_SPP) + 1];
110 for (i = 0; i <= 3 * TRANSFORM_SPP; i++)
111 lookup[i] = sinc((float)i / TRANSFORM_SPP) *
112 sinc((float)i / TRANSFORM_SPP / 3.0f);
123 OverlayKernel::~OverlayKernel()
125 if(lookup) delete [] lookup;
128 OverlayFrame::OverlayFrame(int cpus)
134 memset(kernel, 0, sizeof(kernel));
138 OverlayFrame::~OverlayFrame()
140 if(temp_frame) delete temp_frame;
142 if(direct_engine) delete direct_engine;
143 if(nn_engine) delete nn_engine;
144 if(sample_engine) delete sample_engine;
146 if(kernel[NEAREST_NEIGHBOR]) delete kernel[NEAREST_NEIGHBOR];
147 if(kernel[BILINEAR]) delete kernel[BILINEAR];
148 if(kernel[BICUBIC]) delete kernel[BICUBIC];
149 if(kernel[LANCZOS]) delete kernel[LANCZOS];
152 static float epsilon_snap(float f)
154 return rintf(f * 1024) / 1024.;
157 int OverlayFrame::overlay(VFrame *output, VFrame *input,
158 float in_x1, float in_y1, float in_x2, float in_y2,
159 float out_x1, float out_y1, float out_x2, float out_y2,
160 float alpha, int mode, int interpolation_type)
162 in_x1 = epsilon_snap(in_x1);
163 in_x2 = epsilon_snap(in_x2);
164 in_y1 = epsilon_snap(in_y1);
165 in_y2 = epsilon_snap(in_y2);
166 out_x1 = epsilon_snap(out_x1);
167 out_x2 = epsilon_snap(out_x2);
168 out_y1 = epsilon_snap(out_y1);
169 out_y2 = epsilon_snap(out_y2);
171 if (isnan(in_x1) || isnan(in_x2) ||
172 isnan(in_y1) || isnan(in_y2) ||
173 isnan(out_x1) || isnan(out_x2) ||
174 isnan(out_y1) || isnan(out_y2)) return 1;
176 if( in_x2 <= in_x1 || in_y2 <= in_y1 ) return 1;
177 if( out_x2 <= out_x1 || out_y2 <= out_y1 ) return 1;
179 float xscale = (out_x2 - out_x1) / (in_x2 - in_x1);
180 float yscale = (out_y2 - out_y1) / (in_y2 - in_y1);
181 int in_w = input->get_w(), in_h = input->get_h();
182 int out_w = output->get_w(), out_h = output->get_h();
185 out_x1 -= in_x1 * xscale;
189 out_x2 -= (in_x2 - in_w) * xscale;
193 out_y1 -= in_y1 * yscale;
197 out_y2 -= (in_y2 - in_h) * yscale;
202 in_x1 -= out_x1 / xscale;
205 if( out_x2 > out_w ) {
206 in_x2 -= (out_x2 - out_w) / xscale;
210 in_y1 -= out_y1 / yscale;
213 if( out_y2 > out_h ) {
214 in_y2 -= (out_y2 - out_h) / yscale;
218 if( in_x1 < 0) in_x1 = 0;
219 if( in_y1 < 0) in_y1 = 0;
220 if( in_x2 > in_w ) in_x2 = in_w;
221 if( in_y2 > in_h ) in_y2 = in_h;
222 if( out_x1 < 0) out_x1 = 0;
223 if( out_y1 < 0) out_y1 = 0;
224 if( out_x2 > out_w ) out_x2 = out_w;
225 if( out_y2 > out_h ) out_y2 = out_h;
227 if( in_x2 <= in_x1 || in_y2 <= in_y1 ) return 1;
228 if( out_x2 <= out_x1 || out_y2 <= out_y1 ) return 1;
229 xscale = (out_x2 - out_x1) / (in_x2 - in_x1);
230 yscale = (out_y2 - out_y1) / (in_y2 - in_y1);
232 /* don't interpolate integer translations, or scale no-ops */
233 if(xscale == 1. && yscale == 1. &&
234 (int)in_x1 == in_x1 && (int)in_x2 == in_x2 &&
235 (int)in_y1 == in_y1 && (int)in_y2 == in_y2 &&
236 (int)out_x1 == out_x1 && (int)out_x2 == out_x2 &&
237 (int)out_y1 == out_y1 && (int)out_y2 == out_y2) {
238 if(!direct_engine) direct_engine = new DirectEngine(cpus);
240 direct_engine->output = output; direct_engine->input = input;
241 direct_engine->in_x1 = in_x1; direct_engine->in_y1 = in_y1;
242 direct_engine->out_x1 = out_x1; direct_engine->out_x2 = out_x2;
243 direct_engine->out_y1 = out_y1; direct_engine->out_y2 = out_y2;
244 direct_engine->alpha = alpha; direct_engine->mode = mode;
245 direct_engine->process_packages();
247 else if(interpolation_type == NEAREST_NEIGHBOR) {
248 if(!nn_engine) nn_engine = new NNEngine(cpus);
249 nn_engine->output = output; nn_engine->input = input;
250 nn_engine->in_x1 = in_x1; nn_engine->in_x2 = in_x2;
251 nn_engine->in_y1 = in_y1; nn_engine->in_y2 = in_y2;
252 nn_engine->out_x1 = out_x1; nn_engine->out_x2 = out_x2;
253 nn_engine->out_y1 = out_y1; nn_engine->out_y2 = out_y2;
254 nn_engine->alpha = alpha; nn_engine->mode = mode;
255 nn_engine->process_packages();
258 int xtype = BILINEAR;
259 int ytype = BILINEAR;
261 switch(interpolation_type)
263 case CUBIC_CUBIC: // Bicubic enlargement and reduction
264 xtype = ytype = BICUBIC;
266 case CUBIC_LINEAR: // Bicubic enlargement and bilinear reduction
267 xtype = xscale > 1. ? BICUBIC : BILINEAR;
268 ytype = yscale > 1. ? BICUBIC : BILINEAR;
270 case LINEAR_LINEAR: // Bilinear enlargement and bilinear reduction
271 xtype = ytype = BILINEAR;
273 case LANCZOS_LANCZOS: // Because we can
274 xtype = ytype = LANCZOS;
278 if(xscale == 1. && (int)in_x1 == in_x1 && (int)in_x2 == in_x2 &&
279 (int)out_x1 == out_x1 && (int)out_x2 == out_x2)
282 if(yscale == 1. && (int)in_y1 == in_y1 && (int)in_y2 == in_y2 &&
283 (int)out_y1 == out_y1 && (int)out_y2 == out_y2)
287 kernel[xtype] = new OverlayKernel(xtype);
289 kernel[ytype] = new OverlayKernel(ytype);
292 * horizontal and vertical are separately resampled. First we
293 * resample the input along X into a transposed, temporary frame,
294 * then resample/transpose the temporary space along X into the
295 * output. Fractional pixels along the edge are handled in the X
296 * direction of each step
298 // resampled dimension matches the transposed output space
299 float temp_y1 = out_x1 - floor(out_x1);
300 float temp_y2 = temp_y1 + (out_x2 - out_x1);
301 int temp_h = ceil(temp_y2);
303 // non-resampled dimension merely cropped
304 float temp_x1 = in_y1 - floor(in_y1);
305 float temp_x2 = temp_x1 + (in_y2 - in_y1);
306 int temp_w = ceil(temp_x2);
309 (temp_frame->get_color_model() != input->get_color_model() ||
310 temp_frame->get_w() != temp_w || temp_frame->get_h() != temp_h) ) {
316 temp_frame = new VFrame(0, -1, temp_w, temp_h,
317 input->get_color_model(), -1);
320 temp_frame->clear_frame();
322 if(!sample_engine) sample_engine = new SampleEngine(cpus);
324 sample_engine->output = temp_frame;
325 sample_engine->input = input;
326 sample_engine->kernel = kernel[xtype];
327 sample_engine->col_out1 = 0;
328 sample_engine->col_out2 = temp_w;
329 sample_engine->row_in = floor(in_y1);
331 sample_engine->in1 = in_x1;
332 sample_engine->in2 = in_x2;
333 sample_engine->out1 = temp_y1;
334 sample_engine->out2 = temp_y2;
335 sample_engine->alpha = 1.;
336 sample_engine->mode = TRANSFER_REPLACE;
337 sample_engine->process_packages();
339 sample_engine->output = output;
340 sample_engine->input = temp_frame;
341 sample_engine->kernel = kernel[ytype];
342 sample_engine->col_out1 = floor(out_x1);
343 sample_engine->col_out2 = ceil(out_x2);
344 sample_engine->row_in = 0;
346 sample_engine->in1 = temp_x1;
347 sample_engine->in2 = temp_x2;
348 sample_engine->out1 = out_y1;
349 sample_engine->out2 = out_y2;
350 sample_engine->alpha = alpha;
351 sample_engine->mode = mode;
352 sample_engine->process_packages();