vout_intf.c 43.6 KB
Newer Older
Gildas Bazin's avatar
Gildas Bazin committed
1 2 3
/*****************************************************************************
 * vout_intf.c : video output interface
 *****************************************************************************
4
 * Copyright (C) 2000-2007 the VideoLAN team
Gildas Bazin's avatar
Gildas Bazin committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * Authors: Gildas Bazin <gbazin@videolan.org>
 *
 * 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
Antoine Cellerier's avatar
Antoine Cellerier committed
20
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
Gildas Bazin's avatar
Gildas Bazin committed
21 22 23 24 25
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
26

27 28 29 30
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

31
#include <vlc_common.h>
32 33

#include <stdio.h>
Gildas Bazin's avatar
Gildas Bazin committed
34
#include <stdlib.h>                                                /* free() */
Filippo Carone's avatar
Filippo Carone committed
35
#include <sys/types.h>                                          /* opendir() */
36
#include <sys/stat.h>
Filippo Carone's avatar
Filippo Carone committed
37
#include <dirent.h>                                             /* opendir() */
38
#include <assert.h>
Gildas Bazin's avatar
Gildas Bazin committed
39

Clément Stenac's avatar
Clément Stenac committed
40
#include <vlc_interface.h>
41
#include <vlc_block.h>
Clément Stenac's avatar
Clément Stenac committed
42
#include <vlc_playlist.h>
Gildas Bazin's avatar
Gildas Bazin committed
43

Clément Stenac's avatar
Clément Stenac committed
44
#include <vlc_vout.h>
45
#include <vlc_window.h>
Clément Stenac's avatar
Clément Stenac committed
46 47
#include <vlc_image.h>
#include <vlc_osd.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
48
#include <vlc_charset.h>
49

Clément Stenac's avatar
Clément Stenac committed
50 51
#include <vlc_strings.h>
#include <vlc_charset.h>
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
52
#include "../libvlc.h"
53

Gildas Bazin's avatar
Gildas Bazin committed
54 55 56
/*****************************************************************************
 * Local prototypes
 *****************************************************************************/
57
static void InitWindowSize( vout_thread_t *, unsigned *, unsigned * );
Gildas Bazin's avatar
Gildas Bazin committed
58

59 60 61
/* Object variables callbacks */
static int ZoomCallback( vlc_object_t *, char const *,
                         vlc_value_t, vlc_value_t, void * );
62 63
static int CropCallback( vlc_object_t *, char const *,
                         vlc_value_t, vlc_value_t, void * );
64 65
static int AspectCallback( vlc_object_t *, char const *,
                           vlc_value_t, vlc_value_t, void * );
66 67
static int OnTopCallback( vlc_object_t *, char const *,
                          vlc_value_t, vlc_value_t, void * );
68 69
static int FullscreenCallback( vlc_object_t *, char const *,
                               vlc_value_t, vlc_value_t, void * );
70 71
static int SnapshotCallback( vlc_object_t *, char const *,
                             vlc_value_t, vlc_value_t, void * );
72 73 74 75 76 77 78

static int TitleShowCallback( vlc_object_t *, char const *,
                              vlc_value_t, vlc_value_t, void * );
static int TitleTimeoutCallback( vlc_object_t *, char const *,
                                 vlc_value_t, vlc_value_t, void * );
static int TitlePositionCallback( vlc_object_t *, char const *,
                                  vlc_value_t, vlc_value_t, void * );
79

Gildas Bazin's avatar
Gildas Bazin committed
80 81 82 83 84 85 86 87 88 89 90 91
/*****************************************************************************
 * vout_RequestWindow: Create/Get a video window if possible.
 *****************************************************************************
 * This function looks for the main interface and tries to request
 * a new video window. If it fails then the vout will still need to create the
 * window by itself.
 *****************************************************************************/
void *vout_RequestWindow( vout_thread_t *p_vout,
                          int *pi_x_hint, int *pi_y_hint,
                          unsigned int *pi_width_hint,
                          unsigned int *pi_height_hint )
{
92 93 94
    /* Small kludge */
    if( !var_Type( p_vout, "aspect-ratio" ) ) vout_IntfInit( p_vout );

Gildas Bazin's avatar
Gildas Bazin committed
95
    /* Get requested coordinates */
96 97
    *pi_x_hint = var_GetInteger( p_vout, "video-x" );
    *pi_y_hint = var_GetInteger( p_vout, "video-y" );
Gildas Bazin's avatar
Gildas Bazin committed
98 99 100 101 102

    *pi_width_hint = p_vout->i_window_width;
    *pi_height_hint = p_vout->i_window_height;

    /* Check whether someone provided us with a window ID */
103 104
    int drawable = var_CreateGetInteger( p_vout, "drawable" );
    if( drawable ) return (void *)(intptr_t)drawable;
Gildas Bazin's avatar
Gildas Bazin committed
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
    vout_window_t *wnd = vlc_custom_create (VLC_OBJECT(p_vout), sizeof (*wnd),
                                            VLC_OBJECT_GENERIC, "window");
    if (wnd == NULL)
        return NULL;

    wnd->vout = p_vout;
    wnd->width = *pi_width_hint;
    wnd->height = *pi_height_hint;
    wnd->pos_x = *pi_x_hint;
    wnd->pos_y = *pi_y_hint;
    vlc_object_attach (wnd, p_vout);

    wnd->module = module_Need (wnd, "vout window", 0, 0);
    if (wnd->module == NULL)
    {
        msg_Dbg (wnd, "no window provider available");
        vlc_object_release (wnd);
        return NULL;
    }
    p_vout->p_window = wnd;
    *pi_width_hint = wnd->width;
    *pi_height_hint = wnd->height;
    *pi_x_hint = wnd->pos_x;
    *pi_y_hint = wnd->pos_y;
    return wnd->handle;
Gildas Bazin's avatar
Gildas Bazin committed
131 132
}

