diff mbox

[FFmpeg-devel] avfilter: add unpremultiply filter

Message ID 20170801110336.19704-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Aug. 1, 2017, 11:03 a.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 doc/filters.texi             |  13 ++
 libavfilter/Makefile         |   1 +
 libavfilter/allfilters.c     |   1 +
 libavfilter/vf_premultiply.c | 307 ++++++++++++++++++++++++++++++++++++-------
 4 files changed, 277 insertions(+), 45 deletions(-)

Comments

Nicolas George Aug. 1, 2017, 1 p.m. UTC | #1
Le quartidi 14 thermidor, an CCXXV, Paul B Mahol a écrit :
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi             |  13 ++
>  libavfilter/Makefile         |   1 +
>  libavfilter/allfilters.c     |   1 +
>  libavfilter/vf_premultiply.c | 307 ++++++++++++++++++++++++++++++++++++-------
>  4 files changed, 277 insertions(+), 45 deletions(-)

Thanks for the change. It looks ok to me now.

Regards,
Tobias Rapp Aug. 1, 2017, 1:01 p.m. UTC | #2
On 01.08.2017 13:03, Paul B Mahol wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>
> ---
>  doc/filters.texi             |  13 ++
>  libavfilter/Makefile         |   1 +
>  libavfilter/allfilters.c     |   1 +
>  libavfilter/vf_premultiply.c | 307 ++++++++++++++++++++++++++++++++++++-------
>  4 files changed, 277 insertions(+), 45 deletions(-)
>
> diff --git a/doc/filters.texi b/doc/filters.texi
> index 4089135..a50696a 100644
> --- a/doc/filters.texi
> +++ b/doc/filters.texi
> @@ -14532,6 +14532,19 @@ ffmpeg -i INPUT -vf trim=duration=1
>
>  @end itemize
>
> +@section unpremultiply
> +Apply alpha unpremultiply effect to input video stream using first plane
> +of second stream as alpha.
> +
> +Both streams must have same dimensions and same pixel format.
> +
> +The filter accepts the following option:
> +
> +@table @option
> +@item planes
> +Set which planes will be processed, unprocessed planes will be copied.
> +By default value 0xf, all planes will be processed.
> +@end table

IMHO using a flags-like string "planes=rgb" would be more user-friendly 
than a bitmask. At least the documentation should tell which bit refers 
to what channel.

> [...]
>

Some FATE test for the new filter would be welcome.

Regards,
Tobias
Paul B Mahol Aug. 1, 2017, 1:31 p.m. UTC | #3
On 8/1/17, Tobias Rapp <t.rapp@noa-archive.com> wrote:
> On 01.08.2017 13:03, Paul B Mahol wrote:
>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>> ---
>>  doc/filters.texi             |  13 ++
>>  libavfilter/Makefile         |   1 +
>>  libavfilter/allfilters.c     |   1 +
>>  libavfilter/vf_premultiply.c | 307
>> ++++++++++++++++++++++++++++++++++++-------
>>  4 files changed, 277 insertions(+), 45 deletions(-)
>>
>> diff --git a/doc/filters.texi b/doc/filters.texi
>> index 4089135..a50696a 100644
>> --- a/doc/filters.texi
>> +++ b/doc/filters.texi
>> @@ -14532,6 +14532,19 @@ ffmpeg -i INPUT -vf trim=duration=1
>>
>>  @end itemize
>>
>> +@section unpremultiply
>> +Apply alpha unpremultiply effect to input video stream using first plane
>> +of second stream as alpha.
>> +
>> +Both streams must have same dimensions and same pixel format.
>> +
>> +The filter accepts the following option:
>> +
>> +@table @option
>> +@item planes
>> +Set which planes will be processed, unprocessed planes will be copied.
>> +By default value 0xf, all planes will be processed.
>> +@end table
>
> IMHO using a flags-like string "planes=rgb" would be more user-friendly
> than a bitmask. At least the documentation should tell which bit refers
> to what channel.

