Skip to content

Commit 9bf2d0c

Browse files
committed
Add mesh inspection and native codec support
Introduce OBJ-focused mesh inspection and native image codec backends with Python bindings and example updates. - Add MeshInspectionRequest/MeshInspectionResult types and InspectMeshFile API to report OBJ bounds, UV range, group spans, and usemtl->material ID mapping. - Parse OBJ material IDs in mesh loader, store per-vertex materialId in TriMesh, expose material IDs to GPU as layout(location = 4) uint attribute and propagate into shared mesh resources. - Update example RenderObjPerspectiveBaseColor.py to use rawgl.inspect_mesh_file, bind material IDs as uniforms, and select textures by material ID. - Add nanobind bindings: MeshInspectionRequest/MeshMaterialInfo/MeshGroupInfo/MeshInspectionResult and high-level inspect_mesh_file() helper in Python. - Implement selectable native image backends (JPEG/PNG/TIFF/OpenEXR) with compile-time detection and OpenImageIO fallback; add backend selection logging and stricter native writer error reporting (native writers now fail instead of silently falling back). - Change output writer and runtime APIs: save_image_output now returns bool with errorMessage, IoRuntimeService::saveImageOutput signature updated accordingly. - Update docs and README to describe native codec behavior, options, and mesh inspection usage; add tests and CMake entries for new smoke tests and scripts. These changes add pre-flight mesh inspection utilities, improve image codec control and error handling, and wire the new features through the Python layer and examples.
1 parent 2343ce0 commit 9bf2d0c

30 files changed

Lines changed: 1571 additions & 86 deletions

README.md

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ Command line image processing tool using OpenGL GLSL shaders.
33

44
## Features
55

