Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(oiiotool): additional stack commands and --for improvement #4348

Merged
merged 2 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading