| @@ -0,0 +1,939 @@ |
| 1 | +/* |
| 2 | + * Copyright (c) 2016 ReneBrals |
| 3 | + * Copyright (c) 2021 Paul B Mahol |
| 4 | + * |
| 5 | + * This file is part of FFmpeg. |
| 6 | + * |
| 7 | + * Permission is hereby granted, free of charge, to any person obtaining a copy |
| 8 | + * of this software and associated documentation files (the "Software"), to deal |
| 9 | + * in the Software without restriction, including without limitation the rights |
| 10 | + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 11 | + * copies of the Software, and to permit persons to whom the Software is |
| 12 | + * furnished to do so, subject to the following conditions: |
| 13 | + * |
| 14 | + * The above copyright notice and this permission notice shall be included in all |
| 15 | + * copies or substantial portions of the Software. |
| 16 | + * |
| 17 | + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 18 | + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 19 | + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 20 | + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 21 | + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 22 | + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 23 | + * SOFTWARE. |
| 24 | + */ |
| 25 | + |
| 26 | +#include "libavutil/avassert.h" |
| 27 | +#include "libavutil/imgutils.h" |
| 28 | +#include "libavutil/intreadwrite.h" |
| 29 | +#include "libavutil/opt.h" |
| 30 | +#include "libavutil/pixdesc.h" |
| 31 | +#include "avfilter.h" |
| 32 | +#include "formats.h" |
| 33 | +#include "framesync.h" |
| 34 | +#include "internal.h" |
| 35 | +#include "video.h" |
| 36 | + |
| 37 | +enum MorphModes { |
| 38 | + ERODE, |
| 39 | + DILATE, |
| 40 | + OPEN, |
| 41 | + CLOSE, |
| 42 | + NB_MODES |
| 43 | +}; |
| 44 | + |
| 45 | +typedef struct IPlane { |
| 46 | + uint8_t **img; |
| 47 | + int w, h; |
| 48 | + int range; |
| 49 | + int depth; |
| 50 | + int type_size; |
| 51 | + |
| 52 | + void (*max)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x); |
| 53 | + void (*min)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x); |
| 54 | + void (*max_in_place)(uint8_t *a, const uint8_t *b, int x); |
| 55 | + void (*min_in_place)(uint8_t *a, const uint8_t *b, int x); |
| 56 | +} IPlane; |
| 57 | + |
| 58 | +typedef struct LUT { |
| 59 | + uint8_t ***arr; |
| 60 | + int min_r; |
| 61 | + int max_r; |
| 62 | + int I; |
| 63 | + int X; |
| 64 | + int pre_pad_x; |
| 65 | + int type_size; |
| 66 | +} LUT; |
| 67 | + |
| 68 | +typedef struct chord { |
| 69 | + int x; |
| 70 | + int y; |
| 71 | + int l; |
| 72 | + int i; |
| 73 | +} chord; |
| 74 | + |
| 75 | +typedef struct chord_set { |
| 76 | + chord *C; |
| 77 | + int size; |
| 78 | + int cap; |
| 79 | + |
| 80 | + int *R; |
| 81 | + int Lnum; |
| 82 | + |
| 83 | + int minX; |
| 84 | + int maxX; |
| 85 | + int minY; |
| 86 | + int maxY; |
| 87 | + unsigned nb_elements; |
| 88 | +} chord_set; |
| 89 | + |
| 90 | +typedef struct MorphoContext { |
| 91 | + const AVClass *class; |
| 92 | + FFFrameSync fs; |
| 93 | + |
| 94 | + chord_set SE[4]; |
| 95 | + IPlane SEimg[4]; |
| 96 | + IPlane g[4], f[4], h[4]; |
| 97 | + LUT Ty[2][4]; |
| 98 | + |
| 99 | + int mode; |
| 100 | + int planes; |
| 101 | + int structures; |
| 102 | + |
| 103 | + int planewidth[4]; |
| 104 | + int planeheight[4]; |
| 105 | + int splanewidth[4]; |
| 106 | + int splaneheight[4]; |
| 107 | + int depth; |
| 108 | + int type_size; |
| 109 | + int nb_planes; |
| 110 | + |
| 111 | + int got_structure[4]; |
| 112 | + |
| 113 | + AVFrame *temp; |
| 114 | + |
| 115 | + int64_t *plane_f, *plane_g; |
| 116 | +} MorphoContext; |
| 117 | + |
| 118 | +#define OFFSET(x) offsetof(MorphoContext, x) |
| 119 | +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM |
| 120 | + |
| 121 | +static const AVOption morpho_options[] = { |
| 122 | + { "mode", "set morphological transform", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_MODES-1, FLAGS, "mode" }, |
| 123 | + { "erode", NULL, 0, AV_OPT_TYPE_CONST, {.i64=ERODE}, 0, 0, FLAGS, "mode" }, |
| 124 | + { "dilate", NULL, 0, AV_OPT_TYPE_CONST, {.i64=DILATE}, 0, 0, FLAGS, "mode" }, |
| 125 | + { "open", NULL, 0, AV_OPT_TYPE_CONST, {.i64=OPEN}, 0, 0, FLAGS, "mode" }, |
| 126 | + { "close", NULL, 0, AV_OPT_TYPE_CONST, {.i64=CLOSE}, 0, 0, FLAGS, "mode" }, |
| 127 | + { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=7}, 0, 15, FLAGS }, |
| 128 | + { "structure", "when to process structures", OFFSET(structures), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS, "str" }, |
| 129 | + { "first", "process only first structure, ignore rest", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, "str" }, |
| 130 | + { "all", "process all structure", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, "str" }, |
| 131 | + { NULL } |
| 132 | +}; |
| 133 | + |
| 134 | +FRAMESYNC_DEFINE_CLASS(morpho, MorphoContext, fs); |
| 135 | + |
| 136 | +static int query_formats(AVFilterContext *ctx) |
| 137 | +{ |
| 138 | + static const enum AVPixelFormat pix_fmts[] = { |
| 139 | + AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, |
| 140 | + AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, |
| 141 | + AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, |
| 142 | + AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, |
| 143 | + AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, |
| 144 | + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, |
| 145 | + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, AV_PIX_FMT_GBRP9, |
| 146 | + AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, |
| 147 | + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, |
| 148 | + AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12, |
| 149 | + AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, |
| 150 | + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, |
| 151 | + AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, |
| 152 | + AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12, |
| 153 | + AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, |
| 154 | + AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, |
| 155 | + AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, |
| 156 | + AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16, |
| 157 | + AV_PIX_FMT_NONE |
| 158 | + }; |
| 159 | + |
| 160 | + return ff_set_common_formats_from_list(ctx, pix_fmts); |
| 161 | +} |
| 162 | + |
| 163 | +static void min_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x) |
| 164 | +{ |
| 165 | + for (int i = 0; i < x; i++) |
| 166 | + c[i] = FFMIN(b[i], a[i]); |
| 167 | +} |
| 168 | + |
| 169 | +static void mininplace_fun(uint8_t *a, const uint8_t *b, int x) |
| 170 | +{ |
| 171 | + for (int i = 0; i < x; i++) |
| 172 | + a[i] = FFMIN(a[i], b[i]); |
| 173 | +} |
| 174 | + |
| 175 | +static void max_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x) |
| 176 | +{ |
| 177 | + for (int i = 0; i < x; i++) |
| 178 | + c[i] = FFMAX(a[i], b[i]); |
| 179 | +} |
| 180 | + |
| 181 | +static void maxinplace_fun(uint8_t *a, const uint8_t *b, int x) |
| 182 | +{ |
| 183 | + for (int i = 0; i < x; i++) |
| 184 | + a[i] = FFMAX(a[i], b[i]); |
| 185 | +} |
| 186 | + |
| 187 | +static void min16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x) |
| 188 | +{ |
| 189 | + const uint16_t *a = (const uint16_t *)aa; |
| 190 | + const uint16_t *b = (const uint16_t *)bb; |
| 191 | + uint16_t *c = (uint16_t *)cc; |
| 192 | + |
| 193 | + for (int i = 0; i < x; i++) |
| 194 | + c[i] = FFMIN(b[i], a[i]); |
| 195 | +} |
| 196 | + |
| 197 | +static void mininplace16_fun(uint8_t *aa, const uint8_t *bb, int x) |
| 198 | +{ |
| 199 | + uint16_t *a = (uint16_t *)aa; |
| 200 | + const uint16_t *b = (const uint16_t *)bb; |
| 201 | + |
| 202 | + for (int i = 0; i < x; i++) |
| 203 | + a[i] = FFMIN(a[i], b[i]); |
| 204 | +} |
| 205 | + |
| 206 | +static void max16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x) |
| 207 | +{ |
| 208 | + const uint16_t *a = (const uint16_t *)aa; |
| 209 | + const uint16_t *b = (const uint16_t *)bb; |
| 210 | + uint16_t *c = (uint16_t *)cc; |
| 211 | + |
| 212 | + for (int i = 0; i < x; i++) |
| 213 | + c[i] = FFMAX(a[i], b[i]); |
| 214 | +} |
| 215 | + |
| 216 | +static void maxinplace16_fun(uint8_t *aa, const uint8_t *bb, int x) |
| 217 | +{ |
| 218 | + uint16_t *a = (uint16_t *)aa; |
| 219 | + const uint16_t *b = (const uint16_t *)bb; |
| 220 | + |
| 221 | + for (int i = 0; i < x; i++) |
| 222 | + a[i] = FFMAX(a[i], b[i]); |
| 223 | +} |
| 224 | + |
| 225 | +static int alloc_lut(LUT *Ty, chord_set *SE, int type_size, int mode) |
| 226 | +{ |
| 227 | + const int size = Ty->max_r + 1 - Ty->min_r; |
| 228 | + int pre_pad_x = 0; |
| 229 | + |
| 230 | + if (SE->minX < 0) |
| 231 | + pre_pad_x = 0 - SE->minX; |
| 232 | + Ty->pre_pad_x = pre_pad_x; |
| 233 | + Ty->type_size = type_size; |
| 234 | + |
| 235 | + Ty->arr = av_calloc(size, sizeof(*Ty->arr)); |
| 236 | + if (!Ty->arr) |
| 237 | + return AVERROR(ENOMEM); |
| 238 | + for (int r = 0; r < Ty->max_r - Ty->min_r + 1; r++) { |
| 239 | + Ty->arr[r] = av_calloc(Ty->I, sizeof(uint8_t *)); |
| 240 | + if (!Ty->arr[r]) |
| 241 | + return AVERROR(ENOMEM); |
| 242 | + for (int i = 0; i < Ty->I; i++) { |
| 243 | + Ty->arr[r][i] = av_calloc(Ty->X + pre_pad_x, type_size); |
| 244 | + if (!Ty->arr[r][i]) |
| 245 | + return AVERROR(ENOMEM); |
| 246 | + if (mode == ERODE) |
| 247 | + memset(Ty->arr[r][i], UINT8_MAX, pre_pad_x * type_size); |
| 248 | + /* Shifting the X index such that negative indices correspond to |
| 249 | + * the pre-padding. |
| 250 | + */ |
| 251 | + Ty->arr[r][i] = &(Ty->arr[r][i][pre_pad_x * type_size]); |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + Ty->arr = &(Ty->arr[0 - Ty->min_r]); |
| 256 | + |
| 257 | + return 0; |
| 258 | +} |
| 259 | + |
| 260 | +static void free_lut(LUT *table) |
| 261 | +{ |
| 262 | + uint8_t ***rp; |
| 263 | + |
| 264 | + if (!table->arr) |
| 265 | + return; |
| 266 | + |
| 267 | + // The R index was shifted, create a pointer to the original array |
| 268 | + rp = &(table->arr[table->min_r]); |
| 269 | + |
| 270 | + for (int r = table->min_r; r <= table->max_r; r++) { |
| 271 | + for (int i = 0; i < table->I; i++) { |
| 272 | + // The X index was also shifted, for padding purposes. |
| 273 | + av_free(table->arr[r][i] - table->pre_pad_x * table->type_size); |
| 274 | + } |
| 275 | + av_freep(&table->arr[r]); |
| 276 | + } |
| 277 | + av_freep(&rp); |
| 278 | +} |
| 279 | + |
| 280 | +static void circular_swap(LUT *Ty) |
| 281 | +{ |
| 282 | + /* |
| 283 | + * Swap the pointers to r-indices in a circle. This is useful because |
| 284 | + * Ty(r,i,x) = Ty-1(r+1,i,x) for r < ymax. |
| 285 | + */ |
| 286 | + if (Ty->max_r - Ty->min_r > 0) { |
| 287 | + uint8_t **Ty0 = Ty->arr[Ty->min_r]; |
| 288 | + |
| 289 | + for (int r = Ty->min_r; r < Ty->max_r; r++) |
| 290 | + Ty->arr[r] = Ty->arr[r + 1]; |
| 291 | + |
| 292 | + Ty->arr[Ty->max_r] = Ty0; |
| 293 | + } |
| 294 | +} |
| 295 | + |
| 296 | +static void compute_min_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y) |
| 297 | +{ |
| 298 | + if (y + r >= 0 && y + r < f->h) { |
| 299 | + memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size); |
| 300 | + } else { |
| 301 | + memset(Ty->arr[r][0], UINT8_MAX, Ty->X * Ty->type_size); |
| 302 | + } |
| 303 | + |
| 304 | + for (int i = 1; i < SE->Lnum; i++) { |
| 305 | + int d = SE->R[i] - SE->R[i - 1]; |
| 306 | + |
| 307 | + f->min(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size, |
| 308 | + Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size, |
| 309 | + Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size, |
| 310 | + Ty->X + Ty->pre_pad_x - d); |
| 311 | + memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size, |
| 312 | + Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size, |
| 313 | + d * f->type_size); |
| 314 | + } |
| 315 | +} |
| 316 | + |
| 317 | +static void update_min_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num) |
| 318 | +{ |
| 319 | + for (int i = 0; i < num; i++) |
| 320 | + circular_swap(Ty); |
| 321 | + |
| 322 | + compute_min_row(f, Ty, SE, Ty->max_r - tid, y); |
| 323 | +} |
| 324 | + |
| 325 | +static int compute_min_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num) |
| 326 | +{ |
| 327 | + if (Ty->I != SE->Lnum || |
| 328 | + Ty->X != f->w || |
| 329 | + Ty->min_r != SE->minY || |
| 330 | + Ty->max_r != SE->maxY + num - 1) { |
| 331 | + int ret; |
| 332 | + |
| 333 | + free_lut(Ty); |
| 334 | + |
| 335 | + Ty->I = SE->Lnum; |
| 336 | + Ty->X = f->w; |
| 337 | + Ty->min_r = SE->minY; |
| 338 | + Ty->max_r = SE->maxY + num - 1; |
| 339 | + ret = alloc_lut(Ty, SE, f->type_size, ERODE); |
| 340 | + if (ret < 0) |
| 341 | + return ret; |
| 342 | + } |
| 343 | + |
| 344 | + for (int r = Ty->min_r; r <= Ty->max_r; r++) |
| 345 | + compute_min_row(f, Ty, SE, r, y); |
| 346 | + |
| 347 | + return 0; |
| 348 | +} |
| 349 | + |
| 350 | +static void compute_max_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y) |
| 351 | +{ |
| 352 | + if (y + r >= 0 && y + r < f->h) { |
| 353 | + memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size); |
| 354 | + } else { |
| 355 | + memset(Ty->arr[r][0], 0, Ty->X * Ty->type_size); |
| 356 | + } |
| 357 | + |
| 358 | + for (int i = 1; i < SE->Lnum; i++) { |
| 359 | + int d = SE->R[i] - SE->R[i - 1]; |
| 360 | + |
| 361 | + f->max(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size, |
| 362 | + Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size, |
| 363 | + Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size, |
| 364 | + Ty->X + Ty->pre_pad_x - d); |
| 365 | + memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size, |
| 366 | + Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size, |
| 367 | + d * f->type_size); |
| 368 | + } |
| 369 | +} |
| 370 | + |
| 371 | +static void update_max_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num) |
| 372 | +{ |
| 373 | + for (int i = 0; i < num; i++) |
| 374 | + circular_swap(Ty); |
| 375 | + |
| 376 | + compute_max_row(f, Ty, SE, Ty->max_r - tid, y); |
| 377 | +} |
| 378 | + |
| 379 | +static int compute_max_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num) |
| 380 | +{ |
| 381 | + if (Ty->I != SE->Lnum || |
| 382 | + Ty->X != f->w || |
| 383 | + Ty->min_r != SE->minY || |
| 384 | + Ty->max_r != SE->maxY + num - 1) { |
| 385 | + int ret; |
| 386 | + |
| 387 | + free_lut(Ty); |
| 388 | + |
| 389 | + Ty->I = SE->Lnum; |
| 390 | + Ty->X = f->w; |
| 391 | + Ty->min_r = SE->minY; |
| 392 | + Ty->max_r = SE->maxY + num - 1; |
| 393 | + ret = alloc_lut(Ty, SE, f->type_size, DILATE); |
| 394 | + if (ret < 0) |
| 395 | + return ret; |
| 396 | + } |
| 397 | + |
| 398 | + for (int r = Ty->min_r; r <= Ty->max_r; r++) |
| 399 | + compute_max_row(f, Ty, SE, r, y); |
| 400 | + |
| 401 | + return 0; |
| 402 | +} |
| 403 | + |
| 404 | +static void line_dilate(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid) |
| 405 | +{ |
| 406 | + memset(g->img[y], 0, g->w * g->type_size); |
| 407 | + |
| 408 | + for (int c = 0; c < SE->size; c++) { |
| 409 | + g->max_in_place(g->img[y], |
| 410 | + Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size, |
| 411 | + av_clip(g->w - SE->C[c].x, 0, g->w)); |
| 412 | + } |
| 413 | +} |
| 414 | + |
| 415 | +static void line_erode(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid) |
| 416 | +{ |
| 417 | + memset(g->img[y], UINT8_MAX, g->w * g->type_size); |
| 418 | + |
| 419 | + for (int c = 0; c < SE->size; c++) { |
| 420 | + g->min_in_place(g->img[y], |
| 421 | + Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size, |
| 422 | + av_clip(g->w - SE->C[c].x, 0, g->w)); |
| 423 | + } |
| 424 | +} |
| 425 | + |
| 426 | +static int dilate(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty) |
| 427 | +{ |
| 428 | + int ret = compute_max_lut(Ty, f, SE, 0, 1); |
| 429 | + if (ret < 0) |
| 430 | + return ret; |
| 431 | + |
| 432 | + line_dilate(g, Ty, SE, 0, 0); |
| 433 | + for (int y = 1; y < f->h; y++) { |
| 434 | + update_max_lut(f, Ty, SE, y, 0, 1); |
| 435 | + line_dilate(g, Ty, SE, y, 0); |
| 436 | + } |
| 437 | + |
| 438 | + return 0; |
| 439 | +} |
| 440 | + |
| 441 | +static int erode(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty) |
| 442 | +{ |
| 443 | + int ret = compute_min_lut(Ty, f, SE, 0, 1); |
| 444 | + if (ret < 0) |
| 445 | + return ret; |
| 446 | + |
| 447 | + line_erode(g, Ty, SE, 0, 0); |
| 448 | + for (int y = 1; y < f->h; y++) { |
| 449 | + update_min_lut(f, Ty, SE, y, 0, 1); |
| 450 | + line_erode(g, Ty, SE, y, 0); |
| 451 | + } |
| 452 | + |
| 453 | + return 0; |
| 454 | +} |
| 455 | + |
| 456 | +static int insert_chord_set(chord_set *chords, chord c) |
| 457 | +{ |
| 458 | + // Checking if chord fits in dynamic array, resize if not. |
| 459 | + if (chords->size == chords->cap) { |
| 460 | + chords->C = av_realloc_f(chords->C, chords->cap * 2, sizeof(chord)); |
| 461 | + if (!chords->C) |
| 462 | + return AVERROR(ENOMEM); |
| 463 | + chords->cap *= 2; |
| 464 | + } |
| 465 | + |
| 466 | + // Add the chord to the dynamic array. |
| 467 | + chords->C[chords->size].x = c.x; |
| 468 | + chords->C[chords->size].y = c.y; |
| 469 | + chords->C[chords->size++].l = c.l; |
| 470 | + |
| 471 | + // Update minimum/maximum x/y offsets of the chord set. |
| 472 | + chords->minX = FFMIN(chords->minX, c.x); |
| 473 | + chords->maxX = FFMAX(chords->maxX, c.x); |
| 474 | + |
| 475 | + chords->minY = FFMIN(chords->minY, c.y); |
| 476 | + chords->maxY = FFMAX(chords->maxY, c.y); |
| 477 | + |
| 478 | + return 0; |
| 479 | +} |
| 480 | + |
| 481 | +static void free_chord_set(chord_set *SE) |
| 482 | +{ |
| 483 | + av_freep(&SE->C); |
| 484 | + SE->size = 0; |
| 485 | + SE->cap = 0; |
| 486 | + |
| 487 | + av_freep(&SE->R); |
| 488 | + SE->Lnum = 0; |
| 489 | +} |
| 490 | + |
| 491 | +static int init_chordset(chord_set *chords) |
| 492 | +{ |
| 493 | + chords->nb_elements = 0; |
| 494 | + chords->size = 0; |
| 495 | + chords->C = av_calloc(1, sizeof(chord)); |
| 496 | + if (!chords->C) |
| 497 | + return AVERROR(ENOMEM); |
| 498 | + |
| 499 | + chords->cap = 1; |
| 500 | + chords->minX = INT16_MAX; |
| 501 | + chords->maxX = INT16_MIN; |
| 502 | + chords->minY = INT16_MAX; |
| 503 | + chords->maxY = INT16_MIN; |
| 504 | + |
| 505 | + return 0; |
| 506 | +} |
| 507 | + |
| 508 | +static int comp_chord_length(const void *p, const void *q) |
| 509 | +{ |
| 510 | + chord a, b; |
| 511 | + a = *((chord *)p); |
| 512 | + b = *((chord *)q); |
| 513 | + |
| 514 | + return (a.l > b.l) - (a.l < b.l); |
| 515 | +} |
| 516 | + |
| 517 | +static int comp_chord(const void *p, const void *q) |
| 518 | +{ |
| 519 | + chord a, b; |
| 520 | + a = *((chord *)p); |
| 521 | + b = *((chord *)q); |
| 522 | + |
| 523 | + return (a.y > b.y) - (a.y < b.y); |
| 524 | +} |
| 525 | + |
| 526 | +static int build_chord_set(IPlane *SE, chord_set *chords) |
| 527 | +{ |
| 528 | + const int mid = 1 << (SE->depth - 1); |
| 529 | + int chord_length_index; |
| 530 | + int chord_start, val, ret; |
| 531 | + int centerX, centerY; |
| 532 | + int r_cap = 1; |
| 533 | + chord c; |
| 534 | + |
| 535 | + ret = init_chordset(chords); |
| 536 | + if (ret < 0) |
| 537 | + return ret; |
| 538 | + /* |
| 539 | + * In erosion/dilation, the center of the IPlane has S.E. offset (0,0). |
| 540 | + * Otherwise, the resulting IPlane would be shifted to the top-left. |
| 541 | + */ |
| 542 | + centerX = (SE->w - 1) / 2; |
| 543 | + centerY = (SE->h - 1) / 2; |
| 544 | + |
| 545 | + /* |
| 546 | + * Computing the set of chords C. |
| 547 | + */ |
| 548 | + for (int y = 0; y < SE->h; y++) { |
| 549 | + int x; |
| 550 | + |
| 551 | + chord_start = -1; |
| 552 | + for (x = 0; x < SE->w; x++) { |
| 553 | + if (SE->type_size == 1) { |
| 554 | + chords->nb_elements += (SE->img[y][x] >= mid); |
| 555 | + //A chord is a run of non-zero pixels. |
| 556 | + if (SE->img[y][x] >= mid && chord_start == -1) { |
| 557 | + // Chord starts. |
| 558 | + chord_start = x; |
| 559 | + } else if (SE->img[y][x] < mid && chord_start != -1) { |
| 560 | + // Chord ends before end of line. |
| 561 | + c.x = chord_start - centerX; |
| 562 | + c.y = y - centerY; |
| 563 | + c.l = x - chord_start; |
| 564 | + ret = insert_chord_set(chords, c); |
| 565 | + if (ret < 0) |
| 566 | + return AVERROR(ENOMEM); |
| 567 | + chord_start = -1; |
| 568 | + } |
| 569 | + } else { |
| 570 | + chords->nb_elements += (AV_RN16(&SE->img[y][x * 2]) >= mid); |
| 571 | + //A chord is a run of non-zero pixels. |
| 572 | + if (AV_RN16(&SE->img[y][x * 2]) >= mid && chord_start == -1) { |
| 573 | + // Chord starts. |
| 574 | + chord_start = x; |
| 575 | + } else if (AV_RN16(&SE->img[y][x * 2]) < mid && chord_start != -1) { |
| 576 | + // Chord ends before end of line. |
| 577 | + c.x = chord_start - centerX; |
| 578 | + c.y = y - centerY; |
| 579 | + c.l = x - chord_start; |
| 580 | + ret = insert_chord_set(chords, c); |
| 581 | + if (ret < 0) |
| 582 | + return AVERROR(ENOMEM); |
| 583 | + chord_start = -1; |
| 584 | + } |
| 585 | + } |
| 586 | + } |
| 587 | + if (chord_start != -1) { |
| 588 | + // Chord ends at end of line. |
| 589 | + c.x = chord_start - centerX; |
| 590 | + c.y = y - centerY; |
| 591 | + c.l = x - chord_start; |
| 592 | + ret = insert_chord_set(chords, c); |
| 593 | + if (ret < 0) |
| 594 | + return AVERROR(ENOMEM); |
| 595 | + } |
| 596 | + } |
| 597 | + |
| 598 | + /* |
| 599 | + * Computing the array of chord lengths R(i). |
| 600 | + * This is needed because the lookup table will contain a row for each |
| 601 | + * length index i. |
| 602 | + */ |
| 603 | + qsort(chords->C, chords->size, sizeof(chord), comp_chord_length); |
| 604 | + chords->R = av_calloc(1, sizeof(*chords->R)); |
| 605 | + if (!chords->R) |
| 606 | + return AVERROR(ENOMEM); |
| 607 | + chords->Lnum = 0; |
| 608 | + val = 0; |
| 609 | + r_cap = 1; |
| 610 | + |
| 611 | + if (chords->size > 0) { |
| 612 | + val = 1; |
| 613 | + if (chords->Lnum >= r_cap) { |
| 614 | + chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R)); |
| 615 | + if (!chords->R) |
| 616 | + return AVERROR(ENOMEM); |
| 617 | + r_cap *= 2; |
| 618 | + } |
| 619 | + chords->R[chords->Lnum++] = 1; |
| 620 | + val = 1; |
| 621 | + } |
| 622 | + |
| 623 | + for (int i = 0; i < chords->size; i++) { |
| 624 | + if (val != chords->C[i].l) { |
| 625 | + while (2 * val < chords->C[i].l && val != 0) { |
| 626 | + if (chords->Lnum >= r_cap) { |
| 627 | + chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R)); |
| 628 | + if (!chords->R) |
| 629 | + return AVERROR(ENOMEM); |
| 630 | + r_cap *= 2; |
| 631 | + } |
| 632 | + |
| 633 | + chords->R[chords->Lnum++] = 2 * val; |
| 634 | + val *= 2; |
| 635 | + } |
| 636 | + val = chords->C[i].l; |
| 637 | + |
| 638 | + if (chords->Lnum >= r_cap) { |
| 639 | + chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R)); |
| 640 | + if (!chords->R) |
| 641 | + return AVERROR(ENOMEM); |
| 642 | + r_cap *= 2; |
| 643 | + } |
| 644 | + chords->R[chords->Lnum++] = val; |
| 645 | + } |
| 646 | + } |
| 647 | + |
| 648 | + /* |
| 649 | + * Setting the length indices of chords. |
| 650 | + * These are needed so that the algorithm can, for each chord, |
| 651 | + * access the lookup table at the correct length in constant time. |
| 652 | + */ |
| 653 | + chord_length_index = 0; |
| 654 | + for (int i = 0; i < chords->size; i++) { |
| 655 | + while (chords->R[chord_length_index] < chords->C[i].l) |
| 656 | + chord_length_index++; |
| 657 | + chords->C[i].i = chord_length_index; |
| 658 | + } |
| 659 | + |
| 660 | + /* |
| 661 | + * Chords are sorted on Y. This way, when a row of the lookup table or IPlane |
| 662 | + * is cached, the next chord offset has a better chance of being on the |
| 663 | + * same cache line. |
| 664 | + */ |
| 665 | + qsort(chords->C, chords->size, sizeof(chord), comp_chord); |
| 666 | + |
| 667 | + return 0; |
| 668 | +} |
| 669 | + |
| 670 | +static void free_iplane(IPlane *imp) |
| 671 | +{ |
| 672 | + av_freep(&imp->img); |
| 673 | +} |
| 674 | + |
| 675 | +static int read_iplane(IPlane *imp, const uint8_t *dst, int dst_linesize, |
| 676 | + int w, int h, int R, int type_size, int depth) |
| 677 | +{ |
| 678 | + if (!imp->img) |
| 679 | + imp->img = av_calloc(h, sizeof(*imp->img)); |
| 680 | + if (!imp->img) |
| 681 | + return AVERROR(ENOMEM); |
| 682 | + |
| 683 | + imp->w = w; |
| 684 | + imp->h = h; |
| 685 | + imp->range = R; |
| 686 | + imp->depth = depth; |
| 687 | + imp->type_size = type_size; |
| 688 | + imp->max = type_size == 1 ? max_fun : max16_fun; |
| 689 | + imp->min = type_size == 1 ? min_fun : min16_fun; |
| 690 | + imp->max_in_place = type_size == 1 ? maxinplace_fun : maxinplace16_fun; |
| 691 | + imp->min_in_place = type_size == 1 ? mininplace_fun : mininplace16_fun; |
| 692 | + |
| 693 | + for (int y = 0; y < h; y++) |
| 694 | + imp->img[y] = (uint8_t *)dst + y * dst_linesize; |
| 695 | + |
| 696 | + return 0; |
| 697 | +} |
| 698 | + |
| 699 | +static int config_input(AVFilterLink *inlink) |
| 700 | +{ |
| 701 | + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
| 702 | + MorphoContext *s = inlink->dst->priv; |
| 703 | + |
| 704 | + s->depth = desc->comp[0].depth; |
| 705 | + s->type_size = (s->depth + 7) / 8; |
| 706 | + s->nb_planes = desc->nb_components; |
| 707 | + s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); |
| 708 | + s->planewidth[0] = s->planewidth[3] = inlink->w; |
| 709 | + s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); |
| 710 | + s->planeheight[0] = s->planeheight[3] = inlink->h; |
| 711 | + |
| 712 | + return 0; |
| 713 | +} |
| 714 | + |
| 715 | +static int config_input_structure(AVFilterLink *inlink) |
| 716 | +{ |
| 717 | + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); |
| 718 | + AVFilterContext *ctx = inlink->dst; |
| 719 | + MorphoContext *s = inlink->dst->priv; |
| 720 | + |
| 721 | + av_assert0(ctx->inputs[0]->format == ctx->inputs[1]->format); |
| 722 | + |
| 723 | + s->splanewidth[1] = s->splanewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); |
| 724 | + s->splanewidth[0] = s->splanewidth[3] = inlink->w; |
| 725 | + s->splaneheight[1] = s->splaneheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); |
| 726 | + s->splaneheight[0] = s->splaneheight[3] = inlink->h; |
| 727 | + |
| 728 | + return 0; |
| 729 | +} |
| 730 | + |
| 731 | +static int activate(AVFilterContext *ctx) |
| 732 | +{ |
| 733 | + MorphoContext *s = ctx->priv; |
| 734 | + return ff_framesync_activate(&s->fs); |
| 735 | +} |
| 736 | + |
| 737 | +static int do_morpho(FFFrameSync *fs) |
| 738 | +{ |
| 739 | + AVFilterContext *ctx = fs->parent; |
| 740 | + AVFilterLink *outlink = ctx->outputs[0]; |
| 741 | + MorphoContext *s = ctx->priv; |
| 742 | + AVFrame *in = NULL, *structurepic = NULL; |
| 743 | + AVFrame *out; |
| 744 | + int ret; |
| 745 | + |
| 746 | + ret = ff_framesync_dualinput_get(fs, &in, &structurepic); |
| 747 | + if (ret < 0) |
| 748 | + return ret; |
| 749 | + if (!structurepic) |
| 750 | + return ff_filter_frame(outlink, in); |
| 751 | + |
| 752 | + out = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| 753 | + if (!out) { |
| 754 | + av_frame_free(&in); |
| 755 | + return AVERROR(ENOMEM); |
| 756 | + } |
| 757 | + av_frame_copy_props(out, in); |
| 758 | + |
| 759 | + for (int p = 0; p < s->nb_planes; p++) { |
| 760 | + const uint8_t *src = in->data[p]; |
| 761 | + int src_linesize = in->linesize[p]; |
| 762 | + const uint8_t *ssrc = structurepic->data[p]; |
| 763 | + const int ssrc_linesize = structurepic->linesize[p]; |
| 764 | + uint8_t *dst = out->data[p]; |
| 765 | + int dst_linesize = out->linesize[p]; |
| 766 | + const int swidth = s->splanewidth[p]; |
| 767 | + const int sheight = s->splaneheight[p]; |
| 768 | + const int width = s->planewidth[p]; |
| 769 | + const int height = s->planeheight[p]; |
| 770 | + const int depth = s->depth; |
| 771 | + int type_size = s->type_size; |
| 772 | + |
| 773 | + if (!(s->planes & (1 << p))) { |
| 774 | +copy: |
| 775 | + av_image_copy_plane(out->data[p] + 0 * out->linesize[p], |
| 776 | + out->linesize[p], |
| 777 | + in->data[p] + 0 * in->linesize[p], |
| 778 | + in->linesize[p], |
| 779 | + width * ((s->depth + 7) / 8), |
| 780 | + height); |
| 781 | + continue; |
| 782 | + } |
| 783 | + |
| 784 | + if (!s->got_structure[p] || s->structures) { |
| 785 | + free_chord_set(&s->SE[p]); |
| 786 | + |
| 787 | + ret = read_iplane(&s->SEimg[p], ssrc, ssrc_linesize, swidth, sheight, 1, type_size, depth); |
| 788 | + if (ret < 0) |
| 789 | + goto fail; |
| 790 | + ret = build_chord_set(&s->SEimg[p], &s->SE[p]); |
| 791 | + if (ret < 0) |
| 792 | + goto fail; |
| 793 | + s->got_structure[p] = 1; |
| 794 | + } |
| 795 | + |
| 796 | + if (s->SE[p].minX == INT16_MAX || |
| 797 | + s->SE[p].minY == INT16_MAX || |
| 798 | + s->SE[p].maxX == INT16_MIN || |
| 799 | + s->SE[p].maxY == INT16_MIN) |
| 800 | + goto copy; |
| 801 | + |
| 802 | + ret = read_iplane(&s->f[p], src, src_linesize, width, height, 1, type_size, depth); |
| 803 | + if (ret < 0) |
| 804 | + goto fail; |
| 805 | + |
| 806 | + ret = read_iplane(&s->g[p], dst, dst_linesize, s->f[p].w, s->f[p].h, s->f[p].range, type_size, depth); |
| 807 | + if (ret < 0) |
| 808 | + goto fail; |
| 809 | + |
| 810 | + switch (s->mode) { |
| 811 | + case ERODE: |
| 812 | + ret = erode(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[0][p]); |
| 813 | + break; |
| 814 | + case DILATE: |
| 815 | + ret = dilate(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[0][p]); |
| 816 | + break; |
| 817 | + case OPEN: |
| 818 | + ret = read_iplane(&s->h[p], s->temp->data[p], s->temp->linesize[p], width, height, 1, type_size, depth); |
| 819 | + if (ret < 0) |
| 820 | + break; |
| 821 | + ret = erode(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[0][p]); |
| 822 | + if (ret < 0) |
| 823 | + break; |
| 824 | + ret = dilate(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[1][p]); |
| 825 | + break; |
| 826 | + case CLOSE: |
| 827 | + ret = read_iplane(&s->h[p], s->temp->data[p], s->temp->linesize[p], width, height, 1, type_size, depth); |
| 828 | + if (ret < 0) |
| 829 | + break; |
| 830 | + ret = dilate(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[0][p]); |
| 831 | + if (ret < 0) |
| 832 | + break; |
| 833 | + ret = erode(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[1][p]); |
| 834 | + break; |
| 835 | + default: |
| 836 | + av_assert0(0); |
| 837 | + } |
| 838 | + |
| 839 | + if (ret < 0) |
| 840 | + goto fail; |
| 841 | + } |
| 842 | + |
| 843 | + av_frame_free(&in); |
| 844 | + out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); |
| 845 | + return ff_filter_frame(outlink, out); |
| 846 | +fail: |
| 847 | + av_frame_free(&in); |
| 848 | + return ret; |
| 849 | +} |
| 850 | + |
| 851 | +static int config_output(AVFilterLink *outlink) |
| 852 | +{ |
| 853 | + AVFilterContext *ctx = outlink->src; |
| 854 | + MorphoContext *s = ctx->priv; |
| 855 | + AVFilterLink *mainlink = ctx->inputs[0]; |
| 856 | + int ret; |
| 857 | + |
| 858 | + s->fs.on_event = do_morpho; |
| 859 | + ret = ff_framesync_init_dualinput(&s->fs, ctx); |
| 860 | + if (ret < 0) |
| 861 | + return ret; |
| 862 | + outlink->w = mainlink->w; |
| 863 | + outlink->h = mainlink->h; |
| 864 | + outlink->time_base = mainlink->time_base; |
| 865 | + outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio; |
| 866 | + outlink->frame_rate = mainlink->frame_rate; |
| 867 | + |
| 868 | + if ((ret = ff_framesync_configure(&s->fs)) < 0) |
| 869 | + return ret; |
| 870 | + outlink->time_base = s->fs.time_base; |
| 871 | + |
| 872 | + s->temp = ff_get_video_buffer(outlink, outlink->w, outlink->h); |
| 873 | + if (!s->temp) |
| 874 | + return AVERROR(ENOMEM); |
| 875 | + |
| 876 | + s->plane_f = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_f)); |
| 877 | + s->plane_g = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_g)); |
| 878 | + if (!s->plane_f || !s->plane_g) |
| 879 | + return AVERROR(ENOMEM); |
| 880 | + |
| 881 | + return 0; |
| 882 | +} |
| 883 | + |
| 884 | +static av_cold void uninit(AVFilterContext *ctx) |
| 885 | +{ |
| 886 | + MorphoContext *s = ctx->priv; |
| 887 | + |
| 888 | + for (int p = 0; p < 4; p++) { |
| 889 | + free_iplane(&s->SEimg[p]); |
| 890 | + free_iplane(&s->f[p]); |
| 891 | + free_iplane(&s->g[p]); |
| 892 | + free_iplane(&s->h[p]); |
| 893 | + free_chord_set(&s->SE[p]); |
| 894 | + free_lut(&s->Ty[0][p]); |
| 895 | + free_lut(&s->Ty[1][p]); |
| 896 | + } |
| 897 | + |
| 898 | + ff_framesync_uninit(&s->fs); |
| 899 | + |
| 900 | + av_frame_free(&s->temp); |
| 901 | + av_freep(&s->plane_f); |
| 902 | + av_freep(&s->plane_g); |
| 903 | +} |
| 904 | + |
| 905 | +static const AVFilterPad morpho_inputs[] = { |
| 906 | + { |
| 907 | + .name = "default", |
| 908 | + .type = AVMEDIA_TYPE_VIDEO, |
| 909 | + .config_props = config_input, |
| 910 | + }, |
| 911 | + { |
| 912 | + .name = "structure", |
| 913 | + .type = AVMEDIA_TYPE_VIDEO, |
| 914 | + .config_props = config_input_structure, |
| 915 | + }, |
| 916 | +}; |
| 917 | + |
| 918 | +static const AVFilterPad morpho_outputs[] = { |
| 919 | + { |
| 920 | + .name = "default", |
| 921 | + .type = AVMEDIA_TYPE_VIDEO, |
| 922 | + .config_props = config_output, |
| 923 | + }, |
| 924 | +}; |
| 925 | + |
| 926 | +const AVFilter ff_vf_morpho = { |
| 927 | + .name = "morpho", |
| 928 | + .description = NULL_IF_CONFIG_SMALL("Apply Morphological filter."), |
| 929 | + .preinit = morpho_framesync_preinit, |
| 930 | + .priv_size = sizeof(MorphoContext), |
| 931 | + .priv_class = &morpho_class, |
| 932 | + .activate = activate, |
| 933 | + .uninit = uninit, |
| 934 | + .query_formats = query_formats, |
| 935 | + FILTER_INPUTS(morpho_inputs), |
| 936 | + FILTER_OUTPUTS(morpho_outputs), |
| 937 | + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC, |
| 938 | + .process_command = ff_filter_process_command, |
| 939 | +}; |