+int check_combing_mask( hb_filter_private_t * pv )
+{
+ /* Go through the mask in X*Y blocks. If any of these windows
+ have threshold or more combed pixels, consider the whole
+ frame to be combed and send it on to be deinterlaced. */
+
+ /* Block mask threshold -- The number of pixels
+ in a block_width * block_height window of
+ he mask that need to show combing for the
+ whole frame to be seen as such. */
+ int threshold = pv->block_threshold;
+ int block_width = pv->block_width;
+ int block_height = pv->block_height;
+ int block_x, block_y;
+ int block_score = 0; int send_to_blend = 0;
+ uint8_t * mask_p;
+ int x, y, k;
+
+ for( k = 0; k < 1; k++ )
+ {
+ int ref_stride = pv->ref_stride[k];
+ for( y = 0; y < ( pv->height[k] - block_height ); y = y + block_height )
+ {
+ for( x = 0; x < ( pv->width[k] - block_width ); x = x + block_width )
+ {
+ block_score = 0;
+
+ for( block_y = 0; block_y < block_height; block_y++ )
+ {
+ int mask_y = y + block_y;
+ mask_p = &pv->mask[k][mask_y*ref_stride + x];
+
+ for( block_x = 0; block_x < block_width; block_x++ )
+ {
+ /* We only want to mark a pixel in a block as combed
+ if the adjacent pixels are as well. Got to
+ handle the sides separately. */
+ if( (x + block_x) == 0 )
+ {
+ if( mask_p[ 0 ] == 255 &&
+ mask_p[ 1 ] == 255 )
+ block_score++;
+ }
+ else if( (x + block_x) == (pv->width[k] -1) )
+ {
+ if( mask_p[ -1 ] == 255 &&
+ mask_p[ 0 ] == 255 )
+ block_score++;
+ }
+ else
+ {
+ if( mask_p[ -1 ] == 255 &&
+ mask_p[ 0 ] == 255 &&
+ mask_p[ 1 ] == 255 )
+ block_score++;
+ }
+ mask_p++;
+ }
+ }
+
+ if( block_score >= ( threshold / 2 ) )
+ {
+#if 0
+ hb_log("decomb: frame %i | score %i | type %s", pv->deinterlaced_frames + pv->blended_frames + pv->unfiltered_frames + 1, block_score, pv->buf_settings->flags & 16 ? "Film" : "Video");
+#endif
+ if ( block_score <= threshold && !( pv->buf_settings->flags & 16) )
+ {
+ /* Blend video content that scores between
+ ( threshold / 2 ) and threshold. */
+ send_to_blend = 1;
+ }
+ else if( block_score > threshold )
+ {
+ if( pv->buf_settings->flags & 16 )
+ {
+ /* Blend progressive content above the threshold.*/
+ return 2;
+ }
+ else
+ {
+ /* Yadif deinterlace video content above the threshold. */
+ return 1;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if( send_to_blend )
+ {
+ return 2;
+ }
+ else
+ {
+ /* Consider this frame to be uncombed. */
+ return 0;
+ }
+}
+
+void detect_combed_segment( hb_filter_private_t * pv, int segment_start, int segment_stop )
+{
+ /* A mish-mash of various comb detection tricks
+ picked up from neuron2's Decomb plugin for
+ AviSynth and tritical's IsCombedT and
+ IsCombedTIVTC plugins. */
+
+ int x, y, k, width, height;
+
+ /* Comb scoring algorithm */
+ int spatial_metric = pv->spatial_metric;
+ /* Motion threshold */
+ int mthresh = pv->motion_threshold;
+ /* Spatial threshold */
+ int athresh = pv->spatial_threshold;
+ int athresh_squared = athresh * athresh;
+ int athresh6 = 6 *athresh;
+
+ /* One pas for Y, one pass for U, one pass for V */
+ for( k = 0; k < 1; k++ )
+ {
+ int ref_stride = pv->ref_stride[k];
+ width = pv->width[k];
+ height = pv->height[k];
+
+ /* Comb detection has to start at y = 2 and end at
+ y = height - 2, because it needs to examine
+ 2 pixels above and 2 below the current pixel. */
+ if( segment_start < 2 )
+ segment_start = 2;
+ if( segment_stop > height - 2 )
+ segment_stop = height - 2;
+
+ for( y = segment_start; y < segment_stop; y++ )
+ {
+ /* These are just to make the buffer locations easier to read. */
+ int up_2 = -2*ref_stride ;
+ int up_1 = -1*ref_stride;
+ int down_1 = ref_stride;
+ int down_2 = 2*ref_stride;
+
+ /* We need to examine a column of 5 pixels
+ in the prev, cur, and next frames. */
+ uint8_t * cur = &pv->ref[1][k][y*ref_stride];
+ uint8_t * prev = &pv->ref[0][k][y*ref_stride];
+ uint8_t * next = &pv->ref[2][k][y*ref_stride];
+ uint8_t * mask = &pv->mask[k][y*ref_stride];
+
+ for( x = 0; x < width; x++ )
+ {
+ int up_diff = cur[0] - cur[up_1];
+ int down_diff = cur[0] - cur[down_1];
+
+ if( ( up_diff > athresh && down_diff > athresh ) ||
+ ( up_diff < -athresh && down_diff < -athresh ) )
+ {
+ /* The pixel above and below are different,
+ and they change in the same "direction" too.*/
+ int motion = 0;
+ if( mthresh > 0 )
+ {
+ /* Make sure there's sufficient motion between frame t-1 to frame t+1. */
+ if( abs( prev[0] - cur[0] ) > mthresh &&
+ abs( cur[up_1] - next[up_1] ) > mthresh &&
+ abs( cur[down_1] - next[down_1] ) > mthresh )
+ motion++;
+ if( abs( next[0] - cur[0] ) > mthresh &&
+ abs( prev[up_1] - cur[up_1] ) > mthresh &&
+ abs( prev[down_1] - cur[down_1] ) > mthresh )
+ motion++;
+ }
+ else
+ {
+ /* User doesn't want to check for motion,
+ so move on to the spatial check. */
+ motion = 1;
+ }
+
+ if( motion || ( pv->deinterlaced_frames==0 && pv->blended_frames==0 && pv->unfiltered_frames==0) )
+ {
+ /* That means it's time for the spatial check.
+ We've got several options here. */
+ if( spatial_metric == 0 )
+ {
+ /* Simple 32detect style comb detection */
+ if( ( abs( cur[0] - cur[down_2] ) < 10 ) &&
+ ( abs( cur[0] - cur[down_1] ) > 15 ) )
+ {
+ mask[0] = 255;
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+ }
+ else if( spatial_metric == 1 )
+ {
+ /* This, for comparison, is what IsCombed uses.
+ It's better, but still noise senstive. */
+ int combing = ( cur[up_1] - cur[0] ) *
+ ( cur[down_1] - cur[0] );
+
+ if( combing > athresh_squared )
+ mask[0] = 255;
+ else
+ mask[0] = 0;
+ }
+ else if( spatial_metric == 2 )
+ {
+ /* Tritical's noise-resistant combing scorer.
+ The check is done on a bob+blur convolution. */
+ int combing = abs( cur[up_2]
+ + ( 4 * cur[0] )
+ + cur[down_2]
+ - ( 3 * ( cur[up_1]
+ + cur[down_1] ) ) );
+
+ /* If the frame is sufficiently combed,
+ then mark it down on the mask as 255. */
+ if( combing > athresh6 )
+ {
+ mask[0] = 255;
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+ }
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+ }
+ else
+ {
+ mask[0] = 0;
+ }
+
+ cur++;
+ prev++;
+ next++;
+ mask++;
+ }
+ }
+ }
+}
+
+// This function calls all the eedi2 filters in sequence for a given plane.
+// It outputs the final interpolated image to pv->eedi_full[DST2PF].
+void eedi2_interpolate_plane( hb_filter_private_t * pv, int k )
+{
+ /* We need all these pointers. No, seriously.
+ I swear. It's not a joke. They're used.
+ All nine of them. */
+ uint8_t * mskp = pv->eedi_half[MSKPF][k];
+ uint8_t * srcp = pv->eedi_half[SRCPF][k];
+ uint8_t * tmpp = pv->eedi_half[TMPPF][k];
+ uint8_t * dstp = pv->eedi_half[DSTPF][k];
+ uint8_t * dst2p = pv->eedi_full[DST2PF][k];
+ uint8_t * tmp2p2 = pv->eedi_full[TMP2PF2][k];
+ uint8_t * msk2p = pv->eedi_full[MSK2PF][k];
+ uint8_t * tmp2p = pv->eedi_full[TMP2PF][k];
+ uint8_t * dst2mp = pv->eedi_full[DST2MPF][k];
+ int * cx2 = pv->cx2;
+ int * cy2 = pv->cy2;
+ int * cxy = pv->cxy;
+ int * tmpc = pv->tmpc;
+
+ int pitch = pv->ref_stride[k];
+ int height = pv->height[k]; int width = pv->width[k];
+ int half_height = height / 2;
+
+ // edge mask
+ eedi2_build_edge_mask( mskp, pitch, srcp, pitch,
+ pv->magnitude_threshold, pv->variance_threshold, pv->laplacian_threshold,
+ half_height, width );
+ eedi2_erode_edge_mask( mskp, pitch, tmpp, pitch, pv->erosion_threshold, half_height, width );
+ eedi2_dilate_edge_mask( tmpp, pitch, mskp, pitch, pv->dilation_threshold, half_height, width );
+ eedi2_erode_edge_mask( mskp, pitch, tmpp, pitch, pv->erosion_threshold, half_height, width );
+ eedi2_remove_small_gaps( tmpp, pitch, mskp, pitch, half_height, width );
+
+ // direction mask
+ eedi2_calc_directions( k, mskp, pitch, srcp, pitch, tmpp, pitch,
+ pv->maximum_search_distance, pv->noise_threshold,
+ half_height, width );
+ eedi2_filter_dir_map( mskp, pitch, tmpp, pitch, dstp, pitch, half_height, width );
+ eedi2_expand_dir_map( mskp, pitch, dstp, pitch, tmpp, pitch, half_height, width );
+ eedi2_filter_map( mskp, pitch, tmpp, pitch, dstp, pitch, half_height, width );
+
+ // upscale 2x vertically
+ eedi2_upscale_by_2( srcp, dst2p, half_height, pitch );
+ eedi2_upscale_by_2( dstp, tmp2p2, half_height, pitch );
+ eedi2_upscale_by_2( mskp, msk2p, half_height, pitch );
+
+ // upscale the direction mask
+ eedi2_mark_directions_2x( msk2p, pitch, tmp2p2, pitch, tmp2p, pitch, pv->tff, height, width );
+ eedi2_filter_dir_map_2x( msk2p, pitch, tmp2p, pitch, dst2mp, pitch, pv->tff, height, width );
+ eedi2_expand_dir_map_2x( msk2p, pitch, dst2mp, pitch, tmp2p, pitch, pv->tff, height, width );
+ eedi2_fill_gaps_2x( msk2p, pitch, tmp2p, pitch, dst2mp, pitch, pv->tff, height, width );
+ eedi2_fill_gaps_2x( msk2p, pitch, dst2mp, pitch, tmp2p, pitch, pv->tff, height, width );
+
+ // interpolate a full-size plane
+ eedi2_interpolate_lattice( k, tmp2p, pitch, dst2p, pitch, tmp2p2, pitch, pv->tff,
+ pv->noise_threshold, height, width );
+
+ if( pv->post_processing == 1 || pv->post_processing == 3 )
+ {
+ // make sure the edge directions are consistent
+ eedi2_bit_blit( tmp2p2, pitch, tmp2p, pitch, pv->width[k], pv->height[k] );
+ eedi2_filter_dir_map_2x( msk2p, pitch, tmp2p, pitch, dst2mp, pitch, pv->tff, height, width );
+ eedi2_expand_dir_map_2x( msk2p, pitch, dst2mp, pitch, tmp2p, pitch, pv->tff, height, width );
+ eedi2_post_process( tmp2p, pitch, tmp2p2, pitch, dst2p, pitch, pv->tff, height, width );
+ }
+ if( pv->post_processing == 2 || pv->post_processing == 3 )
+ {
+ // filter junctions and corners
+ eedi2_gaussian_blur1( srcp, pitch, tmpp, pitch, srcp, pitch, half_height, width );
+ eedi2_calc_derivatives( srcp, pitch, half_height, width, cx2, cy2, cxy );
+ eedi2_gaussian_blur_sqrt2( cx2, tmpc, cx2, pitch, half_height, width);
+ eedi2_gaussian_blur_sqrt2( cy2, tmpc, cy2, pitch, half_height, width);
+ eedi2_gaussian_blur_sqrt2( cxy, tmpc, cxy, pitch, half_height, width);
+ eedi2_post_process_corner( cx2, cy2, cxy, pitch, tmp2p2, pitch, dst2p, pitch, height, width, pv->tff );
+ }
+}
+
+/*
+ * eedi2 interpolate this plane in a single thread.
+ */
+void eedi2_filter_thread( void *thread_args_v )
+{
+ eedi2_arguments_t *eedi2_work = NULL;
+ hb_filter_private_t * pv;
+ int run = 1;
+ int plane;
+ eedi2_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ plane = thread_args->plane;
+
+ hb_log("eedi2 thread started for plane %d", plane);
+
+ while( run )
+ {
+ /*
+ * Wait here until there is work to do. hb_lock() blocks until
+ * render releases it to say that there is more work to do.
+ */
+ hb_lock( pv->eedi2_begin_lock[plane] );
+
+ eedi2_work = &pv->eedi2_arguments[plane];
+
+ if( eedi2_work->stop )
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ run = 0;
+ continue;
+ }
+
+ /*
+ * Process plane
+ */
+ eedi2_interpolate_plane( pv, plane );
+
+ /*
+ * Finished this segment, let everyone know.
+ */
+ hb_unlock( pv->eedi2_complete_lock[plane] );
+ }
+ free( thread_args_v );
+}
+
+// Sets up the input field planes for EEDI2 in pv->eedi_half[SRCPF]
+// and then runs eedi2_filter_thread for each plane.
+void eedi2_planer( hb_filter_private_t * pv )
+{
+ /* Copy the first field from the source to a half-height frame. */
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ int pitch = pv->ref_stride[i];
+ int start_line = !pv->tff;
+ eedi2_fill_half_height_buffer_plane( &pv->ref[1][i][pitch*start_line], pv->eedi_half[SRCPF][i], pitch, pv->height[i] );
+ }
+
+ int plane;
+ for( plane = 0; plane < 3; plane++ )
+ {
+ /*
+ * Let the thread for this plane know that we've setup work
+ * for it by releasing the begin lock (ensuring that the
+ * complete lock is already locked so that we block when
+ * we try to lock it again below).
+ */
+ hb_lock( pv->eedi2_complete_lock[plane] );
+ hb_unlock( pv->eedi2_begin_lock[plane] );
+ }
+
+ /*
+ * Wait until all three threads have completed by trying to get
+ * the complete lock that we locked earlier for each thread, which
+ * will block until that thread has completed the work on that
+ * plane.
+ */
+ for( plane = 0; plane < 3; plane++ )
+ {
+ hb_lock( pv->eedi2_complete_lock[plane] );
+ hb_unlock( pv->eedi2_complete_lock[plane] );
+ }
+}
+
+
+/*
+ * comb detect this segment of all three planes in a single thread.
+ */
+void decomb_filter_thread( void *thread_args_v )
+{
+ decomb_arguments_t *decomb_work = NULL;
+ hb_filter_private_t * pv;
+ int run = 1;
+ int segment, segment_start, segment_stop, plane;
+ decomb_thread_arg_t *thread_args = thread_args_v;
+
+ pv = thread_args->pv;
+ segment = thread_args->segment;
+
+ hb_log("decomb thread started for segment %d", segment);
+
+ while( run )
+ {
+ /*
+ * Wait here until there is work to do. hb_lock() blocks until
+ * render releases it to say that there is more work to do.
+ */
+ hb_lock( pv->decomb_begin_lock[segment] );
+
+ decomb_work = &pv->decomb_arguments[segment];
+
+ if( decomb_work->stop )
+ {
+ /*
+ * No more work to do, exit this thread.
+ */
+ run = 0;
+ continue;
+ }
+
+ /*
+ * Process segment (for now just from luma)
+ */
+ for( plane = 0; plane < 1; plane++)
+ {
+
+ int h = pv->height[plane];
+ segment_start = ( h / pv->cpu_count ) * segment;
+ if( segment == pv->cpu_count - 1 )
+ {
+ /*
+ * Final segment
+ */
+ segment_stop = h;
+ } else {
+ segment_stop = ( h / pv->cpu_count ) * ( segment + 1 );
+ }
+
+ detect_combed_segment( pv, segment_start, segment_stop );
+ }
+ /*
+ * Finished this segment, let everyone know.
+ */
+ hb_unlock( pv->decomb_complete_lock[segment] );
+ }
+ free( thread_args_v );
+}
+
+int comb_segmenter( hb_filter_private_t * pv )
+{
+ int segment;
+
+ for( segment = 0; segment < pv->cpu_count; segment++ )
+ {
+ /*
+ * Let the thread for this plane know that we've setup work
+ * for it by releasing the begin lock (ensuring that the
+ * complete lock is already locked so that we block when
+ * we try to lock it again below).
+ */
+ hb_lock( pv->decomb_complete_lock[segment] );
+ hb_unlock( pv->decomb_begin_lock[segment] );
+ }
+
+ /*
+ * Wait until all three threads have completed by trying to get
+ * the complete lock that we locked earlier for each thread, which
+ * will block until that thread has completed the work on that
+ * plane.
+ */
+ for( segment = 0; segment < pv->cpu_count; segment++ )
+ {
+ hb_lock( pv->decomb_complete_lock[segment] );
+ hb_unlock( pv->decomb_complete_lock[segment] );
+ }
+
+ return check_combing_mask( pv );
+}
+