1 /* GStreamer DVD Sub-Picture Unit
2  * Copyright (C) 2007 Fluendo S.A. <info@fluendo.com>
3  * Copyright (C) 2009 Jan Schmidt <thaytan@noraisin.net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif
23 
24 #include <string.h>
25 
26 #include <gst/gst.h>
27 
28 #include "gstdvdspu.h"
29 
30 GST_DEBUG_CATEGORY_EXTERN (dvdspu_debug);
31 #define GST_CAT_DEFAULT dvdspu_debug
32 
33 static void
gstspu_vobsub_recalc_palette(GstDVDSpu * dvdspu,SpuColour * dest,guint8 * idx,guint8 * alpha)34 gstspu_vobsub_recalc_palette (GstDVDSpu * dvdspu,
35     SpuColour * dest, guint8 * idx, guint8 * alpha)
36 {
37   SpuState *state = &dvdspu->spu_state;
38   gint i;
39 
40   if (state->vobsub.current_clut[idx[0]] != 0) {
41     for (i = 0; i < 4; i++, dest++) {
42       guint32 col = state->vobsub.current_clut[idx[i]];
43 
44       /* Convert incoming 4-bit alpha to 8 bit for blending */
45       dest->A = (alpha[i] << 4) | alpha[i];
46       dest->Y = ((guint16) ((col >> 16) & 0xff)) * dest->A;
47       /* U/V are stored as V/U in the clut words, so switch them */
48       dest->V = ((guint16) ((col >> 8) & 0xff)) * dest->A;
49       dest->U = ((guint16) (col & 0xff)) * dest->A;
50     }
51   } else {
52     int y = 240;
53 
54     /* The CLUT presumably hasn't been set, so we'll just guess some
55      * values for the non-transparent colors (white, grey, black) */
56     for (i = 0; i < 4; i++, dest++) {
57       dest->A = (alpha[i] << 4) | alpha[i];
58       if (alpha[i] != 0) {
59         dest[0].Y = y * dest[0].A;
60         y -= 112;
61         if (y < 0)
62           y = 0;
63       }
64       dest[0].U = 128 * dest[0].A;
65       dest[0].V = 128 * dest[0].A;
66     }
67   }
68 }
69 
70 /* Recalculate the main, HL & ChgCol palettes */
71 static void
gstspu_vobsub_update_palettes(GstDVDSpu * dvdspu,SpuState * state)72 gstspu_vobsub_update_palettes (GstDVDSpu * dvdspu, SpuState * state)
73 {
74   guint8 index[4];              /* Indices for the palette */
75   guint8 alpha[4];              /* Alpha values the palette */
76 
77   if (state->vobsub.main_pal_dirty) {
78     gstspu_vobsub_recalc_palette (dvdspu, state->vobsub.main_pal,
79         state->vobsub.main_idx, state->vobsub.main_alpha);
80 
81     /* Need to refresh the hl_ctrl info copies of the main palette too */
82     memcpy (state->vobsub.hl_ctrl_i.pix_ctrl_i[0].pal_cache,
83         state->vobsub.main_pal, 4 * sizeof (SpuColour));
84     memcpy (state->vobsub.hl_ctrl_i.pix_ctrl_i[2].pal_cache,
85         state->vobsub.main_pal, 4 * sizeof (SpuColour));
86 
87     state->vobsub.main_pal_dirty = FALSE;
88   }
89 
90   if (state->vobsub.hl_pal_dirty) {
91     gstspu_vobsub_recalc_palette (dvdspu,
92         state->vobsub.hl_ctrl_i.pix_ctrl_i[1].pal_cache, state->vobsub.hl_idx,
93         state->vobsub.hl_alpha);
94     state->vobsub.hl_pal_dirty = FALSE;
95   }
96 
97   /* Update the offset positions for the highlight region */
98   if (state->vobsub.hl_rect.top != -1) {
99     state->vobsub.hl_ctrl_i.top = state->vobsub.hl_rect.top;
100     state->vobsub.hl_ctrl_i.bottom = state->vobsub.hl_rect.bottom;
101     state->vobsub.hl_ctrl_i.n_changes = 3;
102     state->vobsub.hl_ctrl_i.pix_ctrl_i[0].left = 0;
103     state->vobsub.hl_ctrl_i.pix_ctrl_i[1].left = state->vobsub.hl_rect.left;
104     state->vobsub.hl_ctrl_i.pix_ctrl_i[2].left =
105         state->vobsub.hl_rect.right + 1;
106   }
107 
108   if (state->vobsub.line_ctrl_i_pal_dirty) {
109     gint16 l, c;
110     GST_LOG_OBJECT (dvdspu, "Updating chg-col-con palettes");
111     for (l = 0; l < state->vobsub.n_line_ctrl_i; l++) {
112       SpuVobsubLineCtrlI *cur_line_ctrl = state->vobsub.line_ctrl_i + l;
113 
114       for (c = 0; c < cur_line_ctrl->n_changes; c++) {
115         SpuVobsubPixCtrlI *cur = cur_line_ctrl->pix_ctrl_i + c;
116 
117         index[3] = (cur->palette >> 28) & 0x0f;
118         index[2] = (cur->palette >> 24) & 0x0f;
119         index[1] = (cur->palette >> 20) & 0x0f;
120         index[0] = (cur->palette >> 16) & 0x0f;
121 
122         alpha[3] = (cur->palette >> 12) & 0x0f;
123         alpha[2] = (cur->palette >> 8) & 0x0f;
124         alpha[1] = (cur->palette >> 4) & 0x0f;
125         alpha[0] = (cur->palette) & 0x0f;
126         gstspu_vobsub_recalc_palette (dvdspu, cur->pal_cache, index, alpha);
127       }
128     }
129     state->vobsub.line_ctrl_i_pal_dirty = FALSE;
130   }
131 }
132 
133 static inline guint8
gstspu_vobsub_get_nibble(SpuState * state,guint16 * rle_offset)134 gstspu_vobsub_get_nibble (SpuState * state, guint16 * rle_offset)
135 {
136   guint8 ret;
137 
138   if (G_UNLIKELY (*rle_offset >= state->vobsub.max_offset))
139     return 0;                   /* Overran the buffer */
140 
141   ret = state->vobsub.pix_buf_map.data[(*rle_offset) / 2];
142 
143   /* If the offset is even, we shift the answer down 4 bits, otherwise not */
144   if (*rle_offset & 0x01)
145     ret &= 0x0f;
146   else
147     ret = ret >> 4;
148 
149   (*rle_offset)++;
150   return ret;
151 }
152 
153 static guint16
gstspu_vobsub_get_rle_code(SpuState * state,guint16 * rle_offset)154 gstspu_vobsub_get_rle_code (SpuState * state, guint16 * rle_offset)
155 {
156   guint16 code;
157 
158   code = gstspu_vobsub_get_nibble (state, rle_offset);
159   if (code < 0x4) {             /* 4 .. f */
160     code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset);
161     if (code < 0x10) {          /* 1x .. 3x */
162       code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset);
163       if (code < 0x40) {        /* 04x .. 0fx */
164         code = (code << 4) | gstspu_vobsub_get_nibble (state, rle_offset);
165       }
166     }
167   }
168   return code;
169 }
170 
171 static inline gboolean
gstspu_vobsub_draw_rle_run(SpuState * state,gint16 x,gint16 end,SpuColour * colour)172 gstspu_vobsub_draw_rle_run (SpuState * state, gint16 x, gint16 end,
173     SpuColour * colour)
174 {
175 #if 0
176   GST_LOG ("Y: %d x: %d end %d col %d %d %d %d",
177       state->vobsub.cur_Y, x, end, colour->Y, colour->U, colour->V, colour->A);
178 #endif
179 
180   if (colour->A != 0) {
181     guint32 inv_A = 0xff - colour->A;
182 
183     /* FIXME: This could be more efficient */
184     while (x < end) {
185       state->vobsub.out_Y[x] =
186           (inv_A * state->vobsub.out_Y[x] + colour->Y) / 0xff;
187       state->vobsub.out_U[x / 2] += colour->U;
188       state->vobsub.out_V[x / 2] += colour->V;
189       state->vobsub.out_A[x / 2] += colour->A;
190       x++;
191     }
192     /* Update the compositing buffer so we know how much to blend later */
193     *(state->vobsub.comp_last_x_ptr) = end - 1; /* end is the start of the *next* run */
194 
195     return TRUE;
196   }
197   return FALSE;
198 }
199 
200 static inline gint16
rle_end_x(guint16 rle_code,gint16 x,gint16 end)201 rle_end_x (guint16 rle_code, gint16 x, gint16 end)
202 {
203   /* run length = rle_code >> 2 */
204   if (G_UNLIKELY (((rle_code >> 2) == 0)))
205     return end;
206   else
207     return MIN (end, x + (rle_code >> 2));
208 }
209 
210 static gboolean gstspu_vobsub_render_line_with_chgcol (SpuState * state,
211     guint8 * planes[3], guint16 * rle_offset);
212 static gboolean gstspu_vobsub_update_chgcol (SpuState * state);
213 
214 static gboolean
gstspu_vobsub_render_line(SpuState * state,guint8 * planes[3],guint16 * rle_offset)215 gstspu_vobsub_render_line (SpuState * state, guint8 * planes[3],
216     guint16 * rle_offset)
217 {
218   gint16 x, next_x, end, rle_code, next_draw_x;
219   SpuColour *colour;
220   gboolean visible = FALSE;
221 
222   /* Check for special case of chg_col info to use (either highlight or
223    * ChgCol command */
224   if (state->vobsub.cur_chg_col != NULL) {
225     if (gstspu_vobsub_update_chgcol (state)) {
226       /* Check the top & bottom, because we might not be within the region yet */
227       if (state->vobsub.cur_Y >= state->vobsub.cur_chg_col->top &&
228           state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) {
229         return gstspu_vobsub_render_line_with_chgcol (state, planes,
230             rle_offset);
231       }
232     }
233   }
234 
235   /* No special case. Render as normal */
236 
237   /* Set up our output pointers */
238   state->vobsub.out_Y = planes[0];
239   state->vobsub.out_U = state->comp_bufs[0];
240   state->vobsub.out_V = state->comp_bufs[1];
241   state->vobsub.out_A = state->comp_bufs[2];
242   /* We always need to start our RLE decoding byte_aligned */
243   *rle_offset = GST_ROUND_UP_2 (*rle_offset);
244 
245   x = state->vobsub.disp_rect.left;
246   end = state->vobsub.disp_rect.right + 1;
247   while (x < end) {
248     rle_code = gstspu_vobsub_get_rle_code (state, rle_offset);
249     colour = &state->vobsub.main_pal[rle_code & 3];
250     next_x = rle_end_x (rle_code, x, end);
251     next_draw_x = next_x;
252     if (next_draw_x > state->vobsub.clip_rect.right)
253       next_draw_x = state->vobsub.clip_rect.right;      /* ensure no overflow */
254     /* Now draw the run between [x,next_x) */
255     if (state->vobsub.cur_Y >= state->vobsub.clip_rect.top &&
256         state->vobsub.cur_Y <= state->vobsub.clip_rect.bottom)
257       visible |= gstspu_vobsub_draw_rle_run (state, x, next_draw_x, colour);
258     x = next_x;
259   }
260 
261   return visible;
262 }
263 
264 static gboolean
gstspu_vobsub_update_chgcol(SpuState * state)265 gstspu_vobsub_update_chgcol (SpuState * state)
266 {
267   if (state->vobsub.cur_chg_col == NULL)
268     return FALSE;
269 
270   if (state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom)
271     return TRUE;
272 
273   while (state->vobsub.cur_chg_col < state->vobsub.cur_chg_col_end) {
274     if (state->vobsub.cur_Y >= state->vobsub.cur_chg_col->top &&
275         state->vobsub.cur_Y <= state->vobsub.cur_chg_col->bottom) {
276 #if 0
277       g_print ("Stopped @ entry %d with top %d bottom %d, cur_y %d",
278           (gint16) (state->vobsub.cur_chg_col - state->vobsub.line_ctrl_i),
279           state->vobsub.cur_chg_col->top, state->vobsub.cur_chg_col->bottom, y);
280 #endif
281       return TRUE;
282     }
283     state->vobsub.cur_chg_col++;
284   }
285 
286   /* Finished all our cur_chg_col entries. Use the main palette from here on */
287   state->vobsub.cur_chg_col = NULL;
288   return FALSE;
289 }
290 
291 static gboolean
gstspu_vobsub_render_line_with_chgcol(SpuState * state,guint8 * planes[3],guint16 * rle_offset)292 gstspu_vobsub_render_line_with_chgcol (SpuState * state, guint8 * planes[3],
293     guint16 * rle_offset)
294 {
295   SpuVobsubLineCtrlI *chg_col = state->vobsub.cur_chg_col;
296 
297   gint16 x, next_x, disp_end, rle_code, run_end, run_draw_end;
298   SpuColour *colour;
299   SpuVobsubPixCtrlI *cur_pix_ctrl;
300   SpuVobsubPixCtrlI *next_pix_ctrl;
301   SpuVobsubPixCtrlI *end_pix_ctrl;
302   SpuVobsubPixCtrlI dummy_pix_ctrl;
303   gboolean visible = FALSE;
304   gint16 cur_reg_end;
305   gint i;
306 
307   state->vobsub.out_Y = planes[0];
308   state->vobsub.out_U = state->comp_bufs[0];
309   state->vobsub.out_V = state->comp_bufs[1];
310   state->vobsub.out_A = state->comp_bufs[2];
311 
312   /* We always need to start our RLE decoding byte_aligned */
313   *rle_offset = GST_ROUND_UP_2 (*rle_offset);
314 
315   /* Our run will cover the display rect */
316   x = state->vobsub.disp_rect.left;
317   disp_end = state->vobsub.disp_rect.right + 1;
318 
319   /* Work out the first pixel control info, which may point to the dummy entry if
320    * the global palette/alpha need using initally */
321   cur_pix_ctrl = chg_col->pix_ctrl_i;
322   end_pix_ctrl = chg_col->pix_ctrl_i + chg_col->n_changes;
323 
324   if (cur_pix_ctrl->left != 0) {
325     next_pix_ctrl = cur_pix_ctrl;
326     cur_pix_ctrl = &dummy_pix_ctrl;
327     for (i = 0; i < 4; i++)     /* Copy the main palette to our dummy entry */
328       dummy_pix_ctrl.pal_cache[i] = state->vobsub.main_pal[i];
329   } else {
330     next_pix_ctrl = cur_pix_ctrl + 1;
331   }
332   if (next_pix_ctrl < end_pix_ctrl)
333     cur_reg_end = next_pix_ctrl->left;
334   else
335     cur_reg_end = disp_end;
336 
337   /* Render stuff */
338   while (x < disp_end) {
339     rle_code = gstspu_vobsub_get_rle_code (state, rle_offset);
340     next_x = rle_end_x (rle_code, x, disp_end);
341 
342     /* Now draw the run between [x,next_x), crossing palette regions as needed */
343     while (x < next_x) {
344       run_end = MIN (next_x, cur_reg_end);
345 
346       run_draw_end = run_end;
347       if (run_draw_end > state->vobsub.clip_rect.right)
348         run_draw_end = state->vobsub.clip_rect.right;   /* ensure no overflow */
349 
350       if (G_LIKELY (x < run_end)) {
351         colour = &cur_pix_ctrl->pal_cache[rle_code & 3];
352         visible |= gstspu_vobsub_draw_rle_run (state, x, run_draw_end, colour);
353         x = run_end;
354       }
355 
356       if (x >= cur_reg_end) {
357         /* Advance to next region */
358         cur_pix_ctrl = next_pix_ctrl;
359         next_pix_ctrl++;
360 
361         if (next_pix_ctrl < end_pix_ctrl)
362           cur_reg_end = next_pix_ctrl->left;
363         else
364           cur_reg_end = disp_end;
365       }
366     }
367   }
368 
369   return visible;
370 }
371 
372 static void
gstspu_vobsub_blend_comp_buffers(SpuState * state,guint8 * planes[3])373 gstspu_vobsub_blend_comp_buffers (SpuState * state, guint8 * planes[3])
374 {
375   state->comp_left = state->vobsub.disp_rect.left;
376   state->comp_right =
377       MAX (state->vobsub.comp_last_x[0], state->vobsub.comp_last_x[1]);
378 
379   state->comp_left = MAX (state->comp_left, state->vobsub.clip_rect.left);
380   state->comp_right = MIN (state->comp_right, state->vobsub.clip_rect.right);
381 
382   gstspu_blend_comp_buffers (state, planes);
383 }
384 
385 static void
gstspu_vobsub_clear_comp_buffers(SpuState * state)386 gstspu_vobsub_clear_comp_buffers (SpuState * state)
387 {
388   state->comp_left = state->vobsub.clip_rect.left;
389   state->comp_right = state->vobsub.clip_rect.right;
390 
391   gstspu_clear_comp_buffers (state);
392 
393   state->vobsub.comp_last_x[0] = -1;
394   state->vobsub.comp_last_x[1] = -1;
395 }
396 
397 static void
gstspu_vobsub_draw_highlight(SpuState * state,GstVideoFrame * frame,SpuRect * rect)398 gstspu_vobsub_draw_highlight (SpuState * state,
399     GstVideoFrame * frame, SpuRect * rect)
400 {
401   guint8 *cur;
402   gint16 pos;
403   gint ystride;
404 
405   ystride = GST_VIDEO_FRAME_COMP_STRIDE (frame, 0);
406 
407   cur = GST_VIDEO_FRAME_COMP_DATA (frame, 0) + ystride * rect->top;
408   for (pos = rect->left + 1; pos < rect->right; pos++)
409     cur[pos] = (cur[pos] / 2) + 0x8;
410   cur = GST_VIDEO_FRAME_COMP_DATA (frame, 0) + ystride * rect->bottom;
411   for (pos = rect->left + 1; pos < rect->right; pos++)
412     cur[pos] = (cur[pos] / 2) + 0x8;
413   cur = GST_VIDEO_FRAME_COMP_DATA (frame, 0) + ystride * rect->top;
414   for (pos = rect->top; pos <= rect->bottom; pos++) {
415     cur[rect->left] = (cur[rect->left] / 2) + 0x8;
416     cur[rect->right] = (cur[rect->right] / 2) + 0x8;
417     cur += ystride;
418   }
419 }
420 
421 void
gstspu_vobsub_render(GstDVDSpu * dvdspu,GstVideoFrame * frame)422 gstspu_vobsub_render (GstDVDSpu * dvdspu, GstVideoFrame * frame)
423 {
424   SpuState *state = &dvdspu->spu_state;
425   guint8 *planes[3];            /* YUV frame pointers */
426   gint y, last_y;
427   gint width, height;
428   gint strides[3];
429   gint offset_index = 0;
430 
431   /* Set up our initial state */
432   if (G_UNLIKELY (state->vobsub.pix_buf == NULL))
433     return;
434 
435   if (!gst_buffer_map (state->vobsub.pix_buf, &state->vobsub.pix_buf_map,
436           GST_MAP_READ))
437     return;
438 
439   /* Store the start of each plane */
440   planes[0] = GST_VIDEO_FRAME_COMP_DATA (frame, 0);
441   planes[1] = GST_VIDEO_FRAME_COMP_DATA (frame, 1);
442   planes[2] = GST_VIDEO_FRAME_COMP_DATA (frame, 2);
443 
444   strides[0] = GST_VIDEO_FRAME_COMP_STRIDE (frame, 0);
445   strides[1] = GST_VIDEO_FRAME_COMP_STRIDE (frame, 1);
446   strides[2] = GST_VIDEO_FRAME_COMP_STRIDE (frame, 2);
447 
448   width = GST_VIDEO_FRAME_WIDTH (frame);
449   height = GST_VIDEO_FRAME_HEIGHT (frame);
450 
451   GST_DEBUG_OBJECT (dvdspu,
452       "Rendering SPU. disp_rect %d,%d to %d,%d. hl_rect %d,%d to %d,%d",
453       state->vobsub.disp_rect.left, state->vobsub.disp_rect.top,
454       state->vobsub.disp_rect.right, state->vobsub.disp_rect.bottom,
455       state->vobsub.hl_rect.left, state->vobsub.hl_rect.top,
456       state->vobsub.hl_rect.right, state->vobsub.hl_rect.bottom);
457 
458   GST_DEBUG_OBJECT (dvdspu, "video size %d,%d", width, height);
459 
460   /* When reading RLE data, we track the offset in nibbles... */
461   state->vobsub.cur_offsets[0] = state->vobsub.pix_data[0] * 2;
462   state->vobsub.cur_offsets[1] = state->vobsub.pix_data[1] * 2;
463   state->vobsub.max_offset = state->vobsub.pix_buf_map.size * 2;
464 
465   /* Update all the palette caches */
466   gstspu_vobsub_update_palettes (dvdspu, state);
467 
468   /* Set up HL or Change Color & Contrast rect tracking */
469   if (state->vobsub.hl_rect.top != -1) {
470     state->vobsub.cur_chg_col = &state->vobsub.hl_ctrl_i;
471     state->vobsub.cur_chg_col_end = state->vobsub.cur_chg_col + 1;
472   } else if (state->vobsub.n_line_ctrl_i > 0) {
473     state->vobsub.cur_chg_col = state->vobsub.line_ctrl_i;
474     state->vobsub.cur_chg_col_end =
475         state->vobsub.cur_chg_col + state->vobsub.n_line_ctrl_i;
476   } else
477     state->vobsub.cur_chg_col = NULL;
478 
479   state->vobsub.clip_rect.left = state->vobsub.disp_rect.left;
480   state->vobsub.clip_rect.right = state->vobsub.disp_rect.right;
481 
482   /* center the image when display rectangle exceeds the video width */
483   if (width <= state->vobsub.disp_rect.right) {
484     gint left, disp_width;
485 
486     disp_width = state->vobsub.disp_rect.right - state->vobsub.disp_rect.left
487         + 1;
488     left = (width - disp_width) / 2;
489     state->vobsub.disp_rect.left = left;
490     state->vobsub.disp_rect.right = left + disp_width - 1;
491 
492     /* if it clips to the right, shift it left, but only till zero */
493     if (state->vobsub.disp_rect.right >= width) {
494       gint shift = state->vobsub.disp_rect.right - width - 1;
495       if (shift > state->vobsub.disp_rect.left)
496         shift = state->vobsub.disp_rect.left;
497       state->vobsub.disp_rect.left -= shift;
498       state->vobsub.disp_rect.right -= shift;
499     }
500 
501     /* init clip to disp */
502     state->vobsub.clip_rect.left = state->vobsub.disp_rect.left;
503     state->vobsub.clip_rect.right = state->vobsub.disp_rect.right;
504 
505     /* clip right after the shift */
506     if (state->vobsub.clip_rect.right >= width)
507       state->vobsub.clip_rect.right = width - 1;
508 
509     GST_DEBUG_OBJECT (dvdspu,
510         "clipping width to %d,%d", state->vobsub.clip_rect.left,
511         state->vobsub.clip_rect.right);
512   }
513 
514   /* for the height, bring it up till it fits as well as it can. We
515    * assume the picture is in the lower part. We should better check where it
516    * is and do something more clever. */
517   state->vobsub.clip_rect.top = state->vobsub.disp_rect.top;
518   state->vobsub.clip_rect.bottom = state->vobsub.disp_rect.bottom;
519   if (height <= state->vobsub.disp_rect.bottom) {
520 
521     /* shift it up, but only till zero */
522     gint shift = state->vobsub.disp_rect.bottom - height - 1;
523     if (shift > state->vobsub.disp_rect.top)
524       shift = state->vobsub.disp_rect.top;
525     state->vobsub.disp_rect.top -= shift;
526     state->vobsub.disp_rect.bottom -= shift;
527 
528     /* start on even line */
529     if (state->vobsub.disp_rect.top & 1) {
530       state->vobsub.disp_rect.top--;
531       state->vobsub.disp_rect.bottom--;
532     }
533 
534     /* init clip to disp */
535     state->vobsub.clip_rect.top = state->vobsub.disp_rect.top;
536     state->vobsub.clip_rect.bottom = state->vobsub.disp_rect.bottom;
537 
538     /* clip bottom after the shift */
539     if (state->vobsub.clip_rect.bottom >= height)
540       state->vobsub.clip_rect.bottom = height - 1;
541 
542     GST_DEBUG_OBJECT (dvdspu,
543         "clipping height to %d,%d", state->vobsub.clip_rect.top,
544         state->vobsub.clip_rect.bottom);
545   }
546 
547   /* We start rendering from the first line of the display rect */
548   y = state->vobsub.disp_rect.top;
549   /* We render most lines in pairs starting from an even y,
550    * accumulating 2 lines of chroma then blending it. We might need to render a
551    * single line at the start and end if the display rect starts on an odd line
552    * or ends on an even one */
553   if (y > state->vobsub.disp_rect.bottom)
554     return;                     /* Empty clip rect, nothing to do */
555 
556   /* Update our plane references to the first line of the disp_rect */
557   planes[0] += strides[0] * y;
558   planes[1] += strides[1] * (y / 2);
559   planes[2] += strides[2] * (y / 2);
560 
561   /* If the render rect starts on an odd line, render that only to start */
562   state->vobsub.cur_Y = y;
563   if (state->vobsub.cur_Y & 0x1) {
564     gboolean clip, visible = FALSE;
565 
566     clip = (state->vobsub.cur_Y < state->vobsub.clip_rect.top
567         || state->vobsub.cur_Y > state->vobsub.clip_rect.bottom);
568 
569     if (!clip) {
570       /* Render a first odd line. */
571       gstspu_vobsub_clear_comp_buffers (state);
572       state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x + 1;
573       visible |=
574           gstspu_vobsub_render_line (state, planes,
575           &state->vobsub.cur_offsets[offset_index]);
576       if (visible)
577         gstspu_vobsub_blend_comp_buffers (state, planes);
578     }
579 
580     /* Update all the output pointers */
581     state->vobsub.cur_Y++;
582     planes[0] += strides[0];
583     planes[1] += strides[1];
584     planes[2] += strides[2];
585     /* Switch the offset index 0 <=> 1 */
586     offset_index ^= 0x1;
587   }
588 
589   last_y = (state->vobsub.disp_rect.bottom - 1) & ~(0x01);
590   for (; state->vobsub.cur_Y <= last_y; state->vobsub.cur_Y++) {
591     gboolean clip, visible = FALSE;
592 
593     clip = (state->vobsub.cur_Y < state->vobsub.clip_rect.top
594         || state->vobsub.cur_Y > state->vobsub.clip_rect.bottom);
595 
596     /* Reset the compositing buffer */
597     gstspu_vobsub_clear_comp_buffers (state);
598     /* Render even line */
599     state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x;
600     gstspu_vobsub_render_line (state, planes,
601         &state->vobsub.cur_offsets[offset_index]);
602 
603     /* Advance the luminance output pointer */
604     planes[0] += strides[0];
605     /* Switch the offset index 0 <=> 1 */
606     offset_index ^= 0x1;
607 
608     state->vobsub.cur_Y++;
609 
610     /* Render odd line */
611     state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x + 1;
612     visible |=
613         gstspu_vobsub_render_line (state, planes,
614         &state->vobsub.cur_offsets[offset_index]);
615 
616     if (visible && !clip) {
617       /* Blend the accumulated UV compositing buffers onto the output */
618       gstspu_vobsub_blend_comp_buffers (state, planes);
619     }
620 
621     /* Update all the output pointers */
622     planes[0] += strides[0];
623     planes[1] += strides[1];
624     planes[2] += strides[2];
625     /* Switch the offset index 0 <=> 1 */
626     offset_index ^= 0x1;
627   }
628 
629   if (state->vobsub.cur_Y == state->vobsub.disp_rect.bottom) {
630     gboolean clip, visible = FALSE;
631 
632     clip = (state->vobsub.cur_Y < state->vobsub.clip_rect.top
633         || state->vobsub.cur_Y > state->vobsub.clip_rect.bottom);
634 
635     g_return_if_fail ((state->vobsub.disp_rect.bottom & 0x01) == 0);
636 
637     if (!clip) {
638       /* Render a remaining lone last even line. y already has the correct value
639        * after the above loop exited. */
640       gstspu_vobsub_clear_comp_buffers (state);
641       state->vobsub.comp_last_x_ptr = state->vobsub.comp_last_x;
642       visible |=
643           gstspu_vobsub_render_line (state, planes,
644           &state->vobsub.cur_offsets[offset_index]);
645       if (visible)
646         gstspu_vobsub_blend_comp_buffers (state, planes);
647     }
648   }
649 
650   /* for debugging purposes, draw a faint rectangle at the edges of the disp_rect */
651   if ((dvdspu_debug_flags & GST_DVD_SPU_DEBUG_RENDER_RECTANGLE) != 0) {
652     gstspu_vobsub_draw_highlight (state, frame, &state->vobsub.disp_rect);
653   }
654   /* For debugging purposes, draw a faint rectangle around the highlight rect */
655   if ((dvdspu_debug_flags & GST_DVD_SPU_DEBUG_HIGHLIGHT_RECTANGLE) != 0
656       && state->vobsub.hl_rect.top != -1) {
657     gstspu_vobsub_draw_highlight (state, frame, &state->vobsub.hl_rect);
658   }
659 
660   gst_buffer_unmap (state->vobsub.pix_buf, &state->vobsub.pix_buf_map);
661 }
662