Skip to content

Commit 2677d4c

Browse files
authored
feat(oiiotool): oiiotool --layersplit, new command to split layers (#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>
1 parent 280e1c7 commit 2677d4c

7 files changed

Lines changed: 108 additions & 0 deletions

File tree

src/cmake/testing.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ macro (oiio_add_all_tests)
141141
oiiotool-copy
142142
oiiotool-demosaic
143143
oiiotool-fixnan
144+
oiiotool-layers
144145
oiiotool-pattern
145146
oiiotool-readerror
146147
oiiotool-subimage

src/doc/oiiotool.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2193,6 +2193,18 @@ current top image.
21932193
image comprised of the subimages of all original images appended
21942194
together.
21952195

2196+
.. option:: --layersplit
2197+
2198+
Remove the top image from the stack, split it into a separate image for
2199+
each of its constituent channel-name-based layers, and push them all
2200+
onto the stack (first to last).
2201+
2202+
By "layer" we mean a subset of the initial channels which, when named
2203+
using the convention "LAYERNAME.channelname", all share the same layer
2204+
name. Channels that do not contain a dot in their name are considered
2205+
to be part of an anonymous layer, and thus are all gathered into a
2206+
single image (the first one pushed on the stack).
2207+
21962208
.. option:: --ch <channellist>
21972209

21982210
Replaces the top image with a new image whose channels have been

src/oiiotool/oiiotool.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2880,6 +2880,79 @@ action_subimage_split(Oiiotool& ot, cspan<const char*> argv)
28802880

28812881

28822882

2883+
// --layersplit
2884+
static void
2885+
action_layer_split(Oiiotool& ot, cspan<const char*> argv)
2886+
{
2887+
if (ot.postpone_callback(1, action_layer_split, argv))
2888+
return;
2889+
string_view command = ot.express(argv[0]);
2890+
OTScopedTimer timer(ot, command);
2891+
2892+
ImageRecRef A = ot.pop();
2893+
ot.read(A);
2894+
2895+
// Split and push the individual channel-name-based layers onto the stack
2896+
ImageSpec* spec = A->spec();
2897+
int chbegin = 0;
2898+
std::vector<std::string> newchannelnames;
2899+
for (size_t i = 0; i < spec->channelnames.size(); ++i) {
2900+
// Parse full channel name to extract the layer name
2901+
// and the actual channel name, which will be used for
2902+
// renaming channels during the split
2903+
// Examples:
2904+
// chname = "R" -> layername = "", newchname = "R"
2905+
// chname = "diffuse.G" -> layername = "diffuse", newchname = "G"
2906+
const std::string& chname = spec->channelnames[i];
2907+
const auto parts = Strutil::splits(chname, ".", 2);
2908+
const std::string layername = (parts.size() < 2) ? "" : parts[0];
2909+
const std::string newchname = (parts.size() < 2) ? parts[0] : parts[1];
2910+
newchannelnames.push_back(newchname);
2911+
2912+
bool pushlayer = false;
2913+
if (i < spec->channelnames.size() - 1) {
2914+
// Parse the layer name of the next channel,
2915+
// a different value means that we will be processing
2916+
// a new layer at the next iteration, therefore
2917+
// we should push the current one on the stack
2918+
const std::string& nextchname = spec->channelnames[i + 1];
2919+
const auto nextparts = Strutil::splits(nextchname, ".", 2);
2920+
const std::string nextlayername = (nextparts.size() < 2)
2921+
? ""
2922+
: nextparts[0];
2923+
pushlayer = (nextlayername != layername);
2924+
} else {
2925+
// Force flag to true at last iteration so that
2926+
// last layer is also pushed on the stack
2927+
pushlayer = true;
2928+
}
2929+
2930+
if (pushlayer) {
2931+
// Split the current layer by isolating its channels
2932+
// in a new ImageBuf and renaming them, and store the
2933+
// layer name in the oiio:subimagename metadata so we
2934+
// can reuse it later (e.g for creating a multi-part image)
2935+
const int chend = i + 1;
2936+
ImageBufRef img(new ImageBuf());
2937+
std::vector<int> channelorder(chend - chbegin);
2938+
std::iota(channelorder.begin(), channelorder.end(), chbegin);
2939+
ImageBufAlgo::channels(*img, (*A)(), chend - chbegin, channelorder,
2940+
{}, newchannelnames);
2941+
img->specmod().attribute("oiio:subimagename", layername);
2942+
2943+
// Create corresponding ImageRec and push it on the stack
2944+
ImageRecRef R(new ImageRec(img, true));
2945+
ot.push(R);
2946+
2947+
// Prepare processing of next layer
2948+
chbegin = chend;
2949+
newchannelnames.clear();
2950+
}
2951+
}
2952+
}
2953+
2954+
2955+
28832956
static void
28842957
action_subimage_append_n(Oiiotool& ot, int n, string_view command)
28852958
{
@@ -6898,6 +6971,9 @@ Oiiotool::getargs(int argc, char* argv[])
68986971
ap.arg("--siappendall")
68996972
.help("Append all images on the stack into a single multi-subimage image")
69006973
.OTACTION(action_subimage_append_all);
6974+
ap.arg("--layersplit")
6975+
.help("Split the top image's channel-name-based layers into separate images on the stack")
6976+
.OTACTION(action_layer_split);
69016977
ap.arg("--deepen")
69026978
.help("Deepen normal 2D image to deep")
69036979
.OTACTION(action_deepen);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Comparing "parts.exr" and "ref/parts.exr"
2+
PASS
1.65 KB
Binary file not shown.

testsuite/oiiotool-layers/run.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/usr/bin/env python
2+
3+
# Copyright Contributors to the OpenImageIO project.
4+
# SPDX-License-Identifier: Apache-2.0
5+
# https://github.com/AcademySoftwareFoundation/OpenImageIO
6+
7+
8+
# Test for oiiotool channel-name-based layer splitting
9+
#
10+
11+
12+
# test --layersplit
13+
command += oiiotool ("src/layers.exr --layersplit --siappendall -o parts.exr")
14+
15+
# Outputs to check against references
16+
outputs = [ "parts.exr", "out.txt" ]
17+
672 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)