Coverage Report

Created: 2026-06-25 06:46

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/video/image_writer.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <stdio.h>
19
#include <stdlib.h>
20
#include <string.h>
21
#include <inttypes.h>
22
23
#include <libavcodec/avcodec.h>
24
#include <libavformat/avformat.h>
25
#include <libavutil/mem.h>
26
#include <libavutil/opt.h>
27
#include <libavutil/pixdesc.h>
28
#include <libplacebo/utils/libav.h>
29
30
#include "common/msg.h"
31
#include "config.h"
32
33
#if HAVE_JPEG
34
#include <setjmp.h>
35
#include <jpeglib.h>
36
#endif
37
38
#include "osdep/io.h"
39
#include "misc/path_utils.h"
40
41
#include "common/av_common.h"
42
#include "common/msg.h"
43
#include "image_writer.h"
44
#include "mpv_talloc.h"
45
#include "misc/lavc_compat.h"
46
#include "video/fmt-conversion.h"
47
#include "video/img_format.h"
48
#include "video/mp_image.h"
49
#include "video/sws_utils.h"
50
51
#include "options/m_option.h"
52
53
const struct image_writer_opts image_writer_opts_defaults = {
54
    .format = AV_CODEC_ID_MJPEG,
55
    .high_bit_depth = true,
56
    .png_compression = 7,
57
    .png_filter = 5,
58
    .jpeg_quality = 90,
59
    .jpeg_source_chroma = true,
60
    .webp_quality = 75,
61
    .webp_compression = 4,
62
    .jxl_distance = 1.0,
63
    .jxl_effort = 4,
64
    .avif_encoder = "libaom-av1",
65
    .avif_opts = (char*[]){
66
        "usage",    "allintra",
67
        "crf",      "0",
68
        "cpu-used", "8",
69
        NULL
70
    },
71
    .tag_csp = true,
72
};
73
74
const struct m_opt_choice_alternatives mp_image_writer_formats[] = {
75
    {"jpg",  AV_CODEC_ID_MJPEG},
76
    {"jpeg", AV_CODEC_ID_MJPEG},
77
    {"png",  AV_CODEC_ID_PNG},
78
    {"webp", AV_CODEC_ID_WEBP},
79
    {"jxl",  AV_CODEC_ID_JPEGXL},
80
    {"avif",  AV_CODEC_ID_AV1},
81
    {0}
82
};
83
84
#define OPT_BASE_STRUCT struct image_writer_opts
85
86
const struct m_option image_writer_opts[] = {
87
    {"format", OPT_CHOICE_C(format, mp_image_writer_formats)},
88
    {"jpeg-quality", OPT_INT(jpeg_quality), M_RANGE(0, 100)},
89
    {"jpeg-source-chroma", OPT_BOOL(jpeg_source_chroma)},
90
    {"png-compression", OPT_INT(png_compression), M_RANGE(0, 9)},
91
    {"png-filter", OPT_INT(png_filter), M_RANGE(0, 5)},
92
    {"webp-lossless", OPT_BOOL(webp_lossless)},
93
    {"webp-quality", OPT_INT(webp_quality), M_RANGE(0, 100)},
94
    {"webp-compression", OPT_INT(webp_compression), M_RANGE(0, 6)},
95
    {"jxl-distance", OPT_DOUBLE(jxl_distance), M_RANGE(0.0, 15.0)},
96
    {"jxl-effort", OPT_INT(jxl_effort), M_RANGE(1, 9)},
97
    {"avif-encoder", OPT_STRING(avif_encoder)},
98
    {"avif-opts", OPT_KEYVALUELIST(avif_opts)},
99
    {"avif-pixfmt", OPT_STRING(avif_pixfmt)},
100
    {"high-bit-depth", OPT_BOOL(high_bit_depth)},
101
    {"tag-colorspace", OPT_BOOL(tag_csp)},
102
    {0},
103
};
104
105
struct image_writer_ctx {
106
    struct mp_log *log;
107
    const struct image_writer_opts *opts;
108
    struct mp_imgfmt_desc original_format;
109
};
110
111
static enum AVPixelFormat replace_j_format(enum AVPixelFormat fmt)
112
0
{
113
0
    switch (fmt) {
114
0
    case AV_PIX_FMT_YUV420P: return AV_PIX_FMT_YUVJ420P;
115
0
    case AV_PIX_FMT_YUV422P: return AV_PIX_FMT_YUVJ422P;
116
0
    case AV_PIX_FMT_YUV444P: return AV_PIX_FMT_YUVJ444P;
117
0
    }
118
0
    return fmt;
119
0
}
120
121
static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx,
122
                            mp_image_t *image, bool tag_csp,
