Coverage Report

Created: 2025-06-24 07:38

/src/mpv/video/out/aspect.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv 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 Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
/* Stuff for correct aspect scaling. */
19
#include "aspect.h"
20
#include "math.h"
21
#include "vo.h"
22
#include "common/msg.h"
23
#include "options/options.h"
24
#include "video/mp_image.h"
25
26
#include "vo.h"
27
#include "sub/osd.h"
28
29
static void aspect_calc_panscan(struct mp_vo_opts *opts,
30
                                int w, int h, int d_w, int d_h, int unscaled,
31
                                int window_w, int window_h, double monitor_par,
32
                                int *out_w, int *out_h)
33
69
{
34
69
    int fwidth = window_w;
35
69
    int fheight = (float)window_w / d_w * d_h / monitor_par;
36
69
    if (fheight > window_h || fheight < h) {
37
67
        int tmpw = (float)window_h / d_h * d_w * monitor_par;
38
67
        if (tmpw <= window_w) {
39
9
            fheight = window_h;
40
9
            fwidth = tmpw;
41
9
        }
42
67
    }
43
44
69
    int vo_panscan_area = window_h - fheight;
45
69
    double f_w = fwidth / (double)MPMAX(fheight, 1);
46
69
    double f_h = 1;
47
69
    if (vo_panscan_area == 0) {
48
11
        vo_panscan_area = window_w - fwidth;
49
11
        f_w = 1;
50
11
        f_h = fheight / (double)MPMAX(fwidth, 1);
51
11
    }
52
53
69
    if (unscaled) {
54
0
        vo_panscan_area = 0;
55
0
        if (unscaled != 2 || (d_w <= window_w && d_h <= window_h)) {
56
0
            fwidth = d_w * monitor_par;
57
0
            fheight = d_h;
58
0
        }
59
0
    }
60
61
69
    *out_w = fwidth + vo_panscan_area * opts->panscan * f_w;
62
69
    *out_h = fheight + vo_panscan_area * opts->panscan * f_h;
63
69
}
64
65
// Clamp [start, end) to range [0, size) with various fallbacks.
66
static void clamp_size(int size, int *start, int *end)
67
276
{
68
276
    *start = MPMAX(0, *start);
69
276
    *end = MPMIN(size, *end);
70
276
    if (*start >= *end) {
71
0
        *start = 0;
72
0
        *end = 1;
73
0
    }
74
276
}
75
76
static void src_dst_split_scaling(int src_size, int dst_size,
77
                                  int scaled_src_size,
78
                                  float zoom, float align, float pan, float scale,
79
                                  bool recenter,
80
                                  int *src_start, int *src_end,
81
                                  int *dst_start, int *dst_end,
82
                                  int *osd_margin_a, int *osd_margin_b)
83
138
{
84
138
    scaled_src_size *= powf(2, zoom) * scale;
85
138
    scaled_src_size = MPMAX(scaled_src_size, 1);
86
138
    if (recenter && dst_size >= scaled_src_size)
87
0
        align = 0;
88
138
    align = (align + 1) / 2;
89
90
138
    *dst_start = (dst_size - scaled_src_size) * align + pan * scaled_src_size;
91
138
    *dst_end = *dst_start + scaled_src_size;
92
93
    // Distance of screen frame to video
94
138
    *osd_margin_a = *dst_start;
95
138
    *osd_margin_b = dst_size - *dst_end;
96
97
    // Clip to screen
98
138
    int s_src = *src_end - *src_start;
99
138
    int s_dst = *dst_end - *dst_start;
100
138
    if (*dst_start < 0) {
101
0
        int border = -(*dst_start) * s_src / s_dst;
102
0
        *src_start += border;
103
0
        *dst_start = 0;
104
0
    }
105
138
    if (*dst_end > dst_size) {
106
0
        int border = (*dst_end - dst_size) * s_src / s_dst;
107
0
        *src_end -= border;
108
0
        *dst_end = dst_size;
109
0
    }
110
111
    // For sanity: avoid bothering VOs with corner cases
112
138
    clamp_size(src_size, src_start, src_end);
113
138
    clamp_size(dst_size, dst_start, dst_end);
114
138
}
115
116
static void calc_margin(float opts[2], int out[2], int size)
117
138
{
118
138
    out[0] = MPCLAMP((int)(opts[0] * size), 0, size);
119
138
    out[1] = MPCLAMP((int)(opts[1] * size), 0, size);
120
121
138
    if (out[0] + out[1] >= size) {
122
        // This case is not really supported. Show an error by 1 pixel.
123
0
        out[0] = 0;
124
0
        out[1] = MPMAX(0, size - 1);
125
0
    }
126
138
}
127
128
void mp_get_src_dst_rects(struct mp_log *log, struct mp_vo_opts *opts,
129
                          int vo_caps, struct mp_image_params *video,
130
                          int window_w, int window_h, double monitor_par,
131
                          struct mp_rect *out_src,
132
                          struct mp_rect *out_dst,
133
                          struct mp_osd_res *out_osd)
