Commit a964dc4fc7 for ffmpeg

commit a964dc4fc7889038df1b3a82281451c251d05568
Author: Lynne <dev@lynne.ee>
Date:   Thu Oct 8 13:27:47 2026 +0900

    lavc: add a Panasonic RW2 decoder

    Supports RawFormat 8, used by the S1II, S5II and GH6. Outputs the Bayer
    mosaic, with the sensor borders as cropping and the black and white
    levels as raw color params side data.

    The as-shot white balance (tags 0x24 - 0x26) is exported as
    AVRW2RawColorParams, and the TIFF and EXIF tags of IFD0 and its sub-IFDs
    as EXIF side data, gathered during the single header walk and attached
    without re-parsing. The orientation becomes a display matrix; Panasonic's
    raw parameters, the previews and the strip pointers are left out.

    The whole file is given as one packet.

    Bit-exact with LibRaw on 801 S1II files, at 3x its speed per thread.

diff --git a/Changelog b/Changelog
index 876497f2e1..3a3b3ed0dd 100644
--- a/Changelog
+++ b/Changelog
@@ -16,6 +16,7 @@ version <next>:
 - DSD (dsd_msbf) encoder
 - Rhetorex ADPCM decoder
 - ADPCM IMA Citrix decoder
+- Panasonic RW2 decoder


 version 9.0:
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index ece43a46d6..93d526ee56 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -697,6 +697,7 @@ OBJS-$(CONFIG_RV20_ENCODER)            += rv20enc.o
 OBJS-$(CONFIG_RV30_DECODER)            += rv30.o rv34.o rv30dsp.o
 OBJS-$(CONFIG_RV40_DECODER)            += rv40.o rv34.o rv40dsp.o
 OBJS-$(CONFIG_RV60_DECODER)            += rv60dec.o rv60dsp.o
+OBJS-$(CONFIG_RW2_DECODER)             += rw2dec.o
 OBJS-$(CONFIG_SAMI_DECODER)            += samidec.o ass.o htmlsubtitles.o
 OBJS-$(CONFIG_S302M_DECODER)           += s302m.o
 OBJS-$(CONFIG_S302M_ENCODER)           += s302menc.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 9c3d62f058..178a0e593f 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -304,6 +304,7 @@ extern const FFCodec ff_rv20_decoder;
 extern const FFCodec ff_rv30_decoder;
 extern const FFCodec ff_rv40_decoder;
 extern const FFCodec ff_rv60_decoder;
+extern const FFCodec ff_rw2_decoder;
 extern const FFCodec ff_s302m_encoder;
 extern const FFCodec ff_s302m_decoder;
 extern const FFCodec ff_sanm_decoder;