It is directly related to pixel format.
Tobias Rapp Aug. 1, 2017, 3:11 p.m. UTC | #4
On 01.08.2017 15:31, Paul B Mahol wrote:
> On 8/1/17, Tobias Rapp <t.rapp@noa-archive.com> wrote:
>> On 01.08.2017 13:03, Paul B Mahol wrote:
>>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>>> ---
>>>  doc/filters.texi             |  13 ++
>>>  libavfilter/Makefile         |   1 +
>>>  libavfilter/allfilters.c     |   1 +
>>>  libavfilter/vf_premultiply.c | 307
>>> ++++++++++++++++++++++++++++++++++++-------
>>>  4 files changed, 277 insertions(+), 45 deletions(-)
>>>
>>> diff --git a/doc/filters.texi b/doc/filters.texi
>>> index 4089135..a50696a 100644
>>> --- a/doc/filters.texi
>>> +++ b/doc/filters.texi
>>> @@ -14532,6 +14532,19 @@ ffmpeg -i INPUT -vf trim=duration=1
>>>
>>>  @end itemize
>>>
>>> +@section unpremultiply
>>> +Apply alpha unpremultiply effect to input video stream using first plane
>>> +of second stream as alpha.
>>> +
>>> +Both streams must have same dimensions and same pixel format.
>>> +
>>> +The filter accepts the following option:
>>> +
>>> +@table @option
>>> +@item planes
>>> +Set which planes will be processed, unprocessed planes will be copied.
>>> +By default value 0xf, all planes will be processed.
>>> +@end table
>>
>> IMHO using a flags-like string "planes=rgb" would be more user-friendly
>> than a bitmask. At least the documentation should tell which bit refers
>> to what channel.
>
> It is directly related to pixel format.

I'm just wondering whether I can apply the logic of 
AVPixFmtDescriptor.comp to the planes bitmask or not:

     /**
      * Parameters that describe how pixels are packed.
      * If the format has 1 or 2 components, then luma is 0.
      * If the format has 3 or 4 components:
      *   if the RGB flag is set then 0 is red, 1 is green and 2 is blue;
      *   otherwise 0 is luma, 1 is chroma-U and 2 is chroma-V.
      *
      * If present, the Alpha channel is always the last component.
      */

Regards,
Tobias
Paul B Mahol Aug. 1, 2017, 3:33 p.m. UTC | #5
On 8/1/17, Tobias Rapp <t.rapp@noa-archive.com> wrote:
> On 01.08.2017 15:31, Paul B Mahol wrote:
>> On 8/1/17, Tobias Rapp <t.rapp@noa-archive.com> wrote:
>>> On 01.08.2017 13:03, Paul B Mahol wrote:
>>>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>>>> ---
>>>>  doc/filters.texi             |  13 ++
>>>>  libavfilter/Makefile         |   1 +
>>>>  libavfilter/allfilters.c     |   1 +
>>>>  libavfilter/vf_premultiply.c | 307
>>>> ++++++++++++++++++++++++++++++++++++-------
>>>>  4 files changed, 277 insertions(+), 45 deletions(-)
>>>>
>>>> diff --git a/doc/filters.texi b/doc/filters.texi
>>>> index 4089135..a50696a 100644
>>>> --- a/doc/filters.texi
>>>> +++ b/doc/filters.texi
>>>> @@ -14532,6 +14532,19 @@ ffmpeg -i INPUT -vf trim=duration=1
>>>>
>>>>  @end itemize
>>>>
>>>> +@section unpremultiply
>>>> +Apply alpha unpremultiply effect to input video stream using first
>>>> plane
>>>> +of second stream as alpha.
>>>> +
>>>> +Both streams must have same dimensions and same pixel format.
>>>> +
>>>> +The filter accepts the following option:
>>>> +
>>>> +@table @option
>>>> +@item planes
>>>> +Set which planes will be processed, unprocessed planes will be copied.
>>>> +By default value 0xf, all planes will be processed.
>>>> +@end table
>>>
>>> IMHO using a flags-like string "planes=rgb" would be more user-friendly
>>> than a bitmask. At least the documentation should tell which bit refers
>>> to what channel.
>>
>> It is directly related to pixel format.
>
> I'm just wondering whether I can apply the logic of
> AVPixFmtDescriptor.comp to the planes bitmask or not:
>
>      /**
>       * Parameters that describe how pixels are packed.
>       * If the format has 1 or 2 components, then luma is 0.
>       * If the format has 3 or 4 components:
>       *   if the RGB flag is set then 0 is red, 1 is green and 2 is blue;
>       *   otherwise 0 is luma, 1 is chroma-U and 2 is chroma-V.
>       *
>       * If present, the Alpha channel is always the last component.
>       */

