bool derive_edgeFlags_CTBRow(de265_image* img, int ctby)
{
  const seq_parameter_set& sps = img->get_sps();
  const pic_parameter_set& pps = img->get_pps();

  const int minCbSize = sps.MinCbSizeY;
  bool deblocking_enabled=false; // whether deblocking is enabled in some part of the image

  int ctb_mask = (1<<sps.Log2CtbSizeY)-1;
  int picWidthInCtbs = sps.PicWidthInCtbsY;
  int ctbshift = sps.Log2CtbSizeY;


  int cb_y_start = ( ctby    << sps.Log2CtbSizeY) >> sps.Log2MinCbSizeY;
  int cb_y_end   = ((ctby+1) << sps.Log2CtbSizeY) >> sps.Log2MinCbSizeY;

  cb_y_end = std::min(cb_y_end, sps.PicHeightInMinCbsY);

  for (int cb_y=cb_y_start;cb_y<cb_y_end;cb_y++)
    for (int cb_x=0;cb_x<img->get_sps().PicWidthInMinCbsY;cb_x++)
      {
        int log2CbSize = img->get_log2CbSize_cbUnits(cb_x,cb_y);
        if (log2CbSize==0) {
          continue;
        }

        // we are now at the top corner of a CB

        int x0 = cb_x * minCbSize;
        int y0 = cb_y * minCbSize;

        int x0ctb = x0 >> ctbshift;
        int y0ctb = y0 >> ctbshift;

        // check for corrupted streams
        if (img->is_SliceHeader_available(x0,y0)==false) {
          return false;
        }

        // check whether we should filter this slice

        slice_segment_header* shdr = img->get_SliceHeader(x0,y0);

        // check whether to filter left and top edge

        uint8_t filterLeftCbEdge = DEBLOCK_FLAG_VERTI;
        uint8_t filterTopCbEdge  = DEBLOCK_FLAG_HORIZ;
        if (x0 == 0) filterLeftCbEdge = 0;
        if (y0 == 0) filterTopCbEdge  = 0;

        // check for slice and tile boundaries (8.7.2, step 2 in both processes)

        if (x0 && ((x0 & ctb_mask) == 0)) { // left edge at CTB boundary
          if (shdr->slice_loop_filter_across_slices_enabled_flag == 0 &&
              img->is_SliceHeader_available(x0-1,y0) && // for corrupted streams
              shdr->SliceAddrRS != img->get_SliceHeader(x0-1,y0)->SliceAddrRS)
            {
              filterLeftCbEdge = 0;
            }
          else if (pps.loop_filter_across_tiles_enabled_flag == 0 &&
                   pps.TileIdRS[  x0ctb           +y0ctb*picWidthInCtbs] !=
                   pps.TileIdRS[((x0-1)>>ctbshift)+y0ctb*picWidthInCtbs]) {
            filterLeftCbEdge = 0;
          }
        }

        if (y0 && ((y0 & ctb_mask) == 0)) { // top edge at CTB boundary
          if (shdr->slice_loop_filter_across_slices_enabled_flag == 0 &&
              img->is_SliceHeader_available(x0,y0-1) && // for corrupted streams
              shdr->SliceAddrRS != img->get_SliceHeader(x0,y0-1)->SliceAddrRS)
            {
              filterTopCbEdge = 0;
            }
          else if (pps.loop_filter_across_tiles_enabled_flag == 0 &&
                   pps.TileIdRS[x0ctb+  y0ctb           *picWidthInCtbs] !=
                   pps.TileIdRS[x0ctb+((y0-1)>>ctbshift)*picWidthInCtbs]) {
            filterTopCbEdge = 0;
          }
        }


        // mark edges

        if (shdr->slice_deblocking_filter_disabled_flag==0) {
          deblocking_enabled=true;

          markTransformBlockBoundary(img, x0,y0, log2CbSize,0,
                                     filterLeftCbEdge, filterTopCbEdge);

          markPredictionBlockBoundary(img, x0,y0, log2CbSize,
                                      filterLeftCbEdge, filterTopCbEdge);
        }
      }

  return deblocking_enabled;
}