123
                            struct mp_log *log)
124
0
{
125
0
    for (int n = 0; n < 4; n++) {
126
0
        pic->data[n] = image->planes[n];
127
0
        pic->linesize[n] = image->stride[n];
128
0
    }
129
0
    pic->format = avctx->pix_fmt;
130
0
    pic->width = avctx->width;
131
0
    pic->height = avctx->height;
132
0
    pl_avframe_set_repr(pic, image->params.repr);
133
0
    avctx->colorspace = pic->colorspace;
134
0
    avctx->color_range = pic->color_range;
135
136
0
    if (!tag_csp)
137
0
        return;
138
0
    pl_avframe_set_color(pic, image->params.color);
139
0
    avctx->color_primaries = pic->color_primaries;
140
0
    avctx->color_trc = pic->color_trc;
141
0
    avctx->chroma_sample_location = pic->chroma_location =
142
0
        pl_chroma_to_av(image->params.chroma_location);
143
144
0
    mp_dbg(log, "mapped color params:\n"
145
0
        "  trc = %s\n"
146
0
        "  primaries = %s\n"
147
0
        "  range = %s\n"
148
0
        "  colorspace = %s\n"
149
0
        "  chroma_location = %s\n",
150
0
        av_color_transfer_name(avctx->color_trc),
151
0
        av_color_primaries_name(avctx->color_primaries),
152
0
        av_color_range_name(avctx->color_range),
153
0
        av_color_space_name(avctx->colorspace),
154
0
        av_chroma_location_name(avctx->chroma_sample_location)
155
0
    );
156
0
}
157
158
static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
159
21
{
160
21
    bool success = false;
161
21
    AVFrame *pic = NULL;
162
21
    AVPacket *pkt = NULL;
163
164
21
    const AVCodec *codec;
165
21
    if (ctx->opts->format == AV_CODEC_ID_WEBP) {
166
0
        codec = avcodec_find_encoder_by_name("libwebp"); // non-animated encoder
167
21
    } else {
168
21
        codec = avcodec_find_encoder(ctx->opts->format);
169
21
    }
170
171
21
    AVCodecContext *avctx = NULL;
172
21
    if (!codec)
173
21
        goto print_open_fail;
174
0
    avctx = avcodec_alloc_context3(codec);
175
0
    if (!avctx)
176
0
        goto print_open_fail;
177
178
0
    MP_VERBOSE(ctx, "Using encoder %s\n", codec->name);
179
180
0
    avctx->time_base = AV_TIME_BASE_Q;
181
0
    avctx->width = image->w;
182
0
    avctx->height = image->h;
183
0
    avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt);
184
185
    /*
186
     * tagging avctx->bits_per_raw_sample indicates the number of significant
187
     * bits. For example, if the original video was 10-bit, and the GPU buffer is
188
     * 16-bit, this tells lavc that only 10 bits are significant. lavc encoders may
189
     * ignore this value, but some codecs can make use of it (for example, PNG's
190
     * sBIT chunk or JXL's bit depth header)
191
     *
192
     * This only works when screenshot-sw=yes is set. With hardware screenshots,
193
     * the "original" is whatever is in the GPU buffer, which is likely to be at
194
     * full bit depth already.
195
     */