134
69
{
135
69
    int src_w = video->w;
136
69
    int src_h = video->h;
137
69
    int src_dw, src_dh;
138
139
69
    mp_image_params_get_dsize(video, &src_dw, &src_dh);
140
69
    window_w = MPMAX(1, window_w);
141
69
    window_h = MPMAX(1, window_h);
142
143
69
    int margin_x[2] = {0};
144
69
    int margin_y[2] = {0};
145
69
    if (opts->keepaspect) {
146
69
        calc_margin(opts->margin_x, margin_x, window_w);
147
69
        calc_margin(opts->margin_y, margin_y, window_h);
148
69
    }
149
150
69
    int vid_window_w = window_w - margin_x[0] - margin_x[1];
151
69
    int vid_window_h = window_h - margin_y[0] - margin_y[1];
152
153
69
    struct mp_rect dst = {0, 0, window_w, window_h};
154
69
    struct mp_rect src = {0, 0, src_w,    src_h};
155
69
    if (mp_image_crop_valid(video))
156
11
        src = video->crop;
157
158
69
    if (vo_caps & VO_CAP_ROTATE90) {
159
0
        if (video->rotate % 180 == 90) {
160
0
            MPSWAP(int, src_w, src_h);
161
0
            MPSWAP(int, src_dw, src_dh);
162
0
        }
163
0
        mp_rect_rotate(&src, src_w, src_h, video->rotate);
164
0
    }
165
166
69
    struct mp_osd_res osd = {
167
69
        .w = window_w,
168
69
        .h = window_h,
169
69
        .display_par = monitor_par,
170
69
    };
171
172
69
    if (opts->keepaspect) {
173
69
        int scaled_width, scaled_height;
174
69
        aspect_calc_panscan(opts, src_w, src_h, src_dw, src_dh, opts->unscaled,
175
69
                            vid_window_w, vid_window_h, monitor_par,
176
69
                            &scaled_width, &scaled_height);
177
69
        src_dst_split_scaling(src_w, vid_window_w, scaled_width,
178
69
                              opts->zoom, opts->align_x, opts->pan_x, opts->scale_x,
179
69
                              opts->recenter,
180
69
                              &src.x0, &src.x1, &dst.x0, &dst.x1,
181
69
                              &osd.ml, &osd.mr);
182
69
        src_dst_split_scaling(src_h, vid_window_h, scaled_height,
183
69
                              opts->zoom, opts->align_y, opts->pan_y, opts->scale_y,
184
69
                              opts->recenter,
185
69
                              &src.y0, &src.y1, &dst.y0, &dst.y1,
186
69
                              &osd.mt, &osd.mb);
187
69
    }
188
189
69
    dst.x0 += margin_x[0];
190
69
    dst.y0 += margin_y[0];
191
69
    dst.x1 += margin_x[0];
192
69
    dst.y1 += margin_y[0];
193
194
    // OSD really uses the full window, but was computed on the margin-cut
195
    // video sub-window. Correct it to the full window.
196
69
    osd.ml += margin_x[0];
197
69
    osd.mr += margin_x[1];
198
69
    osd.mt += margin_y[0];
199
69
    osd.mb += margin_y[1];
200
201
69
    *out_src = src;
202
69
    *out_dst = dst;
203
69
    *out_osd = osd;
204
205
69
    int sw = src.x1 - src.x0, sh = src.y1 - src.y0;
206
69
    int dw = dst.x1 - dst.x0, dh = dst.y1 - dst.y0;
207
208
69
    mp_verbose(log, "Window size: %dx%d (Borders: l=%d t=%d r=%d b=%d)\n",
209
69
               window_w, window_h,
210
69
               margin_x[0], margin_y[0], margin_x[1], margin_y[1]);
211
69
    mp_verbose(log, "Video source: %dx%d (%d:%d)\n",
212
69
               video->w, video->h, video->p_w, video->p_h);
213
69
    mp_verbose(log, "Video display: (%d, %d) %dx%d -> (%d, %d) %dx%d\n",
214
69
               src.x0, src.y0, sw, sh, dst.x0, dst.y0, dw, dh);
215
69
    mp_verbose(log, "Video scale: %f/%f\n",
216
69
               (double)dw / sw, (double)dh / sh);
217
69
    mp_verbose(log, "OSD borders: l=%d t=%d r=%d b=%d\n",
218
69
               osd.ml, osd.mt, osd.mr, osd.mb);
219
69
    mp_verbose(log, "Video borders: l=%d t=%d r=%d b=%d\n",
220
69
               dst.x0, dst.y0, window_w - dst.x1, window_h - dst.y1);
221
69
}