diff mbox series

[FFmpeg-devel] Splash encoder/decoder

Message ID 03cdeb54-f063-a75e-f33f-5f0eab582231@rockingship.org
State New
Headers show
Series [FFmpeg-devel] Splash encoder/decoder
Related show

Checks

Context Check Description
andriy/configure warning Failed to apply patch
andriy/configure warning Failed to apply patch

Commit Message

xyzzy Dec. 22, 2020, 6:47 p.m. UTC
Congratulations with your 20th anniversary!

I was planning to submit this codec as part of the jsFractalZoom project 
after it has been officially announced. It felt as a fitting surprise to 
submit it on that day.

The splash encoder/decoder is a lossy/lossless codec based on fractal 
zoom technology.

Project page: https://github.com/RockingShip/jsFractalZoom

This is my first submission to FFmpeg, I hope I did all the things right.

I would also suggest to add a point to the developer checklist. 
(https://ffmpeg.org/developer.html)

Under section 7: "New codecs or formats checklist", please add:
"AVCodecID: must be in the same order in "avcodec.h" as in "codec_id.h"




 From a67439cea5805efa47717812875de76367681ee8 Mon Sep 17 00:00:00 2001
From: Xyzzy <xyzzy@rockingship.org>
Date: Tue, 17 Nov 2020 11:35:45 +0059
Subject: [PATCH] Splash encoder/decoder

---
  Changelog               |   1 +
  libavcodec/Makefile     |   2 +
  libavcodec/allcodecs.c  |   2 +
  libavcodec/codec_desc.c |   7 +
  libavcodec/codec_id.h   |   1 +
  libavcodec/splash.h     | 402 ++++++++++++++++++++++++++++++++++++++++
  libavcodec/splashdec.c  | 129 +++++++++++++
  libavcodec/splashenc.c  | 201 ++++++++++++++++++++
  libavcodec/version.h    |   4 +-
  libavformat/riff.c      |   1 +
  10 files changed, 748 insertions(+), 2 deletions(-)
  create mode 100644 libavcodec/splash.h
  create mode 100644 libavcodec/splashdec.c
  create mode 100644 libavcodec/splashenc.c

Comments

Paul B Mahol Dec. 22, 2020, 8:32 p.m. UTC | #1
On Tue, Dec 22, 2020 at 7:47 PM xyzzy <xyzzy@rockingship.org> wrote:

> Congratulations with your 20th anniversary!
>
> I was planning to submit this codec as part of the jsFractalZoom project
> after it has been officially announced. It felt as a fitting surprise to
> submit it on that day.
>
> The splash encoder/decoder is a lossy/lossless codec based on fractal
> zoom technology.
>
> Project page: https://github.com/RockingShip/jsFractalZoom
>
> This is my first submission to FFmpeg, I hope I did all the things right.
>
> I would also suggest to add a point to the developer checklist.
> (https://ffmpeg.org/developer.html)
>
> Under section 7: "New codecs or formats checklist", please add:
> "AVCodecID: must be in the same order in "avcodec.h" as in "codec_id.h"
>
>
Are you sure about LGPL license? Asking because orginal code is AGPL3.
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index 854e037a0d..ddaa5f3f8e 100644
--- a/Changelog
+++ b/Changelog
@@ -54,6 +54,7 @@  version <next>:
  - AV1 monochrome encoding support via libaom >= 2.0.1
  - asuperpass and asuperstop filter
  - shufflepixels filter
+- Splash encoder/decoder


  version 4.3:
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 450781886d..b6f4351e96 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -628,6 +628,8 @@  OBJS-$(CONFIG_SONIC_LS_ENCODER)        += sonic.o
  OBJS-$(CONFIG_SPEEDHQ_DECODER)         += speedhq.o mpeg12.o 
mpeg12data.o simple_idct.o
  OBJS-$(CONFIG_SPEEDHQ_ENCODER)         += speedhq.o mpeg12data.o 
mpeg12enc.o speedhqenc.o
  OBJS-$(CONFIG_SP5X_DECODER)            += sp5xdec.o
+OBJS-$(CONFIG_SPLASH_DECODER)          += splashdec.o
+OBJS-$(CONFIG_SPLASH_ENCODER)          += splashenc.o
  OBJS-$(CONFIG_SRGC_DECODER)            += mscc.o
  OBJS-$(CONFIG_SRT_DECODER)             += srtdec.o ass.o htmlsubtitles.o
  OBJS-$(CONFIG_SRT_ENCODER)             += srtenc.o ass_split.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index f00d524747..2e350a0807 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -393,6 +393,8 @@  extern AVCodec ff_zlib_encoder;
  extern AVCodec ff_zlib_decoder;
  extern AVCodec ff_zmbv_encoder;
  extern AVCodec ff_zmbv_decoder;
+extern AVCodec ff_splash_encoder;
+extern AVCodec ff_splash_decoder;

  /* audio codecs */
  extern AVCodec ff_aac_encoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 404c460f8f..50fb2ed0f0 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -1426,6 +1426,13 @@  static const AVCodecDescriptor 
codec_descriptors[] = {
          .long_name = NULL_IF_CONFIG_SMALL("Microsoft Paint (MSP) 
version 2"),
          .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSLESS,
      },
+    {
+        .id        = AV_CODEC_ID_SPLASH,
+        .type      = AVMEDIA_TYPE_VIDEO,
+        .name      = "splash",
+        .long_name = NULL_IF_CONFIG_SMALL("Splash"),
+        .props     = AV_CODEC_PROP_LOSSY,
+    },
      {
          .id        = AV_CODEC_ID_Y41P,
          .type      = AVMEDIA_TYPE_VIDEO,
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 6133e03bb9..0b0e33b685 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -244,6 +244,7 @@  enum AVCodecID {
      AV_CODEC_ID_PGX,
      AV_CODEC_ID_AVS3,
      AV_CODEC_ID_MSP2,
+    AV_CODEC_ID_SPLASH,

      AV_CODEC_ID_Y41P = 0x8000,
      AV_CODEC_ID_AVRP,
diff --git a/libavcodec/splash.h b/libavcodec/splash.h
new file mode 100644
index 0000000000..33027f3486
--- /dev/null
+++ b/libavcodec/splash.h
@@ -0,0 +1,402 @@ 
+/*
+ * Splash codec
+ * Copyright (c) 2020 xyzzy@rockingship.org
+ *
+ * This file is part of FFmpeg.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
02110-1301 USA
+ */
+
+/**
+ * @file
+ * jsFractalZoom based codec.
+ *
+ * Project page and background:
+ * https://github.com/RockingShip/jsFractalZoom
+ */
+
+
+#include "libavutil/common.h"
+#include "libavutil/opt.h"
+#include "libavutil/avassert.h"
+
+/*
+ * Frame header
+ */
+#define HEADER_LENGTH 12
+#define HEADER_OFS_VERSION 9
+#define HEADER_OFS_RADIUS 10
+#define HEADER_OFS_COMPRESS 11
+
+/**
+ * Encoder context
+ */
+typedef struct SplashContext {
+        AVClass class;
+
+        int radius;        // pixel/brush radius
+        float ppf;         // pixels per frame (width*height/ppf)
+        float ppk;         // pixels per key frame (width*height*ppk)
+
+        uint32_t *xError;  // total error along the x-axis
+        uint32_t *yError;  // total error along the y-axis
+        uint8_t *pixels;   // pixel data
+
+        uint8_t *data;     // data buffer
+        int pos;           // position within data buffer
+        int size;          // size data buffer
+
+        int numPixels;
+} SplashContext;
+
+static av_cold int splash_init(AVCodecContext *avctx) {
+    SplashContext *const ctx = avctx->priv_data;
+    int width = avctx->width, height = avctx->height;
+
+    avctx->pix_fmt = AV_PIX_FMT_RGB0;
+
+    // allocate
+    ctx->xError = av_malloc(width * 4);
+    ctx->yError = av_malloc(height * 4);
+    ctx->pixels = av_malloc(width * height * 4);
+
+    // test if all successful
+    if (!ctx->xError || !ctx->yError || !ctx->pixels)
+        return AVERROR(ENOMEM);
+
+    // initial image, solid gray50
+    {
+        uint8_t *pixels = ctx->pixels;
+        for (int ji = 0; ji < width * height; ji++) {
+            *pixels++ = 0x7f;
+            *pixels++ = 0x7f;
+            *pixels++ = 0x7f;
+        }
+    }
+
+    return 0;
+}
+
+static av_cold int splash_end(AVCodecContext *avctx) {
+    SplashContext *const ctx = avctx->priv_data;
+
+    av_freep(&ctx->pixels);
+    av_freep(&ctx->yError);
+    av_freep(&ctx->xError);
+
+    return 0;
+}
+
+static int update_lines(AVCodecContext *avctx, const AVFrame *pic, int 
radius, int encodeDecode) {
+
+    SplashContext *const ctx = avctx->priv_data;
+    int logLevel = av_log_get_level();
+
+    int width = avctx->width;
+    int height = avctx->height;
+    uint32_t *xError = ctx->xError;
+    uint32_t *yError = ctx->yError;
+    uint8_t *pixels = ctx->pixels;
+    int maxError;
+
+    uint8_t *pData = ctx->data + ctx->pos;
+
+    // which tabstops have the worst error
+    int worstXerr = xError[0];
+    int worstXi = 0;
+    int worstYerr = yError[0];
+    int worstYj = 0;
+
+    for (int i = 1; i < width; i++) {
+        if (xError[i] > worstXerr) {
+            worstXi = i;
+            worstXerr = xError[i];
+        }
+    }
+    for (int j = 1; j < height; j++) {
+        if (yError[j] > worstYerr) {
+            worstYj = j;
+            worstYerr = yError[j];
+        }
+    }
+
+
+    if (worstXerr + worstYerr == 0)
+        return 0; // nothing to do
+
+    if (worstXerr > worstYerr) {
+        int i = worstXi;
+
+        /*
+         * range of splash
+         */
+        int minI = i, maxI = i;
+        for (int r = 1; r < radius; r++) {
+            if (minI == 0 || xError[minI - 1] == 0)
+                break;
+            --minI;
+        }
+        for (int r = 1; r < radius; r++) {
+            if (maxI >= width - 1 || xError[maxI + 1] == 0)
+                break;
+            ++maxI;
+        }
+
+        if (logLevel >= AV_LOG_TRACE)
+            av_log(NULL, AV_LOG_TRACE, "%d %d X-%d %d\n", worstXerr, 
worstYerr, worstXi, worstYj);
+
+        maxError = xError[i];
+
+        /*
+         * Apply changes to the ruler so X and Y are now balanced
+         */
+        for (int ii = minI; ii <= maxI; ii++) {
+            float alpha = (float) FFABS(ii - i) / radius;
+
+            xError[ii] = round(xError[ii] * alpha);
+            if (i != ii && xError[ii] == 0)
+                xError[ii] = 1;
+        }
+        xError[i] = 0;
+
+        /*
+         * Scan line for cross points
+         */
+        for (int j = 0; j < height; j++) {
+            // only calculate cross points of exact lines, fill the others
+            if (yError[j] == 0) {
+
+                int minJ = j, maxJ = j;
+
+                /*
+                 * Read pixel
+                 */
+                int srcR, srcG, srcB;
+                if (encodeDecode) {
+                    // encode
+                    uint8_t *src = &pic->data[0][j * pic->linesize[0]] 
+ i * 4;
+                    srcR = *src++;
+                    srcG = *src++;
+                    srcB = *src;
+
+                    // emit pixel
+                    *pData++ = srcR;
+                    *pData++ = srcG;
+                    *pData++ = srcB;
+                    ctx->numPixels++;
+                } else {
+                    //decode
+                    srcR = *pData++;
+                    srcG = *pData++;
+                    srcB = *pData++;
+                }
+
+                /*
+                 * range of splash
+                 */
+                for (int r = 1; r < radius; r++) {
+                    if (minJ == 0 || yError[minJ - 1] == 0)
+                        break;
+                    --minJ;
+                }
+                for (int r = 1; r < radius; r++) {
+                    if (maxJ >= height - 1 || yError[maxJ + 1] == 0)
+                        break;
+                    ++maxJ;
+                }
+
+                /*
+                 * Weighted flood-fill cross point
+                 */
+                for (int jj = minJ; jj <= maxJ; jj++) {
+                    for (int ii = minI; ii <= maxI; ii++) {
+                        // get fill alpha
+                        // the further the fill from the center, the 
less effect it has
+                        float fillAlpha = 1 - sqrt((ii - i) * (ii - i) 
+ (jj - j) * (jj - j)) / radius;
+
+                        if (fillAlpha > 0) {
+
+                            // get pixel alpha
+                            // the more accurate the pixel (lower 
error) the lower the effect of the fill
+                            // normally neighbouring pixels have 
neighbouring errors
+                            // this should avoid filling delicate 
pixels like lines and letters
+                            float xerr = (float) ctx->xError[ii] / 
maxError;
+                            float yerr = (float) ctx->yError[jj] / 
maxError;
+                            float xyerr = (xerr + yerr) / 2;
+
+                            int alpha = 256 - round(256 * xyerr);
+
+                            int k = (jj * width + ii) * 4;
+                            int oldR = pixels[k + 0];
+                            int oldG = pixels[k + 1];
+                            int oldB = pixels[k + 2];
+
+                            int newR = ((srcR * alpha) + (oldR * (256 - 
alpha))) >> 8;
+                            int newG = ((srcG * alpha) + (oldG * (256 - 
alpha))) >> 8;
+                            int newB = ((srcB * alpha) + (oldB * (256 - 
alpha))) >> 8;
+
+                            assert0(alpha < 256);
+                            if (i == ii && j == jj)
+                                av_assert0(alpha == 256);
+
+                            // save new pixel value
+                            pixels[k + 0] = newR;
+                            pixels[k + 1] = newG;
+                            pixels[k + 2] = newB;
+                        }
+                    }
+                }
+            }
+        }
+    } else {
+        int j = worstYj;
+
+        /*
+         * range of splash
+         */
+        int minJ = j, maxJ = j;
+        for (int r = 1; r < radius; r++) {
+            if (minJ == 0 || yError[minJ - 1] == 0)
+                break;
+            --minJ;
+        }
+        for (int r = 1; r < radius; r++) {
+            if (maxJ >= height - 1 || yError[maxJ + 1] == 0)
+                break;
+            ++maxJ;
+        }
+
+        if (logLevel >= AV_LOG_TRACE)
+            av_log(NULL, AV_LOG_TRACE, "%d %d %d Y-%d\n", worstXerr, 
worstYerr, worstXi, worstYj);
+
+        maxError = yError[j];
+
+        /*
+         * Apply changes to the ruler so X and Y are now balanced
+         */
+        for (int jj = minJ; jj <= maxJ; jj++) {
+            float alpha = (float) FFABS(jj - j) / radius;
+
+            yError[jj] = round(yError[jj] * alpha);
+            if (j != jj && yError[jj] == 0)
+                yError[jj] = 1;
+        }
+        yError[j] = 0;
+
+        /*
+         * Scan line for cross points
+         */
+        for (int i = 0; i < width; i++) {
+            // only calculate cross points of exact lines, fill the others
+            if (xError[i] == 0) {
+
+                int minI = i, maxI = i;
+
+                /*
+                 * Read pixel
+                 */
+                int srcR, srcG, srcB;
+                if (encodeDecode) {
+                    // encode
+                    uint8_t *src = &pic->data[0][j * pic->linesize[0]] 
+ i * 4;
+                    srcR = *src++;
+                    srcG = *src++;
+                    srcB = *src;
+
+                    // emit pixel
+                    *pData++ = srcR;
+                    *pData++ = srcG;
+                    *pData++ = srcB;
+                    ctx->numPixels++;
+                } else {
+                    // decode
+                    srcR = *pData++;
+                    srcG = *pData++;
+                    srcB = *pData++;
+                }
+
+                /*
+                 * range of splash
+                 */
+                for (int r = 1; r < radius; r++) {
+                    if (minI == 0 || xError[minI - 1] == 0)
+                        break;
+                    --minI;
+                }
+                for (int r = 1; r < radius; r++) {
+                    if (maxI >= width - 1 || xError[maxI + 1] == 0)
+                        break;
+                    ++maxI;
+                }
+
+                /*
+                 * Weighted flood-fill cross point
+                 */
+                for (int ii = minI; ii <= maxI; ii++) {
+                    for (int jj = minJ; jj <= maxJ; jj++) {
+                        float fillAlpha = 1 - sqrt((ii - i) * (ii - i) 
+ (jj - j) * (jj - j)) / radius;
+
+                        if (fillAlpha > 0) {
+                            /*
+                             * fillAlpha is also the distance to the 
splash center
+                             * Dividing it between x and y in an 
attempt to prolong being selected to being the next scanline
+                             *
+                             * low `error[]` implies low chance, so 
change at least as possible
+                             * high `error[]` implies already likely. 
Changing as much as possible might be the only escape.
+                             */
+                            float xerr = (float) ctx->xError[ii] / 
maxError;
+                            float yerr = (float) ctx->yError[jj] / 
maxError;
+                            float xyerr = (xerr + yerr) / 2;
+
+                            int alpha = 256 - round(256 * xyerr);
+
+                            int k = (jj * width + ii) * 4;
+                            int oldR = pixels[k + 0];
+                            int oldG = pixels[k + 1];
+                            int oldB = pixels[k + 2];
+
+                            int newR = ((srcR * alpha) + (oldR * (256 - 
alpha))) >> 8;
+                            int newG = ((srcG * alpha) + (oldG * (256 - 
alpha))) >> 8;
+                            int newB = ((srcB * alpha) + (oldB * (256 - 
alpha))) >> 8;
+
+                            if (i == ii && j == jj)
+                                av_assert0(alpha == 256);
+                            assert0(alpha < 256);
+
+                            /*
+                            totalError -= Math.abs(srcR - oldR);
+                            totalError -= Math.abs(srcG - oldG);
+                            totalError -= Math.abs(srcB - oldB);
+                            totalError += Math.abs(srcR - newR);
+                            totalError += Math.abs(srcG - newG);
+                            totalError += Math.abs(srcB - newB);
+                            */
+
+                            // save new pixel value
+                            pixels[k + 0] = newR;
+                            pixels[k + 1] = newG;
+                            pixels[k + 2] = newB;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    ctx->pos = pData - ctx->data;
+
+    return 1;
+}
+
diff --git a/libavcodec/splashdec.c b/libavcodec/splashdec.c
new file mode 100644
index 0000000000..edafdc039f
--- /dev/null
+++ b/libavcodec/splashdec.c
@@ -0,0 +1,129 @@ 
+/*
+ * Splash Decoder
+ * Copyright (c) 2020 xyzzy@rockingship.org
+ *
+ * This file is part of FFmpeg.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
02110-1301 USA
+ */
+
+/**
+ * @file
+ * jsFractalZoom based codec.
+ *
+ * Project page and background:
+ * https://github.com/RockingShip/jsFractalZoom
+ */
+
+#include "avcodec.h"
+#include "internal.h"
+#include "splash.h"
+
+static int splash_decode(AVCodecContext *avctx, void *data, int 
*got_frame, AVPacket *avpkt) {
+    SplashContext *const ctx = avctx->priv_data;
+    int width = avctx->width, height = avctx->height;
+
+    AVFrame *pic = data;
+    const uint8_t *pData = avpkt->data;
+    int ret;
+    int hdrLength, radius;
+
+    avctx->pix_fmt = AV_PIX_FMT_RGB0;
+
+    if ((ret = ff_get_buffer(avctx, pic, 0)) < 0)
+        return ret;
+
+    /*
+     * Load header
+     */
+
+    hdrLength = pData[0];
+    hdrLength |= pData[1] << 8;
+    hdrLength |= pData[2] << 16;
+    radius = pData[HEADER_OFS_RADIUS];
+
+    ctx->data = avpkt->data + hdrLength;
+    ctx->pos = 0;
+    ctx->size = avpkt->size - HEADER_LENGTH;
+    pData = ctx->data;
+
+    /*
+     * Load initial `xError[]`
+     */
+    for (int i = 0; i < width; i++) {
+        int err = *pData++;
+        err |= *pData++ << 8;
+        err |= *pData++ << 16;
+
+        ctx->xError[i] = err;
+    }
+
+    /*
+     * Load initial `yError[]`
+     */
+    for (int j = 0; j < height; j++) {
+        int err = *pData++;
+        err |= *pData++ << 8;
+        err |= *pData++ << 16;
+
+        ctx->yError[j] = err;
+    }
+
+    ctx->pos = pData - ctx->data;
+
+    do {
+        if (!update_lines(avctx, pic, radius, 0))
+            break; // short frame
+    } while (ctx->pos < ctx->size);
+
+    if (ctx->pos != ctx->size)
+        av_log(avctx, AV_LOG_WARNING, "Incomplete scan line.\n");
+
+    /*
+     * Copy decoded pixels to frame
+     */
+    pData = ctx->pixels;
+    for (int j = 0; j < avctx->height; ++j) {
+        uint8_t *rgb = &pic->data[0][j * pic->linesize[0]];
+        for (int i = 0; i < avctx->width; ++i) {
+            *rgb++ = *pData++;
+            *rgb++ = *pData++;
+            *rgb++ = *pData++;
+            *rgb++ = 255;
+            pData++;
+        }
+    }
+
+    pic->key_frame = 1;
+    pic->pict_type = AV_PICTURE_TYPE_I;
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+
+AVCodec ff_splash_decoder = {
+        .name           = "splash",
+        .long_name      = NULL_IF_CONFIG_SMALL("Splash"),
+        .type           = AVMEDIA_TYPE_VIDEO,
+        .id             = AV_CODEC_ID_SPLASH,
+        .priv_data_size = sizeof(SplashContext),
+        .init           = splash_init,
+        .decode         = splash_decode,
+        .close          = splash_end,
+        .capabilities   = AV_CODEC_CAP_DR1,
+        .pix_fmts = (const enum AVPixelFormat[]) {AV_PIX_FMT_RGB0, 
AV_PIX_FMT_NONE},
+};
diff --git a/libavcodec/splashenc.c b/libavcodec/splashenc.c
new file mode 100644
index 0000000000..98ce5ec82f
--- /dev/null
+++ b/libavcodec/splashenc.c
@@ -0,0 +1,201 @@ 
+/*
+ * Splash Encoder
+ * Copyright (c) 2020 xyzzy@rockingship.org
+ *
+ * This file is part of FFmpeg.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
02110-1301 USA
+ */
+
+/**
+ * @file
+ * jsFractalZoom based codec.
+ *
+ * Project page and background:
+ * https://github.com/RockingShip/jsFractalZoom
+ */
+
+#include "avcodec.h"
+#include "internal.h"
+#include "splash.h"
+
+static int splash_encode(AVCodecContext *avctx, AVPacket *pkt, const 
AVFrame *pic, int *got_packet) {
+    SplashContext *const ctx = avctx->priv_data;
+    int width = avctx->width, height = avctx->height;
+    int ret;
+    uint8_t *pixels = ctx->pixels;
+    uint8_t *pData;
+
+    // allocate worst-case frame
+    if ((ret = ff_alloc_packet2(avctx, pkt, (HEADER_LENGTH + width + 
height + width * height) * 3, 0)) < 0)
+        return ret;
+
+    /*
+     * Write header
+     * +0 length (lsb)
+     * +3 "spl"
+     * +6 "ash"
+     * +9 version
+     * +10 radius
+     * +11 zero
+     * +12 start of data
+     */
+    pData = pkt->data;
+    *pData++ = 12; // 3 bytes header length (lsb)
+    *pData++ = 0;
+    *pData++ = 0;
+    *pData++ = 's'; // magic
+    *pData++ = 'p';
+    *pData++ = 'l';
+    *pData++ = 'a';
+    *pData++ = 's';
+    *pData++ = 'h';
+    *pData++ = 1; // version 1
+    *pData++ = ctx->radius; // radius
+    *pData++ = 0; // reserved for compression
+
+    ctx->data = pkt->data + HEADER_LENGTH;
+    ctx->pos = 0;
+    ctx->size = pkt->size - HEADER_LENGTH;
+    pData = ctx->data;
+
+    /*
+     * Create and output initial `xError[]`
+     */
+    for (int i = 0; i < width; i++) {
+        int err = 0;
+        for (int j = 0; j < height; j++) {
+            int k = (j * width + i) * 4;
+            uint8_t *rgb = &pic->data[0][j * pic->linesize[0]] + i * 4;
+
+            err += FFABS(pixels[k + 0] - rgb[0]);
+            err += FFABS(pixels[k + 1] - rgb[1]);
+            err += FFABS(pixels[k + 2] - rgb[2]);
+        }
+
+        // keep within limits
+        if (err > 0xffffff)
+            err = 0xffffff;
+
+        ctx->xError[i] = err;
+
+        // output xError
+        *pData++ = err;
+        *pData++ = err >> 8;
+        *pData++ = err >> 16;
+    }
+
+    /*
+     * Create and output initial `yError[]`
+     */
+    for (int j = 0; j < height; j++) {
+        int err = 0;
+        for (int i = 0; i < width; i++) {
+            int k = (j * width + i) * 4;
+            uint8_t *rgb = &pic->data[0][j * pic->linesize[0]] + i * 4;
+
+            err += FFABS(pixels[k + 0] - rgb[0]);
+            err += FFABS(pixels[k + 1] - rgb[1]);
+            err += FFABS(pixels[k + 2] - rgb[2]);
+        }
+
+        // keep within limits
+        if (err > 0xffffff)
+            err = 0xffffff;
+
+        ctx->yError[j] = err;
+
+        // output yError
+        *pData++ = err;
+        *pData++ = err >> 8;
+        *pData++ = err >> 16;
+    }
+
+    /*
+     * Start scanning lines
+     */
+    ctx->numPixels = 0;
+    ctx->pos = pData - ctx->data;
+
+    {
+        // number of pixels for this frame
+        int maxPixels;
+        if (avctx->frame_number == 0)
+            maxPixels = round(width * height / ctx->ppk);
+        else
+            maxPixels = round(width * height / ctx->ppf);
+
+        do {
+            if (!update_lines(avctx, pic, ctx->radius, 1))
+                break;  // short frame
+        } while (ctx->numPixels < maxPixels);
+    }
+
+    /*
+     * Test if end frame matches
+     */
+    if (ctx->ppf == 1) {
+        int cntMiss = 0;
+        uint8_t *src = ctx->pixels;
+        for (int j = 0; j < avctx->height; ++j) {
+            uint8_t *rgb = &pic->data[0][j * pic->linesize[0]];
+            for (int i = 0; i < avctx->width; ++i) {
+                if (*rgb++ != *src++)
+                    cntMiss++;
+                if (*rgb++ != *src++)
+                    cntMiss++;
+                if (*rgb++ != *src++)
+                    cntMiss++;
+                rgb++; // skip alpha
+                src++;
+            }
+        }
+        if (cntMiss)
+            av_log(NULL, AV_LOG_WARNING, "Inaccurate %d final 
pixels\n", cntMiss);
+    }
+
+    pkt->size = HEADER_LENGTH + ctx->pos;
+    pkt->flags |= AV_PKT_FLAG_KEY;
+    *got_packet = 1;
+    return 0;
+}
+
+static const AVOption options[] = {
+        {"ppf",    "pixels per frame (width*height/ppf)", 
offsetof(SplashContext, ppf),    AV_OPT_TYPE_FLOAT, {.dbl = 1}, 1, 
INT_MAX, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM},
+        {"ppk",    "pixels per key frame (width*height/ppk)", 
offsetof(SplashContext, ppk),    AV_OPT_TYPE_FLOAT, {.dbl = 2}, 1, 
INT_MAX, AV_OPT_FLAG_ENCODING_PARAM | AV_OPT_FLAG_VIDEO_PARAM},
+        {"radius", "brush radius", offsetof(SplashContext, radius), 
AV_OPT_TYPE_INT,   {.i64 = 5}, 1, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM | 
AV_OPT_FLAG_VIDEO_PARAM},
+        {NULL},
+};
+
+static const AVClass splash_encoder_class = {
+        .class_name = "rasc decoder",
+        .item_name  = av_default_item_name,
+        .option     = options,
+        .version    = LIBAVUTIL_VERSION_INT,
+};
+
+AVCodec ff_splash_encoder = {
+        .name           = "splash",
+        .long_name      = NULL_IF_CONFIG_SMALL("Splash"),
+        .type           = AVMEDIA_TYPE_VIDEO,
+        .id             = AV_CODEC_ID_SPLASH,
+        .priv_data_size = sizeof(SplashContext),
+        .init           = splash_init,
+        .encode2        = splash_encode,
+        .close          = splash_end,
+        .pix_fmts       = (const enum AVPixelFormat[]) 
{AV_PIX_FMT_RGB0, AV_PIX_FMT_NONE},
+        .priv_class     = &splash_encoder_class,
+};
+
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 5b92afe60a..f03afd428f 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -28,8 +28,8 @@ 
  #include "libavutil/version.h"

  #define LIBAVCODEC_VERSION_MAJOR  58
-#define LIBAVCODEC_VERSION_MINOR 115
-#define LIBAVCODEC_VERSION_MICRO 102
+#define LIBAVCODEC_VERSION_MINOR 116
+#define LIBAVCODEC_VERSION_MICRO   0

  #define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
LIBAVCODEC_VERSION_MINOR, \
diff --git a/libavformat/riff.c b/libavformat/riff.c
index 388047fc4b..04f63f5dfe 100644
--- a/libavformat/riff.c
+++ b/libavformat/riff.c
@@ -495,6 +495,7 @@  const AVCodecTag ff_codec_bmp_tags[] = {
      { AV_CODEC_ID_MVHA,         MKTAG('M', 'V', 'H', 'A') },
      { AV_CODEC_ID_MV30,         MKTAG('M', 'V', '3', '0') },
      { AV_CODEC_ID_NOTCHLC,      MKTAG('n', 'l', 'c', '1') },
+    { AV_CODEC_ID_SPLASH,       MKTAG('S', 'P', 'L', 'S') },
      { AV_CODEC_ID_NONE,         0 }
  };