196
0
    if (image->params.repr.bits.color_depth != image->params.repr.bits.sample_depth) {
197
0
        MP_DBG(ctx, "tagging bits_per_raw_sample=%d\n", image->params.repr.bits.color_depth);
198
0
        avctx->bits_per_raw_sample = image->params.repr.bits.color_depth;
199
0
    }
200
201
0
    if (codec->id == AV_CODEC_ID_MJPEG) {
202
        // Annoying deprecated garbage for the jpg encoder.
203
0
        if (image->params.repr.levels == PL_COLOR_LEVELS_FULL)
204
0
            avctx->pix_fmt = replace_j_format(avctx->pix_fmt);
205
0
    }
206
0
    if (avctx->pix_fmt == AV_PIX_FMT_NONE) {
207
0
        MP_ERR(ctx, "Image format %s not supported by lavc.\n",
208
0
               mp_imgfmt_to_name(image->imgfmt));
209
0
        goto error_exit;
210
0
    }
211
212
0
    if (codec->id == AV_CODEC_ID_MJPEG) {
213
0
        avctx->flags |= AV_CODEC_FLAG_QSCALE;
214
        // jpeg_quality is set below
215
0
    } else if (codec->id == AV_CODEC_ID_PNG) {
216
0
        avctx->compression_level = ctx->opts->png_compression;
217
0
        av_opt_set_int(avctx, "pred", ctx->opts->png_filter,
218
0
                       AV_OPT_SEARCH_CHILDREN);
219
0
    } else if (codec->id == AV_CODEC_ID_WEBP) {
220
0
        avctx->compression_level = ctx->opts->webp_compression;
221
0
        av_opt_set_int(avctx, "lossless", ctx->opts->webp_lossless,
222
0
                       AV_OPT_SEARCH_CHILDREN);
223
0
        av_opt_set_int(avctx, "quality", ctx->opts->webp_quality,
224
0
                       AV_OPT_SEARCH_CHILDREN);
225
0
    } else if (codec->id == AV_CODEC_ID_JPEGXL) {
226
0
        av_opt_set_double(avctx, "distance", ctx->opts->jxl_distance,
227
0
                          AV_OPT_SEARCH_CHILDREN);
228
0
        av_opt_set_int(avctx, "effort", ctx->opts->jxl_effort,
229
0
                       AV_OPT_SEARCH_CHILDREN);
230
0
    }
231
232
0
    if (avcodec_open2(avctx, codec, NULL) < 0) {
233
21
    print_open_fail:
234
21
        MP_ERR(ctx, "Could not open libavcodec encoder for saving images\n");
235
21
        goto error_exit;
236
0
    }
237
238
0
    pic = av_frame_alloc();
239
0
    if (!pic)
240
0
        goto error_exit;
241
0
    prepare_avframe(pic, avctx, image, ctx->opts->tag_csp, ctx->log);
242
0
    if (codec->id == AV_CODEC_ID_MJPEG) {
243
0
        int qscale = 1 + (100 - ctx->opts->jpeg_quality) * 30 / 100;
244
0
        pic->quality = qscale * FF_QP2LAMBDA;
245
0
    }
246
247
0
    int ret = avcodec_send_frame(avctx, pic);
248
0
    if (ret < 0)
249
0
        goto error_exit;
250
0
    ret = avcodec_send_frame(avctx, NULL); // send EOF
251
0
    if (ret < 0)
252
0
        goto error_exit;
253
0
    pkt = av_packet_alloc();
254
0
    if (!pkt)
255
0
        goto error_exit;
256
0
    ret = avcodec_receive_packet(avctx, pkt);
257
0
    if (ret < 0)
258
0
        goto error_exit;
259
260
0
    success = fwrite(pkt->data, pkt->size, 1, fp) == 1;
261
262
21
error_exit:
263
21
    avcodec_free_context(&avctx);
264
21
    av_frame_free(&pic);
265
21
    av_packet_free(&pkt);
266
21
    return success;