I mainly did it bitmask way because of additional code needed to handle cases
when there is no r/g/b but y/u/v planes and user supplied r/g/b only.
Tobias Rapp Aug. 2, 2017, 7:41 a.m. UTC | #6
On 01.08.2017 17:33, Paul B Mahol wrote:
> On 8/1/17, Tobias Rapp <t.rapp@noa-archive.com> wrote:
>> On 01.08.2017 15:31, Paul B Mahol wrote:
>>> On 8/1/17, Tobias Rapp <t.rapp@noa-archive.com> wrote:
>>>> On 01.08.2017 13:03, Paul B Mahol wrote:
>>>>> Signed-off-by: Paul B Mahol <onemda@gmail.com>
>>>>> ---
>>>>>  doc/filters.texi             |  13 ++
>>>>>  libavfilter/Makefile         |   1 +
>>>>>  libavfilter/allfilters.c     |   1 +
>>>>>  libavfilter/vf_premultiply.c | 307
>>>>> ++++++++++++++++++++++++++++++++++++-------
>>>>>  4 files changed, 277 insertions(+), 45 deletions(-)
>>>>>
>>>>> diff --git a/doc/filters.texi b/doc/filters.texi
>>>>> index 4089135..a50696a 100644
>>>>> --- a/doc/filters.texi
>>>>> +++ b/doc/filters.texi
>>>>> @@ -14532,6 +14532,19 @@ ffmpeg -i INPUT -vf trim=duration=1
>>>>>
>>>>>  @end itemize
>>>>>
>>>>> +@section unpremultiply
>>>>> +Apply alpha unpremultiply effect to input video stream using first
>>>>> plane
>>>>> +of second stream as alpha.
>>>>> +
>>>>> +Both streams must have same dimensions and same pixel format.
>>>>> +
>>>>> +The filter accepts the following option:
>>>>> +
>>>>> +@table @option
>>>>> +@item planes
>>>>> +Set which planes will be processed, unprocessed planes will be copied.
>>>>> +By default value 0xf, all planes will be processed.
>>>>> +@end table
>>>>
>>>> IMHO using a flags-like string "planes=rgb" would be more user-friendly
>>>> than a bitmask. At least the documentation should tell which bit refers
>>>> to what channel.
>>>
>>> It is directly related to pixel format.
>>
>> I'm just wondering whether I can apply the logic of
>> AVPixFmtDescriptor.comp to the planes bitmask or not:
>>
>>      /**
>>       * Parameters that describe how pixels are packed.
>>       * If the format has 1 or 2 components, then luma is 0.
>>       * If the format has 3 or 4 components:
>>       *   if the RGB flag is set then 0 is red, 1 is green and 2 is blue;
>>       *   otherwise 0 is luma, 1 is chroma-U and 2 is chroma-V.
>>       *
>>       * If present, the Alpha channel is always the last component.
>>       */
>
> I mainly did it bitmask way because of additional code needed to handle cases
> when there is no r/g/b but y/u/v planes and user supplied r/g/b only.


Indeed a bitmask is more generic. I'm not against it but think that 
there should be some more details in the user documentation on how to 
map the bits to planes. For example (in case the code comment above 
applies) something like:

"""
If the format has 1 or 2 components, then luma is bit 0.
If the format has 3 or 4 components:
   for RGB formats bit 0 is red, bit 1 is green and bit 2 is blue;
   for YUV formats bit 0 is luma, bit 1 is chroma-U and bit 2 is chroma-V.
If present, the alpha channel is always the last bit.
"""

Regards,
Tobias
diff mbox

Patch

diff --git a/doc/filters.texi b/doc/filters.texi
index 4089135..a50696a 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -14532,6 +14532,19 @@  ffmpeg -i INPUT -vf trim=duration=1
 
 @end itemize
 
