AcademySoftwareFoundation / AcademySoftwareFoundation/OpenImageIO

making a stab at generated images by bending the null image plugin

Open
#4,197 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
2.4k
Forks
698
Avg merge
3d 4h
Merged PRs (30d)
47

Description

**Is your feature request related to a problem? Please describe.**

I was looking for 'an easy way in' to introduce arbitrary generated image content with minimal coding effort. My problem was that I have such content and I did not find a way to make OIIO 'suck it in'.

**Describe the solution you'd like**

I want this capability to 'look like' an image, in order to make it 'palatable' for the higher OIIO functions.

**Describe alternatives you've considered**

I considered writing a specific plugin, but to flesh out a plugin for the purpose requires considerable coding effort. I looked at the extant plugins, and I found a candidate which can easily be bent to the task: the 'null' image format. While this provides a nice infrastructure, it's core functionality is very simple: colour pixels with some default colour. If, instead, it can be made to colour pixels by calling a user-provided function, it becomes much more versatile and can do the job I want done perfectly.

I have coded a proof of concept. First, I introduced a header 'feeder.h' into the OpenImageIO includes. It looks like this:

```
#include

// to make generated images easily accessible, we introduce a new
// mode to 'null' images which obtains pixel data by calling a 'feeder'.
// The feeder has a member function 'feed' which receives a pointer to
// the pixel's memory it's supposed to feed into, plus the number of
// bytes it's supposed to fill, and the x, y, and z coordinate of that
// pixel. The feeder has a specific function to satisfy the request,
// resulting in the 'null' image being 'painted' by the feeder instead
// of being initialized with a given constant value. With this method,
// we can introduce arbitrary generated image content with minimal
// coding effort - we simply 'piggyback' on the extant null plugin
// end use it's infrastructure.
// feeders are initialized with a std::function like these:

bool feed_not ( void* trg , int nbytes , int x , int y , int z )
{
return true ;
}

bool feed_grey ( void* trg , int nbytes , int x , int y , int z )
{
memset ( trg , 127 , nbytes ) ;
return true ;
}

bool feed_gradient ( void* trg , int nbytes , int x , int y , int z )
{
auto * pc = (unsigned char*) trg ;
pc[0] = x ;
pc[1] = y ;
pc[2] = 0 ;
return true ;
}

// struct feeder_t contains - as of yet - only a single std::fuction
// of the type feed_f - like those above

struct feeder_t
{
typedef std::function < bool ( void* , // target memory
int , // bytes per pixel
int , // x
int , // y
int // z
) > feed_f ;

feed_f feed ;

feeder_t ( const feed_f & _feed = feed_not )
: feed ( _feed )
{ }
} ;

// let's set up two 'stock' feeders

feeder_t black_feeder ( feed_grey ) ;
feeder_t gradient_feeder ( feed_gradient ) ;

// finally, we set up a std::map, which provides feeders by name.
// we'll use this map from the plugin code to look up feeders which
// are passed in as part of the REST parameters. This way we can
// do two things:
// - we can add (or remove) feeders anytime
// - we can prescribe their use by passing a FEED=... REST parameter
// The use of the feeder isn't very fast, because it actually walks
// through all the pixels in a nested loop and calls a rather bulky
// function to deposit it's data, but if the image is read once and
// later on the data from the read are used, this is not an issue:
// the slow code is only executed when reading the image.

std::map < std::string , feeder_t * >
feed_store { { "feed_grey" , & black_feeder } ,
{ "feed_gradient" , & gradient_feeder }
} ;
```
The changes to the plugin are small - rather than pasting the entire code here, I just give the diff, I'm sure you'll get my drift:
```
~/src/OpenImageIO$ git diff
diff --git a/src/null.imageio/nullimageio.cpp b/src/null.imageio/nullimageio.cpp
index 1fce00363..5d4825e82 100644
--- a/src/null.imageio/nullimageio.cpp
+++ b/src/null.imageio/nullimageio.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include

#include "imageio_pvt.h"

@@ -74,6 +75,7 @@ private:
int m_subimage; ///< What subimage are we looking at?
int m_miplevel; ///< What miplevel are we looking at?
bool m_mip; ///< MIP-mapped?
+ feeder_t * m_feed;
std::vector m_value; ///< Pixel value (if not black)
ImageSpec m_topspec;

@@ -84,6 +86,7 @@ private:
m_miplevel = -1;
m_mip = false;
m_value.clear();
+ m_feed = nullptr ;
}
};

@@ -311,6 +314,15 @@ NullInput::open(const std::string& name, ImageSpec& newspec,
} else if (a.first == "PIXEL") {
Strutil::extract_from_list_string(fvalue, a.second);
fvalue.resize(m_topspec.nchannels);
+ } else if (a.first == "FEED") {
+ auto feed = feed_store.find ( std::string ( a.second ) ) ;
+ if ( feed == feed_store.end() ) {
+ std::cout << "can't find feed " << a.second << " in store"
+ << std::endl ;
+ m_feed = nullptr ;
+ } else {
+ m_feed = feed->second ;
+ }
} else if (a.first.size() && a.second.size()) {
parse_param(a.first, a.second, m_topspec);
}
@@ -362,10 +374,14 @@ NullInput::seek_subimage(int subimage, int miplevel)


bool
-NullInput::read_native_scanline(int /*subimage*/, int /*miplevel*/, int /*y*/,
- int /*z*/, void* data)
+NullInput::read_native_scanline(int /*subimage*/, int /*miplevel*/, int y,
+ int z, void* data)
{
- if (m_value.size()) {
+ if ( m_feed ) {
+ size_t s = m_spec.pixel_bytes();
+ for (size_t x = 0, e = m_spec.width; x < e; ++x)
+ m_feed->feed ( (unsigned char*)data + s * x , s , x , y , z ) ;
+ } else if (m_value.size()) {
size_t s = m_spec.pixel_bytes();
for (int x = 0; x < m_spec.width; ++x)
memcpy((char*)data + s * x, m_value.data(), s);
@@ -378,10 +394,23 @@ NullInput::read_native_scanline(int /*subimage*/, int /*miplevel*/, int /*y*/,


bool
-NullInput::read_native_tile(int /*subimage*/, int /*miplevel*/, int /*x*/,
- int /*y*/, int /*z*/, void* data)
+NullInput::read_native_tile(int /*subimage*/, int /*miplevel*/, int x,
+ int y, int z, void* data)
{
- if (m_value.size()) {
+ if ( m_feed ) {
+ auto w = m_topspec.tile_width ;
+ auto h = m_topspec.tile_height ;
+ auto xx = ( x / w ) * w ;
+ auto yy = ( y / h ) * h ;
+ size_t s = m_spec.pixel_bytes();
+ size_t ofs = 0 ;
+ for (size_t v = 0 ; v < h ; ++v ) {
+ for ( size_t u = 0 ; u < w ; ++u , ofs += s ) {
+ m_feed->feed ( (unsigned char*)data + ofs , s ,
+ u + xx , v + yy , z ) ;
+ }
+ }
+ } else if (m_value.size()) {
size_t s = m_spec.pixel_bytes();
for (size_t x = 0, e = m_spec.tile_pixels(); x < e; ++x)
memcpy((char*)data + s * x, m_value.data(), s);
```
To use the 'feeder', it can be specified in the REST parameters, passing an image 'filename' like this:
`foo.null?RES=640x480&CHANNELS=3&FEED=feed_gradient&TYPE=uint8`

Contributor guide

Open the contributing guide

Research direction

Start with src/null.imageio/nullimageio.cpp, especially NullInput::open and the scanline and tile read methods, and review the proposed OpenImageIO/feeder.h interface. Check how the null plugin parses REST parameters and how the FEED=feed_gradient example should behave. Done means the agreed feeder design can generate image data through the null plugin for the shown usage.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
computer-graphics
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.