267
0
}
268
269
#if HAVE_JPEG
270
271
static void write_jpeg_error_exit(j_common_ptr cinfo)
272
{
273
    // NOTE: do not write error message, too much effort to connect the libjpeg
274
    //       log callbacks with mplayer's log function mp_msp()
275
276
    // Return control to the setjmp point
277
    longjmp(*(jmp_buf*)cinfo->client_data, 1);
278
}
279
280
static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
281
{
282
    struct jpeg_compress_struct cinfo;
283
    struct jpeg_error_mgr jerr;
284
285
    cinfo.err = jpeg_std_error(&jerr);
286
    jerr.error_exit = write_jpeg_error_exit;
287
288
    jmp_buf error_return_jmpbuf;
289
    cinfo.client_data = &error_return_jmpbuf;
290
    if (setjmp(cinfo.client_data)) {
291
        jpeg_destroy_compress(&cinfo);
292
        return false;
293
    }
294
295
    jpeg_create_compress(&cinfo);
296
    jpeg_stdio_dest(&cinfo, fp);
297
298
    cinfo.image_width = image->w;
299
    cinfo.image_height = image->h;
300
    cinfo.input_components = 3;
301
    cinfo.in_color_space = JCS_RGB;
302
303
    cinfo.write_JFIF_header = TRUE;
304
    cinfo.JFIF_major_version = 1;
305
    cinfo.JFIF_minor_version = 2;
306
307
    jpeg_set_defaults(&cinfo);
308
    jpeg_set_quality(&cinfo, ctx->opts->jpeg_quality, 0);
309
310
    if (ctx->opts->jpeg_source_chroma) {
311
        cinfo.comp_info[0].h_samp_factor = 1 << ctx->original_format.chroma_xs;
312
        cinfo.comp_info[0].v_samp_factor = 1 << ctx->original_format.chroma_ys;
313
    }
314
315
    jpeg_start_compress(&cinfo, TRUE);
316
317
    while (cinfo.next_scanline < cinfo.image_height) {
318
        JSAMPROW row_pointer[1];
319
        row_pointer[0] = image->planes[0] +
320
                         (ptrdiff_t)cinfo.next_scanline * image->stride[0];
321
        jpeg_write_scanlines(&cinfo, row_pointer, 1);
322
    }
323
324
    jpeg_finish_compress(&cinfo);
325
326
    jpeg_destroy_compress(&cinfo);
327
328
    return true;
329
}
330
331
#endif
332
333
static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data,
334
                          size_t size)
335
0
{
336
0
    if (!mp_msg_test(ctx->log, MSGL_DEBUG))
337
0
        return;
338
0
    char dbgbuff[129];
339
0
    if (size)
340
0
        MP_DBG(ctx, "write_avif() packet side data:\n");
341
0
    for (int i = 0; i < size; i++) {
342
0
        AVPacketSideData *sd = &data[i];
343
0
        for (int k = 0; k < MPMIN(sd->size, 64); k++)
344
0
            snprintf(dbgbuff + k*2, 3, "%02x", (int)sd->data[k]);
345
0
        MP_DBG(ctx, "  [%d] = {[%s], '%s'}\n",
346
0
               i, av_packet_side_data_name(sd->type), dbgbuff);
347
0
    }
348
0
}
349
350
static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
351
0
{
352
0
    const AVCodec *codec = NULL;
353
0
    const AVOutputFormat *ofmt = NULL;
354
0
    AVCodecContext *avctx = NULL;
355
0
    AVIOContext *avioctx = NULL;
356
0
    AVFormatContext *fmtctx = NULL;
357
0
    AVStream *stream = NULL;
358
0
    AVFrame *pic = NULL;
359
0
    AVPacket *pkt = NULL;
360
0
    int ret;
361
0
    bool success = false;
362
363
0
    codec = avcodec_find_encoder_by_name(ctx->opts->avif_encoder);
364
0
    if (!codec) {
365
0
        MP_ERR(ctx, "Could not find encoder '%s', for saving images\n",
366
0
               ctx->opts->avif_encoder);
367
0
        goto free_data;
368
0
    }
369
370
0
    ofmt = av_guess_format("avif", NULL, NULL);
371
0
    if (!ofmt) {
372
0
        MP_ERR(ctx, "Could not guess output format 'avif'\n");
373
0
        goto free_data;
374
0
    }
375
376
0
    avctx = avcodec_alloc_context3(codec);
377
0
    if (!avctx) {
378
0
        MP_ERR(ctx, "Failed to allocate AVContext.\n");
379
0
        goto free_data;
380
0
    }
381
382
0
    avctx->width = image->w;
383
0
    avctx->height = image->h;
384
0
    avctx->time_base = (AVRational){1, 30};
385
0
    avctx->pkt_timebase = (AVRational){1, 30};
386
0
    avctx->codec_type = AVMEDIA_TYPE_VIDEO;
387
0
    avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt);
