zimg: add support for some RGB fringe formats

This covers 8 and 16 bit packed RGB formats. It doesn't really help with
any actual use-cases, other than giving the finger to libswscale.

One problem is with different color depths. For example, rgb565 provides
1 bit more resolution to the green channel. zimg can only dither to a
uniform depth. I tried dithering to the highest depth and shifting away
1 bit for the lower channels, but that looked ugly (or I messed up
somewhere), so instead it dithers to the lowest depth, and adjusts the
value range if needed. Testing with bgr4_byte (extreme case with 1/2/1
depths), it looks more "grainy" (ordered dithering artifacts) than
libswscale, but it also looks cleaner and smoother. It doesn't have
libswscale's weird red-shift. So I call it a success.

Big endian formats need to be handled explicitly; the generic big endian
swapper code assumes byte-aligned components.

Unpacking is done with shifts and 3 LUTs. This is symmetric to the
packer. Using a generated palette might be better, but I preferred to
keep the symmetry, and not having to mess with a generated palette and
the pal8 code.

This uses FFmepg pixfmts constants directly. I would have preferred
keeping zimg completely separate. But neither do I want to add an IMGFMT
alias for every of these formats, nor do I want to extend our imgfmt
code such that it can provide a complete description of each packed RGB
format (similar to FFmpeg pixdesc).

It also appears that FFmpeg pixdesc as well as the FFmpeg pixfmt doxygen
have an error regarding RGB8: the R/B bit depths are swapped. libswscale
appears to be handling them differently. Not completely sure, as this is
the only packed format case with R/B havuing different depths (instead
of G, the middle component, where things are symmetric).
This commit is contained in:
wm4 2020-04-13 02:36:46 +02:00
parent a8b84c9a1a
commit f7f947f960
1 changed files with 174 additions and 0 deletions

View File

