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