388
0
    if (avctx->pix_fmt == AV_PIX_FMT_NONE) {
389
0
        MP_ERR(ctx, "Image format %s not supported by lavc.\n",
390
0
               mp_imgfmt_to_name(image->imgfmt));
391
0
        goto free_data;
392
0
    }
393
394
0
    av_opt_set_int(avctx, "still-picture", 1, AV_OPT_SEARCH_CHILDREN);
395
396
0
    AVDictionary *avd = NULL;
397
0
    mp_set_avdict(&avd, ctx->opts->avif_opts);
398
0
    av_opt_set_dict2(avctx, &avd, AV_OPT_SEARCH_CHILDREN);
399
0
    av_dict_free(&avd);
400
401
0
    pic = av_frame_alloc();
402
0
    if (!pic) {
403
0
        MP_ERR(ctx, "Could not allocate AVFrame\n");
404
0
        goto free_data;
405
0
    }
406
407
0
    prepare_avframe(pic, avctx, image, ctx->opts->tag_csp, ctx->log);
408
    // Not setting this flag caused ffmpeg to output avif that was not passing
409
    // standard checks but ffmpeg would still read and not complain...
410
0
    avctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
411
412
0
    ret = avcodec_open2(avctx, codec, NULL);
413
0
    if (ret < 0) {
414
0
        MP_ERR(ctx, "Could not open libavcodec encoder for saving images\n");
415
0
        goto free_data;
416
0
    }
417
418
0
    avio_open_dyn_buf(&avioctx);
419
0
    MP_HANDLE_OOM(avioctx);
420
421
0
    fmtctx = avformat_alloc_context();
422
0
    if (!fmtctx) {
423
0
        MP_ERR(ctx, "Could not allocate format context\n");
424
0
        goto free_data;
425
0
    }
426
0
    fmtctx->pb = avioctx;
427
0
    fmtctx->oformat = ofmt;
428
429
0
    stream = avformat_new_stream(fmtctx, codec);
430
0
    if (!stream) {
431
0
        MP_ERR(ctx, "Could not allocate stream\n");
432
0
        goto free_data;
433
0
    }
434
435
0
    ret = avcodec_parameters_from_context(stream->codecpar, avctx);
436
0
    if (ret < 0) {
437
0
        MP_ERR(ctx, "Could not copy parameters from context\n");
438
0
        goto free_data;
439
0
    }
440
441
0
    ret = avformat_init_output(fmtctx, NULL);
442
0
    if (ret < 0) {
443
0
        MP_ERR(ctx, "Could not initialize output\n");
444
0
        goto free_data;
445
0
    }
446
447
0
    ret = avformat_write_header(fmtctx, NULL);
448
0
    if (ret < 0) {
449
0
        MP_ERR(ctx, "Could not write format header\n");
450
0
        goto free_data;
451
0
    }
452
453
0
    pkt = av_packet_alloc();
454
0
    if (!pkt) {
455
0
        MP_ERR(ctx, "Could not allocate packet\n");
456
0
        goto free_data;
457
0
    }
458
459
0
    ret = avcodec_send_frame(avctx, pic);
460
0
    if (ret < 0) {
461
0
        MP_ERR(ctx, "Error sending frame\n");
462
0
        goto free_data;
463
0
    }
464
0
    ret = avcodec_send_frame(avctx, NULL); // send EOF