6-
**Image import/export use OpenimageIO**\
7-
Support all image file formats that support by OpenimageIO for import:
8-
* Camera RAWs (oiio compiled with libraw plugin)
9-
* OpenEXR
10-
* TIFF
11-
* PNG
12-
* JPEG
13-
* JPEG-2000 (oiio compiled with openjpeg plugin)
14-
* etc.\
15-
Export for this moment limited to JPG, PNG, TIFF, TGA, HDR, EXR
16-
17-
**On image import and export allow define OpenimageIO reading/writing options like:**
6+
**Image import/export uses native backends for common production formats, with OpenImageIO fallback for formats that are not native yet.**
7+
Native paths currently cover:
8+
* JPEG through libjpeg-turbo/libjpeg
9+
* PNG through libpng
10+
* TIFF through libtiff, including tiled TIFF and BigTIFF
11+
* OpenEXR through OpenEXR, including tiled EXR
12+
13+
OpenImageIO remains available as the fallback path for formats that have not
14+
moved to native backends yet, such as camera RAW through OIIO's LibRaw plugin,
15+
JPEG-2000 through OIIO's OpenJPEG plugin, TGA, HDR, WebP, and other OIIO
16+
plugins available in the dependency build.
17+
18+
**On image import and export allow define codec reading/writing options like:**
1819
* oiio:ColorSpace ACES
1920
* oiio:RawColor 1
2021
* oiio:UnassociatedAlpha
@@ -27,7 +28,9 @@ Export for this moment limited to JPG, PNG, TIFF, TGA, HDR, EXR
2728
* raw:ColorSpace ProPhoto-linear
2829
* raw:Demosaic AHD-Mod
2930
* etc.\
30-
More about possible OpenimageIO options read there:
31+
Native backends support the RawGL options listed above for their format family.
32+
OIIO fallback formats still use OIIO/plugin attributes. More about possible
33+
OpenImageIO options read there:
3134
https://openimageio.readthedocs.io/en/latest/builtinplugins.html
3235
https://openimageio.readthedocs.io/en/latest/stdmetadata.html#sec-metadata-color
3336
https://openimageio.readthedocs.io/en/latest/oiiotool.html
@@ -167,6 +170,11 @@ rawgl.make_host_image(array)
167170
rawgl.host_image_to_array(host_image)
168171
```
169172

173+
Use `rawgl.inspect_mesh_file(path)` when a script needs mesh facts before it
174+
builds a workflow. The current detailed path is OBJ-focused and reports source
175+
counts, bounds, UV range, group spans, and `usemtl` material IDs without loading
176+
MTL files.
177+
170178
For more explicit control, the lower-level nanobind façade remains available under:
171179

172180
```python
@@ -260,6 +268,8 @@ Documentation
260268
| | **true (default)** - file already contains triangles |
261269
| | false - triangulate polygon faces during mesh load |
262270
| | Supported file formats: PLY and OBJ |
271+
| | OBJ `usemtl` material-name IDs are available in shaders as `layout(location = 4) in uint material_id;` |
272+
| | MTL files are ignored on this path. Bind textures explicitly from the workflow/script. |
263273
| | **rend:** |
264274
| | **tr (default)** - GL_TRIANGLES: render as polygons |
265275
| | ln - GL_LINES: render as lines |
@@ -310,7 +320,7 @@ Documentation
310320
| | --in UniformVec3 0.15 0.25 0.165 |
311321
| | --in UniformMatx 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 |
312322
| |
313-
| -t [ --in_attr ] arg | OpenImageIO/plugin attribute value |
323+
| -t [ --in_attr ] arg | Codec/plugin input attribute value |
314324
| | (e.g.: --in_attr oiio:colorspace sRGB). |
315325
| | **Some used defaults** |
316326
| | oiio:ColorSpace Linear |
@@ -336,10 +346,11 @@ Documentation
336346
| | r16, rg16, rgb16, rgba16, |
337347
| | r16f, rg16f, rgb16f, rgba16f, |
338348
| | r32f, rg32f, rgb32f, rgba32f |
339-
| | If format not support this bit depth OpenImageIO automatically convert to supported bit depth. |
349+
| | File writers convert this host format to the requested `--out_bits` when the target codec supports it. |
340350
| |
341-
| -r [ --out_attr ] arg | OpenImageIO/plugin attribute value |
342-
| | (e.g.: --out_attr oiio:colorspace sRGB). |
351+
| -r [ --out_attr ] arg | Codec/plugin output attribute value |
352+
| | (e.g.: --out_attr tiff:compression zip). |
353+
| | Native output families use native attributes directly. Invalid native JPEG, PNG, TIFF, or OpenEXR writer options fail instead of silently falling back to OpenImageIO. |
343354
| |
344355
| -n [ --out_channels ] arg | # of channels in output image |
345356
| |

cmake/rawgl_testing.cmake

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ if(BUILD_TESTING)
4242
endfunction()
4343

