summaryrefslogtreecommitdiffstats
path: root/video/mp_image.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:36:56 +0000
commit51de1d8436100f725f3576aefa24a2bd2057bc28 (patch)
treec6d1d5264b6d40a8d7ca34129f36b7d61e188af3 /video/mp_image.c
parentInitial commit. (diff)
downloadmpv-51de1d8436100f725f3576aefa24a2bd2057bc28.tar.xz
mpv-51de1d8436100f725f3576aefa24a2bd2057bc28.zip
Adding upstream version 0.37.0.upstream/0.37.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'video/mp_image.c')
-rw-r--r--video/mp_image.c1289
1 files changed, 1289 insertions, 0 deletions
diff --git a/video/mp_image.c b/video/mp_image.c
new file mode 100644
index 0000000..dff2051
--- /dev/null
+++ b/video/mp_image.c
@@ -0,0 +1,1289 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv 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.
+ *
+ * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <limits.h>
+#include <assert.h>
+
+#include <libavutil/mem.h>
+#include <libavutil/common.h>
+#include <libavutil/display.h>
+#include <libavutil/bswap.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/intreadwrite.h>
+#include <libavutil/rational.h>
+#include <libavcodec/avcodec.h>
+#include <libavutil/mastering_display_metadata.h>
+#include <libplacebo/utils/libav.h>
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 16, 100)
+# include <libavutil/dovi_meta.h>
+#endif
+
+#include "mpv_talloc.h"
+
+#include "common/av_common.h"
+#include "common/common.h"
+#include "fmt-conversion.h"
+#include "hwdec.h"
+#include "mp_image.h"
+#include "osdep/threads.h"
+#include "sws_utils.h"
+#include "out/placebo/utils.h"
+
+// Determine strides, plane sizes, and total required size for an image
+// allocation. Returns total size on success, <0 on error. Unused planes
+// have out_stride/out_plane_size to 0, and out_plane_offset set to -1 up
+// until MP_MAX_PLANES-1.
+static int mp_image_layout(int imgfmt, int w, int h, int stride_align,
+ int out_stride[MP_MAX_PLANES],
+ int out_plane_offset[MP_MAX_PLANES],
+ int out_plane_size[MP_MAX_PLANES])
+{
+ struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(imgfmt);
+
+ w = MP_ALIGN_UP(w, desc.align_x);
+ h = MP_ALIGN_UP(h, desc.align_y);
+
+ struct mp_image_params params = {.imgfmt = imgfmt, .w = w, .h = h};
+
+ if (!mp_image_params_valid(&params) || desc.flags & MP_IMGFLAG_HWACCEL)
+ return -1;
+
+ // Note: for non-mod-2 4:2:0 YUV frames, we have to allocate an additional
+ // top/right border. This is needed for correct handling of such
+ // images in filter and VO code (e.g. vo_vdpau or vo_gpu).
+
+ for (int n = 0; n < MP_MAX_PLANES; n++) {
+ int alloc_w = mp_chroma_div_up(w, desc.xs[n]);
+ int alloc_h = MP_ALIGN_UP(h, 32) >> desc.ys[n];
+ int line_bytes = (alloc_w * desc.bpp[n] + 7) / 8;
+ out_stride[n] = MP_ALIGN_NPOT(line_bytes, stride_align);
+ out_plane_size[n] = out_stride[n] * alloc_h;
+ }
+ if (desc.flags & MP_IMGFLAG_PAL)
+ out_plane_size[1] = AVPALETTE_SIZE;
+
+ int sum = 0;
+ for (int n = 0; n < MP_MAX_PLANES; n++) {
+ out_plane_offset[n] = out_plane_size[n] ? sum : -1;
+ sum += out_plane_size[n];
+ }
+
+ return sum;
+}
+
+// Return the total size needed for an image allocation of the given
+// configuration (imgfmt, w, h must be set). Returns -1 on error.
+// Assumes the allocation is already aligned on stride_align (otherwise you
+// need to add padding yourself).
+int mp_image_get_alloc_size(int imgfmt, int w, int h, int stride_align)
+{
+ int stride[MP_MAX_PLANES];
+ int plane_offset[MP_MAX_PLANES];
+ int plane_size[MP_MAX_PLANES];
+ return mp_image_layout(imgfmt, w, h, stride_align, stride, plane_offset,
+ plane_size);
+}
+
+// Fill the mpi->planes and mpi->stride fields of the given mpi with data
+// from buffer according to the mpi's w/h/imgfmt fields. See mp_image_from_buffer
+// aboud remarks how to allocate/use buffer/buffer_size.
+// This does not free the data. You are expected to setup refcounting by
+// setting mp_image.bufs before or after this function is called.
+// Returns true on success, false on failure.
+static bool mp_image_fill_alloc(struct mp_image *mpi, int stride_align,
+ void *buffer, int buffer_size)
+{
+ int stride[MP_MAX_PLANES];
+ int plane_offset[MP_MAX_PLANES];
+ int plane_size[MP_MAX_PLANES];
+ int size = mp_image_layout(mpi->imgfmt, mpi->w, mpi->h, stride_align,
+ stride, plane_offset, plane_size);
+ if (size < 0 || size > buffer_size)
+ return false;
+
+ int align = MP_ALIGN_UP((uintptr_t)buffer, stride_align) - (uintptr_t)buffer;
+ if (buffer_size - size < align)
+ return false;
+ uint8_t *s = buffer;
+ s += align;
+
+ for (int n = 0; n < MP_MAX_PLANES; n++) {
+ mpi->planes[n] = plane_offset[n] >= 0 ? s + plane_offset[n] : NULL;
+ mpi->stride[n] = stride[n];
+ }
+
+ return true;
+}
+
+// Create a mp_image from the provided buffer. The mp_image is filled according
+// to the imgfmt/w/h parameters, and respecting the stride_align parameter to
+// align the plane start pointers and strides. Once the last reference to the
+// returned image is destroyed, free(free_opaque, buffer) is called. (Be aware
+// that this can happen from any thread.)
+// The allocated size of buffer must be given by buffer_size. buffer_size should
+// be at least the value returned by mp_image_get_alloc_size(). If buffer is not
+// already aligned to stride_align, the function will attempt to align the
+// pointer itself by incrementing the buffer pointer until their alignment is
+// achieved (if buffer_size is not large enough to allow aligning the buffer
+// safely, the function fails). To be safe, you may want to overallocate the
+// buffer by stride_align bytes, and include the overallocation in buffer_size.
+// Returns NULL on failure. On failure, the free() callback is not called.
+struct mp_image *mp_image_from_buffer(int imgfmt, int w, int h, int stride_align,
+ uint8_t *buffer, int buffer_size,
+ void *free_opaque,
+ void (*free)(void *opaque, uint8_t *data))
+{
+ struct mp_image *mpi = mp_image_new_dummy_ref(NULL);
+ mp_image_setfmt(mpi, imgfmt);
+ mp_image_set_size(mpi, w, h);
+
+ if (!mp_image_fill_alloc(mpi, stride_align, buffer, buffer_size))
+ goto fail;
+
+ mpi->bufs[0] = av_buffer_create(buffer, buffer_size, free, free_opaque, 0);
+ if (!mpi->bufs[0])
+ goto fail;
+
+ return mpi;
+
+fail:
+ talloc_free(mpi);
+ return NULL;
+}
+
+static bool mp_image_alloc_planes(struct mp_image *mpi)
+{
+ assert(!mpi->planes[0]);
+ assert(!mpi->bufs[0]);
+
+ int align = MP_IMAGE_BYTE_ALIGN;
+
+ int size = mp_image_get_alloc_size(mpi->imgfmt, mpi->w, mpi->h, align);
+ if (size < 0)
+ return false;
+
+ // Note: mp_image_pool assumes this creates only 1 AVBufferRef.
+ mpi->bufs[0] = av_buffer_alloc(size + align);
+ if (!mpi->bufs[0])
+ return false;
+
+ if (!mp_image_fill_alloc(mpi, align, mpi->bufs[0]->data, mpi->bufs[0]->size)) {
+ av_buffer_unref(&mpi->bufs[0]);
+ return false;
+ }
+
+ return true;
+}
+
+void mp_image_setfmt(struct mp_image *mpi, int out_fmt)
+{
+ struct mp_image_params params = mpi->params;
+ struct mp_imgfmt_desc fmt = mp_imgfmt_get_desc(out_fmt);
+ params.imgfmt = fmt.id;
+ mpi->fmt = fmt;
+ mpi->imgfmt = fmt.id;
+ mpi->num_planes = fmt.num_planes;
+ mpi->params = params;
+}
+
+static void mp_image_destructor(void *ptr)
+{
+ mp_image_t *mpi = ptr;
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ av_buffer_unref(&mpi->bufs[p]);
+ av_buffer_unref(&mpi->hwctx);
+ av_buffer_unref(&mpi->icc_profile);
+ av_buffer_unref(&mpi->a53_cc);
+ av_buffer_unref(&mpi->dovi);
+ av_buffer_unref(&mpi->film_grain);
+ av_buffer_unref(&mpi->dovi_buf);
+ for (int n = 0; n < mpi->num_ff_side_data; n++)
+ av_buffer_unref(&mpi->ff_side_data[n].buf);
+ talloc_free(mpi->ff_side_data);
+}
+
+int mp_chroma_div_up(int size, int shift)
+{
+ return (size + (1 << shift) - 1) >> shift;
+}
+
+// Return the storage width in pixels of the given plane.
+int mp_image_plane_w(struct mp_image *mpi, int plane)
+{
+ return mp_chroma_div_up(mpi->w, mpi->fmt.xs[plane]);
+}
+
+// Return the storage height in pixels of the given plane.
+int mp_image_plane_h(struct mp_image *mpi, int plane)
+{
+ return mp_chroma_div_up(mpi->h, mpi->fmt.ys[plane]);
+}
+
+// Caller has to make sure this doesn't exceed the allocated plane data/strides.
+void mp_image_set_size(struct mp_image *mpi, int w, int h)
+{
+ assert(w >= 0 && h >= 0);
+ mpi->w = mpi->params.w = w;
+ mpi->h = mpi->params.h = h;
+}
+
+void mp_image_set_params(struct mp_image *image,
+ const struct mp_image_params *params)
+{
+ // possibly initialize other stuff
+ mp_image_setfmt(image, params->imgfmt);
+ mp_image_set_size(image, params->w, params->h);
+ image->params = *params;
+}
+
+struct mp_image *mp_image_alloc(int imgfmt, int w, int h)
+{
+ struct mp_image *mpi = talloc_zero(NULL, struct mp_image);
+ talloc_set_destructor(mpi, mp_image_destructor);
+
+ mp_image_set_size(mpi, w, h);
+ mp_image_setfmt(mpi, imgfmt);
+ if (!mp_image_alloc_planes(mpi)) {
+ talloc_free(mpi);
+ return NULL;
+ }
+ return mpi;
+}
+
+int mp_image_approx_byte_size(struct mp_image *img)
+{
+ int total = sizeof(*img);
+
+ for (int n = 0; n < MP_MAX_PLANES; n++) {
+ struct AVBufferRef *buf = img->bufs[n];
+ if (buf)
+ total += buf->size;
+ }
+
+ return total;
+}
+
+struct mp_image *mp_image_new_copy(struct mp_image *img)
+{
+ struct mp_image *new = mp_image_alloc(img->imgfmt, img->w, img->h);
+ if (!new)
+ return NULL;
+ mp_image_copy(new, img);
+ mp_image_copy_attributes(new, img);
+ return new;
+}
+
+// Make dst take over the image data of src, and free src.
+// This is basically a safe version of *dst = *src; free(src);
+// Only works with ref-counted images, and can't change image size/format.
+void mp_image_steal_data(struct mp_image *dst, struct mp_image *src)
+{
+ assert(dst->imgfmt == src->imgfmt && dst->w == src->w && dst->h == src->h);
+ assert(dst->bufs[0] && src->bufs[0]);
+
+ mp_image_destructor(dst); // unref old
+ talloc_free_children(dst);
+
+ *dst = *src;
+
+ *src = (struct mp_image){0};
+ talloc_free(src);
+}
+
+// Unref most data buffer (and clear the data array), but leave other fields
+// allocated. In particular, mp_image.hwctx is preserved.
+void mp_image_unref_data(struct mp_image *img)
+{
+ for (int n = 0; n < MP_MAX_PLANES; n++) {
+ img->planes[n] = NULL;
+ img->stride[n] = 0;
+ av_buffer_unref(&img->bufs[n]);
+ }
+}
+
+static void ref_buffer(AVBufferRef **dst)
+{
+ if (*dst) {
+ *dst = av_buffer_ref(*dst);
+ MP_HANDLE_OOM(*dst);
+ }
+}
+
+// Return a new reference to img. The returned reference is owned by the caller,
+// while img is left untouched.
+struct mp_image *mp_image_new_ref(struct mp_image *img)
+{
+ if (!img)
+ return NULL;
+
+ if (!img->bufs[0])
+ return mp_image_new_copy(img);
+
+ struct mp_image *new = talloc_ptrtype(NULL, new);
+ talloc_set_destructor(new, mp_image_destructor);
+ *new = *img;
+
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ ref_buffer(&new->bufs[p]);
+
+ ref_buffer(&new->hwctx);
+ ref_buffer(&new->icc_profile);
+ ref_buffer(&new->a53_cc);
+ ref_buffer(&new->dovi);
+ ref_buffer(&new->film_grain);
+ ref_buffer(&new->dovi_buf);
+
+ new->ff_side_data = talloc_memdup(NULL, new->ff_side_data,
+ new->num_ff_side_data * sizeof(new->ff_side_data[0]));
+ for (int n = 0; n < new->num_ff_side_data; n++)
+ ref_buffer(&new->ff_side_data[n].buf);
+
+ return new;
+}
+
+struct free_args {
+ void *arg;
+ void (*free)(void *arg);
+};
+
+static void call_free(void *opaque, uint8_t *data)
+{
+ struct free_args *args = opaque;
+ args->free(args->arg);
+ talloc_free(args);
+}
+
+// Create a new mp_image based on img, but don't set any buffers.
+// Using this is only valid until the original img is unreferenced (including
+// implicit unreferencing of the data by mp_image_make_writeable()), unless
+// a new reference is set.
+struct mp_image *mp_image_new_dummy_ref(struct mp_image *img)
+{
+ struct mp_image *new = talloc_ptrtype(NULL, new);
+ talloc_set_destructor(new, mp_image_destructor);
+ *new = img ? *img : (struct mp_image){0};
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ new->bufs[p] = NULL;
+ new->hwctx = NULL;
+ new->icc_profile = NULL;
+ new->a53_cc = NULL;
+ new->dovi = NULL;
+ new->film_grain = NULL;
+ new->dovi_buf = NULL;
+ new->num_ff_side_data = 0;
+ new->ff_side_data = NULL;
+ return new;
+}
+
+// Return a reference counted reference to img. If the reference count reaches
+// 0, call free(free_arg). The data passed by img must not be free'd before
+// that. The new reference will be writeable.
+// On allocation failure, unref the frame and return NULL.
+// This is only used for hw decoding; this is important, because libav* expects
+// all plane data to be accounted for by AVBufferRefs.
+struct mp_image *mp_image_new_custom_ref(struct mp_image *img, void *free_arg,
+ void (*free)(void *arg))
+{
+ struct mp_image *new = mp_image_new_dummy_ref(img);
+
+ struct free_args *args = talloc_ptrtype(NULL, args);
+ *args = (struct free_args){free_arg, free};
+ new->bufs[0] = av_buffer_create(NULL, 0, call_free, args,
+ AV_BUFFER_FLAG_READONLY);
+ if (new->bufs[0])
+ return new;
+ talloc_free(new);
+ return NULL;
+}
+
+bool mp_image_is_writeable(struct mp_image *img)
+{
+ if (!img->bufs[0])
+ return true; // not ref-counted => always considered writeable
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ if (!img->bufs[p])
+ break;
+ if (!av_buffer_is_writable(img->bufs[p]))
+ return false;
+ }
+ return true;
+}
+
+// Make the image data referenced by img writeable. This allocates new data
+// if the data wasn't already writeable, and img->planes[] and img->stride[]
+// will be set to the copy.
+// Returns success; if false is returned, the image could not be made writeable.
+bool mp_image_make_writeable(struct mp_image *img)
+{
+ if (mp_image_is_writeable(img))
+ return true;
+
+ struct mp_image *new = mp_image_new_copy(img);
+ if (!new)
+ return false;
+ mp_image_steal_data(img, new);
+ assert(mp_image_is_writeable(img));
+ return true;
+}
+
+// Helper function: unrefs *p_img, and sets *p_img to a new ref of new_value.
+// Only unrefs *p_img and sets it to NULL if out of memory.
+void mp_image_setrefp(struct mp_image **p_img, struct mp_image *new_value)
+{
+ if (*p_img != new_value) {
+ talloc_free(*p_img);
+ *p_img = new_value ? mp_image_new_ref(new_value) : NULL;
+ }
+}
+
+// Mere helper function (mp_image can be directly free'd with talloc_free)
+void mp_image_unrefp(struct mp_image **p_img)
+{
+ talloc_free(*p_img);
+ *p_img = NULL;
+}
+
+void memcpy_pic(void *dst, const void *src, int bytesPerLine, int height,
+ int dstStride, int srcStride)
+{
+ if (bytesPerLine == dstStride && dstStride == srcStride && height) {
+ if (srcStride < 0) {
+ src = (uint8_t*)src + (height - 1) * srcStride;
+ dst = (uint8_t*)dst + (height - 1) * dstStride;
+ srcStride = -srcStride;
+ }
+
+ memcpy(dst, src, srcStride * (height - 1) + bytesPerLine);
+ } else {
+ for (int i = 0; i < height; i++) {
+ memcpy(dst, src, bytesPerLine);
+ src = (uint8_t*)src + srcStride;
+ dst = (uint8_t*)dst + dstStride;
+ }
+ }
+}
+
+void mp_image_copy(struct mp_image *dst, struct mp_image *src)
+{
+ assert(dst->imgfmt == src->imgfmt);
+ assert(dst->w == src->w && dst->h == src->h);
+ assert(mp_image_is_writeable(dst));
+ for (int n = 0; n < dst->num_planes; n++) {
+ int line_bytes = (mp_image_plane_w(dst, n) * dst->fmt.bpp[n] + 7) / 8;
+ int plane_h = mp_image_plane_h(dst, n);
+ memcpy_pic(dst->planes[n], src->planes[n], line_bytes, plane_h,
+ dst->stride[n], src->stride[n]);
+ }
+ if (dst->fmt.flags & MP_IMGFLAG_PAL)
+ memcpy(dst->planes[1], src->planes[1], AVPALETTE_SIZE);
+}
+
+static enum mp_csp mp_image_params_get_forced_csp(struct mp_image_params *params)
+{
+ int imgfmt = params->hw_subfmt ? params->hw_subfmt : params->imgfmt;
+ return mp_imgfmt_get_forced_csp(imgfmt);
+}
+
+static void assign_bufref(AVBufferRef **dst, AVBufferRef *new)
+{
+ av_buffer_unref(dst);
+ if (new) {
+ *dst = av_buffer_ref(new);
+ MP_HANDLE_OOM(*dst);
+ }
+}
+
+void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
+{
+ assert(dst != src);
+
+ dst->pict_type = src->pict_type;
+ dst->fields = src->fields;
+ dst->pts = src->pts;
+ dst->dts = src->dts;
+ dst->pkt_duration = src->pkt_duration;
+ dst->params.rotate = src->params.rotate;
+ dst->params.stereo3d = src->params.stereo3d;
+ dst->params.p_w = src->params.p_w;
+ dst->params.p_h = src->params.p_h;
+ dst->params.color = src->params.color;
+ dst->params.chroma_location = src->params.chroma_location;
+ dst->params.alpha = src->params.alpha;
+ dst->params.crop = src->params.crop;
+ dst->nominal_fps = src->nominal_fps;
+
+ // ensure colorspace consistency
+ enum mp_csp dst_forced_csp = mp_image_params_get_forced_csp(&dst->params);
+ if (mp_image_params_get_forced_csp(&src->params) != dst_forced_csp) {
+ dst->params.color.space = dst_forced_csp != MP_CSP_AUTO ?
+ dst_forced_csp :
+ mp_csp_guess_colorspace(src->w, src->h);
+ }
+
+ if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) {
+ if (dst->planes[1] && src->planes[1]) {
+ if (mp_image_make_writeable(dst))
+ memcpy(dst->planes[1], src->planes[1], AVPALETTE_SIZE);
+ }
+ }
+ assign_bufref(&dst->icc_profile, src->icc_profile);
+ assign_bufref(&dst->dovi, src->dovi);
+ assign_bufref(&dst->dovi_buf, src->dovi_buf);
+ assign_bufref(&dst->film_grain, src->film_grain);
+ assign_bufref(&dst->a53_cc, src->a53_cc);
+
+ for (int n = 0; n < dst->num_ff_side_data; n++)
+ av_buffer_unref(&dst->ff_side_data[n].buf);
+
+ MP_RESIZE_ARRAY(NULL, dst->ff_side_data, src->num_ff_side_data);
+ dst->num_ff_side_data = src->num_ff_side_data;
+
+ for (int n = 0; n < dst->num_ff_side_data; n++) {
+ dst->ff_side_data[n].type = src->ff_side_data[n].type;
+ dst->ff_side_data[n].buf = av_buffer_ref(src->ff_side_data[n].buf);
+ MP_HANDLE_OOM(dst->ff_side_data[n].buf);
+ }
+}
+
+// Crop the given image to (x0, y0)-(x1, y1) (bottom/right border exclusive)
+// x0/y0 must be naturally aligned.
+void mp_image_crop(struct mp_image *img, int x0, int y0, int x1, int y1)
+{
+ assert(x0 >= 0 && y0 >= 0);
+ assert(x0 <= x1 && y0 <= y1);
+ assert(x1 <= img->w && y1 <= img->h);
+ assert(!(x0 & (img->fmt.align_x - 1)));
+ assert(!(y0 & (img->fmt.align_y - 1)));
+
+ for (int p = 0; p < img->num_planes; ++p) {
+ img->planes[p] += (y0 >> img->fmt.ys[p]) * img->stride[p] +
+ (x0 >> img->fmt.xs[p]) * img->fmt.bpp[p] / 8;
+ }
+ mp_image_set_size(img, x1 - x0, y1 - y0);
+}
+
+void mp_image_crop_rc(struct mp_image *img, struct mp_rect rc)
+{
+ mp_image_crop(img, rc.x0, rc.y0, rc.x1, rc.y1);
+}
+
+// Repeatedly write count patterns of src[0..src_size] to p.
+static void memset_pattern(void *p, size_t count, uint8_t *src, size_t src_size)
+{
+ assert(src_size >= 1);
+
+ if (src_size == 1) {
+ memset(p, src[0], count);
+ } else if (src_size == 2) { // >8 bit YUV => common, be slightly less naive
+ uint16_t val;
+ memcpy(&val, src, 2);
+ uint16_t *p16 = p;
+ while (count--)
+ *p16++ = val;
+ } else {
+ while (count--) {
+ memcpy(p, src, src_size);
+ p = (char *)p + src_size;
+ }
+ }
+}
+
+static bool endian_swap_bytes(void *d, size_t bytes, size_t word_size)
+{
+ if (word_size != 2 && word_size != 4)
+ return false;
+
+ size_t num_words = bytes / word_size;
+ uint8_t *ud = d;
+
+ switch (word_size) {
+ case 2:
+ for (size_t x = 0; x < num_words; x++)
+ AV_WL16(ud + x * 2, AV_RB16(ud + x * 2));
+ break;
+ case 4:
+ for (size_t x = 0; x < num_words; x++)
+ AV_WL32(ud + x * 2, AV_RB32(ud + x * 2));
+ break;
+ default:
+ MP_ASSERT_UNREACHABLE();
+ }
+
+ return true;
+}
+
+// Bottom/right border is allowed not to be aligned, but it might implicitly
+// overwrite pixel data until the alignment (align_x/align_y) is reached.
+// Alpha is cleared to 0 (fully transparent).
+void mp_image_clear(struct mp_image *img, int x0, int y0, int x1, int y1)
+{
+ assert(x0 >= 0 && y0 >= 0);
+ assert(x0 <= x1 && y0 <= y1);
+ assert(x1 <= img->w && y1 <= img->h);
+ assert(!(x0 & (img->fmt.align_x - 1)));
+ assert(!(y0 & (img->fmt.align_y - 1)));
+
+ struct mp_image area = *img;
+ struct mp_imgfmt_desc *fmt = &area.fmt;
+ mp_image_crop(&area, x0, y0, x1, y1);
+
+ // "Black" color for each plane.
+ uint8_t plane_clear[MP_MAX_PLANES][8] = {0};
+ int plane_size[MP_MAX_PLANES] = {0};
+ int misery = 1; // pixel group width
+
+ // YUV integer chroma needs special consideration, and technically luma is
+ // usually not 0 either.
+ if ((fmt->flags & (MP_IMGFLAG_HAS_COMPS | MP_IMGFLAG_PACKED_SS_YUV)) &&
+ (fmt->flags & MP_IMGFLAG_TYPE_MASK) == MP_IMGFLAG_TYPE_UINT &&
+ (fmt->flags & MP_IMGFLAG_COLOR_MASK) == MP_IMGFLAG_COLOR_YUV)
+ {
+ uint64_t plane_clear_i[MP_MAX_PLANES] = {0};
+
+ // Need to handle "multiple" pixels with packed YUV.
+ uint8_t luma_offsets[4] = {0};
+ if (fmt->flags & MP_IMGFLAG_PACKED_SS_YUV) {
+ misery = fmt->align_x;
+ if (misery <= MP_ARRAY_SIZE(luma_offsets)) // ignore if out of bounds
+ mp_imgfmt_get_packed_yuv_locations(fmt->id, luma_offsets);
+ }
+
+ for (int c = 0; c < 4; c++) {
+ struct mp_imgfmt_comp_desc *cd = &fmt->comps[c];
+ int plane_bits = fmt->bpp[cd->plane] * misery;
+ if (plane_bits <= 64 && plane_bits % 8u == 0 && cd->size) {
+ plane_size[cd->plane] = plane_bits / 8u;
+ int depth = cd->size + MPMIN(cd->pad, 0);
+ double m, o;
+ mp_get_csp_uint_mul(area.params.color.space,
+ area.params.color.levels,
+ depth, c + 1, &m, &o);
+ uint64_t val = MPCLAMP(lrint((0 - o) / m), 0, 1ull << depth);
+ plane_clear_i[cd->plane] |= val << cd->offset;
+ for (int x = 1; x < (c ? 0 : misery); x++)
+ plane_clear_i[cd->plane] |= val << luma_offsets[x];
+ }
+ }
+
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ if (!plane_clear_i[p])
+ plane_size[p] = 0;
+ memcpy(&plane_clear[p][0], &plane_clear_i[p], 8); // endian dependent
+
+ if (fmt->endian_shift) {
+ endian_swap_bytes(&plane_clear[p][0], plane_size[p],
+ 1 << fmt->endian_shift);
+ }
+ }
+ }
+
+ for (int p = 0; p < area.num_planes; p++) {
+ int p_h = mp_image_plane_h(&area, p);
+ int p_w = mp_image_plane_w(&area, p);
+ for (int y = 0; y < p_h; y++) {
+ void *ptr = area.planes[p] + (ptrdiff_t)area.stride[p] * y;
+ if (plane_size[p]) {
+ memset_pattern(ptr, p_w / misery, plane_clear[p], plane_size[p]);
+ } else {
+ memset(ptr, 0, mp_image_plane_bytes(&area, p, 0, area.w));
+ }
+ }
+ }
+}
+
+void mp_image_clear_rc(struct mp_image *mpi, struct mp_rect rc)
+{
+ mp_image_clear(mpi, rc.x0, rc.y0, rc.x1, rc.y1);
+}
+
+// Clear the are of the image _not_ covered by rc.
+void mp_image_clear_rc_inv(struct mp_image *mpi, struct mp_rect rc)
+{
+ struct mp_rect clr[4];
+ int cnt = mp_rect_subtract(&(struct mp_rect){0, 0, mpi->w, mpi->h}, &rc, clr);
+ for (int n = 0; n < cnt; n++)
+ mp_image_clear_rc(mpi, clr[n]);
+}
+
+void mp_image_vflip(struct mp_image *img)
+{
+ for (int p = 0; p < img->num_planes; p++) {
+ int plane_h = mp_image_plane_h(img, p);
+ img->planes[p] = img->planes[p] + img->stride[p] * (plane_h - 1);
+ img->stride[p] = -img->stride[p];
+ }
+}
+
+bool mp_image_crop_valid(const struct mp_image_params *p)
+{
+ return p->crop.x1 > p->crop.x0 && p->crop.y1 > p->crop.y0 &&
+ p->crop.x0 >= 0 && p->crop.y0 >= 0 &&
+ p->crop.x1 <= p->w && p->crop.y1 <= p->h;
+}
+
+// Display size derived from image size and pixel aspect ratio.
+void mp_image_params_get_dsize(const struct mp_image_params *p,
+ int *d_w, int *d_h)
+{
+ if (mp_image_crop_valid(p))
+ {
+ *d_w = mp_rect_w(p->crop);
+ *d_h = mp_rect_h(p->crop);
+ } else {
+ *d_w = p->w;
+ *d_h = p->h;
+ }
+
+ if (p->p_w > p->p_h && p->p_h >= 1)
+ *d_w = MPCLAMP(*d_w * (int64_t)p->p_w / p->p_h, 1, INT_MAX);
+ if (p->p_h > p->p_w && p->p_w >= 1)
+ *d_h = MPCLAMP(*d_h * (int64_t)p->p_h / p->p_w, 1, INT_MAX);
+}
+
+void mp_image_params_set_dsize(struct mp_image_params *p, int d_w, int d_h)
+{
+ AVRational ds = av_div_q((AVRational){d_w, d_h}, (AVRational){p->w, p->h});
+ p->p_w = ds.num;
+ p->p_h = ds.den;
+}
+
+char *mp_image_params_to_str_buf(char *b, size_t bs,
+ const struct mp_image_params *p)
+{
+ if (p && p->imgfmt) {
+ snprintf(b, bs, "%dx%d", p->w, p->h);
+ if (p->p_w != p->p_h || !p->p_w)
+ mp_snprintf_cat(b, bs, " [%d:%d]", p->p_w, p->p_h);
+ mp_snprintf_cat(b, bs, " %s", mp_imgfmt_to_name(p->imgfmt));
+ if (p->hw_subfmt)
+ mp_snprintf_cat(b, bs, "[%s]", mp_imgfmt_to_name(p->hw_subfmt));
+ mp_snprintf_cat(b, bs, " %s/%s/%s/%s/%s",
+ m_opt_choice_str(mp_csp_names, p->color.space),
+ m_opt_choice_str(mp_csp_prim_names, p->color.primaries),
+ m_opt_choice_str(mp_csp_trc_names, p->color.gamma),
+ m_opt_choice_str(mp_csp_levels_names, p->color.levels),
+ m_opt_choice_str(mp_csp_light_names, p->color.light));
+ mp_snprintf_cat(b, bs, " CL=%s",
+ m_opt_choice_str(mp_chroma_names, p->chroma_location));
+ if (mp_image_crop_valid(p)) {
+ mp_snprintf_cat(b, bs, " crop=%dx%d+%d+%d", mp_rect_w(p->crop),
+ mp_rect_h(p->crop), p->crop.x0, p->crop.y0);
+ }
+ if (p->rotate)
+ mp_snprintf_cat(b, bs, " rot=%d", p->rotate);
+ if (p->stereo3d > 0) {
+ mp_snprintf_cat(b, bs, " stereo=%s",
+ MP_STEREO3D_NAME_DEF(p->stereo3d, "?"));
+ }
+ if (p->alpha) {
+ mp_snprintf_cat(b, bs, " A=%s",
+ m_opt_choice_str(mp_alpha_names, p->alpha));
+ }
+ } else {
+ snprintf(b, bs, "???");
+ }
+ return b;
+}
+
+// Return whether the image parameters are valid.
+// Some non-essential fields are allowed to be unset (like colorspace flags).
+bool mp_image_params_valid(const struct mp_image_params *p)
+{
+ // av_image_check_size has similar checks and triggers around 16000*16000
+ // It's mostly needed to deal with the fact that offsets are sometimes
+ // ints. We also should (for now) do the same as FFmpeg, to be sure large
+ // images don't crash with libswscale or when wrapping with AVFrame and
+ // passing the result to filters.
+ if (p->w <= 0 || p->h <= 0 || (p->w + 128LL) * (p->h + 128LL) >= INT_MAX / 8)
+ return false;
+
+ if (p->p_w < 0 || p->p_h < 0)
+ return false;
+
+ if (p->rotate < 0 || p->rotate >= 360)
+ return false;
+
+ struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(p->imgfmt);
+ if (!desc.id)
+ return false;
+
+ if (p->hw_subfmt && !(desc.flags & MP_IMGFLAG_HWACCEL))
+ return false;
+
+ return true;
+}
+
+bool mp_image_params_equal(const struct mp_image_params *p1,
+ const struct mp_image_params *p2)
+{
+ return p1->imgfmt == p2->imgfmt &&
+ p1->hw_subfmt == p2->hw_subfmt &&
+ p1->w == p2->w && p1->h == p2->h &&
+ p1->p_w == p2->p_w && p1->p_h == p2->p_h &&
+ p1->force_window == p2->force_window &&
+ mp_colorspace_equal(p1->color, p2->color) &&
+ p1->chroma_location == p2->chroma_location &&
+ p1->rotate == p2->rotate &&
+ p1->stereo3d == p2->stereo3d &&
+ p1->alpha == p2->alpha &&
+ mp_rect_equals(&p1->crop, &p2->crop);
+}
+
+// Set most image parameters, but not image format or size.
+// Display size is used to set the PAR.
+void mp_image_set_attributes(struct mp_image *image,
+ const struct mp_image_params *params)
+{
+ struct mp_image_params nparams = *params;
+ nparams.imgfmt = image->imgfmt;
+ nparams.w = image->w;
+ nparams.h = image->h;
+ if (nparams.imgfmt != params->imgfmt)
+ nparams.color = (struct mp_colorspace){0};
+ mp_image_set_params(image, &nparams);
+}
+
+static enum mp_csp_levels infer_levels(enum mp_imgfmt imgfmt)
+{
+ switch (imgfmt2pixfmt(imgfmt)) {
+ case AV_PIX_FMT_YUVJ420P:
+ case AV_PIX_FMT_YUVJ411P:
+ case AV_PIX_FMT_YUVJ422P:
+ case AV_PIX_FMT_YUVJ444P:
+ case AV_PIX_FMT_YUVJ440P:
+ case AV_PIX_FMT_GRAY8:
+ case AV_PIX_FMT_YA8:
+ case AV_PIX_FMT_GRAY9LE:
+ case AV_PIX_FMT_GRAY9BE:
+ case AV_PIX_FMT_GRAY10LE:
+ case AV_PIX_FMT_GRAY10BE:
+ case AV_PIX_FMT_GRAY12LE:
+ case AV_PIX_FMT_GRAY12BE:
+ case AV_PIX_FMT_GRAY14LE:
+ case AV_PIX_FMT_GRAY14BE:
+ case AV_PIX_FMT_GRAY16LE:
+ case AV_PIX_FMT_GRAY16BE:
+ case AV_PIX_FMT_YA16BE:
+ case AV_PIX_FMT_YA16LE:
+ return MP_CSP_LEVELS_PC;
+ default:
+ return MP_CSP_LEVELS_TV;
+ }
+}
+
+// If details like params->colorspace/colorlevels are missing, guess them from
+// the other settings. Also, even if they are set, make them consistent with
+// the colorspace as implied by the pixel format.
+void mp_image_params_guess_csp(struct mp_image_params *params)
+{
+ enum mp_csp forced_csp = mp_image_params_get_forced_csp(params);
+ if (forced_csp == MP_CSP_AUTO) { // YUV/other
+ if (params->color.space != MP_CSP_BT_601 &&
+ params->color.space != MP_CSP_BT_709 &&
+ params->color.space != MP_CSP_BT_2020_NC &&
+ params->color.space != MP_CSP_BT_2020_C &&
+ params->color.space != MP_CSP_SMPTE_240M &&
+ params->color.space != MP_CSP_YCGCO)
+ {
+ // Makes no sense, so guess instead
+ // YCGCO should be separate, but libavcodec disagrees
+ params->color.space = MP_CSP_AUTO;
+ }
+ if (params->color.space == MP_CSP_AUTO)
+ params->color.space = mp_csp_guess_colorspace(params->w, params->h);
+ if (params->color.levels == MP_CSP_LEVELS_AUTO) {
+ if (params->color.gamma == MP_CSP_TRC_V_LOG) {
+ params->color.levels = MP_CSP_LEVELS_PC;
+ } else {
+ params->color.levels = infer_levels(params->imgfmt);
+ }
+ }
+ if (params->color.primaries == MP_CSP_PRIM_AUTO) {
+ // Guess based on the colormatrix as a first priority
+ if (params->color.space == MP_CSP_BT_2020_NC ||
+ params->color.space == MP_CSP_BT_2020_C) {
+ params->color.primaries = MP_CSP_PRIM_BT_2020;
+ } else if (params->color.space == MP_CSP_BT_709) {
+ params->color.primaries = MP_CSP_PRIM_BT_709;
+ } else {
+ // Ambiguous colormatrix for BT.601, guess based on res
+ params->color.primaries = mp_csp_guess_primaries(params->w, params->h);
+ }
+ }
+ if (params->color.gamma == MP_CSP_TRC_AUTO)
+ params->color.gamma = MP_CSP_TRC_BT_1886;
+ } else if (forced_csp == MP_CSP_RGB) {
+ params->color.space = MP_CSP_RGB;
+ params->color.levels = MP_CSP_LEVELS_PC;
+
+ // The majority of RGB content is either sRGB or (rarely) some other
+ // color space which we don't even handle, like AdobeRGB or
+ // ProPhotoRGB. The only reasonable thing we can do is assume it's
+ // sRGB and hope for the best, which should usually just work out fine.
+ // Note: sRGB primaries = BT.709 primaries
+ if (params->color.primaries == MP_CSP_PRIM_AUTO)
+ params->color.primaries = MP_CSP_PRIM_BT_709;
+ if (params->color.gamma == MP_CSP_TRC_AUTO)
+ params->color.gamma = MP_CSP_TRC_SRGB;
+ } else if (forced_csp == MP_CSP_XYZ) {
+ params->color.space = MP_CSP_XYZ;
+ params->color.levels = MP_CSP_LEVELS_PC;
+ // Force gamma to ST428 as this is the only correct for DCDM X'Y'Z'
+ params->color.gamma = MP_CSP_TRC_ST428;
+ // Don't care about primaries, they shouldn't be used, or if anything
+ // MP_CSP_PRIM_ST428 should be defined.
+ } else {
+ // We have no clue.
+ params->color.space = MP_CSP_AUTO;
+ params->color.levels = MP_CSP_LEVELS_AUTO;
+ params->color.primaries = MP_CSP_PRIM_AUTO;
+ params->color.gamma = MP_CSP_TRC_AUTO;
+ }
+
+ if (!params->color.hdr.max_luma) {
+ if (params->color.gamma == MP_CSP_TRC_HLG) {
+ params->color.hdr.max_luma = 1000; // reference display
+ } else {
+ // If the signal peak is unknown, we're forced to pick the TRC's
+ // nominal range as the signal peak to prevent clipping
+ params->color.hdr.max_luma = mp_trc_nom_peak(params->color.gamma) * MP_REF_WHITE;
+ }
+ }
+
+ if (!mp_trc_is_hdr(params->color.gamma)) {
+ // Some clips have leftover HDR metadata after conversion to SDR, so to
+ // avoid blowing up the tone mapping code, strip/sanitize it
+ params->color.hdr = pl_hdr_metadata_empty;
+ }
+
+ if (params->chroma_location == MP_CHROMA_AUTO) {
+ if (params->color.levels == MP_CSP_LEVELS_TV)
+ params->chroma_location = MP_CHROMA_LEFT;
+ if (params->color.levels == MP_CSP_LEVELS_PC)
+ params->chroma_location = MP_CHROMA_CENTER;
+ }
+
+ if (params->color.light == MP_CSP_LIGHT_AUTO) {
+ // HLG is always scene-referred (using its own OOTF), everything else
+ // we assume is display-referred by default.
+ if (params->color.gamma == MP_CSP_TRC_HLG) {
+ params->color.light = MP_CSP_LIGHT_SCENE_HLG;
+ } else {
+ params->color.light = MP_CSP_LIGHT_DISPLAY;
+ }
+ }
+}
+
+// Create a new mp_image reference to av_frame.
+struct mp_image *mp_image_from_av_frame(struct AVFrame *src)
+{
+ struct mp_image *dst = &(struct mp_image){0};
+ AVFrameSideData *sd;
+
+ for (int p = 0; p < MP_MAX_PLANES; p++)
+ dst->bufs[p] = src->buf[p];
+
+ dst->hwctx = src->hw_frames_ctx;
+
+ mp_image_setfmt(dst, pixfmt2imgfmt(src->format));
+ mp_image_set_size(dst, src->width, src->height);
+
+ dst->params.p_w = src->sample_aspect_ratio.num;
+ dst->params.p_h = src->sample_aspect_ratio.den;
+
+ for (int i = 0; i < 4; i++) {
+ dst->planes[i] = src->data[i];
+ dst->stride[i] = src->linesize[i];
+ }
+
+ dst->pict_type = src->pict_type;
+
+ dst->params.crop.x0 = src->crop_left;
+ dst->params.crop.y0 = src->crop_top;
+ dst->params.crop.x1 = src->width - src->crop_right;
+ dst->params.crop.y1 = src->height - src->crop_bottom;
+
+ dst->fields = 0;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
+ if (src->flags & AV_FRAME_FLAG_INTERLACED)
+ dst->fields |= MP_IMGFIELD_INTERLACED;
+ if (src->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST)
+ dst->fields |= MP_IMGFIELD_TOP_FIRST;
+#else
+ if (src->interlaced_frame)
+ dst->fields |= MP_IMGFIELD_INTERLACED;
+ if (src->top_field_first)
+ dst->fields |= MP_IMGFIELD_TOP_FIRST;
+#endif
+ if (src->repeat_pict == 1)
+ dst->fields |= MP_IMGFIELD_REPEAT_FIRST;
+
+ dst->params.color = (struct mp_colorspace){
+ .space = avcol_spc_to_mp_csp(src->colorspace),
+ .levels = avcol_range_to_mp_csp_levels(src->color_range),
+ .primaries = avcol_pri_to_mp_csp_prim(src->color_primaries),
+ .gamma = avcol_trc_to_mp_csp_trc(src->color_trc),
+ };
+
+ dst->params.chroma_location = avchroma_location_to_mp(src->chroma_location);
+
+ if (src->opaque_ref) {
+ struct mp_image_params *p = (void *)src->opaque_ref->data;
+ dst->params.stereo3d = p->stereo3d;
+ // Might be incorrect if colorspace changes.
+ dst->params.color.light = p->color.light;
+ dst->params.alpha = p->alpha;
+ }
+
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_DISPLAYMATRIX);
+ if (sd) {
+ double r = av_display_rotation_get((int32_t *)(sd->data));
+ if (!isnan(r))
+ dst->params.rotate = (((int)(-r) % 360) + 360) % 360;
+ }
+
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_ICC_PROFILE);
+ if (sd)
+ dst->icc_profile = sd->buf;
+
+ AVFrameSideData *mdm = av_frame_get_side_data(src, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
+ AVFrameSideData *clm = av_frame_get_side_data(src, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
+ AVFrameSideData *dhp = av_frame_get_side_data(src, AV_FRAME_DATA_DYNAMIC_HDR_PLUS);
+ pl_map_hdr_metadata(&dst->params.color.hdr, &(struct pl_av_hdr_metadata) {
+ .mdm = (void *)(mdm ? mdm->data : NULL),
+ .clm = (void *)(clm ? clm->data : NULL),
+ .dhp = (void *)(dhp ? dhp->data : NULL),
+ });
+
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_A53_CC);
+ if (sd)
+ dst->a53_cc = sd->buf;
+
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(57, 16, 100)
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_DOVI_METADATA);
+ if (sd)
+ dst->dovi = sd->buf;
+
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_DOVI_RPU_BUFFER);
+ if (sd)
+ dst->dovi_buf = sd->buf;
+#endif
+
+ sd = av_frame_get_side_data(src, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
+ if (sd)
+ dst->film_grain = sd->buf;
+
+ for (int n = 0; n < src->nb_side_data; n++) {
+ sd = src->side_data[n];
+ struct mp_ff_side_data mpsd = {
+ .type = sd->type,
+ .buf = sd->buf,
+ };
+ MP_TARRAY_APPEND(NULL, dst->ff_side_data, dst->num_ff_side_data, mpsd);
+ }
+
+ if (dst->hwctx) {
+ AVHWFramesContext *fctx = (void *)dst->hwctx->data;
+ dst->params.hw_subfmt = pixfmt2imgfmt(fctx->sw_format);
+ }
+
+ struct mp_image *res = mp_image_new_ref(dst);
+
+ // Allocated, but non-refcounted data.
+ talloc_free(dst->ff_side_data);
+
+ return res;
+}
+
+
+// Convert the mp_image reference to a AVFrame reference.
+struct AVFrame *mp_image_to_av_frame(struct mp_image *src)
+{
+ struct mp_image *new_ref = mp_image_new_ref(src);
+ AVFrame *dst = av_frame_alloc();
+ if (!dst || !new_ref) {
+ talloc_free(new_ref);
+ av_frame_free(&dst);
+ return NULL;
+ }
+
+ for (int p = 0; p < MP_MAX_PLANES; p++) {
+ dst->buf[p] = new_ref->bufs[p];
+ new_ref->bufs[p] = NULL;
+ }
+
+ dst->hw_frames_ctx = new_ref->hwctx;
+ new_ref->hwctx = NULL;
+
+ dst->format = imgfmt2pixfmt(src->imgfmt);
+ dst->width = src->w;
+ dst->height = src->h;
+
+ dst->crop_left = src->params.crop.x0;
+ dst->crop_top = src->params.crop.y0;
+ dst->crop_right = dst->width - src->params.crop.x1;
+ dst->crop_bottom = dst->height - src->params.crop.y1;
+
+ dst->sample_aspect_ratio.num = src->params.p_w;
+ dst->sample_aspect_ratio.den = src->params.p_h;
+
+ for (int i = 0; i < 4; i++) {
+ dst->data[i] = src->planes[i];
+ dst->linesize[i] = src->stride[i];
+ }
+ dst->extended_data = dst->data;
+
+ dst->pict_type = src->pict_type;
+#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(58, 7, 100)
+ if (src->fields & MP_IMGFIELD_INTERLACED)
+ dst->flags |= AV_FRAME_FLAG_INTERLACED;
+ if (src->fields & MP_IMGFIELD_TOP_FIRST)
+ dst->flags |= AV_FRAME_FLAG_TOP_FIELD_FIRST;
+#else
+ if (src->fields & MP_IMGFIELD_INTERLACED)
+ dst->interlaced_frame = 1;
+ if (src->fields & MP_IMGFIELD_TOP_FIRST)
+ dst->top_field_first = 1;
+#endif
+ if (src->fields & MP_IMGFIELD_REPEAT_FIRST)
+ dst->repeat_pict = 1;
+
+ dst->colorspace = mp_csp_to_avcol_spc(src->params.color.space);
+ dst->color_range = mp_csp_levels_to_avcol_range(src->params.color.levels);
+ dst->color_primaries =
+ mp_csp_prim_to_avcol_pri(src->params.color.primaries);
+ dst->color_trc = mp_csp_trc_to_avcol_trc(src->params.color.gamma);
+
+ dst->chroma_location = mp_chroma_location_to_av(src->params.chroma_location);
+
+ dst->opaque_ref = av_buffer_alloc(sizeof(struct mp_image_params));
+ MP_HANDLE_OOM(dst->opaque_ref);
+ *(struct mp_image_params *)dst->opaque_ref->data = src->params;
+
+ if (src->icc_profile) {
+ AVFrameSideData *sd =
+ av_frame_new_side_data_from_buf(dst, AV_FRAME_DATA_ICC_PROFILE,
+ new_ref->icc_profile);
+ MP_HANDLE_OOM(sd);
+ new_ref->icc_profile = NULL;
+ }
+
+ pl_avframe_set_color(dst, (struct pl_color_space){
+ .primaries = mp_prim_to_pl(src->params.color.primaries),
+ .transfer = mp_trc_to_pl(src->params.color.gamma),
+ .hdr = src->params.color.hdr,
+ });
+
+ {
+ AVFrameSideData *sd = av_frame_new_side_data(dst,
+ AV_FRAME_DATA_DISPLAYMATRIX,
+ sizeof(int32_t) * 9);
+ MP_HANDLE_OOM(sd);
+ av_display_rotation_set((int32_t *)sd->data, src->params.rotate);
+ }
+
+ // Add back side data, but only for types which are not specially handled
+ // above. Keep in mind that the types above will be out of sync anyway.
+ for (int n = 0; n < new_ref->num_ff_side_data; n++) {
+ struct mp_ff_side_data *mpsd = &new_ref->ff_side_data[n];
+ if (!av_frame_get_side_data(dst, mpsd->type)) {
+ AVFrameSideData *sd = av_frame_new_side_data_from_buf(dst, mpsd->type,
+ mpsd->buf);
+ MP_HANDLE_OOM(sd);
+ mpsd->buf = NULL;
+ }
+ }
+
+ talloc_free(new_ref);
+
+ if (dst->format == AV_PIX_FMT_NONE)
+ av_frame_free(&dst);
+ return dst;
+}
+
+// Same as mp_image_to_av_frame(), but unref img. (It does so even on failure.)
+struct AVFrame *mp_image_to_av_frame_and_unref(struct mp_image *img)
+{
+ AVFrame *frame = mp_image_to_av_frame(img);
+ talloc_free(img);
+ return frame;
+}
+
+void memset_pic(void *dst, int fill, int bytesPerLine, int height, int stride)
+{
+ if (bytesPerLine == stride && height) {
+ memset(dst, fill, stride * (height - 1) + bytesPerLine);
+ } else {
+ for (int i = 0; i < height; i++) {
+ memset(dst, fill, bytesPerLine);
+ dst = (uint8_t *)dst + stride;
+ }
+ }
+}
+
+void memset16_pic(void *dst, int fill, int unitsPerLine, int height, int stride)
+{
+ if (fill == 0) {
+ memset_pic(dst, 0, unitsPerLine * 2, height, stride);
+ } else {
+ for (int i = 0; i < height; i++) {
+ uint16_t *line = dst;
+ uint16_t *end = line + unitsPerLine;
+ while (line < end)
+ *line++ = fill;
+ dst = (uint8_t *)dst + stride;
+ }
+ }
+}
+
+// Pixel at the given luma position on the given plane. x/y always refer to
+// non-subsampled coordinates (even if plane is chroma).
+// The coordinates must be aligned to mp_imgfmt_desc.align_x/y (these are byte
+// and chroma boundaries).
+// You cannot access e.g. individual luma pixels on the luma plane with yuv420p.
+void *mp_image_pixel_ptr(struct mp_image *img, int plane, int x, int y)
+{
+ assert(MP_IS_ALIGNED(x, img->fmt.align_x));
+ assert(MP_IS_ALIGNED(y, img->fmt.align_y));
+ return mp_image_pixel_ptr_ny(img, plane, x, y);
+}
+
+// Like mp_image_pixel_ptr(), but do not require alignment on Y coordinates if
+// the plane does not require it. Use with care.
+// Useful for addressing luma rows.
+void *mp_image_pixel_ptr_ny(struct mp_image *img, int plane, int x, int y)
+{
+ assert(MP_IS_ALIGNED(x, img->fmt.align_x));
+ assert(MP_IS_ALIGNED(y, 1 << img->fmt.ys[plane]));
+ return img->planes[plane] +
+ img->stride[plane] * (ptrdiff_t)(y >> img->fmt.ys[plane]) +
+ (x >> img->fmt.xs[plane]) * (size_t)img->fmt.bpp[plane] / 8;
+}
+
+// Return size of pixels [x0, x0+w-1] in bytes. The coordinates refer to non-
+// subsampled pixels (basically plane 0), and the size is rounded to chroma
+// and byte alignment boundaries for the entire image, even if plane!=0.
+// x0!=0 is useful for rounding (e.g. 8 bpp, x0=7, w=7 => 0..15 => 2 bytes).
+size_t mp_image_plane_bytes(struct mp_image *img, int plane, int x0, int w)
+{
+ int x1 = MP_ALIGN_UP(x0 + w, img->fmt.align_x);
+ x0 = MP_ALIGN_DOWN(x0, img->fmt.align_x);
+ size_t bpp = img->fmt.bpp[plane];
+ int xs = img->fmt.xs[plane];
+ return (x1 >> xs) * bpp / 8 - (x0 >> xs) * bpp / 8;
+}