465
0
    if (ret < 0)
466
0
        goto free_data;
467
468
0
    int pts = 0;
469
0
    log_side_data(ctx, avctx->coded_side_data, avctx->nb_coded_side_data);
470
0
    while (ret >= 0) {
471
0
        ret = avcodec_receive_packet(avctx, pkt);
472
0
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
473
0
            break;
474
0
        if (ret < 0) {
475
0
            MP_ERR(ctx, "Error receiving packet\n");
476
0
            goto free_data;
477
0
        }
478
0
        pkt->dts = pkt->pts = ++pts;
479
0
        pkt->stream_index = stream->index;
480
0
        log_side_data(ctx, pkt->side_data, pkt->side_data_elems);
481
482
0
        ret = av_write_frame(fmtctx, pkt);
483
0
        if (ret < 0) {
484
0
            MP_ERR(ctx, "Error writing frame\n");
485
0
            goto free_data;
486
0
        }
487
0
        av_packet_unref(pkt);
488
0
    }
489
490
0
    ret = av_write_trailer(fmtctx);
491
0
    if (ret < 0) {
492
0
        MP_ERR(ctx, "Could not write trailer\n");
493
0
        goto free_data;
494
0
    }
495
0
    MP_DBG(ctx, "write_avif(): avio_size() = %"PRIi64"\n", avio_size(avioctx));
496
497
0
    uint8_t *buf = NULL;
498
0
    int written_size = avio_close_dyn_buf(avioctx, &buf);
499
0
    success = fwrite(buf, written_size, 1, fp) == 1;
500
0
    av_freep(&buf);
501
502
0
free_data:
503
0
    avformat_free_context(fmtctx);
504
0
    avcodec_free_context(&avctx);
505
0
    av_packet_free(&pkt);
506
0
    av_frame_free(&pic);
507
508
0
    return success;
509
0
}
510
511
static int get_encoder_format(const AVCodec *codec, int srcfmt, bool highdepth)
512
0
{
513
0
    const enum AVPixelFormat *pix_fmts;
514
0
    int ret = mp_avcodec_get_supported_config(NULL, codec,
515
0
                                              AV_CODEC_CONFIG_PIX_FORMAT,
516
0
                                              (const void **)&pix_fmts);
517
0
    int current = 0;
518
0
    for (int n = 0; ret >= 0 && pix_fmts && pix_fmts[n] != AV_PIX_FMT_NONE; n++) {
519
0
        int fmt = pixfmt2imgfmt(pix_fmts[n]);
520
0
        if (!fmt)
521
0
            continue;
522
0
        if (!highdepth) {
523
            // Ignore formats larger than 8 bit per pixel. (Or which are unknown.)
524
0
            struct mp_regular_imgfmt rdesc;
525
0
            if (!mp_get_regular_imgfmt(&rdesc, fmt)) {
526
0
                int ofmt = mp_find_other_endian(fmt);
527
0
                if (!mp_get_regular_imgfmt(&rdesc, ofmt))
528
0
                    continue;
529
0
            }
530
0
            if (rdesc.component_size > 1)
531
0
                continue;
532
0
        }
533
0
        current = current ? mp_imgfmt_select_best(current, fmt, srcfmt) : fmt;
534
0
    }
535
0
    return current;
536
0
}
537
538
static int get_target_format(struct image_writer_ctx *ctx)
539
21
{
540
21
    const AVCodec *codec = avcodec_find_encoder(ctx->opts->format);
541
21
    if (!codec)
542
21
        goto unknown;
543
544
0
    int srcfmt = ctx->original_format.id;
545
546
0
    int target = get_encoder_format(codec, srcfmt, ctx->opts->high_bit_depth);
547
0
    if (!target) {
548
0
        mp_dbg(ctx->log, "Falling back to high-depth format.\n");
549
0
        target = get_encoder_format(codec, srcfmt, true);
550
0
    }
551
552
0
    if (!target)
553
0
        goto unknown;
554
555
0
    return target;
556
557
21
unknown:
558
21
    return IMGFMT_RGB0;
559
0
}
560
561
const char *image_writer_file_ext(const struct image_writer_opts *opts)
562
21
{
563
21
    struct image_writer_opts defs = image_writer_opts_defaults;
564
565
21
    if (!opts)
566
0
        opts = &defs;
567
568
21
    return m_opt_choice_str(mp_image_writer_formats, opts->format);
569
21
}
570
571
bool image_writer_high_depth(const struct image_writer_opts *opts)
572
0
{
573
0
    return opts->format == AV_CODEC_ID_PNG
574
0
           || opts->format == AV_CODEC_ID_JPEGXL
575
0
           || opts->format == AV_CODEC_ID_AV1;
576
0
}
577
578
bool image_writer_flexible_csp(const struct image_writer_opts *opts)
579
21
{
580
21
    if (!opts->tag_csp)
581
0
        return false;
582
21
    return opts->format == AV_CODEC_ID_JPEGXL
583
21
        || opts->format == AV_CODEC_ID_AV1
584
21
        || opts->format == AV_CODEC_ID_PNG;
585
21
}
586
587
int image_writer_format_from_ext(const char *ext)
588
0
{
589
0
    for (int n = 0; mp_image_writer_formats[n].name; n++) {
590
0
        if (ext && strcmp(mp_image_writer_formats[n].name, ext) == 0)
591
0
            return mp_image_writer_formats[n].value;
592
0
    }
593
0
    return 0;
594
0
}
595
596
static struct mp_image *convert_image(struct mp_image *image, int destfmt,
597
                                      enum pl_color_levels yuv_levels,
598
                                      const struct image_writer_opts *opts,
599
                                      struct mpv_global *global,
600
                                      struct mp_log *log)