133
void vout_ReleaseWindow( vout_thread_t *p_vout, void *dummy )
Gildas Bazin's avatar
Gildas Bazin committed
134
{
135 136 137 138 139 140 141 142 143 144 145
    vout_window_t *wnd = p_vout->p_window;

    if (wnd == NULL)
        return;
    p_vout->p_window = NULL;

    assert (wnd->module);
    module_Unneed (wnd, wnd->module);

    vlc_object_release (wnd);
    (void)dummy;
Gildas Bazin's avatar
Gildas Bazin committed
146
}
147

148
int vout_ControlWindow( vout_thread_t *p_vout, void *dummy,
149 150
                        int i_query, va_list args )
{
Rémi Duraffort's avatar
Rémi Duraffort committed
151
    (void)dummy;
152 153 154 155 156 157 158
    vout_window_t *wnd = p_vout->p_window;

    if (wnd == NULL)
        return VLC_EGENERIC;

    assert (wnd->control);
    return wnd->control (wnd, i_query, args);
159 160
}

161 162 163
/*****************************************************************************
 * vout_IntfInit: called during the vout creation to initialise misc things.
 *****************************************************************************/
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
static const struct
{
    double f_value;
    const char *psz_label;
} p_zoom_values[] = {
    { 0.25, N_("1:4 Quarter") },
    { 0.5, N_("1:2 Half") },
    { 1, N_("1:1 Original") },
    { 2, N_("2:1 Double") },
    { 0, NULL } };

static const struct
{
    const char *psz_value;
    const char *psz_label;
} p_crop_values[] = {
    { "", N_("Default") },
    { "16:10", "16:10" },
182
    { "16:9", "16:9" },
183
    { "185:100", "1.85:1" },
184 185 186
    { "221:100", "2.21:1" },
    { "235:100", "2.35:1" },
    { "239:100", "2.39:1" },
187 188 189 190
    { "5:3", "5:3" },
    { "4:3", "4:3" },
    { "5:4", "5:4" },
    { "1:1", "1:1" },
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 217 218 219 220 221 222 223 224 225 226 227 228 229 230
    { NULL, NULL } };

static const struct
{
    const char *psz_value;
    const char *psz_label;
} p_aspect_ratio_values[] = {
    { "", N_("Default") },
    { "1:1", "1:1" },
    { "4:3", "4:3" },
    { "16:9", "16:9" },
    { "16:10", "16:10" },
    { "221:100", "2.21:1" },
    { "5:4", "5:4" },
    { NULL, NULL } };

static void AddCustomRatios( vout_thread_t *p_vout, const char *psz_var,
                             char *psz_list )
{
    if( psz_list && *psz_list )
    {
        char *psz_cur = psz_list;
        char *psz_next;
        while( psz_cur && *psz_cur )
        {
            vlc_value_t val, text;
            psz_next = strchr( psz_cur, ',' );
            if( psz_next )
            {
                *psz_next = '\0';
                psz_next++;
            }
            val.psz_string = psz_cur;
            text.psz_string = psz_cur;
            var_Change( p_vout, psz_var, VLC_VAR_ADDCHOICE, &val, &text);
            psz_cur = psz_next;
        }
    }
}