+@section unpremultiply
+Apply alpha unpremultiply effect to input video stream using first plane
+of second stream as alpha.
+
+Both streams must have same dimensions and same pixel format.
+
+The filter accepts the following option:
+
+@table @option
+@item planes
+Set which planes will be processed, unprocessed planes will be copied.
+By default value 0xf, all planes will be processed.
+@end table
 
 @anchor{unsharp}
 @section unsharp
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 4d61d78..f0bb8e7 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -314,6 +314,7 @@  OBJS-$(CONFIG_TILE_FILTER)                   += vf_tile.o
 OBJS-$(CONFIG_TINTERLACE_FILTER)             += vf_tinterlace.o
 OBJS-$(CONFIG_TRANSPOSE_FILTER)              += vf_transpose.o
 OBJS-$(CONFIG_TRIM_FILTER)                   += trim.o
+OBJS-$(CONFIG_UNPREMULTIPLY_FILTER)          += vf_premultiply.o framesync2.o
 OBJS-$(CONFIG_UNSHARP_FILTER)                += vf_unsharp.o
 OBJS-$(CONFIG_USPP_FILTER)                   += vf_uspp.o
 OBJS-$(CONFIG_VAGUEDENOISER_FILTER)          += vf_vaguedenoiser.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index b1c2d11..0fca662 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -325,6 +325,7 @@  static void register_all(void)
     REGISTER_FILTER(TINTERLACE,     tinterlace,     vf);
     REGISTER_FILTER(TRANSPOSE,      transpose,      vf);
     REGISTER_FILTER(TRIM,           trim,           vf);
+    REGISTER_FILTER(UNPREMULTIPLY,  unpremultiply,  vf);
     REGISTER_FILTER(UNSHARP,        unsharp,        vf);
     REGISTER_FILTER(USPP,           uspp,           vf);
     REGISTER_FILTER(VAGUEDENOISER,  vaguedenoiser,  vf);
