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 
22 #include <libavcodec/avcodec.h>
23 #include <libavutil/mem.h>
24 #include <libavutil/opt.h>
25 
26 #include "config.h"
27 
28 #if HAVE_JPEG
29 #include <setjmp.h>
30 #include <jpeglib.h>
31 #endif
32 
33 #include "osdep/io.h"
34 
35 #include "image_writer.h"
36 #include "mpv_talloc.h"
37 #include "video/img_format.h"
38 #include "video/mp_image.h"
39 #include "video/fmt-conversion.h"
40 #include "video/sws_utils.h"
41 
42 #include "options/m_option.h"
43 
44 const struct image_writer_opts image_writer_opts_defaults = {
45     .format = AV_CODEC_ID_MJPEG,
46     .high_bit_depth = 1,
47     .png_compression = 7,
48     .png_filter = 5,
49     .jpeg_quality = 90,
50     .jpeg_source_chroma = 1,
51     .webp_lossless = 0,
52     .webp_quality = 75,
53     .webp_compression = 4,
54     .tag_csp = 0,
55 };
56 
57 const struct m_opt_choice_alternatives mp_image_writer_formats[] = {
58     {"jpg",  AV_CODEC_ID_MJPEG},
59     {"jpeg", AV_CODEC_ID_MJPEG},
60     {"png",  AV_CODEC_ID_PNG},
61     {"webp", AV_CODEC_ID_WEBP},
62     {0}
63 };
64 
65 #define OPT_BASE_STRUCT struct image_writer_opts
66 
67 const struct m_option image_writer_opts[] = {
68     {"format", OPT_CHOICE_C(format, mp_image_writer_formats)},
69     {"jpeg-quality", OPT_INT(jpeg_quality), M_RANGE(0, 100)},
70     {"jpeg-source-chroma", OPT_FLAG(jpeg_source_chroma)},
71     {"png-compression", OPT_INT(png_compression), M_RANGE(0, 9)},
72     {"png-filter", OPT_INT(png_filter), M_RANGE(0, 5)},
73     {"webp-lossless", OPT_FLAG(webp_lossless)},
74     {"webp-quality", OPT_INT(webp_quality), M_RANGE(0, 100)},
75     {"webp-compression", OPT_INT(webp_compression), M_RANGE(0, 6)},
76     {"high-bit-depth", OPT_FLAG(high_bit_depth)},
77     {"tag-colorspace", OPT_FLAG(tag_csp)},
78     {0},
79 };
80 
81 struct image_writer_ctx {
82     struct mp_log *log;
83     const struct image_writer_opts *opts;
84     struct mp_imgfmt_desc original_format;
85 };
86 
replace_j_format(enum AVPixelFormat fmt)87 static enum AVPixelFormat replace_j_format(enum AVPixelFormat fmt)
88 {
89     switch (fmt) {
90     case AV_PIX_FMT_YUV420P: return AV_PIX_FMT_YUVJ420P;
91     case AV_PIX_FMT_YUV422P: return AV_PIX_FMT_YUVJ422P;
92     case AV_PIX_FMT_YUV444P: return AV_PIX_FMT_YUVJ444P;
93     }
94     return fmt;
95 }
96 
write_lavc(struct image_writer_ctx * ctx,mp_image_t * image,FILE * fp)97 static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
98 {
99     bool success = 0;
100     AVFrame *pic = NULL;
101     AVPacket pkt = {0};
102     int got_output = 0;
103 
104     av_init_packet(&pkt);
105 
106     const AVCodec *codec;
107     if (ctx->opts->format == AV_CODEC_ID_WEBP) {
108         codec = avcodec_find_encoder_by_name("libwebp"); // non-animated encoder
109     } else {
110         codec = avcodec_find_encoder(ctx->opts->format);
111     }
112 
113     AVCodecContext *avctx = NULL;
114     if (!codec)
115         goto print_open_fail;
116     avctx = avcodec_alloc_context3(codec);
117     if (!avctx)
118         goto print_open_fail;
119 
120     avctx->time_base = AV_TIME_BASE_Q;
121     avctx->width = image->w;
122     avctx->height = image->h;
123     avctx->color_range = mp_csp_levels_to_avcol_range(image->params.color.levels);
124     avctx->pix_fmt = imgfmt2pixfmt(image->imgfmt);
125     if (codec->id == AV_CODEC_ID_MJPEG) {
126         // Annoying deprecated garbage for the jpg encoder.
127         if (image->params.color.levels == MP_CSP_LEVELS_PC)
128             avctx->pix_fmt = replace_j_format(avctx->pix_fmt);
129     }
130     if (avctx->pix_fmt == AV_PIX_FMT_NONE) {
131         MP_ERR(ctx, "Image format %s not supported by lavc.\n",
132                mp_imgfmt_to_name(image->imgfmt));
133         goto error_exit;
134     }
135     if (codec->id == AV_CODEC_ID_PNG) {
136         avctx->compression_level = ctx->opts->png_compression;
137         av_opt_set_int(avctx, "pred", ctx->opts->png_filter,
138                        AV_OPT_SEARCH_CHILDREN);
139     } else if (codec->id == AV_CODEC_ID_WEBP) {
140         avctx->compression_level = ctx->opts->webp_compression;
141         av_opt_set_int(avctx, "lossless", ctx->opts->webp_lossless,
142                        AV_OPT_SEARCH_CHILDREN);
143         av_opt_set_int(avctx, "quality", ctx->opts->webp_quality,
144                        AV_OPT_SEARCH_CHILDREN);
145     }
146 
147     if (avcodec_open2(avctx, codec, NULL) < 0) {
148      print_open_fail:
149         MP_ERR(ctx, "Could not open libavcodec encoder for saving images\n");
150         goto error_exit;
151     }
152 
153     pic = av_frame_alloc();
154     if (!pic)
155         goto error_exit;
156     for (int n = 0; n < 4; n++) {
157         pic->data[n] = image->planes[n];
158         pic->linesize[n] = image->stride[n];
159     }
160     pic->format = avctx->pix_fmt;
161     pic->width = avctx->width;
162     pic->height = avctx->height;
163     pic->color_range = avctx->color_range;
164     if (ctx->opts->tag_csp) {
165         pic->color_primaries = mp_csp_prim_to_avcol_pri(image->params.color.primaries);
166         pic->color_trc = mp_csp_trc_to_avcol_trc(image->params.color.gamma);
167     }
168 
169     int ret = avcodec_send_frame(avctx, pic);
170     if (ret < 0)
171         goto error_exit;
172     ret = avcodec_send_frame(avctx, NULL); // send EOF
173     if (ret < 0)
174         goto error_exit;
175     ret = avcodec_receive_packet(avctx, &pkt);
176     if (ret < 0)
177         goto error_exit;
178     got_output = 1;
179 
180     fwrite(pkt.data, pkt.size, 1, fp);
181 
182     success = !!got_output;
183 error_exit:
184     avcodec_free_context(&avctx);
185     av_frame_free(&pic);
186     av_packet_unref(&pkt);
187     return success;
188 }
189 
190 #if HAVE_JPEG
191 
write_jpeg_error_exit(j_common_ptr cinfo)192 static void write_jpeg_error_exit(j_common_ptr cinfo)
193 {
194   // NOTE: do not write error message, too much effort to connect the libjpeg
195   //       log callbacks with mplayer's log function mp_msp()
196 
197   // Return control to the setjmp point
198   longjmp(*(jmp_buf*)cinfo->client_data, 1);
199 }
200 
write_jpeg(struct image_writer_ctx * ctx,mp_image_t * image,FILE * fp)201 static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image, FILE *fp)
202 {
203     struct jpeg_compress_struct cinfo;
204     struct jpeg_error_mgr jerr;
205 
206     cinfo.err = jpeg_std_error(&jerr);
207     jerr.error_exit = write_jpeg_error_exit;
208 
209     jmp_buf error_return_jmpbuf;
210     cinfo.client_data = &error_return_jmpbuf;
211     if (setjmp(cinfo.client_data)) {
212         jpeg_destroy_compress(&cinfo);
213         return false;
214     }
215 
216     jpeg_create_compress(&cinfo);
217     jpeg_stdio_dest(&cinfo, fp);
218 
219     cinfo.image_width = image->w;
220     cinfo.image_height = image->h;
221     cinfo.input_components = 3;
222     cinfo.in_color_space = JCS_RGB;
223 
224     cinfo.write_JFIF_header = TRUE;
225     cinfo.JFIF_major_version = 1;
226     cinfo.JFIF_minor_version = 2;
227 
228     jpeg_set_defaults(&cinfo);
229     jpeg_set_quality(&cinfo, ctx->opts->jpeg_quality, 0);
230 
231     if (ctx->opts->jpeg_source_chroma) {
232         cinfo.comp_info[0].h_samp_factor = 1 << ctx->original_format.chroma_xs;
233         cinfo.comp_info[0].v_samp_factor = 1 << ctx->original_format.chroma_ys;
234     }
235 
236     jpeg_start_compress(&cinfo, TRUE);
237 
238     while (cinfo.next_scanline < cinfo.image_height) {
239         JSAMPROW row_pointer[1];
240         row_pointer[0] = image->planes[0] +
241                          (ptrdiff_t)cinfo.next_scanline * image->stride[0];
242         jpeg_write_scanlines(&cinfo, row_pointer,1);
243     }
244 
245     jpeg_finish_compress(&cinfo);
246 
247     jpeg_destroy_compress(&cinfo);
248 
249     return true;
250 }
251 
252 #endif
253 
get_encoder_format(const AVCodec * codec,int srcfmt,bool highdepth)254 static int get_encoder_format(const AVCodec *codec, int srcfmt, bool highdepth)
255 {
256     const enum AVPixelFormat *pix_fmts = codec->pix_fmts;
257     int current = 0;
258     for (int n = 0; pix_fmts && pix_fmts[n] != AV_PIX_FMT_NONE; n++) {
259         int fmt = pixfmt2imgfmt(pix_fmts[n]);
260         if (!fmt)
261             continue;
262         if (!highdepth) {
263             // Ignore formats larger than 8 bit per pixel. (Or which are unknown.)
264             struct mp_regular_imgfmt rdesc;
265             if (!mp_get_regular_imgfmt(&rdesc, fmt)) {
266                 int ofmt = mp_find_other_endian(fmt);
267                 if (!mp_get_regular_imgfmt(&rdesc, ofmt))
268                     continue;
269             }
270             if (rdesc.component_size > 1)
271                 continue;
272         }
273         current = current ? mp_imgfmt_select_best(current, fmt, srcfmt) : fmt;
274     }
275     return current;
276 }
277 
get_target_format(struct image_writer_ctx * ctx)278 static int get_target_format(struct image_writer_ctx *ctx)
279 {
280     const AVCodec *codec = avcodec_find_encoder(ctx->opts->format);
281     if (!codec)
282         goto unknown;
283 
284     int srcfmt = ctx->original_format.id;
285 
286     int target = get_encoder_format(codec, srcfmt, ctx->opts->high_bit_depth);
287     if (!target)
288         target = get_encoder_format(codec, srcfmt, true);
289 
290     if (!target)
291         goto unknown;
292 
293     return target;
294 
295 unknown:
296     return IMGFMT_RGB0;
297 }
298 
image_writer_file_ext(const struct image_writer_opts * opts)299 const char *image_writer_file_ext(const struct image_writer_opts *opts)
300 {
301     struct image_writer_opts defs = image_writer_opts_defaults;
302 
303     if (!opts)
304         opts = &defs;
305 
306     return m_opt_choice_str(mp_image_writer_formats, opts->format);
307 }
308 
image_writer_high_depth(const struct image_writer_opts * opts)309 bool image_writer_high_depth(const struct image_writer_opts *opts)
310 {
311     return opts->format == AV_CODEC_ID_PNG;
312 }
313 
image_writer_format_from_ext(const char * ext)314 int image_writer_format_from_ext(const char *ext)
315 {
316     for (int n = 0; mp_image_writer_formats[n].name; n++) {
317         if (ext && strcmp(mp_image_writer_formats[n].name, ext) == 0)
318             return mp_image_writer_formats[n].value;
319     }
320     return 0;
321 }
322 
convert_image(struct mp_image * image,int destfmt,enum mp_csp_levels yuv_levels,struct mpv_global * global,struct mp_log * log)323 static struct mp_image *convert_image(struct mp_image *image, int destfmt,
324                                       enum mp_csp_levels yuv_levels,
325                                       struct mpv_global *global,
326                                       struct mp_log *log)
327 {
328     int d_w, d_h;
329     mp_image_params_get_dsize(&image->params, &d_w, &d_h);
330 
331     struct mp_image_params p = {
332         .imgfmt = destfmt,
333         .w = d_w,
334         .h = d_h,
335         .p_w = 1,
336         .p_h = 1,
337     };
338     mp_image_params_guess_csp(&p);
339 
340     // If RGB, just assume everything is correct.
341     if (p.color.space != MP_CSP_RGB) {
342         // Currently, assume what FFmpeg's jpg encoder or libwebp needs.
343         // Of course this works only for non-HDR (no HDR support in libswscale).
344         p.color.levels = yuv_levels;
345         p.color.space = MP_CSP_BT_601;
346         p.chroma_location = MP_CHROMA_CENTER;
347         mp_image_params_guess_csp(&p);
348     }
349 
350     if (mp_image_params_equal(&p, &image->params))
351         return mp_image_new_ref(image);
352 
353     struct mp_image *dst = mp_image_alloc(p.imgfmt, p.w, p.h);
354     if (!dst) {
355         mp_err(log, "Out of memory.\n");
356         return NULL;
357     }
358     mp_image_copy_attributes(dst, image);
359 
360     dst->params = p;
361 
362     struct mp_sws_context *sws = mp_sws_alloc(NULL);
363     sws->log = log;
364     if (global)
365         mp_sws_enable_cmdline_opts(sws, global);
366     bool ok = mp_sws_scale(sws, dst, image) >= 0;
367     talloc_free(sws);
368 
369     if (!ok) {
370         mp_err(log, "Error when converting image.\n");
371         talloc_free(dst);
372         return NULL;
373     }
374 
375     return dst;
376 }
377 
write_image(struct mp_image * image,const struct image_writer_opts * opts,const char * filename,struct mpv_global * global,struct mp_log * log)378 bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
379                 const char *filename, struct mpv_global *global,
380                  struct mp_log *log)
381 {
382     struct image_writer_opts defs = image_writer_opts_defaults;
383     if (!opts)
384         opts = &defs;
385 
386     struct image_writer_ctx ctx = { log, opts, image->fmt };
387     bool (*write)(struct image_writer_ctx *, mp_image_t *, FILE *) = write_lavc;
388     int destfmt = 0;
389 
390 #if HAVE_JPEG
391     if (opts->format == AV_CODEC_ID_MJPEG) {
392         write = write_jpeg;
393         destfmt = IMGFMT_RGB24;
394     }
395 #endif
396     if (opts->format == AV_CODEC_ID_WEBP && !opts->webp_lossless) {
397         // For lossy images, libwebp has its own RGB->YUV conversion.
398         // We don't want that, so force YUV/YUVA here.
399         int alpha = image->fmt.flags & MP_IMGFLAG_ALPHA;
400         destfmt = alpha ? pixfmt2imgfmt(AV_PIX_FMT_YUVA420P) : IMGFMT_420P;
401     }
402 
403     if (!destfmt)
404         destfmt = get_target_format(&ctx);
405 
406     enum mp_csp_levels levels; // Ignored if destfmt is a RGB format
407     if (opts->format == AV_CODEC_ID_WEBP) {
408         levels = MP_CSP_LEVELS_TV;
409     } else {
410         levels = MP_CSP_LEVELS_PC;
411     }
412 
413     struct mp_image *dst = convert_image(image, destfmt, levels, global, log);
414     if (!dst)
415         return false;
416 
417     FILE *fp = fopen(filename, "wb");
418     bool success = false;
419     if (fp == NULL) {
420         mp_err(log, "Error opening '%s' for writing!\n", filename);
421     } else {
422         success = write(&ctx, dst, fp);
423         success = !fclose(fp) && success;
424         if (!success)
425             mp_err(log, "Error writing file '%s'!\n", filename);
426     }
427 
428     talloc_free(dst);
429     return success;
430 }
431 
dump_png(struct mp_image * image,const char * filename,struct mp_log * log)432 void dump_png(struct mp_image *image, const char *filename, struct mp_log *log)
433 {
434     struct image_writer_opts opts = image_writer_opts_defaults;
435     opts.format = AV_CODEC_ID_PNG;
436     write_image(image, &opts, filename, NULL, log);
437 }
438