gstvideobox.c 38.9 KB
Newer Older
1 2
/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3
 * Copyright (C) <2010> Sebastian Dröge <sebastian.droege@collabora.co.uk>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/**
 * SECTION:element-videobox
 * @see_also: #GstVideoCrop
 *
 * This plugin crops or enlarges the image. It takes 4 values as input, a
 * top, bottom, left and right offset. Positive values will crop that much
 * pixels from the respective border of the image, negative values will add
 * that much pixels. When pixels are added, you can specify their color. 
 * Some predefined colors are usable with an enum property.
 * 
 * The plugin is alpha channel aware and will try to negotiate with a format
 * that supports alpha channels first. When alpha channel is active two
 * other properties, alpha and border_alpha can be used to set the alpha
 * values of the inner picture and the border respectively. an alpha value of
 * 0.0 means total transparency, 1.0 is opaque.
 * 
 * The videobox plugin has many uses such as doing a mosaic of pictures, 
 * letterboxing video, cutting out pieces of video, picture in picture, etc..
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
 *
 * Setting autocrop to true changes the behavior of the plugin so that
 * caps determine crop properties rather than the other way around: given
 * input and output dimensions, the crop values are selected so that the
 * smaller frame is effectively centered in the larger frame.  This
 * involves either cropping or padding.
 * 
 * If you use autocrop there is little point in setting the other
 * properties manually because they will be overriden if the caps change,
 * but nothing stops you from doing so.
 * 
 * Sample pipeline:
 * |[
 * gst-launch videotestsrc ! videobox autocrop=true ! \
 *   "video/x-raw-yuv, width=600, height=400" ! ffmpegcolorspace ! ximagesink
 * ]|
54
 */
55

56 57 58
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
59 60 61

#include "gstvideobox.h"

62
#include <math.h>
63
#include <liboil/liboil.h>
64 65
#include <string.h>

66 67
#include <gst/controller/gstcontroller.h>

68
GST_DEBUG_CATEGORY_STATIC (videobox_debug);
69 70
#define GST_CAT_DEFAULT videobox_debug

71 72 73 74 75 76 77 78 79 80
#define DEFAULT_LEFT      0
#define DEFAULT_RIGHT     0
#define DEFAULT_TOP       0
#define DEFAULT_BOTTOM    0
#define DEFAULT_FILL_TYPE VIDEO_BOX_FILL_BLACK
#define DEFAULT_ALPHA     1.0
#define DEFAULT_BORDER_ALPHA 1.0

enum
{
81 82 83 84 85 86 87
  PROP_0,
  PROP_LEFT,
  PROP_RIGHT,
  PROP_TOP,
  PROP_BOTTOM,
  PROP_FILL_TYPE,
  PROP_ALPHA,
88 89
  PROP_BORDER_ALPHA,
  PROP_AUTOCROP
90
      /* FILL ME */
91 92 93
};

static GstStaticPadTemplate gst_video_box_src_template =
94
    GST_STATIC_PAD_TEMPLATE ("src",
95 96
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
97 98
    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") ";"
        GST_VIDEO_CAPS_YUV ("I420"))
99 100 101
    );

static GstStaticPadTemplate gst_video_box_sink_template =
102
    GST_STATIC_PAD_TEMPLATE ("sink",
103 104
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
105 106
    GST_STATIC_CAPS (GST_VIDEO_CAPS_YUV ("AYUV") ";"
        GST_VIDEO_CAPS_YUV ("I420"))
107 108
    );

109 110
GST_BOILERPLATE (GstVideoBox, gst_video_box, GstBaseTransform,
    GST_TYPE_BASE_TRANSFORM);
111 112 113 114 115 116

static void gst_video_box_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_video_box_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);

117
static gboolean video_box_recalc_transform (GstVideoBox * video_box);
118
static GstCaps *gst_video_box_transform_caps (GstBaseTransform * trans,
119
    GstPadDirection direction, GstCaps * from);
120 121
static gboolean gst_video_box_set_caps (GstBaseTransform * trans,
    GstCaps * in, GstCaps * out);
122 123
static gboolean gst_video_box_get_unit_size (GstBaseTransform * trans,
    GstCaps * caps, guint * size);
124
static GstFlowReturn gst_video_box_transform (GstBaseTransform * trans,
Wim Taymans's avatar
Wim Taymans committed
125
    GstBuffer * in, GstBuffer * out);
126 127 128 129 130 131

#define GST_TYPE_VIDEO_BOX_FILL (gst_video_box_fill_get_type())
static GType
gst_video_box_fill_get_type (void)
{
  static GType video_box_fill_type = 0;
132
  static const GEnumValue video_box_fill[] = {
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
133 134 135
    {VIDEO_BOX_FILL_BLACK, "Black", "black"},
    {VIDEO_BOX_FILL_GREEN, "Colorkey green", "green"},
    {VIDEO_BOX_FILL_BLUE, "Colorkey blue", "blue"},
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
    {0, NULL, NULL},
  };

  if (!video_box_fill_type) {
    video_box_fill_type =
        g_enum_register_static ("GstVideoBoxFill", video_box_fill);
  }
  return video_box_fill_type;
}


static void
gst_video_box_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);

Sebastian Dröge's avatar
Sebastian Dröge committed
152 153 154 155
  gst_element_class_set_details_simple (element_class, "Video box filter",
      "Filter/Effect/Video",
      "Resizes a video by adding borders or cropping",
      "Wim Taymans <wim@fluendo.com>");
156 157 158 159 160 161

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_video_box_sink_template));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&gst_video_box_src_template));
}
162

