From patchwork Tue Dec 22 18:47:11 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: xyzzy X-Patchwork-Id: 24622 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 60B24449863 for ; Tue, 22 Dec 2020 20:47:20 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 40DDF68AA48; Tue, 22 Dec 2020 20:47:20 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from rockingship.org (rockingship.org [35.158.168.92]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 5F7B568A4F6 for ; Tue, 22 Dec 2020 20:47:14 +0200 (EET) Received: internal info suppressed From: xyzzy To: ffmpeg-devel@ffmpeg.org Message-ID: <03cdeb54-f063-a75e-f33f-5f0eab582231@rockingship.org> Date: Tue, 22 Dec 2020 19:47:11 +0100 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:52.0) Gecko/20100101 Firefox/52.0 SeaMonkey/2.49.5 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] Splash encoder/decoder X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" 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 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 diff --git a/Changelog b/Changelog index 854e037a0d..ddaa5f3f8e 100644 --- a/Changelog +++ b/Changelog @@ -54,6 +54,7 @@ version :  - 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 }  };