diff --git a/libavcodec/rw2.h b/libavcodec/rw2.h
new file mode 100644
index 0000000000..0e03d1e6e8
--- /dev/null
+++ b/libavcodec/rw2.h
@@ -0,0 +1,102 @@
+/*
+ * Panasonic RW2 raw image decoder
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_RW2_H
+#define AVCODEC_RW2_H
+
+#include <stdint.h>
+
+#include "libavutil/frame.h"
+#include "libavutil/pixfmt.h"
+
+#include "vlc.h"
+
+#define RW2_MAX_STRIPES 5
+#define RW2_MAX_IFD0_ENTRIES 512  /* parser cap, sizes exif[] */
+#define RW2_JOINT_CATS  13   /* categories with a code per magnitude, 2^k each */
+#define RW2_MAX_CODES   ((1 << RW2_JOINT_CATS) + 17)
+
+typedef struct RW2Stripe {
+    uint32_t offset;          /* bytes, from the start of the file */
+    uint32_t size_bits;
+    uint32_t left;
+    uint32_t width;
+    uint32_t height;
+} RW2Stripe;
+
+/* Coding parameters, tags 0x39 - 0x41 */
+typedef struct RW2Coding {
+    uint32_t gamma_shift[6];  /* 0x39 */
+    uint16_t gamma_point[6];  /* 0x3A */
+    int datamax;              /* 0x3B */
+    int initial[4];           /* 0x3C - 0x3F */
+    uint8_t code_len[17];     /* 0x40, Huffman code per magnitude category */
+    uint16_t code_val[17];
+    uint8_t code_shift[17];   /* 0x41, magnitude bits left out */
+} RW2Coding;
+
+/* IFD0 contents, RawFormat 8 */
+typedef struct RW2Header {
+    int raw_width, raw_height;
+    int border[4];            /* top, left, bottom, right */
+    int cfa;
+    int bpp;
+    int raw_format;
+    int orientation;
+    int black[3];
+    int linearity[3];
+    int wb[3];                /* as-shot white balance levels */
+
+    RW2Coding c;
+
+    int nb_stripes;
+    RW2Stripe stripes[RW2_MAX_STRIPES];
+
+    /* Standard IFD0 tags kept verbatim for EXIF, gathered during the header
+     * walk, with the IFD0 offset and where the metadata ends (first preview) */
+    uint8_t  exif[RW2_MAX_IFD0_ENTRIES * 12];
+    int      exif_count;
+    uint32_t exif_off;
+    int64_t  exif_end;
+} RW2Header;
+
+typedef struct RW2Code {
+    uint8_t len;
+    uint16_t code;
+    int16_t sym;
+} RW2Code;
+
+typedef struct RW2DecContext {
+    AVFrame *frame;
+    void *hwaccel_picture_private;
+    enum AVPixelFormat pix_fmt;
+
+    RW2Header h;
+
+    const uint8_t *payload;
+
+    VLC vlc;
+    RW2Code codes[RW2_MAX_CODES];
+    uint16_t gamma[1 << 16];
+    int use_gamma;
+} RW2DecContext;
+
+#endif /* AVCODEC_RW2_H */
diff --git a/libavcodec/rw2dec.c b/libavcodec/rw2dec.c
new file mode 100644
index 0000000000..86dc6d95de
--- /dev/null
+++ b/libavcodec/rw2dec.c
@@ -0,0 +1,624 @@
+/*
+ * Panasonic RW2 raw image decoder
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg 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.
+ *
+ * FFmpeg 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 FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * Panasonic RW2 decoder, RawFormat 8 only (S1II, S5II, GH6).
+ * Bit-exact with LibRaw's pana8.cpp, quirks included.
+ *
+ * Input is the whole file as one packet.
+ */
+
+#include "libavutil/internal.h"
+#include "libavutil/buffer.h"
+#include "libavutil/mem.h"
+#include "libavutil/raw_color_params.h"
+#include "libavutil/reverse.h"
+
+#define BITSTREAM_READER_LE
+#define CACHED_BITSTREAM_READER 1
+#include "avcodec.h"
+#include "bytestream.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "exif.h"
+#include "exif_internal.h"
+#include "get_bits.h"
+#include "hwaccel_internal.h"
+#include "thread.h"
+#include "tiff_common.h"
+
+#include "rw2.h"
+
+#define RW2_VLC_BITS   11
+#define RW2_JOINT_BITS 16      /* longest code with its magnitude folded in */
+#define RW2_SYM_DELTA  0x2000  /* joint symbols: delta + RW2_SYM_DELTA */
+#define RW2_SYM_ESC    0x4000  /* other categories: RW2_SYM_ESC + category */
+
+static av_always_inline int get_delta(const RW2DecContext *s,
+                                      GetBitContext *bc, int *delta)
+{
+    int sym = get_vlc2(bc, s->vlc.table, RW2_VLC_BITS, 2);
+    if (sym < RW2_SYM_ESC) {
+        if (sym < 0)
+            return AVERROR_INVALIDDATA;
+        *delta = sym - RW2_SYM_DELTA;
+        return 0;
+    }
+
+    /* Escapes read k - shift magnitude bits, MSB-first, the sign leading */
+    int k = sym - RW2_SYM_ESC;
+    int shift = s->h.c.code_shift[k], extra = k - shift;
+    int d = 0;
+    if (extra > 0) {
+        unsigned v = get_bits(bc, extra);
+        unsigned m = (ff_reverse[v & 0xFF] << 8 | ff_reverse[v >> 8]) >> (16 - extra);
+        d = (m << shift) & 0xFFFF;
+        if (!(v & 1))
+            d += !shift - (1 << k);
+    } else if (k && !show_bits(bc, 1)) {
+        /* No magnitude, but LibRaw still tests the next bit for the sign */
+        d = !shift - (1 << k);
+    }
+
+    *delta = d + (shift ? 1 << (shift - 1) : 0);
+    return 0;
+}
+
+static int gamma_curve(const RW2Coding *c, uint32_t v)
+{
+    int seg = 0;
+    while (seg < 5 && v >= c->gamma_point[seg + 1])
+        seg++;
+
+    /* LibRaw only keeps the second half of each 0x3A pair, which makes the
+     * output offset of every segment 0 */
+    uint32_t sh = c->gamma_shift[seg];
+    uint32_t r  = v - c->gamma_point[seg];
+    if ((sh & 0x1F) == 31)
+        r = seg == 5 ? 0xFFFF : 0;
+    else if (sh & 0x10)
+        r <<= sh & 0xF;
+    else if ((sh & 0x1F) == 15)
+        r = 0;
+    else if (sh & 0x1F)
+        r = (r + (1 << ((sh & 0x1F) - 1))) >> (sh & 0x1F);
+
+    return FFMIN(r, c->datamax);
+}
+
+static int build_tables(RW2DecContext *s)
+{
+    const RW2Coding *c = &s->h.c;
+    int nb = 0;
+
+    /* A code per magnitude where both fit and nothing is shifted, the rest escape */
+    for (int k = 0; k < 17; k++) {
+        int len = c->code_len[k], code = c->code_val[k];
+        if (!len)
+            continue;
+        if (c->code_shift[k] || k >= RW2_JOINT_CATS || len + k > RW2_JOINT_BITS) {
+            s->codes[nb++] = (RW2Code){ len, code, RW2_SYM_ESC + k };
+            continue;
+        }
+        for (int m = 0; m < (1 << k); m++) {
+            int d = k && !(m >> (k - 1)) ? m + 1 - (1 << k) : m;
+            s->codes[nb++] = (RW2Code){ len + k, code << k | m, RW2_SYM_DELTA + d };
+        }
+    }
+
+    ff_vlc_free(&s->vlc);
+    int ret = ff_vlc_init_sparse(&s->vlc, RW2_VLC_BITS, nb,
+                                 &s->codes[0].len,  sizeof(*s->codes), 1,
+                                 &s->codes[0].code, sizeof(*s->codes), 2,
+                                 &s->codes[0].sym,  sizeof(*s->codes), 2,
+                                 VLC_INIT_OUTPUT_LE);
+    if (ret < 0)
+        return ret;
+
+    /* The gamma LUT, an identity on every file seen so far */
+    s->use_gamma = 0;
+    for (int i = 0; i <= c->datamax; i++) {
+        s->gamma[i] = gamma_curve(c, i);
+        s->use_gamma |= s->gamma[i] != i;
+    }
+
+    return 0;
+}
+
+static int decode_stripe(AVCodecContext *avctx, void *tdata,
+                         int jobnr, int threadnr)
+{
+    RW2DecContext *s = avctx->priv_data;
+    AVFrame *frame = tdata;
+    const RW2Stripe *st = &s->h.stripes[jobnr];
+    const ptrdiff_t stride = frame->linesize[0];
+    const uint16_t *gamma = s->use_gamma ? s->gamma : NULL;
+    const int datamax = s->h.c.datamax;
+    int line_base[4], cur[4];
+    GetBitContext bc;
+    int ret;
+
+    ret = init_get_bits(&bc, s->payload + st->offset, st->size_bits);
+    if (ret < 0)
+        return ret;
+
+    for (int i = 0; i < 4; i++)
+        line_base[i] = s->h.c.initial[i];
+
+    for (int y = 0; y < st->height; y += 2) {
+        uint16_t *dst0 = (uint16_t *)(frame->data[0] + y*stride) + st->left;
+        uint16_t *dst1 = (uint16_t *)((uint8_t *)dst0 + stride);
+
+        /* Each sample predicts from the same position in the previous 2x2 */
+        memcpy(cur, line_base, sizeof(cur));
+        for (int x = 0; x < st->width; x += 2) {
+            /* Coding order: top-left, bottom-left, top-right, bottom-right */
+            static const uint8_t slot[4] = { 0, 2, 1, 3 };
+            for (int i = 0; i < 4; i++) {
+                int delta, j = slot[i];
+                if ((ret = get_delta(s, &bc, &delta)) < 0)
+                    return ret;
+                cur[j] = av_clip(cur[j] + delta, 0, datamax);
+            }
+
+            if (gamma) {
+                dst0[x] = gamma[cur[0]]; dst0[x + 1] = gamma[cur[1]];
+                dst1[x] = gamma[cur[2]]; dst1[x + 1] = gamma[cur[3]];
+            } else {
+                dst0[x] = cur[0]; dst0[x + 1] = cur[1];
+                dst1[x] = cur[2]; dst1[x + 1] = cur[3];
+            }
+
+            if (!x)
+                memcpy(line_base, cur, sizeof(line_base));
+        }
+
+        if (get_bits_left(&bc) < 0)
+            return AVERROR_INVALIDDATA;
+    }
+
+    return 0;
+}
+
+static int read_array_count(GetByteContext *gb, unsigned type, unsigned count,
+                            unsigned expected, int max)
+{
+    int n;
+    if (type != AV_TIFF_UNDEFINED || count != expected)
+        return 0;
+    n = bytestream2_get_le16(gb);
+    return FFMIN(n, max);
+}
+
+/* Tag layouts and clamps follow LibRaw */
+static int parse_header(RW2Header *h, const uint8_t *data, int size)
+{
+    RW2Coding *c = &h->c;
+    GetByteContext gb;
+
+    memset(h, 0, sizeof(*h));
+
+    bytestream2_init(&gb, data, size);
+    if (bytestream2_get_le32(&gb) != MKTAG('I', 'I', 'U', '\0'))
+        return AVERROR_INVALIDDATA;
+    int64_t ifd0 = bytestream2_get_le32(&gb);
+    if (ifd0 < 8 || ifd0 + 2 > size)
+        return AVERROR_INVALIDDATA;
+    bytestream2_seek(&gb, ifd0, SEEK_SET);
+
+    int nb_entries = bytestream2_get_le16(&gb);
+    if (nb_entries > RW2_MAX_IFD0_ENTRIES || bytestream2_get_bytes_left(&gb) < nb_entries*12)
+        return AVERROR_INVALIDDATA;
+
+    int64_t ifd_end = ifd0 + 2 + 12*(int64_t)nb_entries + 4;
+    h->exif_off = ifd0;
+    h->exif_end = size;
+
+    for (int i = 0; i < nb_entries; i++) {
+        unsigned tag, type, count, n, v;
+        int next;
+
+        if (ff_tread_tag(&gb, 1, &tag, &type, &count, &next) < 0)
+            goto next;
+
+        /* Keep the standard tags verbatim for EXIF in this same walk, leaving
+         * out the Panasonic-private ones and the orientation that is exported
+         * as a display matrix; the previews mark where the metadata ends */
+        const uint8_t *rec = data + ifd0 + 2 + 12*i;
+        if ((tag == 0x2E || tag == 0x111 || tag == 0x127) && AV_RL32(rec + 8) >= ifd_end)
+            h->exif_end = FFMIN(h->exif_end, (int64_t)AV_RL32(rec + 8));
+        if (tag >= 0x100 && tag != 0x111 && tag != 0x112 && tag != 0x117 &&
+            !(tag >= 0x118 && tag <= 0x127)) {
+            memcpy(h->exif + h->exif_count*12, rec, 12);
+            h->exif_count++;
+        }
+
+        switch (tag) {
+        case 0x02: h->raw_width  = ff_tget(&gb, type, 1); break;
+        case 0x03: h->raw_height = ff_tget(&gb, type, 1); break;
+        case 0x04: case 0x05: case 0x06: case 0x07:
+            h->border[tag - 0x04] = ff_tget(&gb, type, 1);
+            break;
+        case 0x09: h->cfa = ff_tget(&gb, type, 1); break;
+        case 0x0A: h->bpp = ff_tget(&gb, type, 1); break;
+        case 0x0E: case 0x0F: case 0x10:
+            h->linearity[tag - 0x0E] = ff_tget(&gb, type, 1);
+            break;
+        case 0x1C: case 0x1D: case 0x1E:
+            h->black[tag - 0x1C] = ff_tget(&gb, type, 1);
+            break;
+        case 0x24: case 0x25: case 0x26:
+            h->wb[tag - 0x24] = ff_tget(&gb, type, 1);
+            break;
+        case 0x2D: h->raw_format = ff_tget(&gb, type, 1); break;
+        case 0x39:
+            n = read_array_count(&gb, type, count, 26, 6);
+            for (int j = 0; j < n; j++)
+                c->gamma_shift[j] = bytestream2_get_le32(&gb);
+            break;
+        case 0x3A:
+            n = read_array_count(&gb, type, count, 26, 6);
+            for (int j = 0; j < n; j++) {
+                bytestream2_skip(&gb, 2);
+                c->gamma_point[j] = bytestream2_get_le16(&gb);
+            }
+            break;
+        case 0x3B: c->datamax = ff_tget(&gb, type, 1) & 0xFFFF; break;
+        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
+            c->initial[tag - 0x3C] = ff_tget(&gb, type, 1) & 0xFFFF;
+            break;
+        case 0x40:
+            n = read_array_count(&gb, type, count, 70, 17);
+            for (int k = 0; k < n; k++) {
+                v = bytestream2_get_le16(&gb);
+                int len = FFMIN(v, 16);
+                v = bytestream2_get_le16(&gb);
+                int code = FFMIN(v, 0xFFF) & ((1 << len) - 1);
+
+                /* LibRaw takes the first match, drop the codes it never reaches */
+                for (int j = 0; j < k && len; j++)
+                    if (c->code_len[j] && c->code_len[j] <= len &&
+                        code >> (len - c->code_len[j]) == c->code_val[j])
+                        len = 0;
+                c->code_len[k] = len;
+                c->code_val[k] = code;
+            }
+            break;
+        case 0x41:
+            n = read_array_count(&gb, type, count, 36, 17);
+            for (int j = 0; j < n; j++) {
+                v = bytestream2_get_le16(&gb);
+                c->code_shift[j] = FFMIN(v, 64) & 0x1F;
+            }
+            break;
+        case 0x42:
+            v = ff_tget(&gb, type, 1);
+            h->nb_stripes = FFMIN(v, RW2_MAX_STRIPES);
+            break;
+        case 0x44: case 0x45: case 0x46:
+            n = read_array_count(&gb, type, count, 50, RW2_MAX_STRIPES);
+            for (int j = 0; j < n; j++) {
+                v = bytestream2_get_le32(&gb);
+                if (tag == 0x44)
+                    h->stripes[j].offset = v;
+                else if (tag == 0x45)
+                    h->stripes[j].left = v;
+                else
+                    h->stripes[j].size_bits = v;
+            }
+            break;
+        case 0x47: case 0x48:
+            n = read_array_count(&gb, type, count, 26, RW2_MAX_STRIPES);
+            for (int j = 0; j < n; j++) {
+                v = bytestream2_get_le16(&gb);
+                if (tag == 0x47)
+                    h->stripes[j].width = v;
+                else
+                    h->stripes[j].height = v;
+            }
+            break;
+        case 0x112: h->orientation = ff_tget(&gb, type, 1); break;
+        }
+
+next:
+        bytestream2_seek(&gb, next, SEEK_SET);
+    }
+
+    return 0;
+}
+
+static int check_stripes(AVCodecContext *avctx, const RW2Header *h,
+                         int payload_size)
+{
+    uint32_t left = 0;
+
+    if (h->nb_stripes < 1)
+        return AVERROR_INVALIDDATA;
+
+    /* Stripes must tile the image exactly */
+    for (int i = 0; i < h->nb_stripes; i++) {
+        const RW2Stripe *st = &h->stripes[i];
+        if (st->left != left || st->height != h->raw_height ||
+            !st->width || (st->width & 1) || st->width > h->raw_width - left ||
+            !st->size_bits || st->size_bits > INT_MAX - 7 ||
+            st->offset > payload_size ||
+            (st->size_bits + 7) >> 3 > payload_size - st->offset) {
+            av_log(avctx, AV_LOG_ERROR, "Invalid stripe %d\n", i);
+            return AVERROR_INVALIDDATA;
+        }
+        left += st->width;
+    }
+
+    if (left != h->raw_width)
+        return AVERROR_INVALIDDATA;
+
+    return 0;
+}
+
+static enum AVPixelFormat get_pixel_format(AVCodecContext *avctx,
+                                           enum AVPixelFormat pix_fmt)
+{
+    enum AVPixelFormat pix_fmts[] = {
+        pix_fmt,
+        AV_PIX_FMT_NONE,
+    };
+
+    return ff_get_format(avctx, pix_fmts);
+}
+
+/* The TIFF and EXIF tags of the file. Panasonic's own tags, below 0x100 and
+ * at 0x118 - 0x127, hold the raw parameters exported as side data and the
+ * previews; the strips point into the file. */
+static int export_exif(AVCodecContext *avctx, AVFrame *frame,
+                       const uint8_t *data, int size)
+{
+    const RW2Header *h = &((RW2DecContext *)avctx->priv_data)->h;
+    int64_t off = h->exif_off, keep = FFMIN(h->exif_end, size);
+
+    if (!h->exif_count || off + 2 + (int64_t)h->exif_count*12 + 4 > keep)
+        return 0;
+
+    /* The metadata precedes the embedded previews, so attach it whole, pointing
+     * IFD0 at the standard tags gathered during the header walk */
+    uint8_t *buf = av_memdup(data, keep);
+    if (!buf)
+        return AVERROR(ENOMEM);
+
+    AV_WL16(buf + 2, 42);                                   /* "IIU\0" -> TIFF */
+    AV_WL16(buf + off, h->exif_count);
+    memcpy(buf + off + 2, h->exif, h->exif_count * 12);
+    AV_WL32(buf + off + 2 + h->exif_count*12, 0);           /* no next IFD */
+
+    AVBufferRef *bref = av_buffer_create(buf, keep, NULL, NULL, 0);
+    if (!bref) {
+        av_free(buf);
+        return AVERROR(ENOMEM);
+    }
+
+    return ff_frame_new_side_data_from_buf(avctx, frame, AV_FRAME_DATA_EXIF, &bref);
+}
+
+static int export_metadata(AVCodecContext *avctx, AVFrame *frame,
+                           const uint8_t *hdr, int hdr_size)
+{
+    RW2DecContext *s = avctx->priv_data;
+    const RW2Header *h = &s->h;
+    int ret;
+
+    /* Even crops keep the CFA phase */
+    if (h->border[0] >= 0 && h->border[1] >= 0 &&
+        h->border[0] < h->border[2] && h->border[2] <= h->raw_height &&
+        h->border[1] < h->border[3] && h->border[3] <= h->raw_width) {
+        frame->crop_top    = h->border[0] & ~1;
+        frame->crop_left   = h->border[1] & ~1;
+        frame->crop_bottom = (h->raw_height - h->border[2]) & ~1;
+        frame->crop_right  = (h->raw_width  - h->border[3]) & ~1;
+    }
+
+    if (h->orientation > 1 && h->orientation <= 8) {
+        AVFrameSideData *sd;
+        ret = ff_frame_new_side_data(avctx, frame, AV_FRAME_DATA_DISPLAYMATRIX,
+                                     sizeof(int32_t) * 9, &sd);
+        if (ret < 0)
+            return ret;
+        if (sd)
+            av_exif_orientation_to_matrix((int32_t *)sd->data, h->orientation);
+    }
+
+    /* Per-channel black levels are not representable, use green's */
+    int white = FFMAX3(h->linearity[0], h->linearity[1], h->linearity[2]);
+    if (!white)
+        white = (1 << h->bpp) - 1;
+
+    AVRawColorParams *rcp = av_raw_color_params_create_side_data(frame);
+    if (!rcp)
+        return AVERROR(ENOMEM);
+    rcp->black_level = av_make_q(h->black[1], 65535);
+    rcp->white_level = av_make_q(white, 65535);
+
+    if (h->wb[1]) {
+        rcp->type = AV_RAW_COLOR_PARAMS_RW2;
+        rcp->codec.rw2.wb_red  = av_make_q(h->wb[0], h->wb[1]);
+        rcp->codec.rw2.wb_blue = av_make_q(h->wb[2], h->wb[1]);
+    }
+
+    return export_exif(avctx, frame, hdr, hdr_size);
+}
+
+static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
+                        int *got_frame, AVPacket *avpkt)
+{
+    RW2DecContext *s = avctx->priv_data;
+    RW2Header *h = &s->h;
+    int ret;
+
+    static const enum AVPixelFormat cfa_fmts[] = {
+        AV_PIX_FMT_BAYER_RGGB16, AV_PIX_FMT_BAYER_GRBG16,
+        AV_PIX_FMT_BAYER_GBRG16, AV_PIX_FMT_BAYER_BGGR16,
+    };
+
+    ret = parse_header(h, avpkt->data, avpkt->size);
+    if (ret < 0)
+        return ret;
+
+    if (h->raw_format != 8) {
+        if (!h->raw_format)
+            return AVERROR_INVALIDDATA;
+        avpriv_request_sample(avctx, "RawFormat %d", h->raw_format);
+        return AVERROR_PATCHWELCOME;
+    }
+
+    if (h->cfa < 1 || h->cfa > 4 || h->bpp < 8 || h->bpp > 16 ||
+        (h->raw_width & 1) || (h->raw_height & 1))
+        return AVERROR_INVALIDDATA;
+
+    ret = ff_set_dimensions(avctx, h->raw_width, h->raw_height);
+    if (ret < 0)
+        return ret;
+
+    s->payload = avpkt->data;
+
+    ret = check_stripes(avctx, h, avpkt->size);
+    if (ret < 0)
+        return ret;
+
+    if (cfa_fmts[h->cfa - 1] != s->pix_fmt) {
+        s->pix_fmt = cfa_fmts[h->cfa - 1];
+        ret = get_pixel_format(avctx, s->pix_fmt);
+        if (ret < 0)
+            return ret;
+        avctx->pix_fmt = ret;
+    }
+    avctx->bits_per_raw_sample = h->bpp;
+
+    if (avctx->skip_frame >= AVDISCARD_ALL)
+        return avpkt->size;
+
+    ret = ff_thread_get_buffer(avctx, frame, 0);
+    if (ret < 0)
+        return ret;
+
+    s->frame = frame;
+
+    ret = ff_hwaccel_frame_priv_alloc(avctx, &s->hwaccel_picture_private);
+    if (ret < 0)
+        return ret;
+
+    ff_thread_finish_setup(avctx);
+
+    if (avctx->hwaccel) {
+        const FFHWAccel *hwaccel = ffhwaccel(avctx->hwaccel);
+
+        ret = hwaccel->start_frame(avctx, avpkt->buf, avpkt->data, avpkt->size);
+        if (ret < 0)
+            return ret;
+
+        for (int i = 0; i < h->nb_stripes; i++) {
+            const RW2Stripe *st = &h->stripes[i];
+            ret = hwaccel->decode_slice(avctx, s->payload + st->offset,
+                                        (st->size_bits + 7) >> 3);
+            if (ret < 0)
+                return ret;
+        }
+
+        ret = hwaccel->end_frame(avctx);
+        if (ret < 0)
+            return ret;
+
+        av_refstruct_unref(&s->hwaccel_picture_private);
+    } else {
+        int rets[RW2_MAX_STRIPES];
+
+        ret = build_tables(s);
+        if (ret < 0)
+            return ret;
+
+        avctx->execute2(avctx, decode_stripe, frame, rets, h->nb_stripes);
+        for (int i = 0; i < h->nb_stripes; i++) {
+            if (rets[i] < 0) {
+                av_log(avctx, AV_LOG_ERROR, "Error decoding stripe %d\n", i);
+                return rets[i];
+            }
+        }
+    }
+
+    frame->pict_type = AV_PICTURE_TYPE_I;
+    frame->flags    |= AV_FRAME_FLAG_KEY;
+
+    ret = export_metadata(avctx, frame, avpkt->data, avpkt->size);
+    if (ret < 0)
+        return ret;
+
+    *got_frame = 1;
+
+    return avpkt->size;
+}
+
+#if HAVE_THREADS
+static int update_thread_context(AVCodecContext *dst, const AVCodecContext *src)
+{
+    RW2DecContext *ssrc = src->priv_data;
+    RW2DecContext *sdst = dst->priv_data;
+
+    sdst->pix_fmt = ssrc->pix_fmt;
+
+    return 0;
+}
+#endif
+
+static av_cold int decode_init(AVCodecContext *avctx)
+{
+    RW2DecContext *s = avctx->priv_data;
+
+    avctx->color_trc       = AVCOL_TRC_LINEAR;
+    avctx->color_primaries = AVCOL_PRI_UNSPECIFIED;
+    avctx->colorspace      = AVCOL_SPC_UNSPECIFIED;
+
+    s->pix_fmt = AV_PIX_FMT_NONE;
+
+    return 0;
+}
+
+static av_cold int decode_end(AVCodecContext *avctx)
+{
+    RW2DecContext *s = avctx->priv_data;
+    av_refstruct_unref(&s->hwaccel_picture_private);
+    ff_vlc_free(&s->vlc);
+    return 0;
+}
+
+const FFCodec ff_rw2_decoder = {
+    .p.name         = "rw2",
+    CODEC_LONG_NAME("Panasonic RW2"),
+    .p.type         = AVMEDIA_TYPE_VIDEO,
+    .p.id           = AV_CODEC_ID_RW2,
+    .priv_data_size = sizeof(RW2DecContext),
+    .init           = decode_init,
+    .close          = decode_end,
+    FF_CODEC_DECODE_CB(decode_frame),
+    UPDATE_THREAD_CONTEXT(update_thread_context),
+    .p.capabilities = AV_CODEC_CAP_DR1 |
+                      AV_CODEC_CAP_FRAME_THREADS |
+                      AV_CODEC_CAP_SLICE_THREADS,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP |
+                      FF_CODEC_CAP_SKIP_FRAME_FILL_PARAM,
+};