163 164 165 166 167 168 169 170 171 172 173 174 175
static void
gst_video_box_finalize (GObject * object)
{
  GstVideoBox *video_box = GST_VIDEO_BOX (object);

  if (video_box->mutex) {
    g_mutex_free (video_box->mutex);
    video_box->mutex = NULL;
  }

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

176 177 178
static void
gst_video_box_class_init (GstVideoBoxClass * klass)
{
Sebastian Dröge's avatar
Sebastian Dröge committed
179 180
  GObjectClass *gobject_class = (GObjectClass *) klass;
  GstBaseTransformClass *trans_class = (GstBaseTransformClass *) klass;
181

Wim Taymans's avatar
Wim Taymans committed
182 183
  gobject_class->set_property = gst_video_box_set_property;
  gobject_class->get_property = gst_video_box_get_property;
184
  gobject_class->finalize = gst_video_box_finalize;
Wim Taymans's avatar
Wim Taymans committed
185

186
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FILL_TYPE,
187 188
      g_param_spec_enum ("fill", "Fill", "How to fill the borders",
          GST_TYPE_VIDEO_BOX_FILL, DEFAULT_FILL_TYPE,
189
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
190
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LEFT,
191 192
      g_param_spec_int ("left", "Left",
          "Pixels to box at left (<0  = add a border)", G_MININT, G_MAXINT,
193 194
          DEFAULT_LEFT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
195
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_RIGHT,
196 197
      g_param_spec_int ("right", "Right",
          "Pixels to box at right (<0 = add a border)", G_MININT, G_MAXINT,
198 199
          DEFAULT_RIGHT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
200
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TOP,
201 202
      g_param_spec_int ("top", "Top",
          "Pixels to box at top (<0 = add a border)", G_MININT, G_MAXINT,
203 204
          DEFAULT_TOP,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
205
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BOTTOM,
206 207
      g_param_spec_int ("bottom", "Bottom",
          "Pixels to box at bottom (<0 = add a border)", G_MININT, G_MAXINT,
208 209
          DEFAULT_BOTTOM,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
210
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_ALPHA,
211
      g_param_spec_double ("alpha", "Alpha", "Alpha value picture", 0.0, 1.0,
212 213
          DEFAULT_ALPHA,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
214
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BORDER_ALPHA,
Sebastian Dröge's avatar
Sebastian Dröge committed
215
      g_param_spec_double ("border-alpha", "Border Alpha",
216
          "Alpha value of the border", 0.0, 1.0, DEFAULT_BORDER_ALPHA,
217
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | GST_PARAM_CONTROLLABLE));
218 219 220 221 222 223 224 225
  /**
   * GstVideoBox:autocrop
   *
   * If set to %TRUE videobox will automatically crop/pad the input
   * video to be centered in the output.
   *
   * Since: 0.10.16
   **/
226 227
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_AUTOCROP,
      g_param_spec_boolean ("autocrop", "Auto crop",
Sebastian Dröge's avatar
Sebastian Dröge committed
228
          "Auto crop", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
229

230
  trans_class->transform = GST_DEBUG_FUNCPTR (gst_video_box_transform);
231 232 233 234
  trans_class->transform_caps =
      GST_DEBUG_FUNCPTR (gst_video_box_transform_caps);
  trans_class->set_caps = GST_DEBUG_FUNCPTR (gst_video_box_set_caps);
  trans_class->get_unit_size = GST_DEBUG_FUNCPTR (gst_video_box_get_unit_size);
235 236 237
}

static void
238
gst_video_box_init (GstVideoBox * video_box, GstVideoBoxClass * g_class)
239 240 241 242 243
{
  video_box->box_right = DEFAULT_RIGHT;
  video_box->box_left = DEFAULT_LEFT;
  video_box->box_top = DEFAULT_TOP;
  video_box->box_bottom = DEFAULT_BOTTOM;
244 245 246 247
  video_box->crop_right = 0;
  video_box->crop_left = 0;
  video_box->crop_top = 0;
  video_box->crop_bottom = 0;
248 249 250
  video_box->fill_type = DEFAULT_FILL_TYPE;
  video_box->alpha = DEFAULT_ALPHA;
  video_box->border_alpha = DEFAULT_BORDER_ALPHA;
251
  video_box->autocrop = FALSE;
252 253

  video_box->mutex = g_mutex_new ();
254 255 256 257 258 259
}

static void
gst_video_box_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
260
  GstVideoBox *video_box = GST_VIDEO_BOX (object);
261

262
  g_mutex_lock (video_box->mutex);
263
  switch (prop_id) {
264
    case PROP_LEFT:
265 266 267 268 269 270 271 272 273
      video_box->box_left = g_value_get_int (value);
      if (video_box->box_left < 0) {
        video_box->border_left = -video_box->box_left;
        video_box->crop_left = 0;
      } else {
        video_box->border_left = 0;
        video_box->crop_left = video_box->box_left;
      }
      break;
274
    case PROP_RIGHT:
275 276 277 278 279 280 281 282 283
      video_box->box_right = g_value_get_int (value);
      if (video_box->box_right < 0) {
        video_box->border_right = -video_box->box_right;
        video_box->crop_right = 0;
      } else {
        video_box->border_right = 0;
        video_box->crop_right = video_box->box_right;
      }
      break;
284
    case PROP_TOP:
285 286 287 288 289 290 291 292 293
      video_box->box_top = g_value_get_int (value);
      if (video_box->box_top < 0) {
        video_box->border_top = -video_box->box_top;
        video_box->crop_top = 0;
      } else {
        video_box->border_top = 0;
        video_box->crop_top = video_box->box_top;
      }
      break;
294
    case PROP_BOTTOM:
295 296 297 298 299 300 301 302 303
      video_box->box_bottom = g_value_get_int (value);
      if (video_box->box_bottom < 0) {
        video_box->border_bottom = -video_box->box_bottom;
        video_box->crop_bottom = 0;
      } else {
        video_box->border_bottom = 0;
        video_box->crop_bottom = video_box->box_bottom;
      }
      break;
304
    case PROP_FILL_TYPE:
305 306
      video_box->fill_type = g_value_get_enum (value);
      break;
307
    case PROP_ALPHA:
308 309
      video_box->alpha = g_value_get_double (value);
      break;
310
    case PROP_BORDER_ALPHA:
311 312
      video_box->border_alpha = g_value_get_double (value);
      break;
313 314 315
    case PROP_AUTOCROP:
      video_box->autocrop = g_value_get_boolean (value);
      break;
316 317 318 319
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
320
  video_box_recalc_transform (video_box);
321 322

  GST_DEBUG_OBJECT (video_box, "Calling reconfigure");
Sebastian Dröge's avatar
Sebastian Dröge committed
323
  gst_base_transform_reconfigure (GST_BASE_TRANSFORM_CAST (video_box));
324 325

  g_mutex_unlock (video_box->mutex);
326 327 328 329 330
}

static void
gst_video_box_autocrop (GstVideoBox * video_box)
{
331 332
  gint crop_w = video_box->in_width - video_box->out_width;
  gint crop_h = video_box->in_height - video_box->out_height;
333

334
  video_box->box_left = crop_w / 2;
335 336 337 338 339 340 341 342
  if (video_box->box_left < 0) {
    video_box->border_left = -video_box->box_left;
    video_box->crop_left = 0;
  } else {
    video_box->border_left = 0;
    video_box->crop_left = video_box->box_left;
  }

343 344 345 346 347 348 349
  /* Round down/up for odd width differences */
  if (crop_w < 0)
    crop_w -= 1;
  else
    crop_w += 1;

  video_box->box_right = crop_w / 2;
350 351 352 353 354 355 356 357
  if (video_box->box_right < 0) {
    video_box->border_right = -video_box->box_right;
    video_box->crop_right = 0;
  } else {
    video_box->border_right = 0;
    video_box->crop_right = video_box->box_right;
  }

358
  video_box->box_top = crop_h / 2;
359 360 361 362 363 364 365 366
  if (video_box->box_top < 0) {
    video_box->border_top = -video_box->box_top;
    video_box->crop_top = 0;
  } else {
    video_box->border_top = 0;
    video_box->crop_top = video_box->box_top;
  }

367 368 369 370 371 372 373
  /* Round down/up for odd height differences */
  if (crop_h < 0)
    crop_h -= 1;
  else
    crop_h += 1;
  video_box->box_bottom = crop_h / 2;

374 375 376 377 378 379 380
  if (video_box->box_bottom < 0) {
    video_box->border_bottom = -video_box->box_bottom;
    video_box->crop_bottom = 0;
  } else {
    video_box->border_bottom = 0;
    video_box->crop_bottom = video_box->box_bottom;
  }
381
}
382

383 384 385 386
static void
gst_video_box_get_property (GObject * object, guint prop_id, GValue * value,
    GParamSpec * pspec)
{
387
  GstVideoBox *video_box = GST_VIDEO_BOX (object);
388 389

  switch (prop_id) {
390
    case PROP_LEFT:
391 392
      g_value_set_int (value, video_box->box_left);
      break;
393
    case PROP_RIGHT:
394 395
      g_value_set_int (value, video_box->box_right);
      break;
396
    case PROP_TOP:
397 398
      g_value_set_int (value, video_box->box_top);
      break;
399
    case PROP_BOTTOM:
400 401
      g_value_set_int (value, video_box->box_bottom);
      break;
402
    case PROP_FILL_TYPE:
403 404
      g_value_set_enum (value, video_box->fill_type);
      break;
405
    case PROP_ALPHA:
406 407
      g_value_set_double (value, video_box->alpha);
      break;
408
    case PROP_BORDER_ALPHA:
409 410
      g_value_set_double (value, video_box->border_alpha);
      break;
411 412 413
    case PROP_AUTOCROP:
      g_value_set_boolean (value, video_box->autocrop);
      break;
414 415 416 417 418 419
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

420
static GstCaps *
421 422
gst_video_box_transform_caps (GstBaseTransform * trans,
    GstPadDirection direction, GstCaps * from)
423
{
Sebastian Dröge's avatar
Sebastian Dröge committed
424
  GstVideoBox *video_box = GST_VIDEO_BOX (trans);
425 426
  GstCaps *to, *ret;
  const GstCaps *templ;
427
  GstStructure *structure;
428 429
  GstPad *other;
  gint width, height;
430

431
  to = gst_caps_copy (from);
432 433 434 435 436
  structure = gst_caps_get_structure (to, 0);

  /* get rid of format */
  gst_structure_remove_field (structure, "format");

437 438 439 440
  /* otherwise caps nego will fail: */
  if (video_box->autocrop) {
    gst_structure_remove_field (structure, "width");
    gst_structure_remove_field (structure, "height");
441
  } else {
442 443 444 445 446 447 448 449 450 451 452 453 454 455
    /* calculate width and height */
    if (gst_structure_get_int (structure, "width", &width)) {
      if (direction == GST_PAD_SINK) {
        width -= video_box->box_left;
        width -= video_box->box_right;
      } else {
        width += video_box->box_left;
        width += video_box->box_right;
      }
      if (width <= 0)
        width = 1;

      GST_DEBUG_OBJECT (trans, "New caps width: %d", width);
      gst_structure_set (structure, "width", G_TYPE_INT, width, NULL);
456
    }
457

458 459 460 461 462 463 464 465
    if (gst_structure_get_int (structure, "height", &height)) {
      if (direction == GST_PAD_SINK) {
        height -= video_box->box_top;
        height -= video_box->box_bottom;
      } else {
        height += video_box->box_top;
        height += video_box->box_bottom;
      }
466

467 468 469 470 471 472
      if (height <= 0)
        height = 1;

      GST_DEBUG_OBJECT (trans, "New caps height: %d", height);
      gst_structure_set (structure, "height", G_TYPE_INT, height, NULL);
    }
473 474
  }

475 476 477 478 479 480
  /* filter against set allowed caps on the pad */
  other = (direction == GST_PAD_SINK) ? trans->srcpad : trans->sinkpad;

  templ = gst_pad_get_pad_template_caps (other);
  ret = gst_caps_intersect (to, templ);
  gst_caps_unref (to);
481

482
  GST_DEBUG_OBJECT (video_box, "direction %d, transformed %" GST_PTR_FORMAT
483 484 485 486 487
      " to %" GST_PTR_FORMAT, direction, from, ret);

  return ret;
}

488 489 490 491 492 493
static gboolean
video_box_recalc_transform (GstVideoBox * video_box)
{
  gboolean res = TRUE;

  /* if we have the same format in and out and we don't need to perform any
Sebastian Dröge's avatar
Sebastian Dröge committed
494
   * cropping at all, we can just operate in passthrough mode */
495
  if (video_box->in_format == video_box->out_format &&
496 497 498 499
      video_box->box_left == 0 && video_box->box_right == 0 &&
      video_box->box_top == 0 && video_box->box_bottom == 0) {

    GST_LOG_OBJECT (video_box, "we are using passthrough");
Sebastian Dröge's avatar
Sebastian Dröge committed
500 501
    gst_base_transform_set_passthrough (GST_BASE_TRANSFORM_CAST (video_box),
        TRUE);
502 503
  } else {
    GST_LOG_OBJECT (video_box, "we are not using passthrough");
Sebastian Dröge's avatar
Sebastian Dröge committed
504 505
    gst_base_transform_set_passthrough (GST_BASE_TRANSFORM_CAST (video_box),
        FALSE);
506 507 508 509
  }
  return res;
}

Wim Taymans's avatar
Wim Taymans committed
510
static gboolean
511
gst_video_box_set_caps (GstBaseTransform * trans, GstCaps * in, GstCaps * out)
512
{
Sebastian Dröge's avatar
Sebastian Dröge committed
513
  GstVideoBox *video_box = GST_VIDEO_BOX (trans);
514 515
  gboolean ret;

516 517
  g_mutex_lock (video_box->mutex);

518 519 520 521 522 523
  ret =
      gst_video_format_parse_caps (in, &video_box->in_format,
      &video_box->in_width, &video_box->in_height);
  ret &=
      gst_video_format_parse_caps (out, &video_box->out_format,
      &video_box->out_width, &video_box->out_height);
524

525 526 527 528
  /* something wrong getting the caps */
  if (!ret)
    goto no_caps;

529 530 531 532 533
  GST_DEBUG_OBJECT (trans, "Input w: %d h: %d", video_box->in_width,
      video_box->in_height);
  GST_DEBUG_OBJECT (trans, "Output w: %d h: %d", video_box->out_width,
      video_box->out_height);

534 535
  if (video_box->autocrop)
    gst_video_box_autocrop (video_box);
536

537 538
  /* recalc the transformation strategy */
  ret = video_box_recalc_transform (video_box);
539

540 541
  g_mutex_unlock (video_box->mutex);

Wim Taymans's avatar
Wim Taymans committed
542
  return ret;
543 544 545 546

  /* ERRORS */
no_caps:
  {
547 548
    GST_DEBUG_OBJECT (video_box,
        "Invalid caps: %" GST_PTR_FORMAT " -> %" GST_PTR_FORMAT, in, out);
549
    g_mutex_unlock (video_box->mutex);
550 551
    return FALSE;
  }
552 553
}

554 555 556
static gboolean
gst_video_box_get_unit_size (GstBaseTransform * trans, GstCaps * caps,
    guint * size)
Wim Taymans's avatar
Wim Taymans committed
557
{
Sebastian Dröge's avatar
Sebastian Dröge committed
558
  GstVideoBox *video_box = GST_VIDEO_BOX (trans);
559
  GstVideoFormat format;
560
  gint width, height;
561
  gboolean ret;
Wim Taymans's avatar
Wim Taymans committed
562

Stefan Kost's avatar
Stefan Kost committed
563 564
  g_assert (size);

565 566 567 568
  ret = gst_video_format_parse_caps (caps, &format, &width, &height);
  if (!ret) {
    GST_ERROR_OBJECT (video_box, "Invalid caps: %" GST_PTR_FORMAT, caps);
    return FALSE;
Wim Taymans's avatar
Wim Taymans committed
569
  }
570

571 572
  *size = gst_video_format_get_size (format, width, height);

573 574
  GST_LOG_OBJECT (video_box, "Returning from _unit_size %d", *size);

575
  return TRUE;
Wim Taymans's avatar
Wim Taymans committed
576 577
}

578 579 580
static const guint8 yuv_colors_Y[VIDEO_BOX_FILL_LAST] = { 16, 150, 29 };
static const guint8 yuv_colors_U[VIDEO_BOX_FILL_LAST] = { 128, 46, 255 };
static const guint8 yuv_colors_V[VIDEO_BOX_FILL_LAST] = { 128, 21, 107 };
581

582 583 584 585 586 587 588 589 590 591
static void
gst_video_box_copy_plane_i420 (GstVideoBox * video_box, guint8 * src,
    guint8 * dest, gint br, gint bl, gint bt, gint bb, gint src_crop_width,
    gint src_crop_height, gint src_stride, gint dest_width, gint dest_stride,
    guint8 fill_color)
{
  gint j;

  /* top border */
  for (j = 0; j < bt; j++) {
592
    oil_splat_u8_ns (dest, &fill_color, dest_width);
593 594 595 596 597
    dest += dest_stride;
  }

  /* copy and add left and right border */
  for (j = 0; j < src_crop_height; j++) {
598 599 600
    oil_splat_u8_ns (dest, &fill_color, bl);
    oil_memcpy (dest + bl, src, src_crop_width);
    oil_splat_u8_ns (dest + bl + src_crop_width, &fill_color, br);
601 602 603 604 605 606
    dest += dest_stride;
    src += src_stride;
  }

  /* bottom border */
  for (j = 0; j < bb; j++) {
607
    oil_splat_u8_ns (dest, &fill_color, dest_width);
608 609 610 611
    dest += dest_stride;
  }
}

612
static void
613
gst_video_box_apply_alpha (guint8 * dest, guint8 alpha)
614
{
615 616 617
  if (dest[0] != 0)
    dest[0] = alpha;
}
618

619 620 621 622 623 624 625 626 627 628 629 630 631
static void
gst_video_box_ayuv_ayuv (GstVideoBox * video_box, guint8 * src, guint8 * dest)
{
  gint dblen = video_box->out_height * video_box->out_width;
  guint32 *destb = (guint32 *) dest;
  guint32 *srcb = (guint32 *) src;
  guint8 b_alpha = (guint8) (video_box->border_alpha * 255);
  guint8 i_alpha = (guint8) (video_box->alpha * 255);
  gint br, bl, bt, bb, crop_w, crop_h;
  gint i;
  guint32 *loc = destb;
  guint32 empty_pixel;

Sebastian Dröge's avatar
Sebastian Dröge committed
632
  GST_LOG_OBJECT (video_box, "Processing AYUV -> AYUV data");
633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654

  crop_h = 0;
  crop_w = 0;
  empty_pixel = GUINT32_FROM_BE ((b_alpha << 24) |
      (yuv_colors_Y[video_box->fill_type] << 16) |
      (yuv_colors_U[video_box->fill_type] << 8) |
      yuv_colors_V[video_box->fill_type]);

  br = video_box->box_right;
  bl = video_box->box_left;
  bt = video_box->box_top;
  bb = video_box->box_bottom;

  if (br >= 0 && bl >= 0) {
    crop_w = video_box->in_width - (br + bl);
  } else if (br >= 0 && bl < 0) {
    crop_w = video_box->in_width - (br);
  } else if (br < 0 && bl >= 0) {
    crop_w = video_box->in_width - (bl);
  } else if (br < 0 && bl < 0) {
    crop_w = video_box->in_width;
  }
655

656 657 658 659 660 661 662 663 664
  if (bb >= 0 && bt >= 0) {
    crop_h = video_box->in_height - (bb + bt);
  } else if (bb >= 0 && bt < 0) {
    crop_h = video_box->in_height - (bb);
  } else if (bb < 0 && bt >= 0) {
    crop_h = video_box->in_height - (bt);
  } else if (bb < 0 && bt < 0) {
    crop_h = video_box->in_height;
  }
665

Sebastian Dröge's avatar
Sebastian Dröge committed
666 667 668
  GST_DEBUG_OBJECT (video_box, "Borders are: L:%d, R:%d, T:%d, B:%d", bl, br,
      bt, bb);
  GST_DEBUG_OBJECT (video_box, "Alpha value is: %d", i_alpha);
669

670 671 672 673
  if (crop_h <= 0 || crop_w <= 0) {
    oil_splat_u32_ns (destb, &empty_pixel, dblen);
  } else {
    guint32 *src_loc = srcb;
674

675 676 677 678 679 680 681
    /* Top border */
    if (bt < 0) {
      oil_splat_u32_ns (loc, &empty_pixel, (-bt) * video_box->out_width);
      loc = loc + ((-bt) * video_box->out_width);
    } else {
      src_loc = src_loc + (bt * video_box->in_width);
    }
682

683 684
    if (bl >= 0)
      src_loc += bl;
685

686 687
    for (i = 0; i < crop_h; i++) {
      gint j;
688

689 690 691 692 693
      /* Left border */
      if (bl < 0) {
        oil_splat_u32_ns (loc, &empty_pixel, -bl);
        loc += (-bl);
      }
694

695 696
      /* Cropped area */
      oil_copy_u8 ((guint8 *) loc, (guint8 *) src_loc, crop_w * 4);
697

698 699
      for (j = 0; j < crop_w; j++)
        gst_video_box_apply_alpha ((guint8 *) & loc[j], i_alpha);
700

701 702
      src_loc += video_box->in_width;
      loc += crop_w;
703

704 705 706 707 708 709
      /* Right border */
      if (br < 0) {
        oil_splat_u32_ns (loc, &empty_pixel, -br);
        loc += (-br);
      }
    }
710

711 712 713 714 715 716
    /* Bottom border */
    if (bb < 0) {
      oil_splat_u32_ns (loc, &empty_pixel, (-bb) * video_box->out_width);
    }
  }

Sebastian Dröge's avatar
Sebastian Dröge committed
717
  GST_LOG_OBJECT (video_box, "image created");
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
}

static gpointer
gst_video_box_clear (gpointer dest, gint size)
{
  guint8 nil = 255;

  oil_splat_u8_ns (dest, &nil, size);

  return dest;
}

static gint
UVfloor (gint j)
{
  return floor (((float) j) / 2);
}

static gint
UVceil (gint j)
{
  return ceil (((float) j) / 2);
}

static void
gst_video_box_ayuv_i420 (GstVideoBox * video_box, guint8 * src, guint8 * dest)
{
  gint br, bl, bt, bb, crop_w, crop_h, rest;
  gint Ysize, Usize, Vsize;
  guint8 *Ydest, *Udest, *Vdest;
  guint8 *Utemp, *Vtemp;
  guint32 empty_px_values[3];
  gint i, j;
  guint Ywidth, Uwidth, Vwidth;

Sebastian Dröge's avatar
Sebastian Dröge committed
753
  GST_LOG_OBJECT (video_box, "AYUV to I420 conversion");
754 755 756 757 758 759 760 761 762

  crop_h = 0;
  crop_w = 0;
  rest = 0;

  empty_px_values[0] = yuv_colors_Y[video_box->fill_type];
  empty_px_values[1] = yuv_colors_U[video_box->fill_type];
  empty_px_values[2] = yuv_colors_V[video_box->fill_type];

763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784
  Ywidth =
      gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0,
      video_box->out_width);
  Uwidth =
      gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1,
      video_box->out_width);
  Vwidth =
      gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2,
      video_box->out_width);

  Ydest =
      dest + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 0,
      video_box->out_width, video_box->out_height);
  Udest =
      dest + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1,
      video_box->out_width, video_box->out_height);
  Vdest =
      dest + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2,
      video_box->out_width, video_box->out_height);

  Ysize =
      Ywidth * gst_video_format_get_component_height (GST_VIDEO_FORMAT_I420, 0,
785
      video_box->out_height);
786 787
  Usize =
      Ywidth * gst_video_format_get_component_height (GST_VIDEO_FORMAT_I420, 1,
788
      video_box->out_height);
789 790
  Vsize =
      Ywidth * gst_video_format_get_component_height (GST_VIDEO_FORMAT_I420, 2,
791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
      video_box->out_height);

  br = video_box->box_right;
  bl = video_box->box_left;
  bt = video_box->box_top;
  bb = video_box->box_bottom;

  if (br >= 0 && bl >= 0) {
    rest = Ywidth - video_box->out_width;
    crop_w = video_box->in_width - (bl + br);
  } else if (br >= 0 && bl < 0) {
    rest = Ywidth - video_box->out_width;
    crop_w = video_box->in_width - (br);
  } else if (br < 0 && bl >= 0) {
    rest = Ywidth - video_box->out_width;
    crop_w = video_box->in_width - (bl);
  } else if (br < 0 && bl < 0) {
    rest = Ywidth - video_box->out_width;
    crop_w = video_box->in_width;
  }

  if (bb >= 0 && bt >= 0) {
    crop_h = video_box->in_height - (bb + bt);
  } else if (bb >= 0 && bt < 0) {
    crop_h = video_box->in_height - (bb);
  } else if (bb < 0 && bt >= 0) {
    crop_h = video_box->in_height - (bt);
  } else if (bb < 0 && bt < 0) {
    crop_h = video_box->in_height;
  }

822 823
  Utemp = g_malloc0 (Uwidth);
  Vtemp = g_malloc0 (Vwidth);
824

Sebastian Dröge's avatar
Sebastian Dröge committed
825 826
  GST_LOG_OBJECT (video_box, "Borders are: L:%d, R:%d, T:%d, B:%d", bl, br, bt,
      bb);
827

Sebastian Dröge's avatar
Sebastian Dröge committed
828
  GST_LOG_OBJECT (video_box, "Starting conversion");
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 860 861 862 863 864

  if (crop_h <= 0 || crop_w <= 0) {
    oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], Ysize);
    oil_splat_u8_ns (Udest, (guint8 *) & empty_px_values[1], Usize);
    oil_splat_u8_ns (Vdest, (guint8 *) & empty_px_values[2], Vsize);
  } else {
    gboolean sumbuff = FALSE;
    guint32 *src_loc1;
    gint a = 0;

    src_loc1 = (guint32 *) src;

    if (bt < 0) {
      oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], (-bt) * Ywidth);

      oil_splat_u8_ns (Udest, (guint8 *) & empty_px_values[1],
          (UVfloor (-bt) * Uwidth) + 7);
      oil_splat_u8_ns (Vdest, (guint8 *) & empty_px_values[2],
          UVfloor (-bt) * Vwidth);

      if ((-bt) % 2 > 0) {
        oil_splat_u8_ns (Utemp, (guint8 *) & empty_px_values[1], Uwidth);
        oil_splat_u8_ns (Vtemp, (guint8 *) & empty_px_values[2], Vwidth);
        sumbuff = TRUE;
      }

      Ydest += ((-bt) * Ywidth);
      Udest += (UVfloor (-bt) * Uwidth);
      Vdest += (UVfloor (-bt) * Vwidth);
    } else {
      src_loc1 = src_loc1 + (bt * video_box->in_width);
    }

    if (bl >= 0)
      src_loc1 += bl;

Sebastian Dröge's avatar
Sebastian Dröge committed
865 866 867 868 869
    GST_LOG_OBJECT (video_box, "Cropped area");
    GST_LOG_OBJECT (video_box, "Ydest value: %p Ywidth: %u", Ydest, Ywidth);
    GST_LOG_OBJECT (video_box, "Udest value: %p Uwidth: %u", Udest, Uwidth);
    GST_LOG_OBJECT (video_box, "Vdest value: %p Vwidth: %u", Vdest, Vwidth);
    GST_LOG_OBJECT (video_box, "Rest: %d", rest);
870 871 872
    for (i = 0; i < crop_h; i++) {
      a = 0;
      if (sumbuff) {
873
        /* left border */
874 875 876 877 878 879 880 881 882 883 884 885
        if (bl < 0) {
          oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], -bl);

          for (j = 0; j < -bl; j++) {
            Utemp[UVfloor (j)] = (Utemp[UVfloor (j)] + empty_px_values[1]) / 2;
            Vtemp[UVfloor (j)] = (Vtemp[UVfloor (j)] + empty_px_values[2]) / 2;
          }
          Ydest += -bl;
          a = -bl;
        }

        for (j = 0; j < crop_w; j++) {
886
          /* check ARCH */
887 888 889 890 891 892 893 894
          Ydest[j] = ((guint8 *) & src_loc1[j])[1];
          Utemp[UVfloor (a + j)] =
              (Utemp[UVfloor (a + j)] + ((guint8 *) & src_loc1[j])[2]) / 2;
          Vtemp[UVfloor (a + j)] =
              (Vtemp[UVfloor (a + j)] + ((guint8 *) & src_loc1[j])[3]) / 2;
        }
        Ydest += crop_w;

895
        /* right border */
896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915
        if (br < 0) {
          oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], -br);
          for (j = 0; j < -br; j++) {
            Utemp[UVfloor (a + crop_w + j)] =
                (Utemp[UVfloor (a + crop_w + j)] + empty_px_values[1]) / 2;
            Vtemp[UVfloor (a + crop_w + j)] =
                (Vtemp[UVfloor (a + crop_w + j)] + empty_px_values[2]) / 2;
          }
          Ydest += -br;
        }
        oil_copy_u8 (Udest, Utemp, Uwidth);
        oil_copy_u8 (Vdest, Vtemp, Vwidth);
        Udest += Uwidth;
        Vdest += Vwidth;
        Ydest += rest;
        gst_video_box_clear (Utemp, Uwidth);
        gst_video_box_clear (Vtemp, Vwidth);
        src_loc1 += video_box->in_width;
        sumbuff = FALSE;
      } else {
916
        /* left border */
917 918 919 920 921 922 923 924 925 926 927 928
        a = 0;
        if (bl < 0) {
          oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], -bl);
          oil_splat_u8_ns (Vtemp, (guint8 *) & empty_px_values[1],
              UVceil (-bl));
          oil_splat_u8_ns (Utemp, (guint8 *) & empty_px_values[2],
              UVceil (-bl));
          Ydest += -bl;
          a = -bl;
        }

        for (j = 0; j < crop_w; j++) {
929
          /* check ARCH */
930 931 932 933 934 935 936 937
          Ydest[j] = ((guint8 *) & src_loc1[j])[1];

          if ((a + j) % 2 > 0) {
            Utemp[UVfloor (a + j)] =
                (Utemp[UVfloor (a + j)] + ((guint8 *) & src_loc1[j])[2]) / 2;
            Vtemp[UVfloor (a + j)] =
                (Vtemp[UVfloor (a + j)] + ((guint8 *) & src_loc1[j])[3]) / 2;
          } else {
938 939
            Utemp[UVfloor (a + j)] = ((guint8 *) & src_loc1[j])[2];
            Vtemp[UVfloor (a + j)] = ((guint8 *) & src_loc1[j])[3];
940 941 942 943
          }
        }
        Ydest += crop_w;

