diff --git a/src/doc/oiiotool.rst b/src/doc/oiiotool.rst index ed14ff5a82..47d39ae177 100644 --- a/src/doc/oiiotool.rst +++ b/src/doc/oiiotool.rst @@ -273,11 +273,12 @@ The usual programming constructs are supported: * Iteration : `--for` *variable* *range* *commands...* `--endfor` The range is a sequence of one to three comma-separated numbers: *begin*, - *end*, and *step*; *begin* and *end* (step is assumed to be 1); or just - *end* (begin assumed to be 0, step assumed to be 1). As in Python, the range - has an "exclusive end" -- when the *variable* is equal to *end*, the loop - will terminate, without actually running the commands for the *end* value - itself. + *end*, and *step*; *begin* and *end* (step is assumed to be 1 if *begin* + `<`` *end*, or -1 if *begin* `>` *end); or just *end* (begin assumed to be + 0, step assumed to be 1 or -1, depending on the relationship between *begin* + and *end*). As in Python, the range has an "exclusive end" -- when the + *variable* is equal to *end*, the loop will terminate, without actually + running the commands for the *end* value itself. Section :ref:`sec-oiiotool-control-flow-commands` contains more detailed descriptions of these commands and some examples to more clearly illustrate @@ -1023,11 +1024,12 @@ output each one to a different file, with names `sub0001.tif`, for each iteration. The range may be one, two, or three numbers separated by commas, indicating - - *end* : Iterate from 0 to *end*, incrementing by 1 each time. - - *begin* ``,`` *end* : Iterate from *begin* to *end*, incrementing - by 1 each time. + - *end* : Iterate from 0 to *end*, incrementing by 1 each iteration (or + decrementing, if *end* `<` 0). + - *begin* ``,`` *end* : Iterate from *begin* to *end*, incrementing by + 1 each iteration (or decrementing by 1, if *end* `<` *begin*). - *begin* ``,`` *end* ``,`` *step* : Iterate from *begin* to *end*, - incrementing by *step* each time. + adding *step* to the value after each iteration. Note that the *end* value is "exclusive," that is, the loop will terminate once the value is equal to end, and the loop body will @@ -1054,6 +1056,13 @@ output each one to a different file, with names `sub0001.tif`, 7 9 + $ oiiotool --for i 5,0,-1 --echo "i = {i}" --endfor + 5 + 4 + 3 + 2 + 1 + .. option:: --while commands... --endwhile If the *condition* is true, execute *commands*, and keep doing that @@ -2230,10 +2239,16 @@ current top image. :program:`oiiotool` commands that adjust the image stack ======================================================== -.. option:: --pop +.. option:: --label - Pop the image stack, discarding the current image and thereby making the - next image on the stack into the new current image. + Gives a name to (and saves) the current image at the top of the stack. + Thereafter, the label name may be used to refer to that saved image, in + the usual manner that an ordinary input image would be specified by + filename. + + The name of the label must be in the form of an "identifier" (a sequence + of alphanumeric characters and underscores, starting with a letter or + underscore). .. option:: --dup @@ -2245,16 +2260,31 @@ current top image. Swap the current image and the next one on the stack. -.. option:: --label +.. option:: --pop - Gives a name to (and saves) the current image at the top of the stack. - Thereafter, the label name may be used to refer to that saved image, in - the usual manner that an ordinary input image would be specified by - filename. + Pop the image stack, discarding the current image and thereby making the + next image on the stack into the new current image. - The name of the label must be in the form of an "identifier" (a sequence - of alphanumeric characters and underscores, starting with a letter or - underscore). +.. option:: --popbottom + + Remove and discard the bottom image from the image stack. + (Added in OIIO 3.0.) + +.. option:: --stackreverse + + Reverse the order of the entire stack, i.e. making the top be the new + bottom and the old bottom be the new top. (Added in OIIO 3.0.) + +.. option:: --stackextract + + Move the indexed item (0 for the top of the stack, 1 for the next item + down, etc.) to the top of the stack, preserving the relative order of all + other items. (Added in OIIO 3.0.) + +.. option:: --stackclear + + Remove all items from the stack, leaving it empty and with no "current" + image. (Added in OIIO 3.0.) :program:`oiiotool` commands that make entirely new images diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index a7e07b35cc..e517d6bd20 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -1234,12 +1234,30 @@ control_for(Oiiotool& ot, cspan argv) std::string variable = ot.express(argv[1]); string_view range = ot.express(argv[2]); + float val = 0, limit = 0, step = 1; + bool valid = true; auto rangevals = Strutil::extract_from_list_string(range); - if (rangevals.size() == 1) - rangevals.insert(rangevals.begin(), 0.0f); // supply missing start - if (rangevals.size() == 2) - rangevals.push_back(1.0f); // supply missing step - if (rangevals.size() != 3) { + if (rangevals.size() == 1) { + val = 0.0f; + limit = rangevals[0]; + step = limit >= 0.0f ? 1.0f : -1.0f; + } else if (rangevals.size() == 2) { + val = rangevals[0]; + limit = rangevals[1]; + step = limit >= val ? 1.0f : -1.0f; + } else if (rangevals.size() == 3) { + val = rangevals[0]; + limit = rangevals[1]; + step = rangevals[2]; + } else { + valid = false; + } + // step can't be zero or be opposite direction of val -> limit + valid &= (step != 0.0f); + if ((val < limit && step < 0.0f) || (val > limit && step > 0.0f)) + valid = false; + + if (!valid) { ot.errorfmt(argv[0], "Invalid range \"{}\"", range); return; } @@ -1249,24 +1267,22 @@ control_for(Oiiotool& ot, cspan argv) // There are two cases here: either we are hitting this --for // for the first time (need to initialize and set up the control // record), or we are re-iterating on a loop we already set up. - float val; if (ot.control_stack.empty() || ot.control_stack.top().start_arg != ot.ap.current_arg()) { // First time through the loop. Note that we recognize our first // time by the fact that the top of the control stack doesn't have // a start_arg that is this --for command. - val = rangevals[0]; ot.push_control("for", ot.ap.current_arg(), true); // Strutil::print("First for!\n"); } else { // We've started this loop already, this is at least our 2nd time // through. Just increment the variable and update the condition // for another pass through the loop. - val = ot.uservars.get_float(variable) + rangevals[2]; + val = ot.uservars.get_float(variable) + step; // Strutil::print("Repeat for!\n"); } ot.uservars.attribute(variable, val); - bool cond = val < rangevals[1]; + bool cond = step >= 0.0f ? val < limit : val > limit; ot.control_stack.top().condition = cond; ot.ap.running(ot.running()); // Strutil::print("for {} {} : {}={} cond={} (now running={})\n", variable, @@ -3439,6 +3455,16 @@ action_pop(Oiiotool& ot, cspan argv) +// --popbottom +static void +action_popbottom(Oiiotool& ot, cspan argv) +{ + OIIO_DASSERT(argv.size() == 1); + ot.popbottom(); +} + + + // --dup static void action_dup(Oiiotool& ot, cspan argv) @@ -3467,6 +3493,64 @@ action_swap(Oiiotool& ot, cspan argv) +// --stackreverse +static void +action_stackreverse(Oiiotool& ot, cspan argv) +{ + OIIO_DASSERT(argv.size() == 1); + string_view command = ot.express(argv[0]); + if (!ot.curimg) { + ot.error(command, "requires at least one loaded images"); + return; + } + if (ot.image_stack.empty()) + return; // only curimg -- reversing does nothing + ot.image_stack.push_back(ot.curimg); + std::reverse(ot.image_stack.begin(), ot.image_stack.end()); + ot.curimg = ot.image_stack.back(); + ot.image_stack.pop_back(); +} + + + +// --stackextract +static void +action_stackextract(Oiiotool& ot, cspan argv) +{ + OIIO_DASSERT(argv.size() == 2); + string_view command = ot.express(argv[0]); + int index = Strutil::stoi(ot.express(argv[1])); + if (index < 0 || index >= ot.image_stack_depth()) { + ot.errorfmt(command, "index {} out of range for stack depth {}", index, + ot.image_stack_depth()); + return; + } + if (ot.image_stack.empty()) + return; // only curimg -- extract does nothing + ot.image_stack.push_back(ot.curimg); + // Transform the index to the index of the stack data structure + index = int(ot.image_stack.size()) - 1 - index; + // Copy that item for safe keeping + ImageRecRef newtop = ot.image_stack[index]; + // Remove it from the stack + ot.image_stack.erase(ot.image_stack.begin() + size_t(index)); + // Now put it back on the top + ot.curimg = newtop; +} + + + +// --stackclear +static void +action_stackclear(Oiiotool& ot, cspan argv) +{ + OIIO_DASSERT(argv.size() == 1); + ot.image_stack.clear(); + ot.curimg = ImageRecRef(); +} + + + // --create static void action_create(Oiiotool& ot, cspan argv) @@ -6135,7 +6219,7 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("-n", &ot.dryrun) .help("No saved output (dry run)"); ap.arg("--no-error-exit", ot.noerrexit) - .help("Do not exit upon error, try to process additional comands (danger!)"); + .help("Do not exit upon error, try to process additional commands (danger!)"); ap.arg("-a", &ot.allsubimages) .help("Do operations on all subimages/miplevels"); ap.arg("--debug", &ot.debug) @@ -6708,6 +6792,9 @@ Oiiotool::getargs(int argc, char* argv[]) .OTACTION(action_flatten); ap.separator("Image stack manipulation:"); + ap.arg("--label %s") + .help("Label the top image") + .OTACTION(action_label); ap.arg("--dup") .help("Duplicate the current image (push a copy onto the stack)") .OTACTION(action_dup); @@ -6717,9 +6804,18 @@ Oiiotool::getargs(int argc, char* argv[]) ap.arg("--pop") .help("Throw away the current image") .OTACTION(action_pop); - ap.arg("--label %s") - .help("Label the top image") - .OTACTION(action_label); + ap.arg("--popbottom") + .help("Throw away the image on the bottom of the stack") + .OTACTION(action_popbottom); + ap.arg("--stackreverse") + .help("Throw away the image on the bottom of the stack") + .OTACTION(action_stackreverse); + ap.arg("--stackextract %d:INDEX") + .help("Move an indexed stack item to the top of the stack") + .OTACTION(action_stackextract); + ap.arg("--stackclear") + .help("Remove all images from the stack, leaving it empty") + .OTACTION(action_stackclear); ap.separator("Color management:"); ap.arg("--colorconfiginfo") diff --git a/src/oiiotool/oiiotool.h b/src/oiiotool/oiiotool.h index a21a223d16..66e87255eb 100644 --- a/src/oiiotool/oiiotool.h +++ b/src/oiiotool/oiiotool.h @@ -259,6 +259,17 @@ class Oiiotool { return r; } + void popbottom() + { + if (image_stack.size()) { + // There are images on the full stack -- get rid of the bottom + image_stack.erase(image_stack.begin()); + } else { + // Nothing on the stack, so get rid of the current image + curimg = ImageRecRef(); + } + } + ImageRecRef top() { return curimg; } // How many images are on the stack? diff --git a/testsuite/oiiotool-control/ref/out.txt b/testsuite/oiiotool-control/ref/out.txt index 152a08b14f..ba8028348d 100644 --- a/testsuite/oiiotool-control/ref/out.txt +++ b/testsuite/oiiotool-control/ref/out.txt @@ -1,3 +1,38 @@ +Stack holds [0] = d.tif, [1] = c.tif, [2] = b.tif +TOP = d.tif, BOTTOM = a.tif +Stack bottom to top: + a.tif + b.tif + c.tif + d.tif +after --stackreverse: + d.tif + c.tif + b.tif + a.tif +after --stackreverse: + a.tif + b.tif + c.tif + d.tif +after --pop: + a.tif + b.tif + c.tif +after --popbottom: + b.tif + c.tif +after --stackclear: +Re-add a, b, c, d: + a.tif + b.tif + c.tif + d.tif +--stackextract 2: + a.tif + c.tif + d.tif + b.tif 42+2 = 44 42-2 = 40 42*2 = 84 @@ -114,6 +149,20 @@ Testing for i 5,10,2 (expect output 5,7,9): i = 7 i = 9 +Testing for i 10,5,-1 (expect output 10..6): + i = 10 + i = 9 + i = 8 + i = 7 + i = 6 + +Testing for i 10,5 (expect output 10..6): + i = 10 + i = 9 + i = 8 + i = 7 + i = 6 + Testing endfor without for: oiiotool ERROR: -endfor : endfor without matching for Full command line was: diff --git a/testsuite/oiiotool-control/run.py b/testsuite/oiiotool-control/run.py index 4ca8031b69..79eee9038d 100755 --- a/testsuite/oiiotool-control/run.py +++ b/testsuite/oiiotool-control/run.py @@ -14,13 +14,46 @@ redirect += " 2>&1" failureok = True +# Make some temp files +command += oiiotool ('-pattern:type=uint8 constant:color=1,0,0 2x2 3 -o a.tif ' + + '-pattern:type=uint8 constant:color=0,1,0 2x2 3 -o b.tif ' + + '-pattern:type=uint8 constant:color=0,0,1 2x2 3 -o c.tif ' + + '-pattern:type=uint8 constant:color=1,1,1 2x2 3 -o d.tif ') + +# Test TOP, BOTTOM, IMG[] +# TOP should be c.tif, BOTTOM should be a.tif +command += oiiotool ("a.tif b.tif c.tif d.tif " + + "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}, [2] = {IMG[2].filename}\" " + + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " + ) +# Test --pop, --popbottom, --stackreverse, --stackclear, --stackextract +command += oiiotool ( + "a.tif b.tif c.tif d.tif " + + "--echo \"Stack bottom to top:\" " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"after --stackreverse:\" --stackreverse " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"after --stackreverse:\" --stackreverse " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"after --pop:\" --pop " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"after --popbottom:\" --popbottom " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"after --stackclear:\" --stackclear " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"Re-add a, b, c, d:\" " + + "a.tif b.tif c.tif d.tif " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + + "--echo \"--stackextract 2:\" --stackextract 2 " + + "--for i 0,{NIMAGES} --echo \" {IMG[NIMAGES-1-i].filename}\" --endfor " + ) + # test expression substitution command += oiiotool ('-echo "42+2 = {42+2}" ' + '-echo "42-2 = {42-2}" ' + '-echo "42*2 = {42*2}" ' + - '-echo "42/2 = {42/2}"') - -command += oiiotool ('-echo "42<41 = {42<41}" ' + + '-echo "42/2 = {42/2}" ' + + '-echo "42<41 = {42<41}" ' + '-echo "42<42 = {42<42}" ' + '-echo "42<43 = {42<43}" ' + '-echo "42<=41 = {42<=41}" ' + @@ -40,9 +73,8 @@ '-echo "42!=43 = {42!=43}" ' + '-echo "42<=>41 = {42<=>41}" ' + '-echo "42<=>42 = {42<=>42}" ' + - '-echo "42<=>43 = {42<=>43}" ') - -command += oiiotool ('-echo "(1==2)&&(2==2) = {(1==2)&&(2==2)}" ' + + '-echo "42<=>43 = {42<=>43}" ' + + '-echo "(1==2)&&(2==2) = {(1==2)&&(2==2)}" ' + '-echo "(1==1)&&(2==2) = {(1==1)&&(2==2)}" ' + '-echo "(1==2)&&(1==2) = {(1==2)&&(1==2)}" ' + '-echo "(1==2)||(2==2) = {(1==2)||(2==2)}" ' + @@ -51,12 +83,11 @@ '-echo "not(1==1) = {not(1==1)}" ' + '-echo "not(1==2) = {not(1==2)}" ' + '-echo "!(1==1) = {!(1==1)}" ' + - '-echo "!(1==2) = {!(1==2)}"') - -command += oiiotool ('-echo "eq(foo,foo) = {eq(\'foo\',\'foo\')}" ' + + '-echo "!(1==2) = {!(1==2)}" ' + + '-echo "eq(foo,foo) = {eq(\'foo\',\'foo\')}" ' + '-echo "eq(foo,bar) = {eq(\'foo\',\'bar\')}" ' + '-echo "neq(foo,foo) = {neq(\'foo\',\'foo\')}" ' + - '-echo "neq(foo,bar) = {neq(\'foo\',\'bar\')}"') + '-echo "neq(foo,bar) = {neq(\'foo\',\'bar\')}" ') command += oiiotool ('-echo "16+5={16+5}" -echo "16-5={16-5}" -echo "16*5={16*5}"') command += oiiotool ('-echo "16/5={16/5}" -echo "16//5={16//5}" -echo "16%5={16%5}"') @@ -107,12 +138,17 @@ command += oiiotool ('-echo "Testing endwhile without while:" -endwhile -echo " "') # Test --for --endfor -command += oiiotool ('-echo "Testing for i 5 (expect output 0..4):" --for i 5 --echo " i = {i}" --endfor -echo " "') -command += oiiotool ('-echo "Testing for i 5,10 (expect output 5..9):" --for i 5,10 --echo " i = {i}" --endfor -echo " "') -command += oiiotool ('-echo "Testing for i 5,10,2 (expect output 5,7,9):" --for i 5,10,2 --echo " i = {i}" --endfor -echo " "') +command += oiiotool ( + '-echo "Testing for i 5 (expect output 0..4):" --for i 5 --echo " i = {i}" --endfor -echo " " ' + + '-echo "Testing for i 5,10 (expect output 5..9):" --for i 5,10 --echo " i = {i}" --endfor -echo " " ' + + '-echo "Testing for i 5,10,2 (expect output 5,7,9):" --for i 5,10,2 --echo " i = {i}" --endfor -echo " " ' + + '-echo "Testing for i 10,5,-1 (expect output 10..6):" --for i 10,5,-1 --echo " i = {i}" --endfor -echo " " ' + + '-echo "Testing for i 10,5 (expect output 10..6):" --for i 10,5 --echo " i = {i}" --endfor -echo " " ' + ) command += oiiotool ('-echo "Testing endfor without for:" -endfor -echo " "') command += oiiotool ('-echo "Testing for i 5,10,2,8 (bad range):" --for i 5,10,2,8 --echo " i = {i}" --endfor -echo " "') + # test sequences command += oiiotool ("../common/tahoe-tiny.tif -o copyA.1-10#.jpg") command += oiiotool ("--debug copyA.#.jpg -o copyB.#.jpg")