4444
rawgl_add_cpp_smoke_test(rawgl_core_inspect_smoke tests/rawgl_core_inspect_smoke.cpp)
45+
rawgl_add_cpp_smoke_test(rawgl_core_mesh_inspect_smoke tests/rawgl_core_mesh_inspect_smoke.cpp)
4546
rawgl_add_cpp_smoke_test(rawgl_core_graph_smoke tests/rawgl_core_graph_smoke.cpp)
4647
rawgl_add_cpp_smoke_test(rawgl_core_default_vertex_smoke tests/rawgl_core_default_vertex_smoke.cpp)
4748
rawgl_add_cpp_smoke_test(rawgl_core_system_uniform_reject_smoke tests/rawgl_core_system_uniform_reject_smoke.cpp)
@@ -55,6 +56,21 @@ if(BUILD_TESTING)
5556
rawgl_add_cpp_smoke_test(rawgl_core_transient_output_reuse_smoke tests/rawgl_core_transient_output_reuse_smoke.cpp)
5657
rawgl_add_cpp_smoke_test(rawgl_io_workflow_smoke tests/rawgl_io_workflow_smoke.cpp)
5758
rawgl_add_cpp_io_smoke_test(rawgl_io_host_image_smoke tests/rawgl_io_host_image_smoke.cpp)
59+
if(TARGET PNG::PNG AND TARGET TIFF::TIFF AND TARGET OpenEXR::OpenEXR)
60+
rawgl_add_cpp_io_smoke_test(rawgl_io_native_codec_errors_smoke tests/rawgl_io_native_codec_errors_smoke.cpp)
61+
target_include_directories(rawgl_io_native_codec_errors_smoke PRIVATE
62+
"${CMAKE_SOURCE_DIR}/src/io")
63+
endif()
64+
if(TARGET PNG::PNG AND TARGET JPEG::JPEG AND TARGET OpenEXR::OpenEXR)
65+
rawgl_add_cpp_io_smoke_test(rawgl_io_native_codecs_smoke tests/rawgl_io_native_codecs_smoke.cpp)
66+
target_include_directories(rawgl_io_native_codecs_smoke PRIVATE
67+
"${CMAKE_SOURCE_DIR}/src/io")
68+
endif()
69+
if(TARGET TIFF::TIFF)
70+
rawgl_add_cpp_io_smoke_test(rawgl_io_tiff_native_smoke tests/rawgl_io_tiff_native_smoke.cpp)
71+
target_include_directories(rawgl_io_tiff_native_smoke PRIVATE
72+
"${CMAKE_SOURCE_DIR}/src/io")
73+
endif()
5874
if(RAWGL_HAS_OPENMETA)
5975
rawgl_add_cpp_io_smoke_test(rawgl_io_metadata_smoke tests/rawgl_io_metadata_smoke.cpp)
6076
endif()
@@ -65,6 +81,11 @@ if(BUILD_TESTING)
6581
set_tests_properties(rawgl_core_inspect_smoke PROPERTIES
6682
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
6783

84+
add_test(NAME rawgl_core_mesh_inspect_smoke
85+
COMMAND rawgl_core_mesh_inspect_smoke)
86+
set_tests_properties(rawgl_core_mesh_inspect_smoke PROPERTIES
87+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
88+
6889
add_test(NAME rawgl_core_graph_smoke
6990
COMMAND rawgl_core_graph_smoke)
7091
set_tests_properties(rawgl_core_graph_smoke PROPERTIES
@@ -125,6 +146,27 @@ if(BUILD_TESTING)
125146
set_tests_properties(rawgl_io_host_image_smoke PROPERTIES
126147
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
127148

149+
if(TARGET PNG::PNG AND TARGET TIFF::TIFF AND TARGET OpenEXR::OpenEXR)
150+
add_test(NAME rawgl_io_native_codec_errors_smoke
151+
COMMAND rawgl_io_native_codec_errors_smoke)
152+
set_tests_properties(rawgl_io_native_codec_errors_smoke PROPERTIES
153+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
154+
endif()
155+
156+
if(TARGET PNG::PNG AND TARGET JPEG::JPEG AND TARGET OpenEXR::OpenEXR)
157+
add_test(NAME rawgl_io_native_codecs_smoke
158+
COMMAND rawgl_io_native_codecs_smoke)
159+
set_tests_properties(rawgl_io_native_codecs_smoke PROPERTIES
160+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
161+
endif()
162+
163+
if(TARGET TIFF::TIFF)
164+
add_test(NAME rawgl_io_tiff_native_smoke
165+
COMMAND rawgl_io_tiff_native_smoke)
166+
set_tests_properties(rawgl_io_tiff_native_smoke PROPERTIES
167+
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
168+
endif()
169+
128170
add_test(NAME rawgl_io_workflow_smoke
129171
COMMAND rawgl_io_workflow_smoke)
130172
set_tests_properties(rawgl_io_workflow_smoke PROPERTIES
@@ -180,6 +222,7 @@ if(BUILD_TESTING)
180222
rawgl_add_script_test(rawgl_invalid_vertfrag_arity test_invalid_vertfrag_arity)
181223
rawgl_add_script_test(rawgl_mesh_ao_sponge test_mesh_ao_sponge)
182224
rawgl_add_script_test(rawgl_mesh_cli_order test_mesh_cli_order)
225+
rawgl_add_script_test(rawgl_mesh_obj_material_smoke test_mesh_obj_material_smoke)
183226
rawgl_add_script_test(rawgl_mesh_obj_smoke test_mesh_obj_smoke)
184227
rawgl_add_script_test(rawgl_mesh_ply_smoke test_mesh_ply_smoke)
185228
rawgl_add_script_test(rawgl_missing_input_uniform test_missing_input_uniform)

docs/sphinx/examples.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,13 @@ For a larger OBJ import, run
225225
``examples/Mesh/OBJ/RenderObjPerspectiveBaseColor.py``. The file in that
226226
directory uses quads, mesh normals, named groups, and two U-coordinate tiles.
227227
The example sets ``tris`` to ``false`` so RawGL triangulates the faces while
228-
loading the mesh. It uses the first U tile for the bunny base-color texture and
229-
the second U tile for the skateboard texture.
228+
loading the mesh. It reads OBJ material IDs into vertex attribute location 4,
229+
then chooses the bunny or skateboard base-color texture from that material ID.
230+
RawGL derives these IDs from ``usemtl`` names in the OBJ file and does not load
231+
the MTL file for this path.
232+
233+
The script calls ``rawgl.inspect_mesh_file(...)`` before building the workflow
234+
to read bounds, group spans, UV range, and the material-name to ID mapping.
230235

231236
.. image:: _static/examples/obj_perspective_basecolor_640.jpg
232237
:alt: Perspective OBJ render using two base-color texture tiles

docs/sphinx/io.rst

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,101 @@ Current split:
2323
- ``rawgl_core`` executes workflows
2424
- ``rawgl_io`` owns file decode/encode and file-oriented workflow translation
2525

26+
Codec backends
27+
--------------
28+
29+
``rawgl_io`` uses native backends for the common formats that need predictable
30+
settings control:
31+
32+
- JPEG through libjpeg-turbo/libjpeg
33+
- PNG through libpng
34+
- TIFF through libtiff, including tiled TIFF and BigTIFF
35+
- OpenEXR through OpenEXR, including tiled EXR
36+
37+
OpenImageIO remains the fallback path for formats that have not moved to native
38+
backends yet, such as camera RAW, JPEG-2000, TGA, HDR, WebP, and other plugins
39+
available in the dependency build.
40+
41+
Native writers are strict for native output families. If a native JPEG, PNG,
42+
TIFF, or OpenEXR writer rejects an option, RawGL returns that error instead of
43+
silently retrying the write through OpenImageIO. The read path may still fall
44+
back to OpenImageIO when a native reader cannot decode a supported format
45+
variant.
46+
47+
Native codec options
48+
--------------------
49+
50+
JPEG output accepts:
51+
52+
- ``jpeg:quality`` or ``jpg:quality``: integer quality from ``1`` to ``100``
53+
- ``jpeg:progressive`` or ``jpg:progressive``: ``true``/``false``
54+
- ``oiio:Compression`` with ``jpeg:N`` or ``jpg:N``: legacy quality spelling
55+
56+
JPEG writes grayscale or RGB data. Alpha channels are omitted.
57+
58+
PNG output accepts:
59+
60+
- ``png:compressionLevel``: integer from ``0`` to ``9``
61+
- ``png:interlace`` or ``png:interlaced``: ``true`` enables Adam7 interlace
62+
63+
PNG writes 8-bit and 16-bit integer images.
64+
65+
TIFF output accepts:
66+
67+
- ``tiff:compression``: ``none``, ``lzw``, ``packbits``, ``zip``, ``deflate``,
68+
or ``adobe_deflate``
69+
- ``compression`` or ``oiio:Compression``: legacy compression spelling
70+
- ``tiff:predictor``: ``none``/``1``, ``horizontal``/``2``, or ``float``/``3``
71+
- ``tiff:tiled``: ``true``/``false``
72+
- ``tiff:tileWidth`` and ``tiff:tileLength`` or ``tiff:tileHeight``
73+
- ``tiff:rowsPerStrip`` for striped output
74+
- ``tiff:bigTiff`` or ``tiff:bigtiff``: force BigTIFF output
75+
- ``oiio:UnassociatedAlpha``: mark the extra alpha sample as unassociated
76+
77+
TIFF writes 8-bit, 16-bit, and 32-bit float output. ``tiff:rowsPerStrip`` is
78+
not valid when tiled output is enabled. TIFF predictors require LZW or Deflate
79+
compression; the floating-point predictor is valid only for 32-bit float
80+
output.
81+
82+
OpenEXR output accepts:
83+
84+
- ``openexr:compression``: any compression name accepted by OpenEXR, such as
85+
``zip``, ``zips``, ``piz``, ``rle``, ``dwaa``, or ``dwab``
86+
- ``compression`` or ``oiio:Compression``: legacy compression spelling
87+
- ``openexr:tiled``: ``true``/``false``
88+
- ``openexr:tileWidth`` and ``openexr:tileHeight`` or ``openexr:tileLength``
89+
- ``openexr:dwaCompressionLevel`` for DWAA/DWAB
90+
- ``openexr:lineOrder``: ``increasing_y``, ``decreasing_y``, or ``random_y``
91+
- ``openexr:attribute:string:<name>``: write one string header attribute
92+
93+
OpenEXR writes half and 32-bit float output.
94+
95+
Example output attributes:
96+
97+
.. code-block:: bat
98+
99+
RawGL.exe ^
100+
--out OutColor output.tif ^
101+
--out_bits 16 ^
102+
--out_attr tiff:compression zip ^
103+
--out_attr tiff:tiled true ^
104+
--out_attr tiff:tileWidth 256 ^
105+
--out_attr tiff:tileLength 256
106+
107+
.. code-block:: python
108+
109+
rawgl.io.save_image(
110+
image,
111+
"output.exr",
112+
bits=16,
113+
attributes={
114+
"openexr:compression": "zip",
115+
"openexr:tiled": "true",
116+
"openexr:tileWidth": "64",
117+
"openexr:tileHeight": "64",
118+
},
119+
)
120+
26121
Typical file-backed C++ path
27122
----------------------------
28123

docs/sphinx/python.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The normal path is the high-level helper layer:
2121
- ``rawgl.prepare_image(...)``
2222
- ``rawgl.prepare_render(...)``
2323
- ``rawgl.prepare_compute(...)``
24+
- ``rawgl.inspect_mesh_file(...)``
2425

2526
Use the explicit ``rawgl.io`` helper surface when the workflow starts or ends
2627
with files:
@@ -155,6 +156,24 @@ Typical inspection shape:
155156
name_policy=rawgl.MetadataNamePolicy.exif_tool_alias,
156157
)
157158
159+
Mesh inspection
160+
---------------
161+
162+
Use ``rawgl.inspect_mesh_file(...)`` when a script needs mesh facts before it
163+
builds a workflow. The current detailed path is OBJ-focused and reports source
164+
counts, bounds, UV range, group spans, and ``usemtl`` material IDs without
165+
loading any MTL files.
166+
167+
.. code-block:: python
168+
169+
import rawgl
170+
171+
mesh_info = rawgl.inspect_mesh_file("model.obj")
172+
if not mesh_info.success:
173+
raise RuntimeError(mesh_info.error_message)
174+
175+
material_ids = {material.name: material.id for material in mesh_info.materials}
176+
158177
Prepared workflows
159178
------------------
160179

0 commit comments

Comments
 (0)