944
        /* right border */
945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968
        if (br < 0) {
          j = 0;
          if ((a + crop_w) % 2 > 0) {
            Utemp[UVfloor (a + crop_w)] =
                (Utemp[UVfloor (a + crop_w)] + empty_px_values[1]) / 2;
            Vtemp[UVfloor (a + crop_w)] =
                (Vtemp[UVfloor (a + crop_w)] + empty_px_values[2]) / 2;
            a++;
            j = -1;
          }

          oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], -br);
          oil_splat_u8_ns (&Utemp[UVfloor (a + crop_w)],
              (guint8 *) & empty_px_values[1], UVceil ((-br) + j));
          oil_splat_u8_ns (&Vtemp[UVfloor (a + crop_w)],
              (guint8 *) & empty_px_values[2], UVceil ((-br) + j));
          Ydest += -br;
        }
        Ydest += rest;
        src_loc1 += video_box->in_width;
        sumbuff = TRUE;
      }
    }

969
    /* bottom border */
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
    if (bb < 0) {
      oil_splat_u8_ns (Ydest, (guint8 *) & empty_px_values[0], (-bb) * Ywidth);
      if (sumbuff) {
        for (i = 0; i < Uwidth; i++) {
          Utemp[i] = (Utemp[i] + empty_px_values[1]) / 2;
        }
        for (i = 0; i < Vwidth; i++) {
          Vtemp[i] = (Vtemp[i] + empty_px_values[2]) / 2;
        }

        oil_copy_u8 (Udest, Utemp, Uwidth);
        oil_copy_u8 (Vdest, Vtemp, Vwidth);
        Udest += Uwidth;
        Vdest += Vwidth;
        sumbuff = FALSE;
      }
      oil_splat_u8_ns (Udest, (guint8 *) & empty_px_values[1],
          (UVfloor ((-bb))) * Uwidth);
      oil_splat_u8_ns (Vdest, (guint8 *) & empty_px_values[2],
          (UVfloor ((-bb))) * Vwidth);
    }
    if (sumbuff) {
      oil_copy_u8 (Udest, Utemp, Uwidth);
      oil_copy_u8 (Vdest, Vtemp, Vwidth);
    }
  }

