diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index a519264959..d66e9342f2 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -1767,8 +1767,14 @@ current top image. Optional appended modifiers include: - - `type=` *typename* : Specify the metadata type. - + `:subimages=` *indices-or-names* + Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`). + Only included subimages will have the attribute changed. If subimages + are not set, only the first subimage will be changed, or all subimages + if the `-a` command line flag was used. + + `:type=` *typename* : Specify the metadata type. + If the optional `type=` specifier is used, that provides an explicit type for the metadata. If not provided, it will try to infer the type of the metadata from the value: if the value contains only numerals (with @@ -1780,24 +1786,43 @@ current top image. Examples:: + # Set the IPTC:City attribute to "Berkeley" oiiotool in.jpg --attrib "IPTC:City" "Berkeley" -o out.jpg + # Set a name attribute to "0", but force it to be a string oiiotool in.jpg --attrib:type=string "Name" "0" -o out.jpg + # Another way to force a string attribute using --sattrib: + oiiotool in.jpg --sattrib "Name" "0" -o out.jpg + + # Set the worldcam attribute to be a matrix oiiotool in.exr --attrib:type=matrix worldtocam \ "1,0,0,0,0,1,0,0,0,0,1,0,2.3,2.1,0,1" -o out.exr - - oiiotool in.exr --attrib:type=timecode smpte:TimeCode "11:34:04:00" \ - -o out.exr + # Set an attribute to be a timecode + oiiotool in.exr --attrib:type=timecode smpte:TimeCode "11:34:04:00" -o out.exr + + # Set an attribute in all subimages + oiiotool multipart.exr --attrib:subimages=all "Foo" "bar" -o out.exr + + # Set an attribute just in subimages 0 and 3 + oiiotool multipart.exr --attrib:subimages=0,3 "Foo" "bar" -o out.exr .. option:: --caption Sets the image metadata `"ImageDescription"`. This has no effect if the output image format does not support some kind of title, caption, or - description metadata field. Be careful to enclose *text in quotes if you + description metadata field. Be careful to enclose *text* in quotes if you want your caption to include spaces or certain punctuation! + Optional appended modifiers include: + + `:subimages=` *indices-or-names* + Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`). + Only included subimages will have the attribute changed. If subimages + are not set, only the first subimage will be changed, or all subimages + if the `-a` command line flag was used. + .. option:: --keyword Adds a keyword to the image metadata `"Keywords"`. Any existing @@ -1806,7 +1831,7 @@ current top image. effect if the output image format does not support some kind of keyword field. - Be careful to enclose *text in quotes if you want your keyword to + Be careful to enclose *text* in quotes if you want your keyword to include spaces or certain punctuation. For image formats that have only a single field for keywords, OpenImageIO will concatenate the keywords, separated by semicolon (`;`), so don't use semicolons within your @@ -1816,6 +1841,14 @@ current top image. Clears all existing keywords in the current image. + Optional appended modifiers include: + + `:subimages=` *indices-or-names* + Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`). + Only included subimages will have the attribute changed. If subimages + are not set, only the first subimage will be changed, or all subimages + if the `-a` command line flag was used. + .. option:: --nosoftwareattrib When set, this prevents the normal adjustment of "Software" and @@ -1832,6 +1865,14 @@ current top image. Removes any metadata whose name matches the regular expression *pattern*. The pattern will be case insensitive. + Optional appended modifiers include: + + `:subimages=` *indices-or-names* + Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`). + Only included subimages will have the attribute changed. If subimages + are not set, only the first subimage will be changed, or all subimages + if the `-a` command line flag was used. + Examples:: # Remove one item only @@ -1841,7 +1882,7 @@ current top image. oiiotool in.jpg --eraseattrib "GPS:.*" -o no_gps_metadata.jpg # Remove all metadata - oiiotool in.exr --eraseattrib ".*" -o no_metadata.exr + oiiotool in.exr --eraseattrib:subimages=all ".*" -o no_metadata.exr .. option:: --orientation @@ -1852,6 +1893,14 @@ current top image. displayed, it does NOT alter the pixels themselves, and so has no effect for image formats that don't support some kind of orientation metadata. + Optional appended modifiers include: + + `:subimages=` *indices-or-names* + Include/exclude subimages (see :ref:`sec-oiiotool-subimage-modifier`). + Only included subimages will have the attribute changed. If subimages + are not set, only the first subimage will be changed, or all subimages + if the `-a` command line flag was used. + .. option:: --orientcw --orientccw --orient180 diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 9cbd593da7..a1519c4732 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -1264,18 +1264,14 @@ control_endfor(int argc, const char* argv[]) -// --set -static int -set_user_variable(int argc, const char* argv[]) +// Centralized logic to set attribute `attribname` on object `obj` to `value`. +// The value is expressed as a string, with the type specified by `type`, or +// if TypeUnknown, inferred from the apparent formatting of the value. +template +static void +set_attribute_helper(T& obj, string_view attribname, string_view value, + TypeDesc type) { - OIIO_DASSERT(argc == 3); - - string_view command = ot.express(argv[0]); - string_view name = ot.express(argv[1]); - string_view value = ot.express(argv[2]); - auto options = ot.extract_options(command); - TypeDesc type(options["type"].as_string()); - // First, handle the cases where we're told what to expect if (type.basetype == TypeDesc::FLOAT) { size_t n = type.numelements() * type.aggregate; @@ -1284,8 +1280,27 @@ set_user_variable(int argc, const char* argv[]) Strutil::parse_float(value, vals[i]); Strutil::parse_char(value, ','); } - ot.uservars.attribute(name, type, vals.data()); - return 1; + obj.attribute(attribname, type, &vals[0]); + return; + } + if (type == TypeTimeCode && value.find(':') != value.npos) { + // Special case: They are specifying a TimeCode as a "HH:MM:SS:FF" + // string, we need to re-encode as a uint32[2]. + int hmsf[4] = { 0, 0, 0, 0 }; // hour, min, sec, frame + Strutil::scan_values(value, "", hmsf, ":"); + Imf::TimeCode tc(hmsf[0], hmsf[1], hmsf[2], hmsf[3]); + obj.attribute(attribname, type, &tc); + return; + } + if (type == TypeRational && value.find('/') != value.npos) { + // Special case: They are specifying a rational as "a/b", so we need + // to re-encode as a int32[2]. + int v[2]; + Strutil::parse_int(value, v[0]); + Strutil::parse_char(value, '/'); + Strutil::parse_int(value, v[1]); + obj.attribute(attribname, type, v); + return; } if (type.basetype == TypeDesc::INT) { size_t n = type.numelements() * type.aggregate; @@ -1294,8 +1309,8 @@ set_user_variable(int argc, const char* argv[]) Strutil::parse_int(value, vals[i]); Strutil::parse_char(value, ','); } - ot.uservars.attribute(name, type, vals.data()); - return 1; + obj.attribute(attribname, type, &vals[0]); + return; } if (type.basetype == TypeDesc::STRING) { size_t n = type.numelements() * type.aggregate; @@ -1310,177 +1325,155 @@ set_user_variable(int argc, const char* argv[]) Strutil::parse_char(value, ','); } } - ot.uservars.attribute(name, type, vals.data()); - return 1; + obj.attribute(attribname, type, &vals[0]); + return; } - if (type == TypeInt - || (type == TypeUnknown && Strutil::string_is_int(value))) { - // Does it seem to be an int, or did the caller explicitly request - // that it be set as an int? - ot.uservars.attribute(name, Strutil::stoi(value)); - return 1; - } else if (type == TypeFloat - || (type == TypeUnknown && Strutil::string_is_float(value))) { - // Does it seem to be a float, or did the caller explicitly request - // that it be set as a float? - ot.uservars.attribute(name, Strutil::stof(value)); - return 1; + // No explicit type... guess based on the appearance of the value string. + if (Strutil::string_is_int(value)) { + // Does it seem to be an int? + obj.attribute(attribname, Strutil::stoi(value)); + } else if (Strutil::string_is_float(value)) { + // Does it seem to be a float? + obj.attribute(attribname, Strutil::stof(value)); } else { // Otherwise, set it as a string attribute - ot.uservars.attribute(name, value); - return 1; + obj.attribute(attribname, value); } - ot.warningfmt(argv[0], "Don't know how to set {} to \"{}\" ({})", name, - value, type); - return 0; } -// --oiioattrib +// --set static int -set_oiio_attribute(int argc, const char* argv[]) +set_user_variable(int argc, const char* argv[]) { OIIO_DASSERT(argc == 3); + string_view command = ot.express(argv[0]); + string_view name = ot.express(argv[1]); + string_view value = ot.express(argv[2]); + auto options = ot.extract_options(command); + TypeDesc type(options["type"].as_string()); + + set_attribute_helper(ot.uservars, name, value, type); + return 1; +} + + + +// --oiioattrib +static void +set_oiio_attribute(cspan argv) +{ + OIIO_DASSERT(argv.size() == 3); + string_view command = ot.express(argv[0]); string_view attribname = ot.express(argv[1]); string_view value = ot.express(argv[2]); auto options = ot.extract_options(command); TypeDesc type(options["type"].as_string()); - // First, handle the cases where we're told what to expect - if (type.basetype == TypeDesc::FLOAT) { - size_t n = type.numelements() * type.aggregate; - std::vector vals(n, 0.0f); - for (size_t i = 0; i < n && value.size(); ++i) { - Strutil::parse_float(value, vals[i]); - Strutil::parse_char(value, ','); - } - OIIO::attribute(attribname, type, vals.data()); - return 1; - } - if (type.basetype == TypeDesc::INT) { - size_t n = type.numelements() * type.aggregate; - std::vector vals(n, 0); - for (size_t i = 0; i < n && value.size(); ++i) { - Strutil::parse_int(value, vals[i]); - Strutil::parse_char(value, ','); - } - OIIO::attribute(attribname, type, vals.data()); - return 1; - } - if (type.basetype == TypeDesc::STRING) { - size_t n = type.numelements() * type.aggregate; - std::vector vals(n, ustring()); - if (n == 1) - vals[0] = ustring(value); - else { - for (size_t i = 0; i < n && value.size(); ++i) { - string_view s; - Strutil::parse_string(value, s); - vals[i] = ustring(s); - Strutil::parse_char(value, ','); - } - } - OIIO::attribute(attribname, type, vals.data()); - return 1; - } - - if (type == TypeInt - || (type == TypeUnknown && Strutil::string_is_int(value))) { - // Does it seem to be an int, or did the caller explicitly request - // that it be set as an int? - return OIIO::attribute(attribname, Strutil::stoi(value)); - } else if (type == TypeFloat - || (type == TypeUnknown && Strutil::string_is_float(value))) { - // Does it seem to be a float, or did the caller explicitly request - // that it be set as a float? - return OIIO::attribute(attribname, Strutil::stof(value)); - } else { - // Otherwise, set it as a string attribute - return OIIO::attribute(attribname, value); - } - return 0; + // Rather than duplicate the logic of set_attribute_helper for the case of + // the global attribute that doesn't have an object to go with it, cheat + // by putting the attrib into a temporary ParamValueList with + // set_attribute_helper, then transfer to OIIO global attribs. This + // doesn't happen often enough to care about the perf hit of the extra + // copy. + ParamValueList pl; + set_attribute_helper(pl, attribname, value, type); + for (const auto& p : pl) + OIIO::attribute(p.name(), p.type(), p.data()); } -static int -set_string_attribute(int argc, const char* argv[]) -{ - OIIO_DASSERT(argc == 3); - if (!ot.curimg.get()) { - ot.warning(argv[0], "no current image available to modify"); - return 0; +// Special OiiotoolOp whose purpose is to set attributes on the top image. +class OpAttribSetter final : public OiiotoolOp { +public: + OpAttribSetter(Oiiotool& ot, string_view opname, cspan argv) + : OiiotoolOp(ot, opname, argv, 1) + { + inplace(true); // This action operates in-place + attribname = args(1); + value = (nargs() > 2 ? args(2) : ""); + } + OpAttribSetter(Oiiotool& ot, string_view opname, int argc, + const char* argv[]) + : OiiotoolOp(ot, opname, argc, argv, 1) + { + inplace(true); // This action operates in-place + attribname = args(1); + value = (nargs() > 2 ? args(2) : ""); + } + virtual bool setup() override + { + ir(0)->metadata_modified(true); + return true; + } + virtual bool impl(span img) override + { + // Because this is an in-place operation, img[0] is the same as + // img[1]. + if (value.empty()) { + img[0]->specmod().erase_attribute(attribname); + } else { + TypeDesc type(options()["type"].as_string()); + set_attribute_helper(img[0]->specmod(), attribname, value, type); + } + return true; } - string_view command = ot.express(argv[0]); - auto options = ot.extract_options(command); - bool allsubimages = options.get_int("allsubimages", ot.allsubimages); - set_attribute(ot.curimg, argv[1], TypeString, argv[2], allsubimages); - // N.B. set_attribute does expression expansion on its args - return 0; -} +private: + string_view attribname; + string_view value; +}; -static int -set_any_attribute(int argc, const char* argv[]) +// Common helper for attrib setting commands +static void +action_attrib_helper(string_view command, cspan argv) { - OIIO_DASSERT(argc == 3); if (!ot.curimg.get()) { - ot.warning(argv[0], "no current image available to modify"); - return 0; + ot.warning(command, "no current image available to modify"); + return; } - - string_view command = ot.express(argv[0]); - auto options = ot.extract_options(command); - bool allsubimages = options.get_int("allsubimages", ot.allsubimages); - TypeDesc type(options["type"].as_string()); - - set_attribute(ot.curimg, argv[1], type, argv[2], allsubimages); - // N.B. set_attribute does expression expansion on its args - return 0; + OpAttribSetter op(ot, command, argv); + op(); } -static bool -do_erase_attribute(ImageSpec& spec, string_view attribname) +// --attrib +static void +action_attrib(cspan argv) { - spec.erase_attribute(attribname); - return true; + OIIO_DASSERT(argv.size() == 3); + action_attrib_helper(argv[0], argv); } -// --eraseattrib -static int -erase_attribute(int argc, const char* argv[]) +// --sattrib +static void +action_sattrib(cspan argv) { - OIIO_DASSERT(argc == 2); - if (!ot.curimg.get()) { - ot.warning(argv[0], "no current image available to modify"); - return 0; - } - string_view command = ot.express(argv[0]); - auto options = ot.extract_options(command); - bool allsubimages = options.get_int("allsubimages", ot.allsubimages); - string_view pattern = ot.express(argv[1]); - return apply_spec_mod(ot, ot.curimg, do_erase_attribute, pattern, - allsubimages); + // Lean on action_attrib, but force it to think it's a string + action_attrib_helper( + argv[0], { Strutil::fmt::format("{}:type=string", argv[0]).c_str(), + argv[1], argv[2] }); } -template -static bool -do_set_any_attribute(ImageSpec& spec, const std::pair& x) +// --eraseattrib +static void +erase_attribute(cspan argv) { - spec.attribute(x.first, x.second); - return true; + // action_attrib already has the property of erasing the attrib if no + // value is in the args. + action_attrib_helper(argv[0], argv); } @@ -2155,159 +2148,11 @@ set_input_attribute(int argc, const char* argv[]) -bool -OiioTool::set_attribute(ImageRecRef img, string_view attribname, TypeDesc type, - string_view value, bool allsubimages) -{ - // Expression substitution - attribname = ot.express(attribname); - value = ot.express(value); - - ot.read(img); - img->metadata_modified(true); - if (!value.size()) { - // If the value is the empty string, clear the attribute - return apply_spec_mod(ot, img, do_erase_attribute, attribname, - allsubimages); - } - - // First, handle the cases where we're told what to expect - if (type.basetype == TypeDesc::FLOAT) { - size_t n = type.numelements() * type.aggregate; - std::vector vals(n, 0.0f); - for (size_t i = 0; i < n && value.size(); ++i) { - Strutil::parse_float(value, vals[i]); - Strutil::parse_char(value, ','); - } - for (int s = 0, send = img->subimages(); s < send; ++s) { - for (int m = 0, mend = img->miplevels(s); m < mend; ++m) { - ((*img)(s, m).specmod()).attribute(attribname, type, &vals[0]); - img->update_spec_from_imagebuf(s, m); - if (!allsubimages) - break; - } - if (!allsubimages) - break; - } - return true; - } - if (type == TypeTimeCode && value.find(':') != value.npos) { - // Special case: They are specifying a TimeCode as a "HH:MM:SS:FF" - // string, we need to re-encode as a uint32[2]. - int hmsf[4] = { 0, 0, 0, 0 }; // hour, min, sec, frame - Strutil::scan_values(value, "", hmsf, ":"); - Imf::TimeCode tc(hmsf[0], hmsf[1], hmsf[2], hmsf[3]); - for (int s = 0, send = img->subimages(); s < send; ++s) { - for (int m = 0, mend = img->miplevels(s); m < mend; ++m) { - ((*img)(s, m).specmod()).attribute(attribname, type, &tc); - img->update_spec_from_imagebuf(s, m); - if (!allsubimages) - break; - } - if (!allsubimages) - break; - } - return true; - } - if (type == TypeRational && value.find('/') != value.npos) { - // Special case: They are specifying a rational as "a/b", so we need - // to re-encode as a int32[2]. - int v[2]; - Strutil::parse_int(value, v[0]); - Strutil::parse_char(value, '/'); - Strutil::parse_int(value, v[1]); - for (int s = 0, send = img->subimages(); s < send; ++s) { - for (int m = 0, mend = img->miplevels(s); m < mend; ++m) { - ((*img)(s, m).specmod()).attribute(attribname, type, v); - img->update_spec_from_imagebuf(s, m); - if (!allsubimages) - break; - } - if (!allsubimages) - break; - } - return true; - } - if (type.basetype == TypeDesc::INT) { - size_t n = type.numelements() * type.aggregate; - std::vector vals(n, 0); - for (size_t i = 0; i < n && value.size(); ++i) { - Strutil::parse_int(value, vals[i]); - Strutil::parse_char(value, ','); - } - for (int s = 0, send = img->subimages(); s < send; ++s) { - for (int m = 0, mend = img->miplevels(s); m < mend; ++m) { - ((*img)(s, m).specmod()).attribute(attribname, type, &vals[0]); - img->update_spec_from_imagebuf(s, m); - if (!allsubimages) - break; - } - if (!allsubimages) - break; - } - return true; - } - if (type.basetype == TypeDesc::STRING) { - size_t n = type.numelements() * type.aggregate; - std::vector vals(n, ustring()); - if (n == 1) - vals[0] = ustring(value); - else { - for (size_t i = 0; i < n && value.size(); ++i) { - string_view s; - Strutil::parse_string(value, s); - vals[i] = ustring(s); - Strutil::parse_char(value, ','); - } - } - for (int s = 0, send = img->subimages(); s < send; ++s) { - for (int m = 0, mend = img->miplevels(s); m < mend; ++m) { - ((*img)(s, m).specmod()).attribute(attribname, type, &vals[0]); - img->update_spec_from_imagebuf(s, m); - if (!allsubimages) - break; - } - if (!allsubimages) - break; - } - return true; - } - - if (type == TypeInt - || (type == TypeUnknown && Strutil::string_is_int(value))) { - // Does it seem to be an int, or did the caller explicitly request - // that it be set as an int? - int v = Strutil::stoi(value); - return apply_spec_mod(ot, img, do_set_any_attribute, - std::pair(attribname, v), - allsubimages); - } else if (type == TypeFloat - || (type == TypeUnknown && Strutil::string_is_float(value))) { - // Does it seem to be a float, or did the caller explicitly request - // that it be set as a float? - float v = Strutil::stof(value); - return apply_spec_mod(ot, img, do_set_any_attribute, - std::pair(attribname, v), - allsubimages); - } else { - // Otherwise, set it as a string attribute - return apply_spec_mod(ot, img, do_set_any_attribute, - std::pair(attribname, - value), - allsubimages); - } -} - - - // --caption -static int -set_caption(int argc, const char* argv[]) +static void +set_caption(cspan argv) { - OIIO_DASSERT(argc == 2); - const char* newargs[3] = { argv[0], "ImageDescription", argv[1] }; - return set_string_attribute(3, newargs); - // N.B. set_string_attribute does expression expansion on its args + action_sattrib({ argv[0], "ImageDescription", argv[1] }); } @@ -2353,36 +2198,21 @@ set_keyword(int argc, const char* argv[]) // --clear-keywords -static int -clear_keywords(int argc, const char* argv[]) +static void +clear_keywords(cspan argv) { - OIIO_DASSERT(argc == 1); - const char* newargs[3]; - newargs[0] = argv[0]; - newargs[1] = "Keywords"; - newargs[2] = ""; - return set_string_attribute(3, newargs); + action_sattrib({ argv[0], "Keywords", "" }); } // --orientation -static int -set_orientation(int argc, const char* argv[]) +static void +set_orientation(cspan argv) { - OIIO_DASSERT(argc == 2); - if (!ot.curimg.get()) { - ot.warning(argv[0], "no current image available to modify"); - return 0; - } - - string_view command = ot.express(argv[0]); - auto options = ot.extract_options(command); - bool allsubimages = options.get_int("allsubimages", ot.allsubimages); - - return set_attribute(ot.curimg, "Orientation", TypeDesc::INT, argv[1], - allsubimages); - // N.B. set_attribute does expression expansion on its args + action_attrib_helper(argv[0], + { Strutil::fmt::format("{}:type=int", argv[0]).c_str(), + "Orientation", argv[1] }); } @@ -2621,13 +2451,10 @@ set_colorconfig(int argc, const char* argv[]) // --iscolorspace -static int -set_colorspace(int argc, const char* argv[]) +static void +set_colorspace(cspan argv) { - OIIO_DASSERT(argc == 2); - const char* args[3] = { argv[0], "oiio:ColorSpace", argv[1] }; - return set_string_attribute(3, args); - // N.B. set_string_attribute does expression expansion on its args + action_sattrib({ argv[0], "oiio:ColorSpace", argv[1] }); } @@ -6356,10 +6183,10 @@ Oiiotool::getargs(int argc, char* argv[]) ap.separator("Options that change current image metadata (but not pixel values):"); ap.arg("--attrib %s:NAME %s:VALUE") .help("Sets metadata attribute (options: type=...)") - .action(set_any_attribute); + .action(action_attrib); ap.arg("--sattrib %s:NAME %s:VALUE") .help("Sets string metadata attribute") - .action(set_string_attribute); + .action(action_sattrib); ap.arg("--eraseattrib %s:REGEX") .help("Erase attributes matching regex") .action(erase_attribute); diff --git a/src/oiiotool/oiiotool.h b/src/oiiotool/oiiotool.h index 2975b60094..530c955d80 100644 --- a/src/oiiotool/oiiotool.h +++ b/src/oiiotool/oiiotool.h @@ -660,16 +660,6 @@ print_stats(std::ostream& out, Oiiotool& ot, const std::string& filename, ROI roi = {}); -// Set an attribute of the given image. The type should be one of -// TypeDesc::INT (decode the value as an int), FLOAT, STRING, or UNKNOWN -// (look at the string and try to discern whether it's an int, float, or -// string). If the 'value' string is empty, it will delete the -// attribute. If allsubimages is true, apply the attribute to all -// subimages, otherwise just the first subimage. -bool -set_attribute(ImageRecRef img, string_view attribname, TypeDesc type, - string_view value, bool allsubimages); - inline bool same_size(const ImageBuf& A, const ImageBuf& B) { @@ -820,6 +810,13 @@ class OiiotoolOp { : OiiotoolOp(ot, opname, argc, argv, ninputs, {}, impl_func) { } + OiiotoolOp(Oiiotool& ot, string_view opname, cspan argv, + int ninputs, setup_func_t setup_func = nullptr, + impl_func_t impl_func = nullptr) + : OiiotoolOp(ot, opname, (int)argv.size(), (const char**)argv.data(), + ninputs, setup_func, impl_func) + { + } virtual ~OiiotoolOp() {} // The operator(), function-call mode, does most of the work. Although @@ -857,7 +854,14 @@ class OiiotoolOp { // Read the inputs subimages = compute_subimages(); // Initialize the output image - m_ir[0] = new_output_imagerec(); + if (inplace() && nimages() >= 2) { + // If instructed to operate in place, just make the output + // another reference to the first input image. + m_ir[0] = m_ir[1]; + } else { + // Not in-place, so make a new output image. + m_ir[0] = new_output_imagerec(); + } ot.push(m_ir[0]); } @@ -1055,6 +1059,7 @@ class OiiotoolOp { string_view args(int i) const { return m_args[i]; } int nimages() const { return m_nimages; } string_view opname() const { return m_opname; } + ParamValueList& options() { return m_options; } const ParamValueList& options() const { return m_options; } ImageBuf* img(int i) const { return m_img[i]; } @@ -1084,6 +1089,11 @@ class OiiotoolOp { void skip_impl(bool val) { m_skip_impl = val; } bool skip_impl() const { return m_skip_impl; } + // Call inplace(true) if the operation should work on the single top + // image *in place*, i.e. not copying to form a new image. + void inplace(bool val) { m_inplace = val; } + bool inplace() const { return m_inplace; } + protected: Oiiotool& ot; std::string m_opname; @@ -1091,6 +1101,7 @@ class OiiotoolOp { int m_nimages; bool m_preserve_miplevels = false; bool m_skip_impl = false; + bool m_inplace = false; std::vector m_ir; std::vector m_img; std::vector m_args; diff --git a/testsuite/oiiotool-subimage/ref/out-oldfreetype.txt b/testsuite/oiiotool-subimage/ref/out-oldfreetype.txt index 0ade03bfbb..85c2762677 100644 --- a/testsuite/oiiotool-subimage/ref/out-oldfreetype.txt +++ b/testsuite/oiiotool-subimage/ref/out-oldfreetype.txt @@ -1,87 +1,67 @@ +Reading gpgr.exr +gpgr.exr : 64 x 64, 3 channel, half openexr + 4 subimages: 64x64 [h,h,h], 64x64 [h,h,h], 64x64 [h,h,h], 64x64 [h,h,h] + subimage 0: 64 x 64, 3 channel, half openexr + SHA-1: 0C27059220A256F197900FB4EB8C7CF63349A26B + channel list: R, G, B + Beatle: "John" + compression: "zip" + name: "layerA" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerA" + oiio:subimages: 4 + openexr:chunkCount: 4 + subimage 1: 64 x 64, 3 channel, half openexr + SHA-1: 0E19BEFEF868E356A6A4C6450DA9A7B17DD11E12 + channel list: R, G, B + Beatle: "Paul" + compression: "zip" + name: "layerB" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerB" + oiio:subimages: 4 + openexr:chunkCount: 4 + subimage 2: 64 x 64, 3 channel, half openexr + SHA-1: CFAF4AFC253320AC35B8E9014C6D750768354059 + channel list: R, G, B + Beatle: "George" + compression: "zip" + name: "layerC" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerC" + oiio:subimages: 4 + openexr:chunkCount: 4 + subimage 3: 64 x 64, 3 channel, half openexr + SHA-1: 5FFA4616F46509627873D2C53744E47E2F492719 + channel list: R, G, B + Beatle: "Ringo" + compression: "zip" + name: "layerD" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerD" + oiio:subimages: 4 + openexr:chunkCount: 4 Comparing "subimages-2.exr" and "ref/subimages-2.exr" PASS -Comparing "subimages-4.exr" and "ref/subimages-4.exr" -Subimage 0 : 64 x 64, 3 channel - Mean error = 0.00193232 - RMS error = 0.0210741 - Peak SNR = 33.525 - Max error = 0.48999 @ (40, 31, G) - 63 pixels (1.54%) over 0.008 - 64 pixels (1.56%) over 0.004 -Subimage 1 : 64 x 64, 3 channel - Mean error = 0.00369091 - RMS error = 0.0395986 - Peak SNR = 28.0464 - Max error = 0.670654 @ (33, 20, R) - 75 pixels (1.83%) over 0.008 - 75 pixels (1.83%) over 0.004 -Subimage 2 : 64 x 64, 3 channel - Mean error = 0.00266418 - RMS error = 0.0307123 - Peak SNR = 30.2537 - Max error = 0.674561 @ (32, 25, R) - 54 pixels (1.32%) over 0.008 - 56 pixels (1.37%) over 0.004 -Subimage 3 : 64 x 64, 3 channel - Mean error = 0.00319004 - RMS error = 0.0335558 - Peak SNR = 29.4846 - Max error = 0.670654 @ (33, 20, B) - 72 pixels (1.76%) over 0.008 - 75 pixels (1.83%) over 0.004 -FAILURE -Comparing "subimages-4.exr" and "ref/subimageB1-oldfreetype.exr" -Subimage 0 : 64 x 64, 3 channel - Mean error = 0.337 - RMS error = 0.412972 - Peak SNR = 7.68159 - Max error = 1 @ (34, 20, G) - 4095 pixels (100%) over 0.008 - 4095 pixels (100%) over 0.004 -FAILURE -Comparing "subimages-4.exr" and "ref/subimageD3-oldfreetype.exr" -Subimage 0 : 64 x 64, 3 channel - Mean error = 0.173686 - RMS error = 0.297406 - Peak SNR = 10.533 - Max error = 1 @ (34, 20, G) - 4095 pixels (100%) over 0.008 - 4095 pixels (100%) over 0.004 -FAILURE -Comparing "subimages-4.exr" and "ref/subimages-4-oldfreetype.exr" +Comparing "subimages-4.exr" and "ref/subimages-4-freetype2.7.exr" PASS Comparing "subimage1.exr" and "ref/subimage1.exr" PASS Comparing "subimage2.exr" and "ref/subimage2.exr" PASS -Comparing "subimageD3.exr" and "ref/subimageD3.exr" -64 x 64, 3 channel - Mean error = 0.00319004 - RMS error = 0.0335558 - Peak SNR = 29.4846 - Max error = 0.670654 @ (33, 20, B) - 72 pixels (1.76%) over 0.008 - 75 pixels (1.83%) over 0.004 -FAILURE -Comparing "subimageD3.exr" and "ref/subimageB1-oldfreetype.exr" -64 x 64, 3 channel - Mean error = 0.16731 - RMS error = 0.289494 - Peak SNR = 10.7672 - Max error = 1 @ (36, 25, B) - 4079 pixels (99.6%) over 0.008 - 4079 pixels (99.6%) over 0.004 -FAILURE -Comparing "subimageD3.exr" and "ref/subimageD3-oldfreetype.exr" +Comparing "subimageD3.exr" and "ref/subimageD3-freetype2.7.exr" PASS -Comparing "subimageB1.exr" and "ref/subimageB1.exr" -64 x 64, 3 channel - Mean error = 0.00369091 - RMS error = 0.0395986 - Peak SNR = 28.0464 - Max error = 0.670654 @ (33, 20, R) - 75 pixels (1.83%) over 0.008 - 75 pixels (1.83%) over 0.004 -FAILURE -Comparing "subimageB1.exr" and "ref/subimageB1-oldfreetype.exr" +Comparing "subimageB1.exr" and "ref/subimageB1-freetype2.7.exr" PASS diff --git a/testsuite/oiiotool-subimage/ref/out.txt b/testsuite/oiiotool-subimage/ref/out.txt index 9abfd93482..65f0344291 100644 --- a/testsuite/oiiotool-subimage/ref/out.txt +++ b/testsuite/oiiotool-subimage/ref/out.txt @@ -1,3 +1,58 @@ +Reading gpgr.exr +gpgr.exr : 64 x 64, 3 channel, half openexr + 4 subimages: 64x64 [h,h,h], 64x64 [h,h,h], 64x64 [h,h,h], 64x64 [h,h,h] + subimage 0: 64 x 64, 3 channel, half openexr + SHA-1: 0C27059220A256F197900FB4EB8C7CF63349A26B + channel list: R, G, B + Beatle: "John" + compression: "zip" + name: "layerA" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerA" + oiio:subimages: 4 + openexr:chunkCount: 4 + subimage 1: 64 x 64, 3 channel, half openexr + SHA-1: 0E19BEFEF868E356A6A4C6450DA9A7B17DD11E12 + channel list: R, G, B + Beatle: "Paul" + compression: "zip" + name: "layerB" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerB" + oiio:subimages: 4 + openexr:chunkCount: 4 + subimage 2: 64 x 64, 3 channel, half openexr + SHA-1: CFAF4AFC253320AC35B8E9014C6D750768354059 + channel list: R, G, B + Beatle: "George" + compression: "zip" + name: "layerC" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerC" + oiio:subimages: 4 + openexr:chunkCount: 4 + subimage 3: 64 x 64, 3 channel, half openexr + SHA-1: 5FFA4616F46509627873D2C53744E47E2F492719 + channel list: R, G, B + Beatle: "Ringo" + compression: "zip" + name: "layerD" + PixelAspectRatio: 1 + screenWindowCenter: 0, 0 + screenWindowWidth: 1 + oiio:ColorSpace: "Linear" + oiio:subimagename: "layerD" + oiio:subimages: 4 + openexr:chunkCount: 4 Comparing "subimages-2.exr" and "ref/subimages-2.exr" PASS Comparing "subimages-4.exr" and "ref/subimages-4.exr" diff --git a/testsuite/oiiotool-subimage/run.py b/testsuite/oiiotool-subimage/run.py index 3bd7a2ca42..a6748e7811 100755 --- a/testsuite/oiiotool-subimage/run.py +++ b/testsuite/oiiotool-subimage/run.py @@ -17,12 +17,16 @@ command += oiiotool ("subimages-4.exr --subimage layerB -o subimageB1.exr") command += oiiotool ("subimages-2.exr --sisplit -o:all=1 subimage%d.exr") +# test that we can set attributes on individual subimages +command += oiiotool ("subimages-4.exr --attrib:subimages=0 Beatle John --attrib:subimages=1 Beatle Paul --attrib:subimages=2 Beatle George --attrib:subimages=3 Beatle Ringo -o gpgr.exr") +command += info_command ("-a -v gpgr.exr", safematch=1) # Outputs to check against references outputs = [ "subimages-2.exr", "subimages-4.exr", "subimage1.exr", "subimage2.exr", - "subimageD3.exr", "subimageB1.exr" + "subimageD3.exr", "subimageB1.exr", + "out.txt" ] #print "Running this command:\n" + command + "\n"