@ -18,12 +18,14 @@
#include <math.h>
#include <libavutil/bswap.h>
#include <libavutil/pixfmt.h>
#include "common/common.h"
#include "common/msg.h"
#include "csputils.h"
#include "options/m_config.h"
#include "options/m_option.h"
#include "video/fmt-conversion.h"
#include "video/img_format.h"
#include "zimg.h"
@ -85,6 +87,10 @@ struct mp_zimg_repack {
// Called with user==mp_zimg_repack.
zimg_filter_graph_callback repack;
// Output bit depth. If 0, use format defaults. (Used by some packets. This
// is simpler than defining fringe planar RGB formats for each depth.)
int override_depth;
// Endian-swap (done before/after actual repacker).
int endian_size; // 0=no swapping, 2/4=word byte size to swap
int endian_items[4]; // number of words per pixel/plane
@ -95,6 +101,11 @@ struct mp_zimg_repack {
// unpack: p1 is src, p2 is dst
void (*packed_repack_scanline)(void *p1, void *p2[], int x0, int x1);
// Fringe RGB.
uint8_t comp_size;
uint8_t comp_shifts[3];
uint8_t *comp_lut; // 256 * 3
// Temporary memory for slice-wise repacking. This may be set even if repack
// is not set (then it may be used to avoid alignment issues). This has
// about one slice worth of data.
@ -462,6 +473,93 @@ static int packed_repack(void *user, unsigned i, unsigned x0, unsigned x1)
return 0;
}
struct fringe_rgb_repacker {
// To avoid making a mess of IMGFMT_*, we use av formats directly.
enum AVPixelFormat avfmt;
// If true, use BGR instead of RGB.
// False: LSB - R - G - B - pad - MSB
// True: LSB - B - G - R - pad - MSB
bool rev_order;
// Size in bit for each component, strictly from LSB to MSB.
int bits[3];
bool be;
};
static const struct fringe_rgb_repacker fringe_rgb_repackers[] = {
{AV_PIX_FMT_BGR4_BYTE, false, {1, 2, 1}},
{AV_PIX_FMT_RGB4_BYTE, true, {1, 2, 1}},
{AV_PIX_FMT_BGR8, false, {3, 3, 2}},
{AV_PIX_FMT_RGB8, true, {2, 3, 3}}, // pixdesc desc. and doc. bug?
{AV_PIX_FMT_RGB444LE, true, {4, 4, 4}},
{AV_PIX_FMT_RGB444BE, true, {4, 4, 4}, .be = true},
{AV_PIX_FMT_BGR444LE, false, {4, 4, 4}},
{AV_PIX_FMT_BGR444BE, false, {4, 4, 4}, .be = true},
{AV_PIX_FMT_BGR565LE, false, {5, 6, 5}},
{AV_PIX_FMT_BGR565BE, false, {5, 6, 5}, .be = true},
{AV_PIX_FMT_RGB565LE, true, {5, 6, 5}},
{AV_PIX_FMT_RGB565BE, true, {5, 6, 5}, .be = true},
{AV_PIX_FMT_BGR555LE, false, {5, 5, 5}},
{AV_PIX_FMT_BGR555BE, false, {5, 5, 5}, .be = true},
{AV_PIX_FMT_RGB555LE, true, {5, 5, 5}},
{AV_PIX_FMT_RGB555BE, true, {5, 5, 5}, .be = true},
};
#define PA_SHIFT_LUT8(name, packed_t) \
static void name(void *dst, void *src[], int x0, int x1, uint8_t *lut, \
uint8_t s0, uint8_t s1, uint8_t s2) { \
for (int x = x0; x < x1; x++) { \
((packed_t *)dst)[x] = \
(lut[((uint8_t *)src[0])[x] + 256 * 0] << s0) | \
(lut[((uint8_t *)src[1])[x] + 256 * 1] << s1) | \
(lut[((uint8_t *)src[2])[x] + 256 * 2] << s2); \
} \
}
#define UN_SHIFT_LUT8(name, packed_t) \
static void name(void *src, void *dst[], int x0, int x1, uint8_t *lut, \
uint8_t s0, uint8_t s1, uint8_t s2) { \
for (int x = x0; x < x1; x++) { \
packed_t c = ((packed_t *)src)[x]; \
((uint8_t *)dst[0])[x] = lut[((c >> s0) & 0xFF) + 256 * 0]; \
((uint8_t *)dst[1])[x] = lut[((c >> s1) & 0xFF) + 256 * 1]; \
((uint8_t *)dst[2])[x] = lut[((c >> s2) & 0xFF) + 256 * 2]; \
} \
}
PA_SHIFT_LUT8(pa_shift_lut8_8, uint8_t)
PA_SHIFT_LUT8(pa_shift_lut8_16, uint16_t)
UN_SHIFT_LUT8(un_shift_lut8_8, uint8_t)
UN_SHIFT_LUT8(un_shift_lut8_16, uint16_t)
static int fringe_rgb_repack(void *user, unsigned i, unsigned x0, unsigned x1)
{
struct mp_zimg_repack *r = user;
void *p1 = r->mpi->planes[0] + r->mpi->stride[0] * (ptrdiff_t)(i - r->mpi_y0);
void *p2[4] = {0};
for (int p = 0; p < r->num_planes; p++) {
int s = r->components[p];
p2[p] = r->tmp->planes[s] +
r->tmp->stride[s] * (ptrdiff_t)(i & r->zmask[s]);
}
assert(r->comp_size == 1 || r->comp_size == 2);
void (*repack)(void *p1, void *p2[], int x0, int x1, uint8_t *lut,
uint8_t s0, uint8_t s1, uint8_t s2) = NULL;
if (r->pack) {
repack = r->comp_size == 1 ? pa_shift_lut8_8 : pa_shift_lut8_16;
} else {
repack = r->comp_size == 1 ? un_shift_lut8_8 : un_shift_lut8_16;
}
repack(p1, p2, x0, x1, r->comp_lut,
r->comp_shifts[0], r->comp_shifts[1], r->comp_shifts[2]);
return 0;
}
static int unpack_pal(void *user, unsigned i, unsigned x0, unsigned x1)
{
struct mp_zimg_repack *r = user;
@ -588,6 +686,78 @@ static void wrap_buffer(struct mp_zimg_repack *r,
r->user_mpi = mpi;
}
static void setup_fringe_rgb_packer(struct mp_zimg_repack *r,
struct mp_zimg_context *ctx)
{
enum AVPixelFormat avfmt = imgfmt2pixfmt(r->zimgfmt);
const struct fringe_rgb_repacker *fmt = NULL;
for (int n = 0; n < MP_ARRAY_SIZE(fringe_rgb_repackers); n++) {
if (fringe_rgb_repackers[n].avfmt == avfmt) {
fmt = &fringe_rgb_repackers[n];
break;
}
}
if (!fmt)
return;
struct mp_regular_imgfmt gbrp = {
.component_type = MP_COMPONENT_TYPE_UINT,
.forced_csp = MP_CSP_RGB,
.component_size = 1,
.num_planes = 3,
.planes = { {1, {2}}, {1, {3}}, {1, {1}} },
.chroma_w = 1,
.chroma_h = 1,
};
r->zimgfmt = mp_find_regular_imgfmt(&gbrp);
if (!r->zimgfmt)
return;
if (ctx)
r->comp_lut = talloc_array(ctx, uint8_t, 256 * 3);
r->repack = fringe_rgb_repack;
static const int c_order_rgb[] = {3, 1, 2};
static const int c_order_bgr[] = {2, 1, 3};
for (int n = 0; n < 3; n++)
r->components[n] = (fmt->rev_order ? c_order_bgr : c_order_rgb)[n] - 1;
if (r->pack) {
// Dither to lowest depth - loses some precision, but result is saner.
r->override_depth = fmt->bits[0];
for (int n = 0; n < 3; n++)
r->override_depth = MPMIN(r->override_depth, fmt->bits[n]);
}
int bitpos = 0;
for (int n = 0; n < 3; n++) {
int bits = fmt->bits[n];
r->comp_shifts[n] = bitpos;
if (r->comp_lut) {
uint8_t *lut = r->comp_lut + 256 * n;
uint8_t zmax = r->pack ? (1 << r->override_depth) - 1 : 255;
uint8_t cmax = (1 << bits) - 1;
for (int v = 0; v < 256; v++) {
if (r->pack) {
lut[v] = (v * cmax + zmax / 2) / zmax;
} else {
lut[v] = (v & cmax) * zmax / cmax;
}
}
}
bitpos += bits;
}
r->comp_size = (bitpos + 7) / 8;
assert(r->comp_size == 1 || r->comp_size == 2);
if (fmt->be) {
assert(r->comp_size == 2);
r->endian_size = 2;
r->endian_items[0] = 1;
}
}
static void setup_nv_packer(struct mp_zimg_repack *r)
{
struct mp_regular_imgfmt desc;
@ -782,6 +952,8 @@ static bool setup_format_ne(zimg_image_format *zfmt, struct mp_zimg_repack *r,
setup_misc_packer(r);
if (!r->repack)
setup_regular_rgb_packer(r);
if (!r->repack)
setup_fringe_rgb_packer(r, ctx);
struct mp_regular_imgfmt desc;
if (!mp_get_regular_imgfmt(&desc, r->zimgfmt))
@ -876,6 +1048,8 @@ static bool setup_format_ne(zimg_image_format *zfmt, struct mp_zimg_repack *r,
// (Formats like P010 are basically reported as P016.)
zfmt->depth = desc.component_size * 8 + MPMIN(0, desc.component_pad);
if (r->override_depth)
zfmt->depth = r->override_depth;
zfmt->pixel_range = fmt.color.levels == MP_CSP_LEVELS_PC ?
ZIMG_RANGE_FULL : ZIMG_RANGE_LIMITED;