Maniphest T101413

Use OpenImageIO for loading and saving nearly all image formats
Confirmed, NormalDESIGN

Assigned To
None
Authored By
Jesse Yurkovich (deadpin)
Sep 27 2022, 8:47 PM
Tags
  • BF Blender
  • EEVEE & Viewport
  • Images & Movies
Subscribers
Aras Pranckevicius (aras_p)
Bastien Montagne (mont29)
Brecht Van Lommel (brecht)
Campbell Barton (campbellbarton)
Daniel Gryningstjerna (Dangry)
Evan Wilson (EAW)
Hans Goudey (HooglyBoogly)
10 More Subscribers
Tokens
"Love" token, awarded by Baardaap."Like" token, awarded by HooglyBoogly."Mountain of Wealth" token, awarded by jbakker."Love" token, awarded by Bit."Love" token, awarded by Dangry."Like" token, awarded by EAW."Like" token, awarded by aras_p.

Description

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:

FormatBlender R/WOIIO R/WOIIO Memory ProxyNotes
CINEONYes/YesYes/NoNoKeep Blender's implementation
DPXYes/YesYes/YesYes
DDSYes/NoYes/NoYes
OpenEXRYes/YesYes/YesYesKeep Blender's implementation (complexity)
BMPYes/YesYes/YesYes
IrisYes/YesNo/NoNoKeep Blender's implementation
Radiance HDRYes/YesYes/YesYes
JPEGYes/YesYes/YesYesDelay (no thumbnail support)
OpenJPEG JP2Yes/YesYes/YesYesDelay (poor support)
Photoshop PSDYes/NoYes/NoYesAlready uses OIIO
PNGYes/YesYes/YesYes
TargaYes/YesYes/YesYes
TIFFYes/YesYes/YesYes
WebPYes/YesYes/YesYesDelay (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

Event Timeline

Jesse Yurkovich (deadpin) changed the task status from Needs Triage to Confirmed.Sep 27 2022, 8:47 PM
Jesse Yurkovich (deadpin) created this task.
Jesse Yurkovich (deadpin) changed the subtype of this task from "Report" to "Design".
Jesse Yurkovich (deadpin) added projects: EEVEE & Viewport, Images & Movies.
Jesse Yurkovich (deadpin) added subscribers: Aras Pranckevicius (aras_p), Jeroen Bakker (jbakker).
Aras Pranckevicius (aras_p) awarded a token.Sep 27 2022, 8:59 PM
Evan Wilson (EAW) awarded a token.Sep 27 2022, 9:24 PM
Evan Wilson (EAW) added a subscriber: Evan Wilson (EAW).
Daniel Gryningstjerna (Dangry) awarded a token.Sep 27 2022, 9:54 PM
Daniel Gryningstjerna (Dangry) added a subscriber: Daniel Gryningstjerna (Dangry).
Ray Molenkamp (LazyDodo) added a subscriber: Ray Molenkamp (LazyDodo).Sep 27 2022, 10:00 PM
TheRedWaxPolice (TheRedWaxPolice) added a subscriber: TheRedWaxPolice (TheRedWaxPolice).Sep 27 2022, 10:02 PM
Harley Acheson (harley) added a subscriber: Harley Acheson (harley).Sep 27 2022, 10:46 PM
Yuro (Yuro) added a subscriber: Yuro (Yuro).Sep 28 2022, 4:31 AM
Paul Larson (GeorgiaPacific) added a subscriber: Paul Larson (GeorgiaPacific).Sep 28 2022, 7:58 AM
Thomas Dinges (dingto) added a subscriber: Thomas Dinges (dingto).Sep 28 2022, 1:19 PM
Aras Pranckevicius (aras_p) added a comment.Sep 28 2022, 9:33 PM

DDS:

  • I have proposed changes/fixes to OpenImageIO that fix various cases where it does not handle certain DDS correctly, compared to D16087. https://github.com/OpenImageIO/oiio/pull/3573
  • Will do performance measurements next; very initial look does not look terribly good though. Will need to dig more.
Deen (Bit) awarded a token.Sep 28 2022, 9:56 PM
Harley Acheson (harley) added a comment.Sep 28 2022, 10:36 PM

Personally wouldn't worry too much about the thumbnailing, except for EXR. That format gave us actual bug reports and needed special treatment. But then I only added same for JPEG and WebP for completeness. Although the thumbnailing code for those latter formats are a (slight) improvement, I doubt anyone would notice if that was removed.

Aras Pranckevicius (aras_p) added a comment.Oct 3 2022, 10:13 AM

DDS: the various tweaks/fixes for OIIO DDS support as mentioned above have landed to upstream, and were just released as OIIO v2.4.4.2.

Aras Pranckevicius (aras_p) added a comment.Oct 6 2022, 8:28 AM

DDS performance: I've done a quick attempt at replacing Blender's DDS ImBuf code with OIIO. Initial results were that OIIO code was about 4x slower at loading DXT/BCn compressed DDS files compared to D16087. I did some low hanging fruit in OIIO itself to speed that up (just landed in master as #3583), but that still leaves OIIO at around 2x slower compared to Blender code. Getting the remaining speedup would be much more involved due to how OIIO is structured (there are some memory copies that are hard to avoid).

Something like this might be an issue for other formats that we'd want to switch to OIIO, particularly the ones that are "fast to decode" and then an extra one or two memory copies done by OIIO are a substantial cost. Would probably have to be evaluated for each and every format.

Jeroen Bakker (jbakker) added a comment.Oct 6 2022, 8:50 AM

Png and jpg would perhaps be the two formats where performance actually is important. Mainly as these are used as playback caching.

Harley Acheson (harley) added a comment.Oct 6 2022, 6:23 PM

@Jeroen Bakker (jbakker) - Png and jpg would perhaps be the two formats where performance actually is important. Mainly as these are used as playback caching.

Don't quote me, because it has been a while since I looked at this. But using EXR for that type of use could be worth investigating one day. Depends on the formats and compression, but it might be possible that it can give us both smaller file sizes AND faster reads.

Someone had mentioned (Troy probably) that EXR was fast reading and I looked into the idea of using that for our preview thumbnails. Compared to our current PNGs they were smaller and much faster to read. I was quite surprised.

Aras Pranckevicius (aras_p) added a comment.Oct 6 2022, 7:09 PM
In T101413#1428643, @Harley Acheson (harley) wrote:

EXR was fast reading and I looked into the idea of using that for our preview thumbnails. Compared to our current PNGs they were smaller and much faster to read. I was quite surprised.

It's not surprising. PNG is mostly bottlenecked by a completely serial zlib decoding process (which means PNG reading/writing can't be multi-threaded, at all). Whereas EXR splits up the image into chunks of 16-pixel rows and compresses them (when using "zip" aka zlib compression) separately. Which is exactly what allows it to use multi-threading on both reading & writing. This did not use to be a big thing back when disk I/O was the bottleneck, but all that has changed with SSDs.

Jesse Yurkovich (deadpin) updated the task description.Oct 6 2022, 8:06 PM
Jesse Yurkovich (deadpin) updated the task description.Oct 6 2022, 9:31 PM
Jesse Yurkovich (deadpin) updated the task description.Nov 15 2022, 10:02 AM
Jeroen Bakker (jbakker) awarded a token.Nov 15 2022, 6:09 PM
Hans Goudey (HooglyBoogly) added a subscriber: Hans Goudey (HooglyBoogly).Nov 15 2022, 6:15 PM
Hans Goudey (HooglyBoogly) awarded a token.Nov 15 2022, 6:19 PM
Martijn Versteegh (Baardaap) awarded a token.Nov 16 2022, 10:03 AM
Martijn Versteegh (Baardaap) added a subscriber: Martijn Versteegh (Baardaap).
Brecht Van Lommel (brecht) added a subscriber: Brecht Van Lommel (brecht).Nov 19 2022, 1:24 PM

I think this is great for all the reasons listed. Additionally, potential benefits are consistency with Hydra, shipping OIIO python bindings with the same supported file formats, and maybe even replacing our ImBuf with oiio::ImageBuf.

For thumbnails reading we can keep our own code for a bit, but it doesn't have to delay replacing the rest of the JPEG/WebP/OpenEXR code. IRIS support we could consider removing entirely, it's really only there for historical reasons at this point.

Jesse Yurkovich (deadpin) mentioned this in rB38573d515e49: Cleanup: Remove unused IMB tile cache code.Nov 24 2022, 5:14 AM
Steffen Dünner (SteffenD) added a subscriber: Steffen Dünner (SteffenD).Nov 24 2022, 9:03 AM
Jesse Yurkovich (deadpin) added a comment.Nov 28 2022, 10:03 AM

@Brecht Van Lommel (brecht) I'm probably at the point where I can start prepping the individual formats for review this week. I'd start with the support code and the PSD format since that already uses oiio. After that, things can go as fast or as slow as we'd like depending on the time of reviewers with higher priority items on their plates. There's a few items remaining to work through though: Testing and dependencies.

Testing
For testing image loading, I'm using the regular python unittest framework to open each file and then compare Image attributes with what I expect (is_float, colorspace, channel count, and alpha_mode). There's no comparison of what the image looks like however. We'd need a picture of what things look like in the Image Editor or similar. I'm not sure what the best option is here.

For save testing, I have a separate script which can create and save the images as necessary. They're created from scratch with a script and each image contains alpha and pixel values either clamped at 1 for 8bit or >1 for float buffers. From there I can use idiff for comparison against the saved references. But this doesn't fit with the render_test system very well actually. Should I just use the unittest framework here too and perform all the idiff calls myself? The main loss would be the nice html report unless I also implement that piece too.

Dependencies
Lastly, when it comes time to do PNG, oiio becomes a de-facto required dependency for Blender (e.g. the icons are loaded through PNG). At minimum we'd need to prevent the ability to disable that cmake option, and require it for building, but maybe this is a larger discussion point?

Brecht Van Lommel (brecht) added a comment.Nov 28 2022, 5:54 PM

I'm not entirely sure what it means to review this per file format, I was imaging it to be one change where a single load/save code replaces a bunch of file formats. But maybe it's more convenient to do it in smaller parts, it's not clear to me.

For testing image loading, I would compare against a saved OpenEXR reference image. I think what we need to test is that the scene linear values match, display is a different concern I think.

If using the render test mechanism doesn't work well, it's fine to use a separate Python script. But it should still use the same BLENDER_TEST_UPDATE=1 environment variable as we have for renders and modifiers to update tests. In general it's convenient to be able to add an extra image for testing to some folder without having to update a Python file along with it.

Our own PNG loading code is something we might want to keep for lite builds, so OIIO is not strictly a required dependency.

Ray Molenkamp (LazyDodo) added a comment.Nov 28 2022, 6:08 PM

Think we may need to re-evaluate the purpose of the lite build, afaik it was always meant as something that builds fast with minimal features. I wouldn't oppose making oiio a required dep (but that be an admin call to make) leading to a slightly more featured lite, as long as it doesn't impact build time, which i expect would be rather minimal especially compared to some non optional components of a lite build like geometry nodes (seriously, why is this a non-optional component? it's *very* expensive to build) nor am i expecting any regressions in link time , since we're going dynamic with oiio in 3.5. footprint on disk will be slightly larger, but afaik minimal footprint was never really a goal of a lite build?

Brecht Van Lommel (brecht) added subscribers: Bastien Montagne (mont29), Sergey Sharybin (sergey), Campbell Barton (campbellbarton).Nov 28 2022, 6:17 PM

I think lite is also about keeping Blender easy to build against system libraries. It's not something I personally care much about, but would like to have opinion of maybe @Campbell Barton (campbellbarton), @Sergey Sharybin (sergey) or @Bastien Montagne (mont29) about this.

Jesse Yurkovich (deadpin) updated the task description.Nov 28 2022, 9:43 PM
Jesse Yurkovich (deadpin) added a comment.Nov 28 2022, 9:47 PM

The formats maintain their own is_a, load, and save functions still - so no single loader/saver. This was necessary as the saves are very different, some of the loads require specific ImBuf manipulation, formats like DDS have additional code all to their own, and to aid in the review so folks just need to worry about 1 format at a time. I published the WIP all-up patch here: D16640: WIP: Use OpenImageIO for loading and saving nearly all image formats

For the image load test, I'm not sure how to do the comparison though. During development there were many times the image would load incorrectly (too dark, washed out, or ) due to missing a setting on the ImBuf or oiio etc. Would I load both the format image and the exr reference and do a pixel-by-pixel check in python?

Brecht Van Lommel (brecht) added a comment.Nov 29 2022, 8:05 PM

Ok, I see that to keep things strictly compatible some code file format. I think it may be easiest to review this in 3 steps like: add tests, add OIIO utilities, replace file formats. I don't mind having it split up per file format if you prefer, it's just not needed from my side as a reviewer.

For the loading test, I was thinking you load the image, save it as EXR, then compare that to a reference EXR. If the EXR saving code is correct, then I think that should catch cases where the image was too dark, washed out, etc.

Looking at the test in bl_imbuf_load.py, the way I would make that work is to write out both an EXR and a text file (with "is_float" and other metadata), per image. And then compare both to reference files, generated by running tests with BLENDER_TEST_UPDATE=1. That way we can easily add images for testing or add a lot of additional metadata to the text file.

Sergey Sharybin (sergey) added a comment.Nov 30 2022, 10:39 AM

Sorry, was a bit busy to reply faster.

I do not any issues of making OIIO a required dependency. It is easily available on majority of distros now, and I do not see it as a complication of installing libopenimageio-dev alongside with other dependencies.

In fact, I'd rather see core to be streamlined and the amount of permutations reduced. In my experience, those fine-grained optional features and ifdef are crippling the code, introduce compilation errors and bugs, and overall introduce more issues than they solve.

Bastien Montagne (mont29) added a comment.Nov 30 2022, 10:59 AM

Am also totally fine making OIIO a mandatory dependency of Blender, as already said this is an easily available library these days.

Jesse Yurkovich (deadpin) added a comment.EditedDec 1 2022, 12:26 AM

Alright, I've implemented the tests with the suggestions above. Thanks for the guidance! D16657: Tests: Add tests for image format saving and loading

The 3 checkin approach of tests, then support code, then all formats is ok with me. It's actually a bit simpler so it's entirely up to what reviewers would like.

Lastly, if push comes to shove, we don't have to rush this in for 3.5. I'll be around and able to make changes but if folks have other higher priority things to do, especially considering the holidays, than by all means this can wait until the next train.