| @@ -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 | }; |