231 232 233
void vout_IntfInit( vout_thread_t *p_vout )
{
    vlc_value_t val, text, old_val;
234
    bool b_force_par = false;
235
    char *psz_buf;
236
    int i;
237 238

    /* Create a few object variables we'll need later on */
239
    var_Create( p_vout, "snapshot-path", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
240
    var_Create( p_vout, "snapshot-prefix", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
241
    var_Create( p_vout, "snapshot-format", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
242
    var_Create( p_vout, "snapshot-preview", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
243 244 245 246
    var_Create( p_vout, "snapshot-sequential",
                VLC_VAR_BOOL | VLC_VAR_DOINHERIT );
    var_Create( p_vout, "snapshot-num", VLC_VAR_INTEGER );
    var_SetInteger( p_vout, "snapshot-num", 1 );
247 248
    var_Create( p_vout, "snapshot-width", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
    var_Create( p_vout, "snapshot-height", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
249

250 251
    var_Create( p_vout, "width", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
    var_Create( p_vout, "height", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
252
    p_vout->i_alignment = var_CreateGetInteger( p_vout, "align" );
253

254 255 256
    var_Create( p_vout, "video-x", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );
    var_Create( p_vout, "video-y", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );

257 258 259
    var_Create( p_vout, "mouse-hide-timeout",
                VLC_VAR_INTEGER | VLC_VAR_DOINHERIT );

260 261 262 263 264
    p_vout->b_title_show = var_CreateGetBool( p_vout, "video-title-show" );
    p_vout->i_title_timeout =
        (mtime_t)var_CreateGetInteger( p_vout, "video-title-timeout" );
    p_vout->i_title_position =
        var_CreateGetInteger( p_vout, "video-title-position" );
265

266 267 268
    var_AddCallback( p_vout, "video-title-show", TitleShowCallback, NULL );
    var_AddCallback( p_vout, "video-title-timeout", TitleTimeoutCallback, NULL );
    var_AddCallback( p_vout, "video-title-position", TitlePositionCallback, NULL );
269

270
    /* Zoom object var */
271 272 273 274 275 276 277
    var_Create( p_vout, "zoom", VLC_VAR_FLOAT | VLC_VAR_ISCOMMAND |
                VLC_VAR_HASCHOICE | VLC_VAR_DOINHERIT );

    text.psz_string = _("Zoom");
    var_Change( p_vout, "zoom", VLC_VAR_SETTEXT, &text, NULL );

    var_Get( p_vout, "zoom", &old_val );
278 279

    for( i = 0; p_zoom_values[i].f_value; i++ )
280
    {
281 282 283 284 285
        if( old_val.f_float == p_zoom_values[i].f_value )
            var_Change( p_vout, "zoom", VLC_VAR_DELCHOICE, &old_val, NULL );
        val.f_float = p_zoom_values[i].f_value;
        text.psz_string = _( p_zoom_values[i].psz_label );
        var_Change( p_vout, "zoom", VLC_VAR_ADDCHOICE, &val, &text );
286 287
    }

288
    var_Set( p_vout, "zoom", old_val ); /* Is this really needed? */
289 290

    var_AddCallback( p_vout, "zoom", ZoomCallback, NULL );
291

292
    /* Crop offset vars */
293 294 295 296
    var_Create( p_vout, "crop-left", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_Create( p_vout, "crop-top", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_Create( p_vout, "crop-right", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
    var_Create( p_vout, "crop-bottom", VLC_VAR_INTEGER | VLC_VAR_ISCOMMAND );
297 298 299 300 301 302

    var_AddCallback( p_vout, "crop-left", CropCallback, NULL );
    var_AddCallback( p_vout, "crop-top", CropCallback, NULL );
    var_AddCallback( p_vout, "crop-right", CropCallback, NULL );
    var_AddCallback( p_vout, "crop-bottom", CropCallback, NULL );

303
    /* Crop object var */
304
    var_Create( p_vout, "crop", VLC_VAR_STRING | VLC_VAR_ISCOMMAND |
305 306 307 308 309
                VLC_VAR_HASCHOICE | VLC_VAR_DOINHERIT );

    text.psz_string = _("Crop");
    var_Change( p_vout, "crop", VLC_VAR_SETTEXT, &text, NULL );

310
    val.psz_string = (char*)"";
311 312
    var_Change( p_vout, "crop", VLC_VAR_DELCHOICE, &val, 0 );

313
    for( i = 0; p_crop_values[i].psz_value; i++ )
314
    {
315 316 317
        val.psz_string = (char*)p_crop_values[i].psz_value;
        text.psz_string = _( p_crop_values[i].psz_label );
        var_Change( p_vout, "crop", VLC_VAR_ADDCHOICE, &val, &text );
318
    }
319

320
    /* update triggered every time the vout's crop parameters are changed */
Antoine Cellerier's avatar
Antoine Cellerier committed
321
    var_Create( p_vout, "crop-update", VLC_VAR_VOID );
322

323 324 325 326
    /* Add custom crop ratios */
    psz_buf = config_GetPsz( p_vout, "custom-crop-ratios" );
    AddCustomRatios( p_vout, "crop", psz_buf );
    free( psz_buf );
327

328
    var_AddCallback( p_vout, "crop", CropCallback, NULL );
329 330 331
    var_Get( p_vout, "crop", &old_val );
    if( old_val.psz_string && *old_val.psz_string )
        var_Change( p_vout, "crop", VLC_VAR_TRIGGER_CALLBACKS, 0, 0 );
332
    free( old_val.psz_string );
333 334

    /* Monitor pixel aspect-ratio */
335 336 337
    var_Create( p_vout, "monitor-par", VLC_VAR_STRING | VLC_VAR_DOINHERIT );
    var_Get( p_vout, "monitor-par", &val );
    if( val.psz_string && *val.psz_string )
338 339 340 341 342 343
    {
        char *psz_parser = strchr( val.psz_string, ':' );
        unsigned int i_aspect_num = 0, i_aspect_den = 0;
        float i_aspect = 0;
        if( psz_parser )
        {
344 345
            i_aspect_num = strtol( val.psz_string, 0, 10 );
            i_aspect_den = strtol( ++psz_parser, 0, 10 );
346 347 348 349 350 351 352
        }
        else
        {
            i_aspect = atof( val.psz_string );
            vlc_ureduce( &i_aspect_num, &i_aspect_den,
                         i_aspect *VOUT_ASPECT_FACTOR, VOUT_ASPECT_FACTOR, 0 );
        }
353 354 355
        if( !i_aspect_num || !i_aspect_den ) i_aspect_num = i_aspect_den = 1;

        p_vout->i_par_num = i_aspect_num;
356 357 358 359
        p_vout->i_par_den = i_aspect_den;

        vlc_ureduce( &p_vout->i_par_num, &p_vout->i_par_den,
                     p_vout->i_par_num, p_vout->i_par_den, 0 );
360

361
        msg_Dbg( p_vout, "overriding monitor pixel aspect-ratio: %i:%i",
362
                 p_vout->i_par_num, p_vout->i_par_den );
363
        b_force_par = true;
364
    }
365
    free( val.psz_string );
366 367

    /* Aspect-ratio object var */
368
    var_Create( p_vout, "aspect-ratio", VLC_VAR_STRING | VLC_VAR_ISCOMMAND |
369 370 371 372 373
                VLC_VAR_HASCHOICE | VLC_VAR_DOINHERIT );

    text.psz_string = _("Aspect-ratio");
    var_Change( p_vout, "aspect-ratio", VLC_VAR_SETTEXT, &text, NULL );

374
    val.psz_string = (char*)"";
375 376
    var_Change( p_vout, "aspect-ratio", VLC_VAR_DELCHOICE, &val, 0 );

377
    for( i = 0; p_aspect_ratio_values[i].psz_value; i++ )
378
    {
379 380 381
        val.psz_string = (char*)p_aspect_ratio_values[i].psz_value;
        text.psz_string = _( p_aspect_ratio_values[i].psz_label );
        var_Change( p_vout, "aspect-ratio", VLC_VAR_ADDCHOICE, &val, &text );
382
    }
383 384 385 386 387

    /* Add custom aspect ratios */
    psz_buf = config_GetPsz( p_vout, "custom-aspect-ratios" );
    AddCustomRatios( p_vout, "aspect-ratio", psz_buf );
    free( psz_buf );
388

389 390
    var_AddCallback( p_vout, "aspect-ratio", AspectCallback, NULL );
    var_Get( p_vout, "aspect-ratio", &old_val );
391
    if( (old_val.psz_string && *old_val.psz_string) || b_force_par )
392
        var_Change( p_vout, "aspect-ratio", VLC_VAR_TRIGGER_CALLBACKS, 0, 0 );
393
    free( old_val.psz_string );
394

395 396 397 398
    /* Initialize the dimensions of the video window */
    InitWindowSize( p_vout, &p_vout->i_window_width,
                    &p_vout->i_window_height );

399
    /* Add a variable to indicate if the window should be on top of others */
400 401
    var_Create( p_vout, "video-on-top", VLC_VAR_BOOL | VLC_VAR_DOINHERIT
                | VLC_VAR_ISCOMMAND );
402 403 404
    text.psz_string = _("Always on top");
    var_Change( p_vout, "video-on-top", VLC_VAR_SETTEXT, &text, NULL );
    var_AddCallback( p_vout, "video-on-top", OnTopCallback, NULL );
405

406 407 408
    /* Add a variable to indicate whether we want window decoration or not */
    var_Create( p_vout, "video-deco", VLC_VAR_BOOL | VLC_VAR_DOINHERIT );

409
    /* Add a fullscreen variable */
410
    if( var_CreateGetBoolCommand( p_vout, "fullscreen" ) )
411 412 413 414
    {
        /* user requested fullscreen */
        p_vout->i_changes |= VOUT_FULLSCREEN_CHANGE;
    }
415 416
    text.psz_string = _("Fullscreen");
    var_Change( p_vout, "fullscreen", VLC_VAR_SETTEXT, &text, NULL );
417
    var_AddCallback( p_vout, "fullscreen", FullscreenCallback, NULL );
418

419 420 421 422 423 424
    /* Add a snapshot variable */
    var_Create( p_vout, "video-snapshot", VLC_VAR_VOID | VLC_VAR_ISCOMMAND );
    text.psz_string = _("Snapshot");
    var_Change( p_vout, "video-snapshot", VLC_VAR_SETTEXT, &text, NULL );
    var_AddCallback( p_vout, "video-snapshot", SnapshotCallback, NULL );

425 426 427 428 429 430 431
    /* Mouse coordinates */
    var_Create( p_vout, "mouse-x", VLC_VAR_INTEGER );
    var_Create( p_vout, "mouse-y", VLC_VAR_INTEGER );
    var_Create( p_vout, "mouse-button-down", VLC_VAR_INTEGER );
    var_Create( p_vout, "mouse-moved", VLC_VAR_BOOL );
    var_Create( p_vout, "mouse-clicked", VLC_VAR_INTEGER );

432
    var_Create( p_vout, "intf-change", VLC_VAR_BOOL );
433
    var_SetBool( p_vout, "intf-change", true );
434 435
}

436 437 438
/*****************************************************************************
 * vout_Snapshot: generates a snapshot.
 *****************************************************************************/
Laurent Aimar's avatar
Laurent Aimar committed
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
/**
 * This function will inject a subpicture into the vout with the provided
 * picture
 */
static int VoutSnapshotPip( vout_thread_t *p_vout, image_handler_t *p_image, picture_t *p_pic, const video_format_t *p_fmt_in )
{
    video_format_t fmt_in = *p_fmt_in;
    video_format_t fmt_out;
    picture_t *p_pip;
    subpicture_t *p_subpic;

    /* */
    memset( &fmt_out, 0, sizeof(fmt_out) );
    fmt_out = fmt_in;
    fmt_out.i_chroma = VLC_FOURCC('Y','U','V','A');

    /* */
    p_pip = image_Convert( p_image, p_pic, &fmt_in, &fmt_out );
    if( !p_pip )
        return VLC_EGENERIC;

    p_subpic = spu_CreateSubpicture( p_vout->p_spu );
    if( p_subpic == NULL )
    {
         picture_Release( p_pip );
         return VLC_EGENERIC;
    }

    p_subpic->i_channel = 0;
    p_subpic->i_start = mdate();
    p_subpic->i_stop = mdate() + 4000000;
    p_subpic->b_ephemer = true;
    p_subpic->b_fade = true;
    p_subpic->i_original_picture_width = fmt_out.i_width * 4;
    p_subpic->i_original_picture_height = fmt_out.i_height * 4;
    fmt_out.i_aspect = 0;
    fmt_out.i_sar_num =
    fmt_out.i_sar_den = 0;

    p_subpic->p_region = spu_CreateRegion( p_vout->p_spu, &fmt_out );
    if( p_subpic->p_region )
        vout_CopyPicture( p_image->p_parent, &p_subpic->p_region->picture, p_pip );
    picture_Release( p_pip );

    spu_DisplaySubpicture( p_vout->p_spu, p_subpic );
    return VLC_SUCCESS;
}
/**
 * This function will return the default directory used for snapshots
 */
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
489
static char *VoutSnapshotGetDefaultDirectory( void )
Laurent Aimar's avatar
Laurent Aimar committed
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
{
    char *psz_path;
#if defined(__APPLE__) || defined(SYS_BEOS)

    if( asprintf( &psz_path, "%s/Desktop",
                  config_GetHomeDir() ) == -1 )
        psz_path = NULL;

#elif defined(WIN32) && !defined(UNDER_CE)

    /* Get the My Pictures folder path */
    char *p_mypicturesdir = NULL;
    typedef HRESULT (WINAPI *SHGETFOLDERPATH)( HWND, int, HANDLE, DWORD,
                                               LPWSTR );
    #ifndef CSIDL_FLAG_CREATE
    #   define CSIDL_FLAG_CREATE 0x8000
    #endif
    #ifndef CSIDL_MYPICTURES
    #   define CSIDL_MYPICTURES 0x27
    #endif
    #ifndef SHGFP_TYPE_CURRENT
    #   define SHGFP_TYPE_CURRENT 0
    #endif

    HINSTANCE shfolder_dll;
    SHGETFOLDERPATH SHGetFolderPath ;

    /* load the shfolder dll to retrieve SHGetFolderPath */
    if( ( shfolder_dll = LoadLibrary( _T("SHFolder.dll") ) ) != NULL )
    {
       wchar_t wdir[PATH_MAX];
       SHGetFolderPath = (void *)GetProcAddress( shfolder_dll,
                                                  _T("SHGetFolderPathW") );
        if ((SHGetFolderPath != NULL )
         && SUCCEEDED (SHGetFolderPath (NULL,
                                       CSIDL_MYPICTURES | CSIDL_FLAG_CREATE,
                                       NULL, SHGFP_TYPE_CURRENT,
                                       wdir)))
            p_mypicturesdir = FromWide (wdir);

        FreeLibrary( shfolder_dll );
    }

    if( p_mypicturesdir == NULL )
        psz_path = strdup( config_GetHomeDir() );
    else
        psz_path = p_mypicturesdir;

#else

    /* XXX: This saves in the data directory. Shouldn't we try saving
     *      to psz_homedir/Desktop or something nicer ? */
    char *psz_datadir = config_GetUserDataDir();
    if( psz_datadir )
    {
        if( asprintf( &psz_path, "%s", psz_datadir ) == -1 )
            psz_path = NULL;
        free( psz_datadir );
    }

#endif

    return psz_path;
}

555 556 557
int vout_Snapshot( vout_thread_t *p_vout, picture_t *p_pic )
{
    image_handler_t *p_image = image_HandlerCreate( p_vout );
558
    video_format_t fmt_in, fmt_out;
559
    char *psz_filename = NULL;
560
    vlc_value_t val, format;
Filippo Carone's avatar
Filippo Carone committed
561
    DIR *path;
562
    int i_ret;
Laurent Aimar's avatar
Laurent Aimar committed
563
    bool b_embedded_snapshot;
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
564
    uintmax_t i_id = (uintptr_t)NULL;
Laurent Aimar's avatar
Laurent Aimar committed
565 566 567

    /* */
    val.psz_string = var_GetNonEmptyString( p_vout, "snapshot-path" );
568

Laurent Aimar's avatar
Laurent Aimar committed
569 570 571 572 573 574 575
    /* Embedded snapshot : if snapshot-path == object:object_ptr */
    if( val.psz_string && sscanf( val.psz_string, "object:%ju", &i_id ) > 0 )
        b_embedded_snapshot = true;
    else
        b_embedded_snapshot = false;

    /* */
576
    memset( &fmt_in, 0, sizeof(video_format_t) );
Laurent Aimar's avatar
Laurent Aimar committed
577 578 579 580 581 582 583 584
    fmt_in = p_vout->fmt_in;
    if( fmt_in.i_sar_num <= 0 || fmt_in.i_sar_den <= 0 )
    {
        fmt_in.i_sar_num =
        fmt_in.i_sar_den = 1;
    }

    /* */
585
    memset( &fmt_out, 0, sizeof(video_format_t) );
Laurent Aimar's avatar
Laurent Aimar committed
586 587 588 589 590
    fmt_out.i_sar_num =
    fmt_out.i_sar_den = 1;
    fmt_out.i_chroma = b_embedded_snapshot ? VLC_FOURCC('p','n','g',' ') : 0;
    fmt_out.i_width = var_GetInteger( p_vout, "snapshot-width" );
    fmt_out.i_height = var_GetInteger( p_vout, "snapshot-height" );
591

Laurent Aimar's avatar
Laurent Aimar committed
592 593
    if( b_embedded_snapshot &&
        fmt_out.i_width == 0 && fmt_out.i_height == 0 )
594
    {
Laurent Aimar's avatar
Laurent Aimar committed
595 596 597
        /* If snapshot-width and/or snapshot height were not specified,
           use a default snapshot width of 320 */
        fmt_out.i_width = 320;
598
    }
599

Laurent Aimar's avatar
Laurent Aimar committed
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
    if( fmt_out.i_height == 0 && fmt_out.i_width > 0 )
    {
        fmt_out.i_height = fmt_in.i_height * fmt_out.i_width / fmt_in.i_width;
        const int i_height = fmt_out.i_height * fmt_in.i_sar_den / fmt_in.i_sar_num;
        if( i_height > 0 )
            fmt_out.i_height = i_height;
    }
    else
    {
        if( fmt_out.i_width == 0 && fmt_out.i_height > 0 )
        {
            fmt_out.i_width = fmt_in.i_width * fmt_out.i_height / fmt_in.i_height;
        }
        else
        {
            fmt_out.i_width = fmt_in.i_width;
            fmt_out.i_height = fmt_in.i_height;
        }
        const int i_width = fmt_out.i_width * fmt_in.i_sar_num / fmt_in.i_sar_den;
        if( i_width > 0 )
            fmt_out.i_width = i_width;
    }

    /* Embedded snapshot
624
       create a snapshot_t* and store it in
625
       object_ptr->p_private, then unlock and signal the
626 627
       waiting object.
     */
Laurent Aimar's avatar
Laurent Aimar committed
628
    if( b_embedded_snapshot )
629
    {
630
        vlc_object_t* p_dest = (vlc_object_t *)(uintptr_t)i_id;
631 632
        block_t *p_block;
        snapshot_t *p_snapshot;
Laurent Aimar's avatar
Laurent Aimar committed
633
        size_t i_size;
634 635 636 637 638 639

        /* Object must be locked. We will unlock it once we get the
           snapshot and written it to p_private */
        p_dest->p_private = NULL;

        /* Save the snapshot to a memory zone */
Laurent Aimar's avatar
Laurent Aimar committed
640
        p_block = image_Write( p_image, p_pic, &fmt_in, &fmt_out );
641
        if( !p_block )
642 643 644
        {
            msg_Err( p_vout, "Could not get snapshot" );
            image_HandlerDelete( p_image );
645
            vlc_object_signal_unlocked( p_dest );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
646
            vlc_object_release( p_dest );
647 648 649 650 651
            return VLC_EGENERIC;
        }

        /* Copy the p_block data to a snapshot structure */
        /* FIXME: get the timestamp */
Laurent Aimar's avatar
Laurent Aimar committed
652
        p_snapshot = malloc( sizeof( snapshot_t ) );
653 654 655 656
        if( !p_snapshot )
        {
            block_Release( p_block );
            image_HandlerDelete( p_image );
657
            vlc_object_signal_unlocked( p_dest );
Jean-Paul Saman's avatar
Jean-Paul Saman committed
658
            vlc_object_release( p_dest );
659 660 661
            return VLC_ENOMEM;
        }

Jean-Paul Saman's avatar
Jean-Paul Saman committed
662
        i_size = p_block->i_buffer;
663 664 665 666 667

        p_snapshot->i_width = fmt_out.i_width;
        p_snapshot->i_height = fmt_out.i_height;
        p_snapshot->i_datasize = i_size;
        p_snapshot->date = p_block->i_pts; /* FIXME ?? */
Laurent Aimar's avatar
Laurent Aimar committed
668
        p_snapshot->p_data = malloc( i_size );
669 670 671 672 673
        if( !p_snapshot->p_data )
        {
            block_Release( p_block );
            free( p_snapshot );
            image_HandlerDelete( p_image );
674
            vlc_object_signal_unlocked( p_dest );
675
            vlc_object_release( p_dest );
676 677
            return VLC_ENOMEM;
        }
678
        memcpy( p_snapshot->p_data, p_block->p_buffer, p_block->i_buffer );
679

680
        p_dest->p_private = p_snapshot;
681 682

        block_Release( p_block );
683

684
        /* Unlock the object */
685
        vlc_object_signal_unlocked( p_dest );
686
        vlc_object_release( p_dest );
687 688

        image_HandlerDelete( p_image );
689
        return VLC_SUCCESS;
690 691
    }

Laurent Aimar's avatar
Laurent Aimar committed
692
    /* Get default directory if none provided */
693
    if( !val.psz_string )
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
694
        val.psz_string = VoutSnapshotGetDefaultDirectory( );
695 696
    if( !val.psz_string )
    {
Filippo Carone's avatar
Filippo Carone committed
697
        msg_Err( p_vout, "no path specified for snapshots" );
698
        image_HandlerDelete( p_image );
699 700
        return VLC_EGENERIC;
    }
Laurent Aimar's avatar
Laurent Aimar committed
701 702 703 704

    /* Get snapshot format, default being "png" */
    format.psz_string = var_GetNonEmptyString( p_vout, "snapshot-format" );
    if( !format.psz_string )
705
        format.psz_string = strdup( "png" );
Laurent Aimar's avatar
Laurent Aimar committed
706 707 708 709 710
    if( !format.psz_string )
    {
        free( val.psz_string );
        image_HandlerDelete( p_image );
        return VLC_ENOMEM;
711 712
    }

Filippo Carone's avatar
Filippo Carone committed
713 714 715
    /*
     * Did the user specify a directory? If not, path = NULL.
     */
716
    path = utf8_opendir ( (const char *)val.psz_string  );
717
    if( path != NULL )
Filippo Carone's avatar
Filippo Carone committed
718
    {
719 720 721
        char *psz_prefix = var_GetNonEmptyString( p_vout, "snapshot-prefix" );
        if( psz_prefix == NULL )
            psz_prefix = strdup( "vlcsnap-" );
722 723
        else
        {
724 725
            char *psz_tmp = str_format( p_vout, psz_prefix );
            filename_sanitize( psz_tmp );
726
            free( psz_prefix );
727
            psz_prefix = psz_tmp;
728
        }
729

730
        closedir( path );
731
        if( var_GetBool( p_vout, "snapshot-sequential" ) == true )
732 733
        {
            int i_num = var_GetInteger( p_vout, "snapshot-num" );
734 735
            struct stat st;

736 737
            do
            {
Rémi Denis-Courmont's avatar
Rémi Denis-Courmont committed
738
                free( psz_filename );
739 740 741 742 743 744 745 746
                if( asprintf( &psz_filename, "%s" DIR_SEP "%s%05d.%s",
                              val.psz_string, psz_prefix, i_num++,
                              format.psz_string ) == -1 )
                {
                    msg_Err( p_vout, "could not create snapshot" );
                    image_HandlerDelete( p_image );
                    return VLC_EGENERIC;
                }
747
            }
748 749
            while( utf8_stat( psz_filename, &st ) == 0 );

750 751 752 753
            var_SetInteger( p_vout, "snapshot-num", i_num );
        }
        else
        {
754 755 756 757 758 759 760 761 762
            if( asprintf( &psz_filename, "%s" DIR_SEP "%s%u.%s",
                          val.psz_string, psz_prefix,
                          (unsigned int)(p_pic->date / 100000) & 0xFFFFFF,
                          format.psz_string ) == -1 )
            {
                msg_Err( p_vout, "could not create snapshot" );
                image_HandlerDelete( p_image );
                return VLC_EGENERIC;
            }
763 764 765
        }

        free( psz_prefix );
Filippo Carone's avatar
Filippo Carone committed
766 767 768
    }
    else // The user specified a full path name (including file name)
    {
769 770
        psz_filename = str_format( p_vout, val.psz_string );
        path_sanitize( psz_filename );
Filippo Carone's avatar
Filippo Carone committed
771
    }
772

773
    free( val.psz_string );
774
    free( format.psz_string );
775 776 777 778 779 780 781 782 783 784 785

    /* Save the snapshot */
    i_ret = image_WriteUrl( p_image, p_pic, &fmt_in, &fmt_out, psz_filename );
    if( i_ret != VLC_SUCCESS )
    {
        msg_Err( p_vout, "could not create snapshot %s", psz_filename );
        free( psz_filename );
        image_HandlerDelete( p_image );
        return VLC_EGENERIC;
    }

Laurent Aimar's avatar
Laurent Aimar committed
786
    /* */
787
    msg_Dbg( p_vout, "snapshot taken (%s)", psz_filename );
788
    vout_OSDMessage( VLC_OBJECT( p_vout ), DEFAULT_CHAN,
789
                     "%s", psz_filename );
790 791
    free( psz_filename );

Laurent Aimar's avatar
Laurent Aimar committed
792
    /* */
793
    if( var_GetBool( p_vout, "snapshot-preview" ) )
794
    {
Laurent Aimar's avatar
Laurent Aimar committed
795 796
        if( VoutSnapshotPip( p_vout, p_image, p_pic, &fmt_in ) )
            msg_Warn( p_vout, "Failed to display snapshot" );
797
    }
Laurent Aimar's avatar
Laurent Aimar committed
798
    image_HandlerDelete( p_image );
799 800 801 802

    return VLC_SUCCESS;
}

803 804 805 806 807
/*****************************************************************************
 * Handle filters
 *****************************************************************************/

void vout_EnableFilter( vout_thread_t *p_vout, char *psz_name,
808
                        bool b_add, bool b_setconfig )
809 810 811 812 813 814 815 816 817 818 819 820 821
{
    char *psz_parser;
    char *psz_string = config_GetPsz( p_vout, "vout-filter" );

    /* Todo : Use some generic chain manipulation functions */
    if( !psz_string ) psz_string = strdup("");

    psz_parser = strstr( psz_string, psz_name );
    if( b_add )
    {
        if( !psz_parser )
        {
            psz_parser = psz_string;
822 823 824 825 826 827
            if( asprintf( &psz_string, (*psz_string) ? "%s:%s" : "%s%s",
                          psz_string, psz_name ) == -1 )
            {
                free( psz_parser );
                return;
            }
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859
            free( psz_parser );
        }
        else
            return;
    }
    else
    {
        if( psz_parser )
        {
            memmove( psz_parser, psz_parser + strlen(psz_name) +
                            (*(psz_parser + strlen(psz_name)) == ':' ? 1 : 0 ),
                            strlen(psz_parser + strlen(psz_name)) + 1 );

            /* Remove trailing : : */
            if( *(psz_string+strlen(psz_string ) -1 ) == ':' )
            {
                *(psz_string+strlen(psz_string ) -1 ) = '\0';
            }
         }
         else
         {
             free( psz_string );
             return;
         }
    }
    if( b_setconfig )
        config_PutPsz( p_vout, "vout-filter", psz_string );

    var_SetString( p_vout, "vout-filter", psz_string );
    free( psz_string );
}

860 861 862 863 864
/*****************************************************************************
 * vout_ControlDefault: default methods for video output control.
 *****************************************************************************/
int vout_vaControlDefault( vout_thread_t *p_vout, int i_query, va_list args )
{
865
    (void)args;
866 867 868 869
    switch( i_query )
    {
    case VOUT_REPARENT:
    case VOUT_CLOSE:
870
        vout_ReleaseWindow( p_vout, NULL );
871 872
        return VLC_SUCCESS;

873
    case VOUT_SNAPSHOT:
874
        p_vout->b_snapshot = true;
875 876
        return VLC_SUCCESS;

877 878 879
    default:
        msg_Dbg( p_vout, "control query not supported" );
    }
880
    return VLC_EGENERIC;
881 882
}

883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908
/*****************************************************************************
 * InitWindowSize: find the initial dimensions the video window should have.
 *****************************************************************************
 * This function will check the "width", "height" and "zoom" config options and
 * will calculate the size that the video window should have.
 *****************************************************************************/
static void InitWindowSize( vout_thread_t *p_vout, unsigned *pi_width,
                            unsigned *pi_height )
{
    vlc_value_t val;
    int i_width, i_height;
    uint64_t ll_zoom;

#define FP_FACTOR 1000                             /* our fixed point factor */

    var_Get( p_vout, "width", &val );
    i_width = val.i_int;
    var_Get( p_vout, "height", &val );
    i_height = val.i_int;
    var_Get( p_vout, "zoom", &val );
    ll_zoom = (uint64_t)( FP_FACTOR * val.f_float );

    if( i_width > 0 && i_height > 0)
    {
        *pi_width = (int)( i_width * ll_zoom / FP_FACTOR );
        *pi_height = (int)( i_height * ll_zoom / FP_FACTOR );
909
        goto initwsize_end;
910 911 912 913 914 915 916
    }
    else if( i_width > 0 )
    {
        *pi_width = (int)( i_width * ll_zoom / FP_FACTOR );
        *pi_height = (int)( p_vout->fmt_in.i_visible_height * ll_zoom *
            p_vout->fmt_in.i_sar_den * i_width / p_vout->fmt_in.i_sar_num /
            FP_FACTOR / p_vout->fmt_in.i_visible_width );
917
        goto initwsize_end;
918 919 920 921 922 923 924
    }
    else if( i_height > 0 )
    {
        *pi_height = (int)( i_height * ll_zoom / FP_FACTOR );
        *pi_width = (int)( p_vout->fmt_in.i_visible_width * ll_zoom *
            p_vout->fmt_in.i_sar_num * i_height / p_vout->fmt_in.i_sar_den /
            FP_FACTOR / p_vout->fmt_in.i_visible_height );
925
        goto initwsize_end;
926 927
    }

928 929 930 931 932 933
    if( p_vout->fmt_in.i_sar_num == 0 || p_vout->fmt_in.i_sar_den == 0 ) {
        msg_Warn( p_vout, "fucked up aspect" );
        *pi_width = (int)( p_vout->fmt_in.i_visible_width * ll_zoom / FP_FACTOR );
        *pi_height = (int)( p_vout->fmt_in.i_visible_height * ll_zoom /FP_FACTOR);
    }
    else if( p_vout->fmt_in.i_sar_num >= p_vout->fmt_in.i_sar_den )
934 935 936
    {
        *pi_width = (int)( p_vout->fmt_in.i_visible_width * ll_zoom *
            p_vout->fmt_in.i_sar_num / p_vout->fmt_in.i_sar_den / FP_FACTOR );
937
        *pi_height = (int)( p_vout->fmt_in.i_visible_height * ll_zoom
938 939 940 941
            / FP_FACTOR );
    }
    else
    {
942
        *pi_width = (int)( p_vout->fmt_in.i_visible_width * ll_zoom
943 944 945 946 947
            / FP_FACTOR );
        *pi_height = (int)( p_vout->fmt_in.i_visible_height * ll_zoom *
            p_vout->fmt_in.i_sar_den / p_vout->fmt_in.i_sar_num / FP_FACTOR );
    }

948
initwsize_end:
949
    msg_Dbg( p_vout, "window size: %dx%d", p_vout->i_window_width,
950 951
             p_vout->i_window_height );

952 953 954
#undef FP_FACTOR
}

955 956 957 958 959 960 961
/*****************************************************************************
 * Object variables callbacks
 *****************************************************************************/
static int ZoomCallback( vlc_object_t *p_this, char const *psz_cmd,
                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    vout_thread_t *p_vout = (vout_thread_t *)p_this;
962
    (void)psz_cmd; (void)oldval; (void)newval; (void)p_data;
963 964
    InitWindowSize( p_vout, &p_vout->i_window_width,
                    &p_vout->i_window_height );
965
    vout_Control( p_vout, VOUT_SET_SIZE, p_vout->i_window_width,
966
                  p_vout->i_window_height );
967 968
    return VLC_SUCCESS;
}
969

970 971 972 973 974 975 976
static int CropCallback( vlc_object_t *p_this, char const *psz_cmd,
                         vlc_value_t oldval, vlc_value_t newval, void *p_data )
{
    vout_thread_t *p_vout = (vout_thread_t *)p_this;
    int64_t i_aspect_num, i_aspect_den;
    unsigned int i_width, i_height;

977 978
    (void)oldval; (void)p_data;

979 980 981 982 983 984
    /* Restore defaults */
    p_vout->fmt_in.i_x_offset = p_vout->fmt_render.i_x_offset;
    p_vout->fmt_in.i_visible_width = p_vout->fmt_render.i_visible_width;
    p_vout->fmt_in.i_y_offset = p_vout->fmt_render.i_y_offset;
    p_vout->fmt_in.i_visible_height = p_vout->fmt_render.i_visible_height;

985
    if( !strcmp( psz_cmd, "crop" ) )
986
    {
Jean-Paul Saman's avatar
Jean-Paul Saman committed
987
        char *psz_end = NULL, *psz_parser = strchr( newval.psz_string, ':' );
988 989 990 991 992
        if( psz_parser )
        {
            /* We're using the 3:4 syntax */
            i_aspect_num = strtol( newval.psz_string, &psz_end, 10 );
            if( psz_end == newval.psz_string || !i_aspect_num ) goto crop_end;
993

994 995
            i_aspect_den = strtol( ++psz_parser, &psz_end, 10 );
            if( psz_end == psz_parser || !i_aspect_den ) goto crop_end;
996

997 998 999 1000
            i_width = p_vout->fmt_in.i_sar_den*p_vout->fmt_render.i_visible_height *
                i_aspect_num / i_aspect_den / p_vout->fmt_in.i_sar_num;
            i_height = p_vout->fmt_render.i_visible_width*p_vout->fmt_in.i_sar_num *
                i_aspect_den / i_aspect_num / p_vout->fmt_in.i_sar_den;
1001

1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
            if( i_width < p_vout->fmt_render.i_visible_width )
            {
                p_vout->fmt_in.i_x_offset = p_vout->fmt_render.i_x_offset +
                    (p_vout->fmt_render.i_visible_width - i_width) / 2;
                p_vout->fmt_in.i_visible_width = i_width;
            }
            else
            {
                p_vout->fmt_in.i_y_offset = p_vout->fmt_render.i_y_offset +
                    (p_vout->fmt_render.i_visible_height - i_height) / 2;
                p_vout->fmt_in.i_visible_height = i_height;
            }
1014 1015 1016
        }
        else
        {
1017 1018 1019 1020 1021
            psz_parser = strchr( newval.psz_string, 'x' );
            if( psz_parser )
            {
                /* Maybe we're using the <width>x<height>+<left>+<top> syntax */
                unsigned int i_crop_width, i_crop_height, i_crop_top, i_crop_left;
1022

1023 1024
                i_crop_width = strtol( newval.psz_string, &psz_end, 10 );
                if( psz_end != psz_parser ) goto crop_end;
1025

1026 1027 1028
                psz_parser = strchr( ++psz_end, '+' );
                i_crop_height = strtol( psz_end, &psz_end, 10 );
                if( psz_end != psz_parser ) goto crop_end;
1029

1030 1031 1032
                psz_parser = strchr( ++psz_end, '+' );
                i_crop_left = strtol( psz_end, &psz_end, 10 );
                if( psz_end != psz_parser ) goto crop_end;
1033

Jean-Paul Saman's avatar
Jean-Paul Saman committed
1034 1035
                psz_end++;
                i_crop_top = strtol( psz_end, &psz_end, 10 );
1036
                if( *psz_end != '\0' ) goto crop_end;
1037

1038 1039
                i_width = i_crop_width;
                p_vout->fmt_in.i_visible_width = i_width;
1040

1041 1042
                i_height = i_crop_height;
                p_vout->fmt_in.i_visible_height = i_height;
1043

1044 1045 1046 1047 1048 1049 1050
                p_vout->fmt_in.i_x_offset = i_crop_left;
                p_vout->fmt_in.i_y_offset = i_crop_top;
            }
            else
            {
                /* Maybe we're using the <left>+<top>+<right>+<bottom> syntax */
                unsigned int i_crop_top, i_crop_left, i_crop_bottom, i_crop_right;
1051

1052 1053 1054
                psz_parser = strchr( newval.psz_string, '+' );
                i_crop_left = strtol( newval.psz_string, &psz_end, 10 );
                if( psz_end != psz_parser ) goto crop_end;
1055

1056 1057 1058
                psz_parser = strchr( ++psz_end, '+' );
                i_crop_top = strtol( psz_end, &psz_end, 10 );
                if( psz_end != psz_parser ) goto crop_end;
1059

1060 1061 1062
                psz_parser = strchr( ++psz_end, '+' );
                i_crop_right = strtol( psz_end, &psz_end, 10 );
                if( psz_end != psz_parser ) goto crop_end;
1063

Jean-Paul Saman's avatar
Jean-Paul Saman committed
1064 1065
                psz_end++;
                i_crop_bottom = strtol( psz_end, &psz_end, 10 );
1066
                if( *psz_end != '\0' ) goto crop_end;
1067

1068 1069