Skip to content

Commit

Permalink
feat(oiiotool): additional stack commands and --for improvement (#4348)
Browse files Browse the repository at this point in the history
Additional stack manipulation commands:
* `--popbottom` discards the bottom-of-stack image
* `--stackreverse` reverses the order of the whole stack
* `--stackclear` fully empties the stack
* `--stackextract <index>` moves the indexed item from the stack (index
0 means the top) to the top.

Make `--for` work correctly in both directions:
* Correct behavior if `--for` has a negative step value.
* If the end value is less than the begin value and no step is supplied,
assume -1 (analogous to how we usually assueme step=1 under ordinary
circumstances).
* Error if step is 0 (presume it will make an infinite loop).

Signed-off-by: Larry Gritz <[email protected]>
  • Loading branch information
lgritz committed Aug 7, 2024
1 parent 8a09888 commit e947e79
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 46 deletions.
70 changes: 50 additions & 20 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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 <condition> commands... --endwhile

If the *condition* is true, execute *commands*, and keep doing that
Expand Down Expand Up @@ -2230,10 +2239,16 @@ current top image.
:program:`oiiotool` commands that adjust the image stack
========================================================

.. option:: --pop
.. option:: --label <name>

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

Expand All @@ -2245,16 +2260,31 @@ current top image.

Swap the current image and the next one on the stack.

.. option:: --label <name>
.. 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 <index>

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 <index>

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
Expand Down
122 changes: 109 additions & 13 deletions src/oiiotool/oiiotool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1234,12 +1234,30 @@ control_for(Oiiotool& ot, cspan<const char*> 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<float>(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;
}
Expand All @@ -1249,24 +1267,22 @@ control_for(Oiiotool& ot, cspan<const char*> 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,
Expand Down Expand Up @@ -3439,6 +3455,16 @@ action_pop(Oiiotool& ot, cspan<const char*> argv)



// --popbottom
static void
action_popbottom(Oiiotool& ot, cspan<const char*> argv)
{
OIIO_DASSERT(argv.size() == 1);
ot.popbottom();
}



// --dup
static void
action_dup(Oiiotool& ot, cspan<const char*> argv)
Expand Down Expand Up @@ -3467,6 +3493,64 @@ action_swap(Oiiotool& ot, cspan<const char*> argv)



// --stackreverse
static void
action_stackreverse(Oiiotool& ot, cspan<const char*> 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<const char*> 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<const char*> argv)
{
OIIO_DASSERT(argv.size() == 1);
ot.image_stack.clear();
ot.curimg = ImageRecRef();
}



// --create
static void
action_create(Oiiotool& ot, cspan<const char*> argv)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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")
Expand Down
11 changes: 11 additions & 0 deletions src/oiiotool/oiiotool.h
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
49 changes: 49 additions & 0 deletions testsuite/oiiotool-control/ref/out.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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:
Expand Down
Loading

0 comments on commit e947e79

Please sign in to comment.