601
21
{
602
21
    int d_w, d_h;
603
21
    mp_image_params_get_dsize(&image->params, &d_w, &d_h);
604
605
21
    struct mp_image_params p = {
606
21
        .imgfmt = destfmt,
607
21
        .w = d_w,
608
21
        .h = d_h,
609
21
        .p_w = 1,
610
21
        .p_h = 1,
611
21
        .color = image->params.color,
612
21
        .repr = image->params.repr,
613
21
        .chroma_location = image->params.chroma_location,
614
21
        .crop = {0, 0, d_w, d_h},
615
21
    };
616
21
    mp_image_params_guess_csp(&p);
617
618
21
    if (!image_writer_flexible_csp(opts)) {
619
        // If our format can't tag csps, set something sane
620
21
        p.color.primaries = PL_COLOR_PRIM_BT_709;
621
21
        p.color.transfer = PL_COLOR_TRC_UNKNOWN;
622
21
        p.light = MP_CSP_LIGHT_DISPLAY;
623
21
        p.color.hdr = (struct pl_hdr_metadata){0};
624
21
        if (p.repr.sys != PL_COLOR_SYSTEM_RGB) {
625
0
            p.repr.levels = yuv_levels;
626
0
            p.repr.sys = PL_COLOR_SYSTEM_BT_601;
627
0
            p.chroma_location = PL_CHROMA_CENTER;
628
0
        }
629
21
        mp_image_params_guess_csp(&p);
630
21
    }
631
632
21
    if (mp_image_params_equal(&p, &image->params))
633
0
        return mp_image_new_ref(image);
634
635
21
    mp_verbose(log, "converted: %s\n", mp_image_params_to_str(&p));
636
637
21
    struct mp_image *src = image;
638
21
    if (mp_image_crop_valid(&src->params) &&
639
21
        (mp_rect_w(src->params.crop) != src->w ||
640
21
         mp_rect_h(src->params.crop) != src->h))
641
0
    {
642
0
        src = mp_image_new_ref(src);
643
0
        if (!src) {
644
0
            mp_err(log, "mp_image_new_ref failed!\n");
645
0
            return NULL;
646
0
        }
647
0
        mp_image_crop_rc(src, src->params.crop);
648
0
    }
649
650
21
    struct mp_image *dst = mp_image_alloc(p.imgfmt, p.w, p.h);
651
21
    if (!dst) {
652
0
        mp_err(log, "Out of memory.\n");
653
0
        return NULL;
654
0
    }
655
21
    mp_image_copy_attributes(dst, src);
656
657
21
    dst->params = p;
658
659
21
    struct mp_sws_context *sws = mp_sws_alloc(NULL);
660
21
    sws->log = log;
661
21
    if (global)
662
21
        mp_sws_enable_cmdline_opts(sws, global);
663
21
    bool ok = mp_sws_scale(sws, dst, src) >= 0;
664
21
    talloc_free(sws);
665
666
21
    if (src != image)
667
0
        talloc_free(src);
668
669
21
    if (!ok) {
670
0
        mp_err(log, "Error when converting image.\n");
671
0
        talloc_free(dst);
672
0
        return NULL;
673
0
    }
674
675
21
    return dst;
676
21
}
677
678
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
679
                 const char *filename, struct mpv_global *global,
