| Line | Branch | Exec | Source |
|---|---|---|---|
| 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/mem.h" | ||
| 30 | #include "libavutil/opt.h" | ||
| 31 | #include "libavutil/pixdesc.h" | ||
| 32 | #include "avfilter.h" | ||
| 33 | #include "filters.h" | ||
| 34 | #include "framesync.h" | ||
| 35 | #include "video.h" | ||
| 36 | |||
| 37 | enum MorphModes { | ||
| 38 | ERODE, | ||
| 39 | DILATE, | ||
| 40 | OPEN, | ||
| 41 | CLOSE, | ||
| 42 | GRADIENT, | ||
| 43 | TOPHAT, | ||
| 44 | BLACKHAT, | ||
| 45 | NB_MODES | ||
| 46 | }; | ||
| 47 | |||
| 48 | typedef struct IPlane { | ||
| 49 | uint8_t **img; | ||
| 50 | int w, h; | ||
| 51 | int range; | ||
| 52 | int depth; | ||
| 53 | int type_size; | ||
| 54 | |||
| 55 | void (*max_out_place)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x); | ||
| 56 | void (*min_out_place)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x); | ||
| 57 | void (*diff_rin_place)(uint8_t *a, const uint8_t *b, int x); | ||
| 58 | void (*max_in_place)(uint8_t *a, const uint8_t *b, int x); | ||
| 59 | void (*min_in_place)(uint8_t *a, const uint8_t *b, int x); | ||
| 60 | void (*diff_in_place)(uint8_t *a, const uint8_t *b, int x); | ||
| 61 | } IPlane; | ||
| 62 | |||
| 63 | typedef struct LUT { | ||
| 64 | /* arr is shifted from base_arr by FFMAX(min_r, 0). | ||
| 65 | * arr != NULL means "lut completely allocated" */ | ||
| 66 | uint8_t ***arr; | ||
| 67 | uint8_t ***base_arr; | ||
| 68 | int min_r; | ||
| 69 | int max_r; | ||
| 70 | int I; | ||
| 71 | int X; | ||
| 72 | int pre_pad_x; | ||
| 73 | int type_size; | ||
| 74 | } LUT; | ||
| 75 | |||
| 76 | typedef struct chord { | ||
| 77 | int x; | ||
| 78 | int y; | ||
| 79 | int l; | ||
| 80 | int i; | ||
| 81 | } chord; | ||
| 82 | |||
| 83 | typedef struct chord_set { | ||
| 84 | chord *C; | ||
| 85 | int size; | ||
| 86 | int cap; | ||
| 87 | |||
| 88 | int *R; | ||
| 89 | int Lnum; | ||
| 90 | |||
| 91 | int minX; | ||
| 92 | int maxX; | ||
| 93 | int minY; | ||
| 94 | int maxY; | ||
| 95 | unsigned nb_elements; | ||
| 96 | } chord_set; | ||
| 97 | |||
| 98 | #define MAX_THREADS 64 | ||
| 99 | |||
| 100 | typedef struct MorphoContext { | ||
| 101 | const AVClass *class; | ||
| 102 | FFFrameSync fs; | ||
| 103 | |||
| 104 | chord_set SE[4]; | ||
| 105 | IPlane SEimg[4]; | ||
| 106 | IPlane g[4], f[4], h[4]; | ||
| 107 | LUT Ty[MAX_THREADS][2][4]; | ||
| 108 | |||
| 109 | int mode; | ||
| 110 | int planes; | ||
| 111 | int structures; | ||
| 112 | |||
| 113 | int planewidth[4]; | ||
| 114 | int planeheight[4]; | ||
| 115 | int splanewidth[4]; | ||
| 116 | int splaneheight[4]; | ||
| 117 | int depth; | ||
| 118 | int type_size; | ||
| 119 | int nb_planes; | ||
| 120 | |||
| 121 | int got_structure[4]; | ||
| 122 | |||
| 123 | AVFrame *temp; | ||
| 124 | |||
| 125 | int64_t *plane_f, *plane_g; | ||
| 126 | } MorphoContext; | ||
| 127 | |||
| 128 | #define OFFSET(x) offsetof(MorphoContext, x) | ||
| 129 | #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM | ||
| 130 | |||
| 131 | static const AVOption morpho_options[] = { | ||
| 132 | { "mode", "set morphological transform", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_MODES-1, FLAGS, .unit = "mode" }, | ||
| 133 | { "erode", NULL, 0, AV_OPT_TYPE_CONST, {.i64=ERODE}, 0, 0, FLAGS, .unit = "mode" }, | ||
| 134 | { "dilate", NULL, 0, AV_OPT_TYPE_CONST, {.i64=DILATE}, 0, 0, FLAGS, .unit = "mode" }, | ||
| 135 | { "open", NULL, 0, AV_OPT_TYPE_CONST, {.i64=OPEN}, 0, 0, FLAGS, .unit = "mode" }, | ||
| 136 | { "close", NULL, 0, AV_OPT_TYPE_CONST, {.i64=CLOSE}, 0, 0, FLAGS, .unit = "mode" }, | ||
| 137 | { "gradient",NULL, 0, AV_OPT_TYPE_CONST, {.i64=GRADIENT},0, 0, FLAGS, .unit = "mode" }, | ||
| 138 | { "tophat",NULL, 0, AV_OPT_TYPE_CONST, {.i64=TOPHAT}, 0, 0, FLAGS, .unit = "mode" }, | ||
| 139 | { "blackhat",NULL, 0, AV_OPT_TYPE_CONST, {.i64=BLACKHAT},0, 0, FLAGS, .unit = "mode" }, | ||
| 140 | { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=7}, 0, 15, FLAGS }, | ||
| 141 | { "structure", "when to process structures", OFFSET(structures), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS, .unit = "str" }, | ||
| 142 | { "first", "process only first structure, ignore rest", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, .unit = "str" }, | ||
| 143 | { "all", "process all structure", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, .unit = "str" }, | ||
| 144 | { NULL } | ||
| 145 | }; | ||
| 146 | |||
| 147 | ✗ | FRAMESYNC_DEFINE_CLASS(morpho, MorphoContext, fs); | |
| 148 | |||
| 149 | static const enum AVPixelFormat pix_fmts[] = { | ||
| 150 | AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P, | ||
| 151 | AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P, | ||
| 152 | AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P, | ||
| 153 | AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P, | ||
| 154 | AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P, | ||
| 155 | AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9, | ||
| 156 | AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, AV_PIX_FMT_GBRP9, | ||
| 157 | AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9, | ||
| 158 | AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, | ||
| 159 | AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12, | ||
| 160 | AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14, | ||
| 161 | AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, | ||
| 162 | AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10, | ||
| 163 | AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12, | ||
| 164 | AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16, | ||
| 165 | AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16, | ||
| 166 | AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16, | ||
| 167 | AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16, | ||
| 168 | AV_PIX_FMT_NONE | ||
| 169 | }; | ||
| 170 | |||
| 171 | ✗ | static void min_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x) | |
| 172 | { | ||
| 173 | ✗ | for (int i = 0; i < x; i++) | |
| 174 | ✗ | c[i] = FFMIN(b[i], a[i]); | |
| 175 | ✗ | } | |
| 176 | |||
| 177 | ✗ | static void mininplace_fun(uint8_t *a, const uint8_t *b, int x) | |
| 178 | { | ||
| 179 | ✗ | for (int i = 0; i < x; i++) | |
| 180 | ✗ | a[i] = FFMIN(a[i], b[i]); | |
| 181 | ✗ | } | |
| 182 | |||
| 183 | ✗ | static void max_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x) | |
| 184 | { | ||
| 185 | ✗ | for (int i = 0; i < x; i++) | |
| 186 | ✗ | c[i] = FFMAX(a[i], b[i]); | |
| 187 | ✗ | } | |
| 188 | |||
| 189 | ✗ | static void maxinplace_fun(uint8_t *a, const uint8_t *b, int x) | |
| 190 | { | ||
| 191 | ✗ | for (int i = 0; i < x; i++) | |
| 192 | ✗ | a[i] = FFMAX(a[i], b[i]); | |
| 193 | ✗ | } | |
| 194 | |||
| 195 | ✗ | static void diff_fun(uint8_t *a, const uint8_t *b, int x) | |
| 196 | { | ||
| 197 | ✗ | for (int i = 0; i < x; i++) | |
| 198 | ✗ | a[i] = FFMAX(b[i] - a[i], 0); | |
| 199 | ✗ | } | |
| 200 | |||
| 201 | ✗ | static void diffinplace_fun(uint8_t *a, const uint8_t *b, int x) | |
| 202 | { | ||
| 203 | ✗ | for (int i = 0; i < x; i++) | |
| 204 | ✗ | a[i] = FFMAX(a[i] - b[i], 0); | |
| 205 | ✗ | } | |
| 206 | |||
| 207 | ✗ | static void min16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x) | |
| 208 | { | ||
| 209 | ✗ | const uint16_t *a = (const uint16_t *)aa; | |
| 210 | ✗ | const uint16_t *b = (const uint16_t *)bb; | |
| 211 | ✗ | uint16_t *c = (uint16_t *)cc; | |
| 212 | |||
| 213 | ✗ | for (int i = 0; i < x; i++) | |
| 214 | ✗ | c[i] = FFMIN(b[i], a[i]); | |
| 215 | ✗ | } | |
| 216 | |||
| 217 | ✗ | static void mininplace16_fun(uint8_t *aa, const uint8_t *bb, int x) | |
| 218 | { | ||
| 219 | ✗ | uint16_t *a = (uint16_t *)aa; | |
| 220 | ✗ | const uint16_t *b = (const uint16_t *)bb; | |
| 221 | |||
| 222 | ✗ | for (int i = 0; i < x; i++) | |
| 223 | ✗ | a[i] = FFMIN(a[i], b[i]); | |
| 224 | ✗ | } | |
| 225 | |||
| 226 | ✗ | static void diff16_fun(uint8_t *aa, const uint8_t *bb, int x) | |
| 227 | { | ||
| 228 | ✗ | const uint16_t *b = (const uint16_t *)bb; | |
| 229 | ✗ | uint16_t *a = (uint16_t *)aa; | |
| 230 | |||
| 231 | ✗ | for (int i = 0; i < x; i++) | |
| 232 | ✗ | a[i] = FFMAX(b[i] - a[i], 0); | |
| 233 | ✗ | } | |
| 234 | |||
| 235 | ✗ | static void diffinplace16_fun(uint8_t *aa, const uint8_t *bb, int x) | |
| 236 | { | ||
| 237 | ✗ | uint16_t *a = (uint16_t *)aa; | |
| 238 | ✗ | const uint16_t *b = (const uint16_t *)bb; | |
| 239 | |||
| 240 | ✗ | for (int i = 0; i < x; i++) | |
| 241 | ✗ | a[i] = FFMAX(a[i] - b[i], 0); | |
| 242 | ✗ | } | |
| 243 | |||
| 244 | ✗ | static void max16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x) | |
| 245 | { | ||
| 246 | ✗ | const uint16_t *a = (const uint16_t *)aa; | |
| 247 | ✗ | const uint16_t *b = (const uint16_t *)bb; | |
| 248 | ✗ | uint16_t *c = (uint16_t *)cc; | |
| 249 | |||
| 250 | ✗ | for (int i = 0; i < x; i++) | |
| 251 | ✗ | c[i] = FFMAX(a[i], b[i]); | |
| 252 | ✗ | } | |
| 253 | |||
| 254 | ✗ | static void maxinplace16_fun(uint8_t *aa, const uint8_t *bb, int x) | |
| 255 | { | ||
| 256 | ✗ | uint16_t *a = (uint16_t *)aa; | |
| 257 | ✗ | const uint16_t *b = (const uint16_t *)bb; | |
| 258 | |||
| 259 | ✗ | for (int i = 0; i < x; i++) | |
| 260 | ✗ | a[i] = FFMAX(a[i], b[i]); | |
| 261 | ✗ | } | |
| 262 | |||
| 263 | ✗ | static int alloc_lut(LUT *Ty, chord_set *SE, int type_size, int mode) | |
| 264 | { | ||
| 265 | ✗ | const int min = FFMAX(Ty->min_r, 0); | |
| 266 | ✗ | const int max = min + (Ty->max_r - Ty->min_r); | |
| 267 | ✗ | int pre_pad_x = 0; | |
| 268 | |||
| 269 | ✗ | if (SE->minX < 0) | |
| 270 | ✗ | pre_pad_x = 0 - SE->minX; | |
| 271 | ✗ | Ty->pre_pad_x = pre_pad_x; | |
| 272 | ✗ | Ty->type_size = type_size; | |
| 273 | |||
| 274 | ✗ | Ty->base_arr = av_calloc(max + 1, sizeof(*Ty->base_arr)); | |
| 275 | ✗ | if (!Ty->base_arr) | |
| 276 | ✗ | return AVERROR(ENOMEM); | |
| 277 | ✗ | for (int r = min; r <= max; r++) { | |
| 278 | ✗ | uint8_t **arr = Ty->base_arr[r] = av_calloc(Ty->I, sizeof(uint8_t *)); | |
| 279 | ✗ | if (!Ty->base_arr[r]) | |
| 280 | ✗ | return AVERROR(ENOMEM); | |
| 281 | ✗ | for (int i = 0; i < Ty->I; i++) { | |
| 282 | ✗ | arr[i] = av_calloc(Ty->X + pre_pad_x, type_size); | |
| 283 | ✗ | if (!arr[i]) | |
| 284 | ✗ | return AVERROR(ENOMEM); | |
| 285 | ✗ | if (mode == ERODE) | |
| 286 | ✗ | memset(arr[i], UINT8_MAX, pre_pad_x * type_size); | |
| 287 | /* Shifting the X index such that negative indices correspond to | ||
| 288 | * the pre-padding. | ||
| 289 | */ | ||
| 290 | ✗ | arr[i] = &(arr[i][pre_pad_x * type_size]); | |
| 291 | } | ||
| 292 | } | ||
| 293 | |||
| 294 | ✗ | Ty->arr = &(Ty->base_arr[min - Ty->min_r]); | |
| 295 | |||
| 296 | ✗ | return 0; | |
| 297 | } | ||
| 298 | |||
| 299 | ✗ | static void free_lut(LUT *table) | |
| 300 | { | ||
| 301 | ✗ | const int min = FFMAX(table->min_r, 0); | |
| 302 | ✗ | const int max = min + (table->max_r - table->min_r); | |
| 303 | |||
| 304 | ✗ | if (!table->base_arr) | |
| 305 | ✗ | return; | |
| 306 | |||
| 307 | ✗ | for (int r = min; r <= max; r++) { | |
| 308 | ✗ | if (!table->base_arr[r]) | |
| 309 | ✗ | break; | |
| 310 | ✗ | for (int i = 0; i < table->I; i++) { | |
| 311 | ✗ | if (!table->base_arr[r][i]) | |
| 312 | ✗ | break; | |
| 313 | // The X index was also shifted, for padding purposes. | ||
| 314 | ✗ | av_free(table->base_arr[r][i] - table->pre_pad_x * table->type_size); | |
| 315 | } | ||
| 316 | ✗ | av_freep(&table->base_arr[r]); | |
| 317 | } | ||
| 318 | ✗ | av_freep(&table->base_arr); | |
| 319 | ✗ | table->arr = NULL; | |
| 320 | } | ||
| 321 | |||
| 322 | ✗ | static int alloc_lut_if_necessary(LUT *Ty, IPlane *f, chord_set *SE, | |
| 323 | int num, enum MorphModes mode) | ||
| 324 | { | ||
| 325 | ✗ | if (!Ty->arr || Ty->I != SE->Lnum || | |
| 326 | ✗ | Ty->X != f->w || | |
| 327 | ✗ | SE->minX < 0 && -SE->minX > Ty->pre_pad_x || | |
| 328 | ✗ | Ty->min_r != SE->minY || | |
| 329 | ✗ | Ty->max_r != SE->maxY + num - 1) { | |
| 330 | int ret; | ||
| 331 | |||
| 332 | ✗ | free_lut(Ty); | |
| 333 | |||
| 334 | ✗ | Ty->I = SE->Lnum; | |
| 335 | ✗ | Ty->X = f->w; | |
| 336 | ✗ | Ty->min_r = SE->minY; | |
| 337 | ✗ | Ty->max_r = SE->maxY + num - 1; | |
| 338 | ✗ | ret = alloc_lut(Ty, SE, f->type_size, mode); | |
| 339 | ✗ | if (ret < 0) | |
| 340 | ✗ | return ret; | |
| 341 | } | ||
| 342 | ✗ | return 0; | |
| 343 | } | ||
| 344 | |||
| 345 | ✗ | static void circular_swap(LUT *Ty) | |
| 346 | { | ||
| 347 | /* | ||
| 348 | * Swap the pointers to r-indices in a circle. This is useful because | ||
| 349 | * Ty(r,i,x) = Ty-1(r+1,i,x) for r < ymax. | ||
| 350 | */ | ||
| 351 | ✗ | if (Ty->max_r - Ty->min_r > 0) { | |
| 352 | ✗ | uint8_t **Ty0 = Ty->arr[Ty->min_r]; | |
| 353 | |||
| 354 | ✗ | for (int r = Ty->min_r; r < Ty->max_r; r++) | |
| 355 | ✗ | Ty->arr[r] = Ty->arr[r + 1]; | |
| 356 | |||
| 357 | ✗ | Ty->arr[Ty->max_r] = Ty0; | |
| 358 | } | ||
| 359 | ✗ | } | |
| 360 | |||
| 361 | ✗ | static void compute_min_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y) | |
| 362 | { | ||
| 363 | ✗ | if (y + r >= 0 && y + r < f->h) { | |
| 364 | ✗ | memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size); | |
| 365 | } else { | ||
| 366 | ✗ | memset(Ty->arr[r][0], UINT8_MAX, Ty->X * Ty->type_size); | |
| 367 | } | ||
| 368 | |||
| 369 | ✗ | for (int i = 1; i < SE->Lnum; i++) { | |
| 370 | ✗ | int d = SE->R[i] - SE->R[i - 1]; | |
| 371 | |||
| 372 | ✗ | f->min_out_place(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size, | |
| 373 | ✗ | Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size, | |
| 374 | ✗ | Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size, | |
| 375 | ✗ | Ty->X + Ty->pre_pad_x - d); | |
| 376 | ✗ | memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size, | |
| 377 | ✗ | Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size, | |
| 378 | ✗ | d * f->type_size); | |
| 379 | } | ||
| 380 | ✗ | } | |
| 381 | |||
| 382 | ✗ | static void update_min_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num) | |
| 383 | { | ||
| 384 | ✗ | for (int i = 0; i < num; i++) | |
| 385 | ✗ | circular_swap(Ty); | |
| 386 | |||
| 387 | ✗ | compute_min_row(f, Ty, SE, Ty->max_r - tid, y); | |
| 388 | ✗ | } | |
| 389 | |||
| 390 | ✗ | static int compute_min_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num) | |
| 391 | { | ||
| 392 | ✗ | int ret = alloc_lut_if_necessary(Ty, f, SE, num, ERODE); | |
| 393 | ✗ | if (ret < 0) | |
| 394 | ✗ | return ret; | |
| 395 | |||
| 396 | ✗ | for (int r = Ty->min_r; r <= Ty->max_r; r++) | |
| 397 | ✗ | compute_min_row(f, Ty, SE, r, y); | |
| 398 | |||
| 399 | ✗ | return 0; | |
| 400 | } | ||
| 401 | |||
| 402 | ✗ | static void compute_max_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y) | |
| 403 | { | ||
| 404 | ✗ | if (y + r >= 0 && y + r < f->h) { | |
| 405 | ✗ | memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size); | |
| 406 | } else { | ||
| 407 | ✗ | memset(Ty->arr[r][0], 0, Ty->X * Ty->type_size); | |
| 408 | } | ||
| 409 | |||
| 410 | ✗ | for (int i = 1; i < SE->Lnum; i++) { | |
| 411 | ✗ | int d = SE->R[i] - SE->R[i - 1]; | |
| 412 | |||
| 413 | ✗ | f->max_out_place(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size, | |
| 414 | ✗ | Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size, | |
| 415 | ✗ | Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size, | |
| 416 | ✗ | Ty->X + Ty->pre_pad_x - d); | |
| 417 | ✗ | memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size, | |
| 418 | ✗ | Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size, | |
| 419 | ✗ | d * f->type_size); | |
| 420 | } | ||
| 421 | ✗ | } | |
| 422 | |||
| 423 | ✗ | static void update_max_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num) | |
| 424 | { | ||
| 425 | ✗ | for (int i = 0; i < num; i++) | |
| 426 | ✗ | circular_swap(Ty); | |
| 427 | |||
| 428 | ✗ | compute_max_row(f, Ty, SE, Ty->max_r - tid, y); | |
| 429 | ✗ | } | |
| 430 | |||
| 431 | ✗ | static int compute_max_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num) | |
| 432 | { | ||
| 433 | ✗ | int ret = alloc_lut_if_necessary(Ty, f, SE, num, DILATE); | |
| 434 | ✗ | if (ret < 0) | |
| 435 | ✗ | return ret; | |
| 436 | |||
| 437 | ✗ | for (int r = Ty->min_r; r <= Ty->max_r; r++) | |
| 438 | ✗ | compute_max_row(f, Ty, SE, r, y); | |
| 439 | |||
| 440 | ✗ | return 0; | |
| 441 | } | ||
| 442 | |||
| 443 | ✗ | static void line_dilate(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid) | |
| 444 | { | ||
| 445 | ✗ | memset(g->img[y], 0, g->w * g->type_size); | |
| 446 | |||
| 447 | ✗ | for (int c = 0; c < SE->size; c++) { | |
| 448 | ✗ | g->max_in_place(g->img[y], | |
| 449 | ✗ | Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size, | |
| 450 | ✗ | av_clip(g->w - SE->C[c].x, 0, g->w)); | |
| 451 | } | ||
| 452 | ✗ | } | |
| 453 | |||
| 454 | ✗ | static void line_erode(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid) | |
| 455 | { | ||
| 456 | ✗ | memset(g->img[y], UINT8_MAX, g->w * g->type_size); | |
| 457 | |||
| 458 | ✗ | for (int c = 0; c < SE->size; c++) { | |
| 459 | ✗ | g->min_in_place(g->img[y], | |
| 460 | ✗ | Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size, | |
| 461 | ✗ | av_clip(g->w - SE->C[c].x, 0, g->w)); | |
| 462 | } | ||
| 463 | ✗ | } | |
| 464 | |||
| 465 | ✗ | static int dilate(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty, int y0, int y1) | |
| 466 | { | ||
| 467 | ✗ | int ret = compute_max_lut(Ty, f, SE, y0, 1); | |
| 468 | ✗ | if (ret < 0) | |
| 469 | ✗ | return ret; | |
| 470 | |||
| 471 | ✗ | line_dilate(g, Ty, SE, y0, 0); | |
| 472 | ✗ | for (int y = y0 + 1; y < y1; y++) { | |
| 473 | ✗ | update_max_lut(f, Ty, SE, y, 0, 1); | |
| 474 | ✗ | line_dilate(g, Ty, SE, y, 0); | |
| 475 | } | ||
| 476 | |||
| 477 | ✗ | return 0; | |
| 478 | } | ||
| 479 | |||
| 480 | ✗ | static int erode(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty, int y0, int y1) | |
| 481 | { | ||
| 482 | ✗ | int ret = compute_min_lut(Ty, f, SE, y0, 1); | |
| 483 | ✗ | if (ret < 0) | |
| 484 | ✗ | return ret; | |
| 485 | |||
| 486 | ✗ | line_erode(g, Ty, SE, y0, 0); | |
| 487 | ✗ | for (int y = y0 + 1; y < y1; y++) { | |
| 488 | ✗ | update_min_lut(f, Ty, SE, y, 0, 1); | |
| 489 | ✗ | line_erode(g, Ty, SE, y, 0); | |
| 490 | } | ||
| 491 | |||
| 492 | ✗ | return 0; | |
| 493 | } | ||
| 494 | |||
| 495 | ✗ | static void difference(IPlane *g, IPlane *f, int y0, int y1) | |
| 496 | { | ||
| 497 | ✗ | for (int y = y0; y < y1; y++) | |
| 498 | ✗ | f->diff_in_place(g->img[y], f->img[y], f->w); | |
| 499 | ✗ | } | |
| 500 | |||
| 501 | ✗ | static void difference2(IPlane *g, IPlane *f, int y0, int y1) | |
| 502 | { | ||
| 503 | ✗ | for (int y = y0; y < y1; y++) | |
| 504 | ✗ | f->diff_rin_place(g->img[y], f->img[y], f->w); | |
| 505 | ✗ | } | |
| 506 | |||
| 507 | ✗ | static int insert_chord_set(chord_set *chords, chord c) | |
| 508 | { | ||
| 509 | // Checking if chord fits in dynamic array, resize if not. | ||
| 510 | ✗ | if (chords->size == chords->cap) { | |
| 511 | ✗ | chords->C = av_realloc_f(chords->C, chords->cap * 2, sizeof(chord)); | |
| 512 | ✗ | if (!chords->C) | |
| 513 | ✗ | return AVERROR(ENOMEM); | |
| 514 | ✗ | chords->cap *= 2; | |
| 515 | } | ||
| 516 | |||
| 517 | // Add the chord to the dynamic array. | ||
| 518 | ✗ | chords->C[chords->size].x = c.x; | |
| 519 | ✗ | chords->C[chords->size].y = c.y; | |
| 520 | ✗ | chords->C[chords->size++].l = c.l; | |
| 521 | |||
| 522 | // Update minimum/maximum x/y offsets of the chord set. | ||
| 523 | ✗ | chords->minX = FFMIN(chords->minX, c.x); | |
| 524 | ✗ | chords->maxX = FFMAX(chords->maxX, c.x); | |
| 525 | |||
| 526 | ✗ | chords->minY = FFMIN(chords->minY, c.y); | |
| 527 | ✗ | chords->maxY = FFMAX(chords->maxY, c.y); | |
| 528 | |||
| 529 | ✗ | return 0; | |
| 530 | } | ||
| 531 | |||
| 532 | ✗ | static void free_chord_set(chord_set *SE) | |
| 533 | { | ||
| 534 | ✗ | av_freep(&SE->C); | |
| 535 | ✗ | SE->size = 0; | |
| 536 | ✗ | SE->cap = 0; | |
| 537 | |||
| 538 | ✗ | av_freep(&SE->R); | |
| 539 | ✗ | SE->Lnum = 0; | |
| 540 | ✗ | } | |
| 541 | |||
| 542 | ✗ | static int init_chordset(chord_set *chords) | |
| 543 | { | ||
| 544 | ✗ | chords->nb_elements = 0; | |
| 545 | ✗ | chords->size = 0; | |
| 546 | ✗ | chords->C = av_calloc(1, sizeof(chord)); | |
| 547 | ✗ | if (!chords->C) | |
| 548 | ✗ | return AVERROR(ENOMEM); | |
| 549 | |||
| 550 | ✗ | chords->cap = 1; | |
| 551 | ✗ | chords->minX = INT16_MAX; | |
| 552 | ✗ | chords->maxX = INT16_MIN; | |
| 553 | ✗ | chords->minY = INT16_MAX; | |
| 554 | ✗ | chords->maxY = INT16_MIN; | |
| 555 | |||
| 556 | ✗ | return 0; | |
| 557 | } | ||
| 558 | |||
| 559 | ✗ | static int comp_chord_length(const void *p, const void *q) | |
| 560 | { | ||
| 561 | chord a, b; | ||
| 562 | ✗ | a = *((chord *)p); | |
| 563 | ✗ | b = *((chord *)q); | |
| 564 | |||
| 565 | ✗ | return (a.l > b.l) - (a.l < b.l); | |
| 566 | } | ||
| 567 | |||
| 568 | ✗ | static int comp_chord(const void *p, const void *q) | |
| 569 | { | ||
| 570 | chord a, b; | ||
| 571 | ✗ | a = *((chord *)p); | |
| 572 | ✗ | b = *((chord *)q); | |
| 573 | |||
| 574 | ✗ | return (a.y > b.y) - (a.y < b.y); | |
| 575 | } | ||
| 576 | |||
| 577 | ✗ | static int build_chord_set(IPlane *SE, chord_set *chords) | |
| 578 | { | ||
| 579 | ✗ | const int mid = 1 << (SE->depth - 1); | |
| 580 | int chord_length_index; | ||
| 581 | int chord_start, val, ret; | ||
| 582 | int centerX, centerY; | ||
| 583 | ✗ | int r_cap = 1; | |
| 584 | chord c; | ||
| 585 | |||
| 586 | ✗ | ret = init_chordset(chords); | |
| 587 | ✗ | if (ret < 0) | |
| 588 | ✗ | return ret; | |
| 589 | /* | ||
| 590 | * In erosion/dilation, the center of the IPlane has S.E. offset (0,0). | ||
| 591 | * Otherwise, the resulting IPlane would be shifted to the top-left. | ||
| 592 | */ | ||
| 593 | ✗ | centerX = (SE->w - 1) / 2; | |
| 594 | ✗ | centerY = (SE->h - 1) / 2; | |
| 595 | |||
| 596 | /* | ||
| 597 | * Computing the set of chords C. | ||
| 598 | */ | ||
| 599 | ✗ | for (int y = 0; y < SE->h; y++) { | |
| 600 | int x; | ||
| 601 | |||
| 602 | ✗ | chord_start = -1; | |
| 603 | ✗ | for (x = 0; x < SE->w; x++) { | |
| 604 | ✗ | if (SE->type_size == 1) { | |
| 605 | ✗ | chords->nb_elements += (SE->img[y][x] >= mid); | |
| 606 | //A chord is a run of non-zero pixels. | ||
| 607 | ✗ | if (SE->img[y][x] >= mid && chord_start == -1) { | |
| 608 | // Chord starts. | ||
| 609 | ✗ | chord_start = x; | |
| 610 | ✗ | } else if (SE->img[y][x] < mid && chord_start != -1) { | |
| 611 | // Chord ends before end of line. | ||
| 612 | ✗ | c.x = chord_start - centerX; | |
| 613 | ✗ | c.y = y - centerY; | |
| 614 | ✗ | c.l = x - chord_start; | |
| 615 | ✗ | ret = insert_chord_set(chords, c); | |
| 616 | ✗ | if (ret < 0) | |
| 617 | ✗ | return AVERROR(ENOMEM); | |
| 618 | ✗ | chord_start = -1; | |
| 619 | } | ||
| 620 | } else { | ||
| 621 | ✗ | chords->nb_elements += (AV_RN16(&SE->img[y][x * 2]) >= mid); | |
| 622 | //A chord is a run of non-zero pixels. | ||
| 623 | ✗ | if (AV_RN16(&SE->img[y][x * 2]) >= mid && chord_start == -1) { | |
| 624 | // Chord starts. | ||
| 625 | ✗ | chord_start = x; | |
| 626 | ✗ | } else if (AV_RN16(&SE->img[y][x * 2]) < mid && chord_start != -1) { | |
| 627 | // Chord ends before end of line. | ||
| 628 | ✗ | c.x = chord_start - centerX; | |
| 629 | ✗ | c.y = y - centerY; | |
| 630 | ✗ | c.l = x - chord_start; | |
| 631 | ✗ | ret = insert_chord_set(chords, c); | |
| 632 | ✗ | if (ret < 0) | |
| 633 | ✗ | return AVERROR(ENOMEM); | |
| 634 | ✗ | chord_start = -1; | |
| 635 | } | ||
| 636 | } | ||
| 637 | } | ||
| 638 | ✗ | if (chord_start != -1) { | |
| 639 | // Chord ends at end of line. | ||
| 640 | ✗ | c.x = chord_start - centerX; | |
| 641 | ✗ | c.y = y - centerY; | |
| 642 | ✗ | c.l = x - chord_start; | |
| 643 | ✗ | ret = insert_chord_set(chords, c); | |
| 644 | ✗ | if (ret < 0) | |
| 645 | ✗ | return AVERROR(ENOMEM); | |
| 646 | } | ||
| 647 | } | ||
| 648 | |||
| 649 | /* | ||
| 650 | * Computing the array of chord lengths R(i). | ||
| 651 | * This is needed because the lookup table will contain a row for each | ||
| 652 | * length index i. | ||
| 653 | */ | ||
| 654 | ✗ | qsort(chords->C, chords->size, sizeof(chord), comp_chord_length); | |
| 655 | ✗ | chords->R = av_calloc(1, sizeof(*chords->R)); | |
| 656 | ✗ | if (!chords->R) | |
| 657 | ✗ | return AVERROR(ENOMEM); | |
| 658 | ✗ | chords->Lnum = 0; | |
| 659 | ✗ | val = 0; | |
| 660 | ✗ | r_cap = 1; | |
| 661 | |||
| 662 | ✗ | if (chords->size > 0) { | |
| 663 | ✗ | val = 1; | |
| 664 | ✗ | if (chords->Lnum >= r_cap) { | |
| 665 | ✗ | chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R)); | |
| 666 | ✗ | if (!chords->R) | |
| 667 | ✗ | return AVERROR(ENOMEM); | |
| 668 | ✗ | r_cap *= 2; | |
| 669 | } | ||
| 670 | ✗ | chords->R[chords->Lnum++] = 1; | |
| 671 | ✗ | val = 1; | |
| 672 | } | ||
| 673 | |||
| 674 | ✗ | for (int i = 0; i < chords->size; i++) { | |
| 675 | ✗ | if (val != chords->C[i].l) { | |
| 676 | ✗ | while (2 * val < chords->C[i].l && val != 0) { | |
| 677 | ✗ | if (chords->Lnum >= r_cap) { | |
| 678 | ✗ | chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R)); | |
| 679 | ✗ | if (!chords->R) | |
| 680 | ✗ | return AVERROR(ENOMEM); | |
| 681 | ✗ | r_cap *= 2; | |
| 682 | } | ||
| 683 | |||
| 684 | ✗ | chords->R[chords->Lnum++] = 2 * val; | |
| 685 | ✗ | val *= 2; | |
| 686 | } | ||
| 687 | ✗ | val = chords->C[i].l; | |
| 688 | |||
| 689 | ✗ | if (chords->Lnum >= r_cap) { | |
| 690 | ✗ | chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R)); | |
| 691 | ✗ | if (!chords->R) | |
| 692 | ✗ | return AVERROR(ENOMEM); | |
| 693 | ✗ | r_cap *= 2; | |
| 694 | } | ||
| 695 | ✗ | chords->R[chords->Lnum++] = val; | |
| 696 | } | ||
| 697 | } | ||
| 698 | |||
| 699 | /* | ||
| 700 | * Setting the length indices of chords. | ||
| 701 | * These are needed so that the algorithm can, for each chord, | ||
| 702 | * access the lookup table at the correct length in constant time. | ||
| 703 | */ | ||
| 704 | ✗ | chord_length_index = 0; | |
| 705 | ✗ | for (int i = 0; i < chords->size; i++) { | |
| 706 | ✗ | while (chords->R[chord_length_index] < chords->C[i].l) | |
| 707 | ✗ | chord_length_index++; | |
| 708 | ✗ | chords->C[i].i = chord_length_index; | |
| 709 | } | ||
| 710 | |||
| 711 | /* | ||
| 712 | * Chords are sorted on Y. This way, when a row of the lookup table or IPlane | ||
| 713 | * is cached, the next chord offset has a better chance of being on the | ||
| 714 | * same cache line. | ||
| 715 | */ | ||
| 716 | ✗ | qsort(chords->C, chords->size, sizeof(chord), comp_chord); | |
| 717 | |||
| 718 | ✗ | return 0; | |
| 719 | } | ||
| 720 | |||
| 721 | ✗ | static void free_iplane(IPlane *imp) | |
| 722 | { | ||
| 723 | ✗ | av_freep(&imp->img); | |
| 724 | ✗ | } | |
| 725 | |||
| 726 | ✗ | static int read_iplane(IPlane *imp, const uint8_t *dst, int dst_linesize, | |
| 727 | int w, int h, int R, int type_size, int depth) | ||
| 728 | { | ||
| 729 | ✗ | if (!imp->img) | |
| 730 | ✗ | imp->img = av_calloc(h, sizeof(*imp->img)); | |
| 731 | ✗ | if (!imp->img) | |
| 732 | ✗ | return AVERROR(ENOMEM); | |
| 733 | |||
| 734 | ✗ | imp->w = w; | |
| 735 | ✗ | imp->h = h; | |
| 736 | ✗ | imp->range = R; | |
| 737 | ✗ | imp->depth = depth; | |
| 738 | ✗ | imp->type_size = type_size; | |
| 739 | ✗ | imp->max_out_place = type_size == 1 ? max_fun : max16_fun; | |
| 740 | ✗ | imp->min_out_place = type_size == 1 ? min_fun : min16_fun; | |
| 741 | ✗ | imp->diff_rin_place = type_size == 1 ? diff_fun : diff16_fun; | |
| 742 | ✗ | imp->max_in_place = type_size == 1 ? maxinplace_fun : maxinplace16_fun; | |
| 743 | ✗ | imp->min_in_place = type_size == 1 ? mininplace_fun : mininplace16_fun; | |
| 744 | ✗ | imp->diff_in_place = type_size == 1 ? diffinplace_fun : diffinplace16_fun; | |
| 745 | |||
| 746 | ✗ | for (int y = 0; y < h; y++) | |
| 747 | ✗ | imp->img[y] = (uint8_t *)dst + y * dst_linesize; | |
| 748 | |||
| 749 | ✗ | return 0; | |
| 750 | } | ||
| 751 | |||
| 752 | ✗ | static int config_input(AVFilterLink *inlink) | |
| 753 | { | ||
| 754 | ✗ | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 755 | ✗ | MorphoContext *s = inlink->dst->priv; | |
| 756 | |||
| 757 | ✗ | s->depth = desc->comp[0].depth; | |
| 758 | ✗ | s->type_size = (s->depth + 7) / 8; | |
| 759 | ✗ | s->nb_planes = desc->nb_components; | |
| 760 | ✗ | s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); | |
| 761 | ✗ | s->planewidth[0] = s->planewidth[3] = inlink->w; | |
| 762 | ✗ | s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); | |
| 763 | ✗ | s->planeheight[0] = s->planeheight[3] = inlink->h; | |
| 764 | |||
| 765 | ✗ | return 0; | |
| 766 | } | ||
| 767 | |||
| 768 | ✗ | static int config_input_structure(AVFilterLink *inlink) | |
| 769 | { | ||
| 770 | ✗ | const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); | |
| 771 | ✗ | AVFilterContext *ctx = inlink->dst; | |
| 772 | ✗ | MorphoContext *s = inlink->dst->priv; | |
| 773 | |||
| 774 | ✗ | av_assert0(ctx->inputs[0]->format == ctx->inputs[1]->format); | |
| 775 | |||
| 776 | ✗ | s->splanewidth[1] = s->splanewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w); | |
| 777 | ✗ | s->splanewidth[0] = s->splanewidth[3] = inlink->w; | |
| 778 | ✗ | s->splaneheight[1] = s->splaneheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h); | |
| 779 | ✗ | s->splaneheight[0] = s->splaneheight[3] = inlink->h; | |
| 780 | |||
| 781 | ✗ | return 0; | |
| 782 | } | ||
| 783 | |||
| 784 | ✗ | static int activate(AVFilterContext *ctx) | |
| 785 | { | ||
| 786 | ✗ | MorphoContext *s = ctx->priv; | |
| 787 | ✗ | return ff_framesync_activate(&s->fs); | |
| 788 | } | ||
| 789 | |||
| 790 | typedef struct ThreadData { | ||
| 791 | AVFrame *in, *out; | ||
| 792 | } ThreadData; | ||
| 793 | |||
| 794 | ✗ | static int morpho_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 795 | { | ||
| 796 | ✗ | MorphoContext *s = ctx->priv; | |
| 797 | ✗ | ThreadData *td = arg; | |
| 798 | ✗ | AVFrame *out = td->out; | |
| 799 | ✗ | AVFrame *in = td->in; | |
| 800 | int ret; | ||
| 801 | |||
| 802 | ✗ | for (int p = 0; p < s->nb_planes; p++) { | |
| 803 | ✗ | const int width = s->planewidth[p]; | |
| 804 | ✗ | const int height = s->planeheight[p]; | |
| 805 | ✗ | const int y0 = (height * jobnr ) / nb_jobs; | |
| 806 | ✗ | const int y1 = (height * (jobnr+1)) / nb_jobs; | |
| 807 | ✗ | const int depth = s->depth; | |
| 808 | |||
| 809 | ✗ | if (ctx->is_disabled || !(s->planes & (1 << p))) { | |
| 810 | ✗ | copy: | |
| 811 | ✗ | av_image_copy_plane(out->data[p] + y0 * out->linesize[p], | |
| 812 | out->linesize[p], | ||
| 813 | ✗ | in->data[p] + y0 * in->linesize[p], | |
| 814 | in->linesize[p], | ||
| 815 | ✗ | width * ((depth + 7) / 8), | |
| 816 | y1 - y0); | ||
| 817 | ✗ | continue; | |
| 818 | } | ||
| 819 | |||
| 820 | ✗ | if (s->SE[p].minX == INT16_MAX || | |
| 821 | ✗ | s->SE[p].minY == INT16_MAX || | |
| 822 | ✗ | s->SE[p].maxX == INT16_MIN || | |
| 823 | ✗ | s->SE[p].maxY == INT16_MIN) | |
| 824 | ✗ | goto copy; | |
| 825 | |||
| 826 | ✗ | switch (s->mode) { | |
| 827 | ✗ | case ERODE: | |
| 828 | ✗ | ret = erode(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1); | |
| 829 | ✗ | break; | |
| 830 | ✗ | case DILATE: | |
| 831 | case GRADIENT: | ||
| 832 | ✗ | ret = dilate(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1); | |
| 833 | ✗ | break; | |
| 834 | ✗ | case OPEN: | |
| 835 | case TOPHAT: | ||
| 836 | ✗ | ret = erode(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1); | |
| 837 | ✗ | break; | |
| 838 | ✗ | case CLOSE: | |
| 839 | case BLACKHAT: | ||
| 840 | ✗ | ret = dilate(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1); | |
| 841 | ✗ | break; | |
| 842 | ✗ | default: | |
| 843 | ✗ | av_assert0(0); | |
| 844 | } | ||
| 845 | |||
| 846 | ✗ | if (ret < 0) | |
| 847 | ✗ | return ret; | |
| 848 | } | ||
| 849 | |||
| 850 | ✗ | return 0; | |
| 851 | } | ||
| 852 | |||
| 853 | ✗ | static int morpho_sliceX(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) | |
| 854 | { | ||
| 855 | ✗ | MorphoContext *s = ctx->priv; | |
| 856 | int ret; | ||
| 857 | |||
| 858 | ✗ | for (int p = 0; p < s->nb_planes; p++) { | |
| 859 | ✗ | const int height = s->planeheight[p]; | |
| 860 | ✗ | const int y0 = (height * jobnr ) / nb_jobs; | |
| 861 | ✗ | const int y1 = (height * (jobnr+1)) / nb_jobs; | |
| 862 | |||
| 863 | ✗ | if (ctx->is_disabled || !(s->planes & (1 << p))) { | |
| 864 | ✗ | copy: | |
| 865 | ✗ | continue; | |
| 866 | } | ||
| 867 | |||
| 868 | ✗ | if (s->SE[p].minX == INT16_MAX || | |
| 869 | ✗ | s->SE[p].minY == INT16_MAX || | |
| 870 | ✗ | s->SE[p].maxX == INT16_MIN || | |
| 871 | ✗ | s->SE[p].maxY == INT16_MIN) | |
| 872 | ✗ | goto copy; | |
| 873 | |||
| 874 | ✗ | switch (s->mode) { | |
| 875 | ✗ | case OPEN: | |
| 876 | ✗ | ret = dilate(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1); | |
| 877 | ✗ | break; | |
| 878 | ✗ | case CLOSE: | |
| 879 | ✗ | ret = erode(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1); | |
| 880 | ✗ | break; | |
| 881 | ✗ | case GRADIENT: | |
| 882 | ✗ | ret = erode(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1); | |
| 883 | ✗ | if (ret < 0) | |
| 884 | ✗ | break; | |
| 885 | ✗ | difference(&s->g[p], &s->h[p], y0, y1); | |
| 886 | ✗ | break; | |
| 887 | ✗ | case TOPHAT: | |
| 888 | ✗ | ret = dilate(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1); | |
| 889 | ✗ | if (ret < 0) | |
| 890 | ✗ | break; | |
| 891 | ✗ | difference2(&s->g[p], &s->f[p], y0, y1); | |
| 892 | ✗ | break; | |
| 893 | ✗ | case BLACKHAT: | |
| 894 | ✗ | ret = erode(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1); | |
| 895 | ✗ | if (ret < 0) | |
| 896 | ✗ | break; | |
| 897 | ✗ | difference(&s->g[p], &s->f[p], y0, y1); | |
| 898 | ✗ | break; | |
| 899 | ✗ | default: | |
| 900 | ✗ | av_assert0(0); | |
| 901 | } | ||
| 902 | |||
| 903 | ✗ | if (ret < 0) | |
| 904 | ✗ | return ret; | |
| 905 | } | ||
| 906 | |||
| 907 | ✗ | return 0; | |
| 908 | } | ||
| 909 | |||
| 910 | ✗ | static int do_morpho(FFFrameSync *fs) | |
| 911 | { | ||
| 912 | ✗ | AVFilterContext *ctx = fs->parent; | |
| 913 | ✗ | AVFilterLink *outlink = ctx->outputs[0]; | |
| 914 | ✗ | MorphoContext *s = ctx->priv; | |
| 915 | ✗ | AVFrame *in = NULL, *structurepic = NULL; | |
| 916 | ThreadData td; | ||
| 917 | AVFrame *out; | ||
| 918 | int ret; | ||
| 919 | |||
| 920 | ✗ | ret = ff_framesync_dualinput_get(fs, &in, &structurepic); | |
| 921 | ✗ | if (ret < 0) | |
| 922 | ✗ | return ret; | |
| 923 | ✗ | if (!structurepic) | |
| 924 | ✗ | return ff_filter_frame(outlink, in); | |
| 925 | |||
| 926 | ✗ | out = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 927 | ✗ | if (!out) { | |
| 928 | ✗ | av_frame_free(&in); | |
| 929 | ✗ | return AVERROR(ENOMEM); | |
| 930 | } | ||
| 931 | ✗ | av_frame_copy_props(out, in); | |
| 932 | |||
| 933 | ✗ | for (int p = 0; p < s->nb_planes; p++) { | |
| 934 | ✗ | const uint8_t *ssrc = structurepic->data[p]; | |
| 935 | ✗ | const int ssrc_linesize = structurepic->linesize[p]; | |
| 936 | ✗ | const int swidth = s->splanewidth[p]; | |
| 937 | ✗ | const int sheight = s->splaneheight[p]; | |
| 938 | ✗ | const uint8_t *src = in->data[p]; | |
| 939 | ✗ | int src_linesize = in->linesize[p]; | |
| 940 | ✗ | uint8_t *dst = out->data[p]; | |
| 941 | ✗ | int dst_linesize = out->linesize[p]; | |
| 942 | ✗ | const int width = s->planewidth[p]; | |
| 943 | ✗ | const int height = s->planeheight[p]; | |
| 944 | ✗ | const int depth = s->depth; | |
| 945 | ✗ | int type_size = s->type_size; | |
| 946 | |||
| 947 | ✗ | if (!s->got_structure[p] || s->structures) { | |
| 948 | ✗ | free_chord_set(&s->SE[p]); | |
| 949 | |||
| 950 | ✗ | ret = read_iplane(&s->SEimg[p], ssrc, ssrc_linesize, swidth, sheight, 1, type_size, depth); | |
| 951 | ✗ | if (ret < 0) | |
| 952 | ✗ | goto fail; | |
| 953 | ✗ | ret = build_chord_set(&s->SEimg[p], &s->SE[p]); | |
| 954 | ✗ | if (ret < 0) | |
| 955 | ✗ | goto fail; | |
| 956 | ✗ | s->got_structure[p] = 1; | |
| 957 | } | ||
| 958 | |||
| 959 | ✗ | ret = read_iplane(&s->f[p], src, src_linesize, width, height, 1, type_size, depth); | |
| 960 | ✗ | if (ret < 0) | |
| 961 | ✗ | goto fail; | |
| 962 | |||
| 963 | ✗ | ret = read_iplane(&s->g[p], dst, dst_linesize, s->f[p].w, s->f[p].h, s->f[p].range, type_size, depth); | |
| 964 | ✗ | if (ret < 0) | |
| 965 | ✗ | goto fail; | |
| 966 | |||
| 967 | ✗ | switch (s->mode) { | |
| 968 | ✗ | case OPEN: | |
| 969 | case CLOSE: | ||
| 970 | case GRADIENT: | ||
| 971 | case TOPHAT: | ||
| 972 | case BLACKHAT: | ||
| 973 | ✗ | ret = read_iplane(&s->h[p], s->temp->data[p], s->temp->linesize[p], width, height, 1, type_size, depth); | |
| 974 | ✗ | break; | |
| 975 | } | ||
| 976 | |||
| 977 | ✗ | if (ret < 0) | |
| 978 | ✗ | goto fail; | |
| 979 | } | ||
| 980 | |||
| 981 | ✗ | td.in = in; td.out = out; | |
| 982 | ✗ | ret = ff_filter_execute(ctx, morpho_slice, &td, NULL, | |
| 983 | ✗ | FFMIN3(s->planeheight[1], s->planeheight[2], | |
| 984 | FFMIN(MAX_THREADS, ff_filter_get_nb_threads(ctx)))); | ||
| 985 | ✗ | if (ret == 0 && (s->mode != ERODE && s->mode != DILATE)) { | |
| 986 | ✗ | ff_filter_execute(ctx, morpho_sliceX, NULL, NULL, | |
| 987 | ✗ | FFMIN3(s->planeheight[1], s->planeheight[2], | |
| 988 | FFMIN(MAX_THREADS, ff_filter_get_nb_threads(ctx)))); | ||
| 989 | } | ||
| 990 | |||
| 991 | ✗ | av_frame_free(&in); | |
| 992 | ✗ | out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base); | |
| 993 | ✗ | return ff_filter_frame(outlink, out); | |
| 994 | ✗ | fail: | |
| 995 | ✗ | av_frame_free(&out); | |
| 996 | ✗ | av_frame_free(&in); | |
| 997 | ✗ | return ret; | |
| 998 | } | ||
| 999 | |||
| 1000 | ✗ | static int config_output(AVFilterLink *outlink) | |
| 1001 | { | ||
| 1002 | ✗ | AVFilterContext *ctx = outlink->src; | |
| 1003 | ✗ | MorphoContext *s = ctx->priv; | |
| 1004 | ✗ | AVFilterLink *mainlink = ctx->inputs[0]; | |
| 1005 | ✗ | FilterLink *il = ff_filter_link(mainlink); | |
| 1006 | ✗ | FilterLink *ol = ff_filter_link(outlink); | |
| 1007 | int ret; | ||
| 1008 | |||
| 1009 | ✗ | s->fs.on_event = do_morpho; | |
| 1010 | ✗ | ret = ff_framesync_init_dualinput(&s->fs, ctx); | |
| 1011 | ✗ | if (ret < 0) | |
| 1012 | ✗ | return ret; | |
| 1013 | ✗ | outlink->w = mainlink->w; | |
| 1014 | ✗ | outlink->h = mainlink->h; | |
| 1015 | ✗ | outlink->time_base = mainlink->time_base; | |
| 1016 | ✗ | outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio; | |
| 1017 | ✗ | ol->frame_rate = il->frame_rate; | |
| 1018 | |||
| 1019 | ✗ | if ((ret = ff_framesync_configure(&s->fs)) < 0) | |
| 1020 | ✗ | return ret; | |
| 1021 | ✗ | outlink->time_base = s->fs.time_base; | |
| 1022 | |||
| 1023 | ✗ | s->temp = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
| 1024 | ✗ | if (!s->temp) | |
| 1025 | ✗ | return AVERROR(ENOMEM); | |
| 1026 | |||
| 1027 | ✗ | s->plane_f = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_f)); | |
| 1028 | ✗ | s->plane_g = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_g)); | |
| 1029 | ✗ | if (!s->plane_f || !s->plane_g) | |
| 1030 | ✗ | return AVERROR(ENOMEM); | |
| 1031 | |||
| 1032 | ✗ | return 0; | |
| 1033 | } | ||
| 1034 | |||
| 1035 | ✗ | static av_cold void uninit(AVFilterContext *ctx) | |
| 1036 | { | ||
| 1037 | ✗ | MorphoContext *s = ctx->priv; | |
| 1038 | |||
| 1039 | ✗ | for (int p = 0; p < 4; p++) { | |
| 1040 | ✗ | free_iplane(&s->SEimg[p]); | |
| 1041 | ✗ | free_iplane(&s->f[p]); | |
| 1042 | ✗ | free_iplane(&s->g[p]); | |
| 1043 | ✗ | free_iplane(&s->h[p]); | |
| 1044 | ✗ | free_chord_set(&s->SE[p]); | |
| 1045 | ✗ | for (int n = 0; n < MAX_THREADS; n++) { | |
| 1046 | ✗ | free_lut(&s->Ty[n][0][p]); | |
| 1047 | ✗ | free_lut(&s->Ty[n][1][p]); | |
| 1048 | } | ||
| 1049 | } | ||
| 1050 | |||
| 1051 | ✗ | ff_framesync_uninit(&s->fs); | |
| 1052 | |||
| 1053 | ✗ | av_frame_free(&s->temp); | |
| 1054 | ✗ | av_freep(&s->plane_f); | |
| 1055 | ✗ | av_freep(&s->plane_g); | |
| 1056 | ✗ | } | |
| 1057 | |||
| 1058 | static const AVFilterPad morpho_inputs[] = { | ||
| 1059 | { | ||
| 1060 | .name = "default", | ||
| 1061 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 1062 | .config_props = config_input, | ||
| 1063 | }, | ||
| 1064 | { | ||
| 1065 | .name = "structure", | ||
| 1066 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 1067 | .config_props = config_input_structure, | ||
| 1068 | }, | ||
| 1069 | }; | ||
| 1070 | |||
| 1071 | static const AVFilterPad morpho_outputs[] = { | ||
| 1072 | { | ||
| 1073 | .name = "default", | ||
| 1074 | .type = AVMEDIA_TYPE_VIDEO, | ||
| 1075 | .config_props = config_output, | ||
| 1076 | }, | ||
| 1077 | }; | ||
| 1078 | |||
| 1079 | const FFFilter ff_vf_morpho = { | ||
| 1080 | .p.name = "morpho", | ||
| 1081 | .p.description = NULL_IF_CONFIG_SMALL("Apply Morphological filter."), | ||
| 1082 | .p.priv_class = &morpho_class, | ||
| 1083 | .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL | | ||
| 1084 | AVFILTER_FLAG_SLICE_THREADS, | ||
| 1085 | .preinit = morpho_framesync_preinit, | ||
| 1086 | .priv_size = sizeof(MorphoContext), | ||
| 1087 | .activate = activate, | ||
| 1088 | .uninit = uninit, | ||
| 1089 | FILTER_INPUTS(morpho_inputs), | ||
| 1090 | FILTER_OUTPUTS(morpho_outputs), | ||
| 1091 | FILTER_PIXFMTS_ARRAY(pix_fmts), | ||
| 1092 | .process_command = ff_filter_process_command, | ||
| 1093 | }; | ||
| 1094 |