1 /*
2 * Copyright (C) 2000-2020 the xine project
3 *
4 * This file is part of xine, a free video player.
5 *
6 * xine is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * xine is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
19 *
20 * Contains common code to calculate video scaling parameters.
21 * In short, it will map frame dimensions to screen/window size.
22 * Takes into account aspect ratio correction and zooming.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <stdio.h>
30 #include <string.h>
31 #include <math.h>
32
33 #define LOG_MODULE "vo_scale"
34 #define LOG_VERBOSE
35 /*
36 #define LOG
37 */
38
39 #include <xine/xine_internal.h>
40 #include <xine/vo_scale.h>
41
42 /*
43 * convert delivered height/width to ideal width/height
44 * taking into account aspect ratio and zoom factor
45 */
46
_x_vo_scale_compute_ideal_size(vo_scale_t * this)47 void _x_vo_scale_compute_ideal_size (vo_scale_t *this) {
48
49 if (this->scaling_disabled & ~1) {
50
51 this->video_pixel_aspect = (this->scaling_disabled & 1) ? 1.0 : this->gui_pixel_aspect;
52
53 } else {
54 double image_ratio, desired_ratio;
55
56 /*
57 * aspect ratio
58 */
59
60 image_ratio = (double) (this->delivered_width - (this->crop_left + this->crop_right)) /
61 (double) (this->delivered_height - (this->crop_top + this->crop_bottom));
62
63 switch (this->user_ratio) {
64 case XINE_VO_ASPECT_AUTO:
65 desired_ratio = this->delivered_ratio;
66 break;
67 case XINE_VO_ASPECT_ANAMORPHIC:
68 desired_ratio = 16.0 / 9.0;
69 break;
70 case XINE_VO_ASPECT_DVB:
71 desired_ratio = 2.0 / 1.0;
72 break;
73 case XINE_VO_ASPECT_SQUARE:
74 desired_ratio = image_ratio;
75 break;
76 case XINE_VO_ASPECT_4_3:
77 default:
78 desired_ratio = 4.0 / 3.0;
79 }
80
81 this->video_pixel_aspect = desired_ratio / image_ratio;
82
83 _x_assert(this->gui_pixel_aspect != 0.0);
84
85 /* dont scale just for tiny pixel aspect shift */
86 if (this->scaling_disabled & 1) {
87 if (fabs (this->video_pixel_aspect - 1.0) < 0.01)
88 this->video_pixel_aspect = 1.0;
89 } else {
90 if (fabs (this->video_pixel_aspect / this->gui_pixel_aspect - 1.0) < 0.01)
91 this->video_pixel_aspect = this->gui_pixel_aspect;
92 }
93
94 #if 0
95
96 /* onefield_xv divide by 2 the number of lines */
97 if (this->deinterlace_enabled
98 && (this->deinterlace_method == DEINTERLACE_ONEFIELDXV)
99 && (this->cur_frame->format == XINE_IMGFMT_YV12)) {
100 this->displayed_height = this->displayed_height / 2;
101 this->displayed_yoffset = this->displayed_yoffset / 2;
102 }
103 #endif
104 }
105 }
106
107
108 /*
109 * make ideal width/height "fit" into the gui
110 */
111
_x_vo_scale_compute_output_size(vo_scale_t * this)112 void _x_vo_scale_compute_output_size (vo_scale_t *this) {
113
114 int cropped_width, cropped_height;
115
116 cropped_width = this->delivered_width - (this->crop_left + this->crop_right);
117 cropped_height = this->delivered_height - (this->crop_top + this->crop_bottom);
118
119
120 if (this->scaling_disabled & ~1) {
121
122 this->output_width = cropped_width;
123 this->output_height = cropped_height;
124 this->displayed_width = cropped_width;
125 this->displayed_height = cropped_height;
126
127 } else {
128 int sw, sh;
129 double aspect;
130
131 aspect = this->video_pixel_aspect;
132 if (!(this->scaling_disabled & 1))
133 aspect /= this->gui_pixel_aspect;
134
135 sw = (double)(cropped_width * this->gui_height) * aspect / (double)cropped_height + 0.5;
136 sh = (double)(cropped_height * this->gui_width) / ((double)cropped_width * aspect) + 0.5;
137
138 if ( this->support_zoom ) {
139
140 /* zoom behaviour:
141 * - window size never changes due zooming
142 * - output image shall be increased whenever there are
143 * black borders to use.
144 * - exceding zoom shall be accounted by reducing displayed image.
145 */
146 if ((this->gui_width - sw) < (this->gui_height - sh)) {
147 int zh;
148 this->output_width = this->gui_width;
149 this->displayed_width = (double)cropped_width / this->zoom_factor_x + 0.5;
150
151 this->output_height = sh;
152 zh = (double)this->output_height * this->zoom_factor_y + 0.5;
153 if( zh <= this->gui_height ) {
154 this->displayed_height = cropped_height;
155 this->output_height = zh;
156 } else {
157 this->displayed_height = (double)cropped_height *
158 this->gui_height / this->output_height / this->zoom_factor_y + 0.5;
159 this->output_height = this->gui_height;
160 }
161 } else {
162 int zw;
163 this->output_height = this->gui_height;
164 this->displayed_height = (double)cropped_height / this->zoom_factor_y + 0.5;
165
166 this->output_width = sw;
167 zw = (double)this->output_width * this->zoom_factor_x + 0.5;
168 if( zw <= this->gui_width ) {
169 this->displayed_width = cropped_width;
170 this->output_width = zw;
171 } else {
172 this->displayed_width = (double)cropped_width *
173 this->gui_width / this->output_width / this->zoom_factor_x + 0.5;
174 this->output_width = this->gui_width;
175 }
176 }
177
178 } else {
179 if ((this->gui_width - sw) < (this->gui_height - sh)) {
180 this->output_width = this->gui_width;
181 this->output_height = sh;
182 } else {
183 this->output_width = sw;
184 this->output_height = this->gui_height;
185 }
186 this->displayed_width = cropped_width;
187 this->displayed_height = cropped_height;
188 }
189 }
190
191 /* make sure displayed values are sane, that is, we are not trying to display
192 * something outside the delivered image. may happen when zoom < 100%.
193 */
194 if( this->displayed_width > this->delivered_width ) {
195 int w;
196 w = this->output_width * this->delivered_width;
197 w += this->displayed_width >> 1;
198 w /= this->displayed_width;
199 this->output_width = w;
200 this->displayed_width = this->delivered_width;
201 }
202 if( this->displayed_height > this->delivered_height ) {
203 int h;
204 h = this->output_height * this->delivered_height;
205 h += this->displayed_height >> 1;
206 h /= this->displayed_height;
207 this->output_height = h;
208 this->displayed_height = this->delivered_height;
209 }
210
211 this->output_xoffset =
212 (this->gui_width - this->output_width) * this->output_horizontal_position + this->gui_x;
213 this->output_yoffset =
214 (this->gui_height - this->output_height) * this->output_vertical_position + this->gui_y;
215
216 this->displayed_xoffset = ((cropped_width - this->displayed_width) / 2) + this->crop_left;
217 this->displayed_yoffset = ((cropped_height - this->displayed_height) / 2) + this->crop_top;
218
219 lprintf ("frame source %d x %d (%d x %d) => screen output %d x %d\n",
220 this->delivered_width, this->delivered_height,
221 this->displayed_width, this->displayed_height,
222 this->output_width, this->output_height);
223
224 /* calculate borders */
225 if (this->output_height < this->gui_height) {
226 /* top */
227 this->border[0].x = 0;
228 this->border[0].y = 0;
229 this->border[0].w = this->gui_width;
230 this->border[0].h = this->output_yoffset;
231 /* bottom */
232 this->border[1].x = 0;
233 this->border[1].y = this->output_yoffset + this->output_height;
234 this->border[1].w = this->gui_width;
235 this->border[1].h = this->gui_height - this->border[1].y;
236 } else {
237 /* no top/bottom borders */
238 this->border[0].w = this->border[0].h = 0;
239 this->border[1].w = this->border[1].h = 0;
240 }
241
242 if (this->output_width < this->gui_width) {
243 /* left */
244 this->border[2].x = 0;
245 this->border[2].y = 0;
246 this->border[2].w = this->output_xoffset;
247 this->border[2].h = this->gui_height;
248 /* right */
249 this->border[3].x = this->output_xoffset + this->output_width;
250 this->border[3].y = 0;
251 this->border[3].w = this->gui_width - this->border[3].x;
252 this->border[3].h = this->gui_height;
253 } else {
254 /* no left/right borders */
255 this->border[2].w = this->border[2].h = 0;
256 this->border[3].w = this->border[3].h = 0;
257 }
258 }
259
260 /*
261 * return true if a redraw is needed due resizing, zooming,
262 * aspect ratio changing, etc.
263 */
264
_x_vo_scale_redraw_needed(vo_scale_t * this)265 int _x_vo_scale_redraw_needed (vo_scale_t *this) {
266 int gui_x, gui_y, gui_width, gui_height, gui_win_x, gui_win_y;
267 double gui_pixel_aspect, video_pixel_aspect;
268 int ret = 0;
269
270 _x_assert(this->frame_output_cb);
271 if ( ! this->frame_output_cb )
272 return 0;
273
274 video_pixel_aspect = this->video_pixel_aspect;
275 /* Nasty: tweaking GUI into different output window size will work from 2nd attempt on only... */
276 if ( this->scaling_disabled & 1 )
277 video_pixel_aspect *= this->gui_pixel_aspect;
278
279 this->frame_output_cb (this->user_data,
280 this->delivered_width - (this->crop_left + this->crop_right),
281 this->delivered_height - (this->crop_top + this->crop_bottom),
282 video_pixel_aspect,
283 &gui_x, &gui_y, &gui_width, &gui_height,
284 &gui_pixel_aspect, &gui_win_x, &gui_win_y );
285
286 if ( (gui_x != this->gui_x) || (gui_y != this->gui_y)
287 || (gui_width != this->gui_width) || (gui_height != this->gui_height)
288 || (gui_pixel_aspect != this->gui_pixel_aspect)
289 || (gui_win_x != this->gui_win_x) || (gui_win_y != this->gui_win_y) ) {
290
291 this->gui_x = gui_x;
292 this->gui_y = gui_y;
293 this->gui_width = gui_width;
294 this->gui_height = gui_height;
295 this->gui_win_x = gui_win_x;
296 this->gui_win_y = gui_win_y;
297 this->gui_pixel_aspect = gui_pixel_aspect;
298
299 ret = 1;
300 }
301 else
302 ret = this->force_redraw;
303
304 this->force_redraw = 0;
305 return ret;
306 }
307
308 /*
309 *
310 */
311
_x_vo_scale_translate_gui2video(vo_scale_t * this,int x,int y,int * vid_x,int * vid_y)312 void _x_vo_scale_translate_gui2video(vo_scale_t *this,
313 int x, int y,
314 int *vid_x, int *vid_y) {
315
316 if (this->output_width > 0 && this->output_height > 0) {
317 /*
318 * 1.
319 * the driver may center a small output area inside a larger
320 * gui area. This is the case in fullscreen mode, where we often
321 * have black borders on the top/bottom/left/right side.
322 */
323 x -= this->output_xoffset;
324 y -= this->output_yoffset;
325
326 /*
327 * 2.
328 * the driver scales the delivered area into an output area.
329 * translate output area coordianates into the delivered area
330 * coordiantes.
331 */
332
333 x = x * this->displayed_width / this->output_width + this->displayed_xoffset;
334 y = y * this->displayed_height / this->output_height + this->displayed_yoffset;
335 }
336
337 *vid_x = x;
338 *vid_y = y;
339 }
340
341
_x_vo_scale_map(vo_scale_t * this,vo_scale_map_t * map)342 vo_scale_map_res_t _x_vo_scale_map (vo_scale_t *this, vo_scale_map_t *map) {
343 double fx, fy, t;
344 int vw, vh, ax, ay;
345
346 if (!this || !map)
347 return VO_SCALE_MAP_WRONG_ARGS;
348
349 if ((this->displayed_width <= 0) || (this->displayed_height <= 0))
350 return VO_SCALE_MAP_ERROR;
351
352 vw = this->delivered_width - (this->crop_left + this->crop_right);
353 vh = this->delivered_height - (this->crop_top + this->crop_bottom);
354
355 if ((map->out.x1 <= 0) || (map->out.y1 <= 0)) {
356 map->out.x1 = vw;
357 map->out.y1 = vh;
358 if ((map->out.x1 <= 0) || (map->out.y1 <= 0))
359 return VO_SCALE_MAP_ERROR;
360 }
361
362 t = (this->output_width == this->displayed_width) ? 1.0 : (double)this->output_width / (double)this->displayed_width;
363 fx = (map->out.x1 == vw) ? 1.0 : (double)vw / (double)map->out.x1;
364 ax = fx * (this->output_xoffset - (t * this->displayed_xoffset));
365 fx *= t;
366
367 t = (this->output_height == this->displayed_height) ? 1.0 : (double)this->output_height / (double)this->displayed_height;
368 fy = (map->out.y1 == vh) ? 1.0 : (double)vh / (double)map->out.y1;
369 ay = fy * (this->output_yoffset - (t * this->displayed_yoffset));
370 fy *= t;
371
372 map->out.x1 = ax + (fx * (map->out.x0 + map->in.x1));
373 map->out.x0 = ax + (fx * map->out.x0);
374 map->out.y1 = ay + (fy * (map->out.y0 + map->in.y1));
375 map->out.y0 = ay + (fy * map->out.y0);
376
377 map->in.x0 = 0;
378 ax = this->output_xoffset;
379 if (map->out.x0 < ax) {
380 map->in.x0 = (ax - map->out.x0) / fx;
381 if (map->in.x0 >= map->in.x1)
382 return VO_SCALE_MAP_OUTSIDE;
383 map->out.x0 = ax;
384 }
385 map->in.y0 = 0;
386 ay = this->output_yoffset;
387 if (map->out.y0 < ay) {
388 map->in.y0 = (ay - map->out.y0) / fy;
389 if (map->in.y0 >= map->in.y1)
390 return VO_SCALE_MAP_OUTSIDE;
391 map->out.y0 = ay;
392 }
393
394 ax = this->output_xoffset + this->output_width;
395 if (map->out.x1 > ax) {
396 map->in.x1 -= (map->out.x1 - ax) / fx;
397 if (map->in.x1 <= map->in.x0)
398 return VO_SCALE_MAP_OUTSIDE;
399 map->out.x1 = ax;
400 }
401 ay = this->output_yoffset + this->output_height;
402 if (map->out.y1 > ay) {
403 map->in.y1 -= (map->out.y1 - ay) / fy;
404 if (map->in.y1 <= map->in.y0)
405 return VO_SCALE_MAP_OUTSIDE;
406 map->out.y1 = ay;
407 }
408
409 return VO_SCALE_MAP_OK;
410 }
411
412 /*/
413 * @brief Table for description of a given ratio code.
414 *
415 * @note changing the size of the elements of the array will break
416 * ABI, so please don't do that unless you absolutely can't continue
417 * with the current size.
418 */
419 const char _x_vo_scale_aspect_ratio_name_table[][8] = {
420 "auto", /* XINE_VO_ASPECT_AUTO */
421 "square", /* XINE_VO_ASPECT_SQUARE */
422 "4:3", /* XINE_VO_ASPECT_4_3 */
423 "16:9", /* XINE_VO_ASPECT_ANAMORPHIC */
424 "2:1", /* XINE_VO_ASPECT_DVB */
425 "unknown" /* All the rest */
426 };
427
428 /*
429 * config callbacks
430 */
vo_scale_horizontal_pos_changed(void * data,xine_cfg_entry_t * entry)431 static void vo_scale_horizontal_pos_changed(void *data, xine_cfg_entry_t *entry) {
432 vo_scale_t *this = (vo_scale_t *)data;
433
434 this->output_horizontal_position = entry->num_value / 100.0;
435 this->force_redraw = 1;
436 }
437
vo_scale_vertical_pos_changed(void * data,xine_cfg_entry_t * entry)438 static void vo_scale_vertical_pos_changed(void *data, xine_cfg_entry_t *entry) {
439 vo_scale_t *this = (vo_scale_t *)data;
440
441 this->output_vertical_position = entry->num_value / 100.0;
442 this->force_redraw = 1;
443 }
444
vo_scale_disable_scaling_changed(void * data,xine_cfg_entry_t * entry)445 static void vo_scale_disable_scaling_changed(void *data, xine_cfg_entry_t *entry) {
446 vo_scale_t *this = (vo_scale_t *)data;
447
448 this->scaling_disabled &= ~2;
449 this->scaling_disabled |= (entry->num_value & 1) << 1;
450 this->force_redraw = 1;
451 }
452
vo_scale_square_pixels_changed(void * data,xine_cfg_entry_t * entry)453 static void vo_scale_square_pixels_changed(void *data, xine_cfg_entry_t *entry) {
454 vo_scale_t *this = (vo_scale_t *)data;
455
456 this->scaling_disabled &= ~1;
457 this->scaling_disabled |= entry->num_value & 1;
458 this->force_redraw = 1;
459 }
460
461 /*
462 * initialize rescaling struct
463 */
464
_x_vo_scale_cleanup(vo_scale_t * self,config_values_t * config)465 void _x_vo_scale_cleanup(vo_scale_t *self, config_values_t *config) {
466 config->unregister_callbacks (config, NULL, NULL, self, sizeof (*self));
467 }
468
_x_vo_scale_init(vo_scale_t * this,int support_zoom,int scaling_disabled,config_values_t * config)469 void _x_vo_scale_init(vo_scale_t *this, int support_zoom, int scaling_disabled,
470 config_values_t *config ) {
471
472 memset( this, 0, sizeof(vo_scale_t) );
473 this->support_zoom = support_zoom;
474 this->force_redraw = 1;
475 this->zoom_factor_x = 1.0;
476 this->zoom_factor_y = 1.0;
477 this->gui_pixel_aspect = 1.0;
478 this->user_ratio = XINE_VO_ASPECT_AUTO;
479 this->delivered_ratio = 0.0;
480
481 this->crop_left = 0;
482 this->crop_right = 0;
483 this->crop_top = 0;
484 this->crop_bottom = 0;
485
486 this->output_horizontal_position =
487 config->register_range(config, "video.output.horizontal_position", 50, 0, 100,
488 _("horizontal image position in the output window"),
489 _("If the video window's horizontal size is bigger than the actual image "
490 "to show, you can adjust the position where the image will be placed.\n"
491 "The position is given as a percentage, so a value of 50 means \"in the "
492 "middle\", while 0 means \"at the very left\" and 100 \"at the very right\"."),
493 10, vo_scale_horizontal_pos_changed, this) / 100.0;
494 this->output_vertical_position =
495 config->register_range(config, "video.output.vertical_position", 50, 0, 100,
496 _("vertical image position in the output window"),
497 _("If the video window's vertical size is bigger than the actual image "
498 "to show, you can adjust the position where the image will be placed.\n"
499 "The position is given as a percentage, so a value of 50 means \"in the "
500 "middle\", while 0 means \"at the top\" and 100 \"at the bottom\"."),
501 10, vo_scale_vertical_pos_changed, this) / 100.0;
502 this->scaling_disabled = scaling_disabled << 2;
503 this->scaling_disabled |=
504 config->register_bool(config, "video.output.disable_scaling", 0,
505 _("disable all video scaling"),
506 _("If you want the video image to be always shown at its original resolution, "
507 "you can disable all image scaling here.\n"
508 "This of course means that the image will no longer adapt to the size of the "
509 "video window and that videos with a pixel aspect ratio other than 1:1, like "
510 "anamorphic DVDs, will be shown distorted. But on the other hand, with some "
511 "video output drivers like XShm, where the image scaling is not hardware "
512 "accelerated, this can dramatically reduce CPU usage."),
513 10, vo_scale_disable_scaling_changed, this) << 1;
514 this->scaling_disabled |=
515 config->register_bool(config, "video.output.square_pixels", 0,
516 _("treat screen pixels as exactly square"),
517 _("Many screens have \"only\" almost square pixels, like 94x93 dpi.\n"
518 "This little deviation is important for true size graphics applications.\n"
519 "For video, however, it often just means unnecessary black bars and less "
520 "sharpness.\n"),
521 10, vo_scale_square_pixels_changed, this);
522 }
523
524