Sebastian Dröge's avatar
Sebastian Dröge committed
997
  GST_LOG_OBJECT (video_box, "image created");
998 999
  g_free (Utemp);
  g_free (Vtemp);
1000 1001 1002
}

static void
1003
gst_video_box_i420_ayuv (GstVideoBox * video_box, guint8 * src, guint8 * dest)
1004 1005 1006
{
  guint8 *srcY, *srcU, *srcV;
  gint crop_width, crop_width2, crop_height;
1007
  gint out_width, out_height;
1008
  gint src_stridey, src_strideu, src_stridev;
1009 1010 1011 1012 1013
  gint br, bl, bt, bb;
  gint colorY, colorU, colorV;
  gint i, j;
  guint8 b_alpha = (guint8) (video_box->border_alpha * 255);
  guint8 i_alpha = (guint8) (video_box->alpha * 255);
1014 1015
  guint32 *destp;
  guint32 *destb = (guint32 *) dest;
1016 1017 1018 1019 1020 1021 1022 1023
  guint32 ayuv;

  br = video_box->border_right;
  bl = video_box->border_left;
  bt = video_box->border_top;
  bb = video_box->border_bottom;

  out_width = video_box->out_width;
1024
  out_height = video_box->out_height;
1025

1026 1027 1028 1029 1030 1031 1032 1033 1034
  src_stridey =
      gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 0,
      video_box->in_width);
  src_strideu =
      gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 1,
      video_box->in_width);
  src_stridev =
      gst_video_format_get_row_stride (GST_VIDEO_FORMAT_I420, 2,
      video_box->in_width);