680
                 struct mp_log *log, bool overwrite)
681
21
{
682
21
    struct image_writer_opts defs = image_writer_opts_defaults;
683
21
    if (!opts)
684
0
        opts = &defs;
685
686
21
    mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params));
687
688
21
    struct image_writer_ctx ctx = { log, opts, image->fmt };
689
21
    bool (*write)(struct image_writer_ctx *, mp_image_t *, FILE *) = write_lavc;
690
21
    int destfmt = 0;
691
692
#if HAVE_JPEG
693
    if (opts->format == AV_CODEC_ID_MJPEG) {
694
        write = write_jpeg;
695
        destfmt = IMGFMT_RGB24;
696
    }
697
#endif
698
21
    if (opts->format == AV_CODEC_ID_AV1) {
699
0
        write = write_avif;
700
0
        if (opts->avif_pixfmt && opts->avif_pixfmt[0])
701
0
            destfmt = mp_imgfmt_from_name(bstr0(opts->avif_pixfmt));
702
21
    } else if (opts->format == AV_CODEC_ID_WEBP && !opts->webp_lossless) {
703
        // For lossy images, libwebp has its own RGB->YUV conversion.
704
        // We don't want that, so force YUV/YUVA here.
705
0
        int alpha = image->fmt.flags & MP_IMGFLAG_ALPHA;
706
0
        destfmt = alpha ? pixfmt2imgfmt(AV_PIX_FMT_YUVA420P) : IMGFMT_420P;
707
0
    }
708
709
21
    if (!destfmt)
710
21
        destfmt = get_target_format(&ctx);
711
712
21
    enum pl_color_levels levels; // Ignored if destfmt is a RGB format
713
21
    if (opts->format == AV_CODEC_ID_WEBP) {
714
0
        levels = PL_COLOR_LEVELS_LIMITED;
715
21
    } else {
716
21
        levels = PL_COLOR_LEVELS_FULL;
717
21
    }
718
719
21
    struct mp_image *dst = convert_image(image, destfmt, levels, opts, global, log);
720
21
    if (!dst)
721
0
        return false;
722
723
21
    bool success = false;
724
21
    FILE *fp = fopen(filename, overwrite ? "wb" : "wbx");
725
21
    if (!fp) {
726
0
        mp_err(log, "Error creating '%s' for writing: %s!\n",
727
0
               filename, mp_strerror(errno));
728
0
        goto done;
729
0
    }
730
731
21
    success = write(&ctx, dst, fp);
732
21
    if (fclose(fp) || !success) {
733
21
        mp_err(log, "Error writing file '%s'!\n", filename);
734
21
        unlink(filename);
735
21
    }
736
737
21
done:
738
21
    talloc_free(dst);
739
21
    return success;
740
21
}
741
742
void dump_png(struct mp_image *image, const char *filename, struct mp_log *log)
743
0
{
744
0
    struct image_writer_opts opts = image_writer_opts_defaults;
745
0
    opts.format = AV_CODEC_ID_PNG;
746
0
    write_image(image, &opts, filename, NULL, log, true);
747
0
}