diff options
author | Brecht Van Lommel <brecht@blender.org> | 2022-03-18 23:28:04 +0300 |
---|---|---|
committer | Brecht Van Lommel <brecht@blender.org> | 2022-03-21 18:38:13 +0300 |
commit | 8530e48f8692f4b92c43489029f2d2ef67c7cb62 (patch) | |
tree | d335479baf037b897ddf14f42c238184f65d0472 /source/blender/blenkernel/intern/image_save.cc | |
parent | b96462519f748fcf04028084354fbbf97eb8ce92 (diff) |
Cleanup: move render image and multilayer EXR write code to image_save.cc
These share a lot of logic with regular image saving and should be unified
more in the future.
Diffstat (limited to 'source/blender/blenkernel/intern/image_save.cc')
-rw-r--r-- | source/blender/blenkernel/intern/image_save.cc | 336 |
1 files changed, 332 insertions, 4 deletions
diff --git a/source/blender/blenkernel/intern/image_save.cc b/source/blender/blenkernel/intern/image_save.cc index 180008e0f64..fdeb5eedcde 100644 --- a/source/blender/blenkernel/intern/image_save.cc +++ b/source/blender/blenkernel/intern/image_save.cc @@ -20,6 +20,8 @@ #include "IMB_imbuf.h" #include "IMB_imbuf_types.h" +#include "intern/openexr/openexr_multi.h" + #include "BKE_colortools.h" #include "BKE_image.h" #include "BKE_image_format.h" @@ -219,14 +221,14 @@ static bool image_save_single(ReportList *reports, /* fancy multiview OpenEXR */ if (imf->views_format == R_IMF_VIEWS_MULTIVIEW && is_exr_rr) { /* save render result */ - ok = RE_WriteRenderResult(reports, rr, opts->filepath, imf, nullptr, layer); + ok = BKE_image_render_write_exr(reports, rr, opts->filepath, imf, NULL, layer); image_save_post(reports, ima, ibuf, ok, opts, true, opts->filepath, r_colorspace_changed); BKE_image_release_ibuf(ima, ibuf, lock); } /* regular mono pipeline */ else if (is_mono) { if (is_exr_rr) { - ok = RE_WriteRenderResult(reports, rr, opts->filepath, imf, nullptr, layer); + ok = BKE_image_render_write_exr(reports, rr, opts->filepath, imf, NULL, layer); } else { colormanaged_ibuf = IMB_colormanagement_imbuf_for_write( @@ -261,7 +263,7 @@ static bool image_save_single(ReportList *reports, if (is_exr_rr) { BKE_scene_multiview_view_filepath_get(&opts->scene->r, opts->filepath, view, filepath); - ok_view = RE_WriteRenderResult(reports, rr, filepath, imf, view, layer); + ok_view = BKE_image_render_write_exr(reports, rr, filepath, imf, view, layer); image_save_post(reports, ima, ibuf, ok_view, opts, true, filepath, r_colorspace_changed); } else { @@ -308,7 +310,7 @@ static bool image_save_single(ReportList *reports, /* stereo (multiview) images */ else if (opts->im_format.views_format == R_IMF_VIEWS_STEREO_3D) { if (imf->imtype == R_IMF_IMTYPE_MULTILAYER) { - ok = RE_WriteRenderResult(reports, rr, opts->filepath, imf, nullptr, layer); + ok = BKE_image_render_write_exr(reports, rr, opts->filepath, imf, NULL, layer); image_save_post(reports, ima, ibuf, ok, opts, true, opts->filepath, r_colorspace_changed); BKE_image_release_ibuf(ima, ibuf, lock); } @@ -449,3 +451,329 @@ bool BKE_image_save( return ok; } + +/* OpenEXR saving, single and multilayer. */ + +bool BKE_image_render_write_exr(ReportList *reports, + const RenderResult *rr, + const char *filename, + const ImageFormatData *imf, + const char *view, + int layer) +{ + void *exrhandle = IMB_exr_get_handle(); + const bool half_float = (imf && imf->depth == R_IMF_CHAN_DEPTH_16); + const bool multi_layer = !(imf && imf->imtype == R_IMF_IMTYPE_OPENEXR); + const bool write_z = !multi_layer && (imf && (imf->flag & R_IMF_FLAG_ZBUF)); + + /* Write first layer if not multilayer and no layer was specified. */ + if (!multi_layer && layer == -1) { + layer = 0; + } + + /* First add views since IMB_exr_add_channel checks number of views. */ + const RenderView *first_rview = (const RenderView *)rr->views.first; + if (first_rview && (first_rview->next || first_rview->name[0])) { + LISTBASE_FOREACH (RenderView *, rview, &rr->views) { + if (!view || STREQ(view, rview->name)) { + IMB_exr_add_view(exrhandle, rview->name); + } + } + } + + /* Compositing result. */ + if (rr->have_combined) { + LISTBASE_FOREACH (RenderView *, rview, &rr->views) { + if (!rview->rectf) { + continue; + } + + const char *viewname = rview->name; + if (view) { + if (!STREQ(view, viewname)) { + continue; + } + + viewname = ""; + } + + /* Skip compositing if only a single other layer is requested. */ + if (!multi_layer && layer != 0) { + continue; + } + + float *output_rect = rview->rectf; + + for (int a = 0; a < 4; a++) { + char passname[EXR_PASS_MAXNAME]; + char layname[EXR_PASS_MAXNAME]; + const char *chan_id = "RGBA"; + + if (multi_layer) { + IMB_exr_channel_name(passname, NULL, "Combined", NULL, chan_id, a); + BLI_strncpy(layname, "Composite", sizeof(layname)); + } + else { + passname[0] = chan_id[a]; + passname[1] = '\0'; + layname[0] = '\0'; + } + + IMB_exr_add_channel( + exrhandle, layname, passname, viewname, 4, 4 * rr->rectx, output_rect + a, half_float); + } + + if (write_z && rview->rectz) { + const char *layname = (multi_layer) ? "Composite" : ""; + IMB_exr_add_channel(exrhandle, layname, "Z", viewname, 1, rr->rectx, rview->rectz, false); + } + } + } + + /* Other render layers. */ + int nr = (rr->have_combined) ? 1 : 0; + LISTBASE_FOREACH (RenderLayer *, rl, &rr->layers) { + /* Skip other render layers if requested. */ + if (!multi_layer && nr != layer) { + continue; + } + + LISTBASE_FOREACH (RenderPass *, rp, &rl->passes) { + /* Skip non-RGBA and Z passes if not using multi layer. */ + if (!multi_layer && !(STREQ(rp->name, RE_PASSNAME_COMBINED) || STREQ(rp->name, "") || + (STREQ(rp->name, RE_PASSNAME_Z) && write_z))) { + continue; + } + + /* Skip pass if it does not match the requested view(s). */ + const char *viewname = rp->view; + if (view) { + if (!STREQ(view, viewname)) { + continue; + } + + viewname = ""; + } + + /* We only store RGBA passes as half float, for + * others precision loss can be problematic. */ + const bool pass_RGBA = (STR_ELEM(rp->chan_id, "RGB", "RGBA", "R", "G", "B", "A")); + const bool pass_half_float = half_float && pass_RGBA; + + float *output_rect = rp->rect; + + for (int a = 0; a < rp->channels; a++) { + /* Save Combined as RGBA if single layer save. */ + char passname[EXR_PASS_MAXNAME]; + char layname[EXR_PASS_MAXNAME]; + + if (multi_layer) { + IMB_exr_channel_name(passname, NULL, rp->name, NULL, rp->chan_id, a); + BLI_strncpy(layname, rl->name, sizeof(layname)); + } + else { + passname[0] = rp->chan_id[a]; + passname[1] = '\0'; + layname[0] = '\0'; + } + + IMB_exr_add_channel(exrhandle, + layname, + passname, + viewname, + rp->channels, + rp->channels * rr->rectx, + output_rect + a, + pass_half_float); + } + } + } + + errno = 0; + + BLI_make_existing_file(filename); + + int compress = (imf ? imf->exr_codec : 0); + bool success = IMB_exr_begin_write( + exrhandle, filename, rr->rectx, rr->recty, compress, rr->stamp_data); + if (success) { + IMB_exr_write_channels(exrhandle); + } + else { + /* TODO: get the error from openexr's exception. */ + BKE_reportf( + reports, RPT_ERROR, "Error writing render result, %s (see console)", strerror(errno)); + } + + IMB_exr_close(exrhandle); + return success; +} + +/* Render output. */ + +static void image_render_print_save_message(ReportList *reports, const char *name, int ok, int err) +{ + if (ok) { + /* no need to report, just some helpful console info */ + printf("Saved: '%s'\n", name); + } + else { + /* report on error since users will want to know what failed */ + BKE_reportf(reports, RPT_ERROR, "Render error (%s) cannot save: '%s'", strerror(err), name); + } +} + +static int image_render_write_stamp_test(ReportList *reports, + const Scene *scene, + const RenderResult *rr, + ImBuf *ibuf, + const char *name, + const ImageFormatData *imf, + const bool stamp) +{ + int ok; + + if (stamp) { + /* writes the name of the individual cameras */ + ok = BKE_imbuf_write_stamp(scene, rr, ibuf, name, imf); + } + else { + ok = BKE_imbuf_write(ibuf, name, imf); + } + + image_render_print_save_message(reports, name, ok, errno); + + return ok; +} + +bool BKE_image_render_write(ReportList *reports, + RenderResult *rr, + const Scene *scene, + const bool stamp, + const char *filename) +{ + bool ok = true; + const RenderData *rd = &scene->r; + + if (!rr) { + return false; + } + + const bool is_mono = BLI_listbase_count_at_most(&rr->views, 2) < 2; + const bool is_exr_rr = ELEM(rd->im_format.imtype, + R_IMF_IMTYPE_OPENEXR, + R_IMF_IMTYPE_MULTILAYER) && + RE_HasFloatPixels(rr); + const float dither = scene->r.dither_intensity; + + if (rd->im_format.views_format == R_IMF_VIEWS_MULTIVIEW && is_exr_rr) { + ok = BKE_image_render_write_exr(reports, rr, filename, &rd->im_format, NULL, -1); + image_render_print_save_message(reports, filename, ok, errno); + } + + /* mono, legacy code */ + else if (is_mono || (rd->im_format.views_format == R_IMF_VIEWS_INDIVIDUAL)) { + int view_id = 0; + for (const RenderView *rv = (const RenderView *)rr->views.first; rv; + rv = rv->next, view_id++) { + char filepath[FILE_MAX]; + if (is_mono) { + STRNCPY(filepath, filename); + } + else { + BKE_scene_multiview_view_filepath_get(&scene->r, filename, rv->name, filepath); + } + + if (is_exr_rr) { + ok = BKE_image_render_write_exr(reports, rr, filepath, &rd->im_format, rv->name, -1); + image_render_print_save_message(reports, filepath, ok, errno); + + /* optional preview images for exr */ + if (ok && (rd->im_format.flag & R_IMF_FLAG_PREVIEW_JPG)) { + ImageFormatData imf = rd->im_format; + imf.imtype = R_IMF_IMTYPE_JPEG90; + + if (BLI_path_extension_check(filepath, ".exr")) { + filepath[strlen(filepath) - 4] = 0; + } + BKE_image_path_ensure_ext_from_imformat(filepath, &imf); + + ImBuf *ibuf = RE_render_result_rect_to_ibuf(rr, &imf, dither, view_id); + ibuf->planes = 24; + IMB_colormanagement_imbuf_for_write( + ibuf, true, false, &scene->view_settings, &scene->display_settings, &imf); + + ok = image_render_write_stamp_test(reports, scene, rr, ibuf, filepath, &imf, stamp); + + IMB_freeImBuf(ibuf); + } + } + else { + ImBuf *ibuf = RE_render_result_rect_to_ibuf(rr, &rd->im_format, dither, view_id); + + IMB_colormanagement_imbuf_for_write( + ibuf, true, false, &scene->view_settings, &scene->display_settings, &rd->im_format); + + ok = image_render_write_stamp_test( + reports, scene, rr, ibuf, filepath, &rd->im_format, stamp); + + /* imbuf knows which rects are not part of ibuf */ + IMB_freeImBuf(ibuf); + } + } + } + else { /* R_IMF_VIEWS_STEREO_3D */ + BLI_assert(rd->im_format.views_format == R_IMF_VIEWS_STEREO_3D); + + char filepath[FILE_MAX]; + STRNCPY(filepath, filename); + + if (rd->im_format.imtype == R_IMF_IMTYPE_MULTILAYER) { + printf("Stereo 3D not supported for MultiLayer image: %s\n", filepath); + } + else { + ImBuf *ibuf_arr[3] = {NULL}; + const char *names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME}; + int i; + + for (i = 0; i < 2; i++) { + int view_id = BLI_findstringindex(&rr->views, names[i], offsetof(RenderView, name)); + ibuf_arr[i] = RE_render_result_rect_to_ibuf(rr, &rd->im_format, dither, view_id); + IMB_colormanagement_imbuf_for_write(ibuf_arr[i], + true, + false, + &scene->view_settings, + &scene->display_settings, + &rd->im_format); + IMB_prepare_write_ImBuf(IMB_isfloat(ibuf_arr[i]), ibuf_arr[i]); + } + + ibuf_arr[2] = IMB_stereo3d_ImBuf(&rd->im_format, ibuf_arr[0], ibuf_arr[1]); + + ok = image_render_write_stamp_test( + reports, scene, rr, ibuf_arr[2], filepath, &rd->im_format, stamp); + + /* optional preview images for exr */ + if (ok && is_exr_rr && (rd->im_format.flag & R_IMF_FLAG_PREVIEW_JPG)) { + ImageFormatData imf = rd->im_format; + imf.imtype = R_IMF_IMTYPE_JPEG90; + + if (BLI_path_extension_check(filepath, ".exr")) { + filepath[strlen(filepath) - 4] = 0; + } + + BKE_image_path_ensure_ext_from_imformat(filepath, &imf); + ibuf_arr[2]->planes = 24; + + ok = image_render_write_stamp_test(reports, scene, rr, ibuf_arr[2], filepath, &imf, stamp); + } + + /* imbuf knows which rects are not part of ibuf */ + for (i = 0; i < 3; i++) { + IMB_freeImBuf(ibuf_arr[i]); + } + } + } + + return ok; +} |