1035

1036 1037
  crop_width = video_box->in_width;
  crop_width -= (video_box->crop_left + video_box->crop_right);
1038
  crop_width2 = crop_width / 2;
1039 1040
  crop_height = video_box->in_height;
  crop_height -= (video_box->crop_top + video_box->crop_bottom);
1041 1042

  srcY =
1043 1044
      src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 0,
      video_box->in_width, video_box->in_height);
1045
  srcY += src_stridey * video_box->crop_top + video_box->crop_left;
1046
  srcU =
1047 1048
      src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 1,
      video_box->in_width, video_box->in_height);
1049
  srcU += src_strideu * (video_box->crop_top / 2) + (video_box->crop_left / 2);
1050
  srcV =
1051 1052
      src + gst_video_format_get_component_offset (GST_VIDEO_FORMAT_I420, 2,
      video_box->in_width, video_box->in_height);
1053
  srcV += src_stridev * (video_box->crop_top / 2) + (video_box->crop_left / 2);
1054 1055 1056 1057 1058 1059 1060 1061 1062 1063

  colorY = yuv_colors_Y[video_box->fill_type];
  colorU = yuv_colors_U[video_box->fill_type];
  colorV = yuv_colors_V[video_box->fill_type];

  ayuv =
      GUINT32_FROM_BE ((b_alpha << 24) | (colorY << 16) | (colorU << 8) |
      colorV);

  /* top border */
