Proposal
Use OpenImageIO (OIIO) to manage our image loading/saving code for nearly all supported formats.
Pros:
- Reduce maintenance of error-prone code for all but a few of Blender's supported formats
- Reduce platform and build complexity by removing several SVN libraries, Cmake code, and #defines for many of the individual file formats (jpeg/tiff/png etc.)
- Reduce differences between Eevee and Cycles
- Eliminate binary size waste resulting from the duplication of these dependencies in other shared libraries
- Provide an easy path for additional format support once this proposal is in place (RAW etc.)
- Bugs/features found by us will benefit the entire ecosystem of OIIO users and vice versa
Cons:
- Features and fixes will need to wait for upstream to handle and then for the Blender platform maintainers to pull in the updated library
Unknown:
- Unclear whose code has been better tested in terms of broken and/or malicious files. Both have had their share of security issues.
- There may be unknown performance pitfalls
Why now
OIIO 2.4.x introduces complete support for "IO proxies" which allows it to read/write images directly in memory. This is required for fitting in with Blender's current systems and was a key point of contention in the past.
Recent work in the DDS space has caused this topic to surface again: T101405: DDS image format improvements
Implementation Goals
Goals
- Existing image formats, and their options, should remain unchanged
- Existing features of the current system should remain unchanged (e.g. efficient thumbnail extraction)
- Limit code churn in neighboring areas unless necessary
- Commit the code in stages; as much as makes sense
Non-Goals
- Introduce new image formats or features as part of this initial work
- Use additional OIIO features beyond what's required for current feature parity
Approach
Each format will retain custom load/save entry points inside the format registration array: ImFileType IMB_FILE_TYPES[] inside filetype.c All the machinery surrounding usage of that system will remain unchanged.
The entry points for each format will use simple, declarative, APIs from OIIO to setup the proper configuration for the operation in question.
They will then call into newly created OIIO read/write routines to handle all the processing in a centralized and standardized way (i.e. common handling of IB_test, IB_mem, IB_metadata flags etc.)
Open Issues
Issue 1 Efficient thumbnail extraction doesn't exist in OIIO
Recent additions to Blender provide efficient thumbnail extraction and generation for JPEG, EXR, and WebP for the file browser. We will extract embedded thumbnails directly from the file if they exist, or will generate them otherwise, and do so as efficiently as possible by making use of size-hints that the low-level APIs can use to prevent the need for additional memory or resizing of images.
OIIO does have a get_thumbnail API, however it is insufficient for our needs due to two reasons. The first is that it doesn't exist for the formats we would need for parity. The second is that it currently doesn't have any size-hinting and adding it would be considered a breaking change which means it wouldn't show up until OIIO 2.5.
Issue 2 JPEG-2000 feature parity
Several of JPEG-2000's options aren't supported by OIIO: Compression quality, YCC colorspace, and OPJ_CINEMA2K_48 are not present. Additionally the "J2K" codec must be saved as .j2k (not .j2c) or otherwise it'll be processed incorrectly.
Issue 3 DDS textures
It turns out that Eevee makes use of the raw DDS compressed data in certain scenarios. Blender will load in the uncompressed data as well as the compressed data. This was very surprising to discover! We can keep doing this but we'll have to step a little bit outside of pure OIIO during loading. Either way we can still maintain parity here with a very small portion of low-level code.
DDS normal map compressed formats are now supported as of recent OIIO 2.4.4.2 and should no longer be an issue
Plan
The code required for each image format is well-insulated and can be checked in individually.
The common reading/writing code will be checked in alongside the first format to be converted.
The following table outlines the fate of each format given the Goals and Issues above. We'll use OIIO unless otherwise stated:
| Format | Blender R/W | OIIO R/W | OIIO Memory Proxy | Notes |
|---|---|---|---|---|
| CINEON | Yes/Yes | Yes/No | No | Keep Blender's implementation |
| DPX | Yes/Yes | Yes/Yes | Yes | |
| DDS | Yes/No | Yes/No | Yes | |
| OpenEXR | Yes/Yes | Yes/Yes | Yes | Keep Blender's implementation (complexity) |
| BMP | Yes/Yes | Yes/Yes | Yes | |
| Iris | Yes/Yes | No/No | No | Keep Blender's implementation |
| Radiance HDR | Yes/Yes | Yes/Yes | Yes | |
| JPEG | Yes/Yes | Yes/Yes | Yes | Delay (no thumbnail support) |
| OpenJPEG JP2 | Yes/Yes | Yes/Yes | Yes | Delay (poor support) |
| Photoshop PSD | Yes/No | Yes/No | Yes | Already uses OIIO |
| PNG | Yes/Yes | Yes/Yes | Yes | |
| Targa | Yes/Yes | Yes/Yes | Yes | |
| TIFF | Yes/Yes | Yes/Yes | Yes | |
| WebP | Yes/Yes | Yes/Yes | Yes | Delay (no thumbnail support) |
Possible checkin sequence:
- Cleanup: Remove IMB_gettile and related support functions as they are dead code and would complicate the TIFF conversion
- Cleanup: Fix slightly error-prone colorspace handling in the load loop (see note below)
- Per-Format conversion
- For a given format, do the entire OIIO conversion in one set of changes and commit (SVN can happen any time afterwards)
- Repeat for each additional format
- In Parallel
- Help design and implement better thumbnail support in OIIO
- Help implement missing JPEG-2000 support in OIIO
Notes
- Some of the existing formats do not handle the IB_test flag at all or not optimally. These will be fixed during their conversion.
- Many formats do not handle IB_mem. We will get this for "free" for all formats during their conversion.
- The load loop which loops over known formats is slightly error-prone because the effective_colorspace variable is not cleared between iterations. It is possible for a file to load far enough to set a colorspace but then fail shortly afterwards. In practice this probably wouldn't happen though.
Risks
Testing
Image handling is extremely nuanced. Proper review and testing will take time. All of the following must be checked and validated with folks familiar in the matter:
- Alpha channel handling (associated vs. unassociated)
- Colorspace handling
- Correct data type: float vs. byte (images can "look" ok but these details can differ)
- Correct number of channels / planes (images can "look" ok but these details can differ)
- Correct GPU buffer format, especially for 1-channel grayscale images (images can "look" ok but these details can differ; this information is only exposed in the UI making automation impossible)
The above items need to be inspected in 2 situations: 1) With an older blender version loading newly saved images and 2) With the new blender version loading previously saved images. (i.e. general cross testing)
Automation to prevent regressions would be nice. I'm not sure what's possible/allowable with our frameworks for verifying that list above. If we want to test all formats and all options we're looking at ~100 separate image files that would need to be verified.
Performance
This is mostly for Blender-specific scenarios as Cycles has always depended on OIIO for bulk load of texture data.
- Playback caching in the VSE
- Using the filebrowser in Thumbnail mode to view directories with large amounts of images
- Loading many images from python (sequential nature of python would amplify any large perf regression)
A quick test of the Playback caching scenario using Timeline->Playback->Play Every Frame to force-load all the images did not show anything outright broken at least for PNGs so far:
100 frame 1080p Image sequence of PNGs
master Took 9.05 seconds
prototype Took 8.94 seconds
100 frame 4k Image sequence of PNGs
master Took 20.1 seconds
prototype Took 20.1 seconds
Q/A
Q. Is it worth doing if we can't convert all the formats?
A. PSD files are already using OIIO and Cycles already uses OIIO for all but a few situations. Even if we don't get all of the formats, we're still looking at thousands of lines of code that can be removed. Considering that the new code would be simpler and more declarative, it makes the proposal worth exploring at least.
Q. What happens if the thumbnail issue isn't solved?
A. There's 2 middle grounds that we could explore:
- Skip those formats entirely but convert all the others
- Use OIIO for Load/Save for those formats but keep the thumbnail code using the low level libraries
The second option has the benefit that we'll still reduce some unneeded code. Both options have the downside that Blender still requires the library to be linked in.
Q. What about perf?
A. Beyond the scenarios mentioned above there's the following:
- The code paths that use the .is_a check might be slower. OIIO does fast signature checks but if that passes it then proceeds to do more invasive work before returning. I don't know how much this will affect things in practice. The .is_a checks are done in 2 cases -- thumbnails (sigh…) and some packed file scenarios.
- Loading through OIIO happens with the exact number of channels as the file uses. E.g. a 3-channel file will load into 3 channels worth of memory. However, Blender operates on 4-channel nearly exclusively. This requires a post-load operation to fill the missing channel(s). The current code accounts for this as it goes; and might be faster.
Working examples
Current all-up diff here now: D16640: WIP: Use OpenImageIO for loading and saving nearly all image formats