Skip to content

Commit

Permalink
feat(oiiotool): oiiotool --layersplit, new command to split layers (#…
Browse files Browse the repository at this point in the history
…4591)

Implement FR from #4546

Add a new `--layersplit` command to `oiiotool` to split an image into
its channel-name-based layers onto the stack.

The extracted layer names are then stored in the `oiio:subimagename`
metadata (in case it is needed for later use, e.g merging these "layers"
as sub-images), and the extracted channel names replace the old channel
names in the new images.

Example:
an image with channels `R, G, B, A, diffuse.R, diffuse.G, diffuse.B`
will be split into two images, the first one with channels `R, G, B, A`
and the second one named `diffuse` with channels `R, G, B`.

> Note: we did not implement the `--layertosi` command suggested in the
FR as it can already be done with a combination of `--layersplit` and
`--siappendall`

We add a test in the test suite that takes a "multi-layer" image, splits
the layers and merges them as sub-images. The final image should have 3
sub-images.

---------

Signed-off-by: Loïc Vital <mugulmotion@gmail.com>
  • Loading branch information
mugulmd authored Jan 22, 2025
1 parent 280e1c7 commit 2677d4c
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/cmake/testing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ macro (oiio_add_all_tests)
oiiotool-copy
oiiotool-demosaic
oiiotool-fixnan
oiiotool-layers
oiiotool-pattern
oiiotool-readerror
oiiotool-subimage
Expand Down
12 changes: 12 additions & 0 deletions src/doc/oiiotool.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2193,6 +2193,18 @@ current top image.
image comprised of the subimages of all original images appended
together.

.. option:: --layersplit

Remove the top image from the stack, split it into a separate image for
each of its constituent channel-name-based layers, and push them all
onto the stack (first to last).

By "layer" we mean a subset of the initial channels which, when named
using the convention "LAYERNAME.channelname", all share the same layer
name. Channels that do not contain a dot in their name are considered
to be part of an anonymous layer, and thus are all gathered into a
single image (the first one pushed on the stack).

.. option:: --ch <channellist>

Replaces the top image with a new image whose channels have been
Expand Down
76 changes: 76 additions & 0 deletions src/oiiotool/oiiotool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2880,6 +2880,79 @@ action_subimage_split(Oiiotool& ot, cspan<const char*> argv)



// --layersplit
static void
action_layer_split(Oiiotool& ot, cspan<const char*> argv)
{
if (ot.postpone_callback(1, action_layer_split, argv))
return;
string_view command = ot.express(argv[0]);
OTScopedTimer timer(ot, command);

ImageRecRef A = ot.pop();
ot.read(A);

// Split and push the individual channel-name-based layers onto the stack
ImageSpec* spec = A->spec();
int chbegin = 0;
std::vector<std::string> newchannelnames;
for (size_t i = 0; i < spec->channelnames.size(); ++i) {
// Parse full channel name to extract the layer name
// and the actual channel name, which will be used for
// renaming channels during the split
// Examples:
// chname = "R" -> layername = "", newchname = "R"
// chname = "diffuse.G" -> layername = "diffuse", newchname = "G"
const std::string& chname = spec->channelnames[i];
const auto parts = Strutil::splits(chname, ".", 2);
const std::string layername = (parts.size() < 2) ? "" : parts[0];
const std::string newchname = (parts.size() < 2) ? parts[0] : parts[1];
newchannelnames.push_back(newchname);

bool pushlayer = false;
if (i < spec->channelnames.size() - 1) {
// Parse the layer name of the next channel,
// a different value means that we will be processing
// a new layer at the next iteration, therefore
// we should push the current one on the stack
const std::string& nextchname = spec->channelnames[i + 1];
const auto nextparts = Strutil::splits(nextchname, ".", 2);
const std::string nextlayername = (nextparts.size() < 2)
? ""
: nextparts[0];
pushlayer = (nextlayername != layername);
} else {
// Force flag to true at last iteration so that
// last layer is also pushed on the stack
pushlayer = true;
}

if (pushlayer) {
// Split the current layer by isolating its channels
// in a new ImageBuf and renaming them, and store the
// layer name in the oiio:subimagename metadata so we
// can reuse it later (e.g for creating a multi-part image)
const int chend = i + 1;
ImageBufRef img(new ImageBuf());
std::vector<int> channelorder(chend - chbegin);
std::iota(channelorder.begin(), channelorder.end(), chbegin);
ImageBufAlgo::channels(*img, (*A)(), chend - chbegin, channelorder,
{}, newchannelnames);
img->specmod().attribute("oiio:subimagename", layername);

// Create corresponding ImageRec and push it on the stack
ImageRecRef R(new ImageRec(img, true));
ot.push(R);

// Prepare processing of next layer
chbegin = chend;
newchannelnames.clear();
}
}
}



static void
action_subimage_append_n(Oiiotool& ot, int n, string_view command)
{
Expand Down Expand Up @@ -6898,6 +6971,9 @@ Oiiotool::getargs(int argc, char* argv[])
ap.arg("--siappendall")
.help("Append all images on the stack into a single multi-subimage image")
.OTACTION(action_subimage_append_all);
ap.arg("--layersplit")
.help("Split the top image's channel-name-based layers into separate images on the stack")
.OTACTION(action_layer_split);
ap.arg("--deepen")
.help("Deepen normal 2D image to deep")
.OTACTION(action_deepen);
Expand Down
2 changes: 2 additions & 0 deletions testsuite/oiiotool-layers/ref/out.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Comparing "parts.exr" and "ref/parts.exr"
PASS
Binary file added testsuite/oiiotool-layers/ref/parts.exr
Binary file not shown.
17 changes: 17 additions & 0 deletions testsuite/oiiotool-layers/run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python

# Copyright Contributors to the OpenImageIO project.
# SPDX-License-Identifier: Apache-2.0
# https://github.com/AcademySoftwareFoundation/OpenImageIO


# Test for oiiotool channel-name-based layer splitting
#


# test --layersplit
command += oiiotool ("src/layers.exr --layersplit --siappendall -o parts.exr")

# Outputs to check against references
outputs = [ "parts.exr", "out.txt" ]

Binary file added testsuite/oiiotool-layers/src/layers.exr
Binary file not shown.

0 comments on commit 2677d4c

Please sign in to comment.