diff --git a/libavfilter/vf_premultiply.c b/libavfilter/vf_premultiply.c
index 4bb850e..99d93c9 100644
--- a/libavfilter/vf_premultiply.c
+++ b/libavfilter/vf_premultiply.c
@@ -33,7 +33,8 @@  typedef struct PreMultiplyContext {
     int linesize[4];
     int nb_planes;
     int planes;
-    int half, depth, offset;
+    int inverse;
+    int half, depth, offset, max;
     FFFrameSync fs;
 
     void (*premultiply[4])(const uint8_t *msrc, const uint8_t *asrc,
@@ -47,11 +48,12 @@  typedef struct PreMultiplyContext {
 #define OFFSET(x) offsetof(PreMultiplyContext, x)
 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
 
-static const AVOption premultiply_options[] = {
+static const AVOption options[] = {
     { "planes", "set planes", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=0xF}, 0, 0xF, FLAGS },
     { NULL }
 };
 
+#define premultiply_options options
 AVFILTER_DEFINE_CLASS(premultiply);
 
 static int query_formats(AVFilterContext *ctx)
@@ -199,6 +201,153 @@  static void premultiply16offset(const uint8_t *mmsrc, const uint8_t *aasrc,
     }
 }
 
+static void unpremultiply8(const uint8_t *msrc, const uint8_t *asrc,
+                           uint8_t *dst,
+                           ptrdiff_t mlinesize, ptrdiff_t alinesize,
+                           ptrdiff_t dlinesize,
+                           int w, int h,
+                           int half, int max, int offset)
+{
+    int x, y;
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (asrc[x] > 0 && asrc[x] < 255)
+                dst[x] = FFMIN(msrc[x] * 255 / asrc[x], 255);
+            else
+                dst[x] = msrc[x];
+        }
+
+        dst  += dlinesize;
+        msrc += mlinesize;
+        asrc += alinesize;
+    }
+}
+
+static void unpremultiply8yuv(const uint8_t *msrc, const uint8_t *asrc,
+                              uint8_t *dst,
+                              ptrdiff_t mlinesize, ptrdiff_t alinesize,
+                              ptrdiff_t dlinesize,
+                              int w, int h,
+                              int half, int max, int offset)
+{
+    int x, y;
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (asrc[x] > 0 && asrc[x] < 255)
+                dst[x] = FFMIN((msrc[x] - 128) * 255 / asrc[x] + 128, 255);
+            else
+                dst[x] = msrc[x];
+        }
+
+        dst  += dlinesize;
+        msrc += mlinesize;
+        asrc += alinesize;
+    }
+}
+
+static void unpremultiply8offset(const uint8_t *msrc, const uint8_t *asrc,
+                                 uint8_t *dst,
+                                 ptrdiff_t mlinesize, ptrdiff_t alinesize,
+                                 ptrdiff_t dlinesize,
+                                 int w, int h,
+                                 int half, int max, int offset)
+{
+    int x, y;
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (asrc[x] > 0 && asrc[x] < 255)
+                dst[x] = FFMIN((msrc[x] - offset) * 255 / asrc[x] + offset, 255);
+            else
+                dst[x] = msrc[x];
+        }
+
+        dst  += dlinesize;
+        msrc += mlinesize;
+        asrc += alinesize;
+    }
+}
+
+static void unpremultiply16(const uint8_t *mmsrc, const uint8_t *aasrc,
+                            uint8_t *ddst,
+                            ptrdiff_t mlinesize, ptrdiff_t alinesize,
+                            ptrdiff_t dlinesize,
+                            int w, int h,
+                            int half, int max, int offset)
+{
+    const uint16_t *msrc = (const uint16_t *)mmsrc;
+    const uint16_t *asrc = (const uint16_t *)aasrc;
+    uint16_t *dst = (uint16_t *)ddst;
+    int x, y;
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (asrc[x] > 0 && asrc[x] < max)
+                dst[x] = FFMIN(msrc[x] * max / asrc[x], max);
+            else
+                dst[x] = msrc[x];
+        }
+
+        dst  += dlinesize / 2;
+        msrc += mlinesize / 2;
+        asrc += alinesize / 2;
+    }
+}
+
+static void unpremultiply16yuv(const uint8_t *mmsrc, const uint8_t *aasrc,
+                               uint8_t *ddst,
+                               ptrdiff_t mlinesize, ptrdiff_t alinesize,
+                               ptrdiff_t dlinesize,
+                               int w, int h,
+                               int half, int max, int offset)
+{
+    const uint16_t *msrc = (const uint16_t *)mmsrc;
+    const uint16_t *asrc = (const uint16_t *)aasrc;
+    uint16_t *dst = (uint16_t *)ddst;
+    int x, y;
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (asrc[x] > 0 && asrc[x] < max)
+                dst[x] = FFMIN((msrc[x] - half) * max / asrc[x] + half, max);
+            else
+                dst[x] = msrc[x];
+        }
+
+        dst  += dlinesize / 2;
+        msrc += mlinesize / 2;
+        asrc += alinesize / 2;
+    }
+}
+
+static void unpremultiply16offset(const uint8_t *mmsrc, const uint8_t *aasrc,
+                                  uint8_t *ddst,
+                                  ptrdiff_t mlinesize, ptrdiff_t alinesize,
+                                  ptrdiff_t dlinesize,
+                                  int w, int h,
+                                  int half, int max, int offset)
+{
+    const uint16_t *msrc = (const uint16_t *)mmsrc;
+    const uint16_t *asrc = (const uint16_t *)aasrc;
+    uint16_t *dst = (uint16_t *)ddst;
+    int x, y;
+
+    for (y = 0; y < h; y++) {
+        for (x = 0; x < w; x++) {
+            if (asrc[x] > 0 && asrc[x] < max)
+                dst[x] = FFMIN((msrc[x] - offset) * max / asrc[x] + offset, max);
+            else
+                dst[x] = msrc[x];
+        }
+
+        dst  += dlinesize / 2;
+        msrc += mlinesize / 2;
+        asrc += alinesize / 2;
+    }
+}
+
 static int process_frame(FFFrameSync *fs)
 {
     AVFilterContext *ctx = fs->parent;
@@ -226,48 +375,80 @@  static int process_frame(FFFrameSync *fs)
         full = base->color_range == AVCOL_RANGE_JPEG;
         limited = base->color_range == AVCOL_RANGE_MPEG;
 
-        switch (outlink->format) {
-        case AV_PIX_FMT_YUV444P:
-            s->premultiply[0] = full ? premultiply8 : premultiply8offset;
-            s->premultiply[1] = premultiply8yuv;
-            s->premultiply[2] = premultiply8yuv;
-            break;
-        case AV_PIX_FMT_YUVJ444P:
-            s->premultiply[0] = premultiply8;
-            s->premultiply[1] = premultiply8yuv;
-            s->premultiply[2] = premultiply8yuv;
-            break;
-        case AV_PIX_FMT_GBRP:
-            s->premultiply[0] = limited ? premultiply8offset : premultiply8;
-            s->premultiply[1] = limited ? premultiply8offset : premultiply8;
-            s->premultiply[2] = limited ? premultiply8offset : premultiply8;
-            break;
-        case AV_PIX_FMT_YUV444P9:
-        case AV_PIX_FMT_YUV444P10:
-        case AV_PIX_FMT_YUV444P12:
-        case AV_PIX_FMT_YUV444P14:
-        case AV_PIX_FMT_YUV444P16:
-            s->premultiply[0] = full ? premultiply16 : premultiply16offset;
-            s->premultiply[1] = premultiply16yuv;
-            s->premultiply[2] = premultiply16yuv;
-            break;
-        case AV_PIX_FMT_GBRP9:
-        case AV_PIX_FMT_GBRP10:
-        case AV_PIX_FMT_GBRP12:
-        case AV_PIX_FMT_GBRP14:
-        case AV_PIX_FMT_GBRP16:
-            s->premultiply[0] = limited ? premultiply16offset : premultiply16;
-            s->premultiply[1] = limited ? premultiply16offset : premultiply16;
-            s->premultiply[2] = limited ? premultiply16offset : premultiply16;
-            break;
-        case AV_PIX_FMT_GRAY8:
-            s->premultiply[0] = limited ? premultiply8offset : premultiply8;
-            break;
-        case AV_PIX_FMT_GRAY10:
-        case AV_PIX_FMT_GRAY12:
-        case AV_PIX_FMT_GRAY16:
-            s->premultiply[0] = limited ? premultiply16offset : premultiply16;
-            break;
+        if (s->inverse) {
+            switch (outlink->format) {
+            case AV_PIX_FMT_YUV444P:
+                s->premultiply[0] = full ? unpremultiply8 : unpremultiply8offset;
+                s->premultiply[1] = s->premultiply[2] = unpremultiply8yuv;
+                break;
+            case AV_PIX_FMT_YUVJ444P:
+                s->premultiply[0] = unpremultiply8;
+                s->premultiply[1] = s->premultiply[2] = unpremultiply8yuv;
+                break;
+            case AV_PIX_FMT_GBRP:
+                s->premultiply[0] = s->premultiply[1] = s->premultiply[2] = limited ? unpremultiply8offset : unpremultiply8;
+                break;
+            case AV_PIX_FMT_YUV444P9:
+            case AV_PIX_FMT_YUV444P10:
+            case AV_PIX_FMT_YUV444P12:
+            case AV_PIX_FMT_YUV444P14:
+            case AV_PIX_FMT_YUV444P16:
+                s->premultiply[0] = full ? unpremultiply16 : unpremultiply16offset;
+                s->premultiply[1] = s->premultiply[2] = unpremultiply16yuv;
+                break;
+            case AV_PIX_FMT_GBRP9:
+            case AV_PIX_FMT_GBRP10:
+            case AV_PIX_FMT_GBRP12:
+            case AV_PIX_FMT_GBRP14:
+            case AV_PIX_FMT_GBRP16:
+                s->premultiply[0] = s->premultiply[1] = s->premultiply[2] = limited ? unpremultiply16offset : unpremultiply16;
+                break;
+            case AV_PIX_FMT_GRAY8:
+                s->premultiply[0] = limited ? unpremultiply8offset : unpremultiply8;
+                break;
+            case AV_PIX_FMT_GRAY10:
+            case AV_PIX_FMT_GRAY12:
+            case AV_PIX_FMT_GRAY16:
+                s->premultiply[0] = limited ? unpremultiply16offset : unpremultiply16;
+                break;
+            }
+        } else {
+            switch (outlink->format) {
+            case AV_PIX_FMT_YUV444P:
+                s->premultiply[0] = full ? premultiply8 : premultiply8offset;
+                s->premultiply[1] = s->premultiply[2] = premultiply8yuv;
+                break;
+            case AV_PIX_FMT_YUVJ444P:
+                s->premultiply[0] = premultiply8;
+                s->premultiply[1] = s->premultiply[2] = premultiply8yuv;
+                break;
+            case AV_PIX_FMT_GBRP:
+                s->premultiply[0] = s->premultiply[1] = s->premultiply[2] = limited ? premultiply8offset : premultiply8;
+                break;
+            case AV_PIX_FMT_YUV444P9:
+            case AV_PIX_FMT_YUV444P10:
+            case AV_PIX_FMT_YUV444P12:
+            case AV_PIX_FMT_YUV444P14:
+            case AV_PIX_FMT_YUV444P16:
+                s->premultiply[0] = full ? premultiply16 : premultiply16offset;
+                s->premultiply[1] = s->premultiply[2] = premultiply16yuv;
+                break;
+            case AV_PIX_FMT_GBRP9:
+            case AV_PIX_FMT_GBRP10:
+            case AV_PIX_FMT_GBRP12:
+            case AV_PIX_FMT_GBRP14:
+            case AV_PIX_FMT_GBRP16:
+                s->premultiply[0] = s->premultiply[1] = s->premultiply[2] = limited ? premultiply16offset : premultiply16;
+                break;
+            case AV_PIX_FMT_GRAY8:
+                s->premultiply[0] = limited ? premultiply8offset : premultiply8;
+                break;
+            case AV_PIX_FMT_GRAY10:
+            case AV_PIX_FMT_GRAY12:
+            case AV_PIX_FMT_GRAY16:
+                s->premultiply[0] = limited ? premultiply16offset : premultiply16;
+                break;
+            }
         }
 
         for (p = 0; p < s->nb_planes; p++) {
@@ -282,7 +463,7 @@  static int process_frame(FFFrameSync *fs)
                               base->linesize[p], alpha->linesize[0],
                               out->linesize[p],
                               s->width[p], s->height[p],
-                              s->half, s->depth, s->offset);
+                              s->half, s->inverse ? s->max : s->depth, s->offset);
         }
     }
     out->pts = av_rescale_q(base->pts, s->fs.time_base, outlink->time_base);
@@ -310,6 +491,7 @@  static int config_input(AVFilterLink *inlink)
     s->width[0]  = s->width[3]  = inlink->w;
 
     s->depth = desc->comp[0].depth;
+    s->max = (1 << s->depth) - 1;
     s->half = (1 << s->depth) / 2;
     s->offset = 16 << (s->depth - 8);
 
@@ -369,6 +551,16 @@  static int activate(AVFilterContext *ctx)
     return ff_framesync2_activate(&s->fs);
 }
 
+static av_cold int init(AVFilterContext *ctx)
+{
+    PreMultiplyContext *s = ctx->priv;
+
+    if (!strcmp(ctx->filter->name, "unpremultiply"))
+        s->inverse = 1;
+
+    return 0;
+}
+
 static av_cold void uninit(AVFilterContext *ctx)
 {
     PreMultiplyContext *s = ctx->priv;
@@ -398,6 +590,8 @@  static const AVFilterPad premultiply_outputs[] = {
     { NULL }
 };
 
+#if CONFIG_PREMULTIPLY_FILTER
+
 AVFilter ff_vf_premultiply = {
     .name          = "premultiply",
     .description   = NULL_IF_CONFIG_SMALL("PreMultiply first stream with first plane of second stream."),
@@ -410,3 +604,26 @@  AVFilter ff_vf_premultiply = {
     .priv_class    = &premultiply_class,
     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
 };
+
+#endif /* CONFIG_PREMULTIPLY_FILTER */
+
+#if CONFIG_UNPREMULTIPLY_FILTER
+
+#define unpremultiply_options options
+AVFILTER_DEFINE_CLASS(unpremultiply);
+
+AVFilter ff_vf_unpremultiply = {
+    .name          = "unpremultiply",
+    .description   = NULL_IF_CONFIG_SMALL("UnPreMultiply first stream with first plane of second stream."),
+    .priv_size     = sizeof(PreMultiplyContext),
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .activate      = activate,
+    .inputs        = premultiply_inputs,
+    .outputs       = premultiply_outputs,
+    .priv_class    = &unpremultiply_class,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL,
+};
+
+#endif /* CONFIG_UNPREMULTIPLY_FILTER */