1064 1065 1066
  if (bt) {
    size_t nb_pixels = bt * out_width;

1067 1068
    oil_splat_u32_ns (destb, &ayuv, nb_pixels);
    destb += nb_pixels;
1069 1070
  }
  for (i = 0; i < crop_height; i++) {
1071
    destp = destb;
1072
    /* left border */
1073 1074 1075
    if (bl) {
      oil_splat_u32_ns (destp, &ayuv, bl);
      destp += bl;
1076 1077 1078
    }
    dest = (guint8 *) destp;
    /* center */
1079 1080
    /* We can splat the alpha channel for the whole line */
    oil_splat_u8 (dest, 4, &i_alpha, crop_width);
1081
    for (j = 0; j < crop_width2; j++) {
1082
      dest++;
1083 1084 1085
      *dest++ = *srcY++;
      *dest++ = *srcU;
      *dest++ = *srcV;
1086
      dest++;
1087 1088 1089 1090 1091 1092 1093 1094
      *dest++ = *srcY++;
      *dest++ = *srcU++;
      *dest++ = *srcV++;
    }
    if (i % 2 == 0) {
      srcU -= crop_width2;
      srcV -= crop_width2;
    } else {
1095 1096
      srcU += src_strideu - crop_width2;
      srcV += src_stridev - crop_width2;
1097
    }
1098
    srcY += src_stridey - (crop_width2 * 2);
1099 1100 1101

    destp = (guint32 *) dest;
    /* right border */
1102 1103
    if (br) {
      oil_splat_u32_ns (destp, &ayuv, br);
1104
    }
1105
    destb += out_width;
1106 1107
  }
  /* bottom border */
1108 1109 1110
  if (bb) {
    size_t nb_pixels = bb * out_width;

1111
    oil_splat_u32_ns (destb, &ayuv, nb_pixels);
1112 1113 1114
  }
}

1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141

static void
gst_video_box_i420_i420 (GstVideoBox * video_box, guint8 * src, guint8 * dest)
{
  guint8 *srcY, *srcU, *srcV;
  guint8 *destY, *destU,