Iray Programmer's Manual

Implementation of an image exporter

This topic introduces:

  1. Basic concepts about the export of rendered pixel data to disk:
  2. An example image exporter program, example_psd_exporter.cpp, which exports images in the Photoshop PSD file format PFFS10. The example program demonstrates how to export several canvases with different content (rendered image, normals, z-buffer, etc.) into the same file.

Implementing an image exporter

There are two ways to add support for an image format:

  • By using an image plugin that adds support for this file format. The image plugin has the advantage that it extends the generic export facilities with the new image format.
  • By using an explicit method that does the job. The explicit method has the advantage that you have more freedom and are not bound to the framework of image plugins.

In this example, the goal is to:

  1. Export multiple canvases into one file, and
  2. Export the names of the canvases

An explicit method is used because the framework for image plugins does not support these two features.

The example code is structured as follows:

  • Auxilliary methods (from write_int8 to get_pixel_type()) that deal with low-level export of certain data formats and pixel type properties
  • The main method export_psd() that accepts an array of canvases to export
  • An adaptor of this main method with the same name that accepts an instance of mi::neuraylib::IRender_target to export
  • An implementation of the mi::neuraylib::IRender_target interface with three canvases that will be used in this example
  • The example code to set up and to render a scene (essentially the same as in example_rendering.cpp).

While the implementation of an image exporter heavily depends on the image format in question, there are often similarities in the file format structure and in the tasks to be accomplished. Typically, there is some sort of file header with meta information, followed by the actual pixel data.

To export a PSD file, the example program iterates over all the canvases to compute the needed metadata (width, height, number of layers, bits per channel) and reject invalid inputs. Next, the file is opened and the file header is written.

The next section in the PSD file format is the layer and mask information section. This section is split into two parts:

  • The first part contains all the layer records which are basically a header with metadata for each layer, including the name of the layer.
  • The second part contains the actual pixel data for each layer (in the same order as described by the layer records).

Finally, there is a last section named image data section. This section is supposed to contain the pixel data of all layers merged. It is primarily needed for applications that do not deal with the layer and mask information section. In this example, the pixel data of the first canvas is exported again (for simplicity).

When writing your own image exporter, or importer, there are several conversion tasks you have to handle:

  1. Mapping neuray pixel types (see Types) to pixel types of the image format and/or vice versa.
  2. Conversion of pixel data to a different pixel type (if needed).
  3. Flipping the scanline order (if needed). In neuray, scanlines are ordered bottom-up.
  4. Different tiling of pixel data.

The methods mi::neuraylib::IImage_api::read_raw_pixels() and mi::neuraylib::IImage_api::write_raw_pixels() can be very handy for the last three tasks. If requested, they can convert the pixel type as needed and flip the scanline order. They also convert the data between a possibly tiled canvas and a plain memory buffer.

Using the PSD example exporter

To demonstrate the PSD exporter, the example code is almost identical to that used in Example program section. To demonstrate the export of multiple canvases, a different implementation of the mi::neuraylib::IRender_target interface is used. This implementation holds three canvases:

  • One for the rendered image
  • One for the normals
  • One for the z-buffer

An additional method normalize() modifies the values for the normals and z-buffer such that the data is in the range [0,1] expected by most image formats.

After the image has been rendered, the render target is passed to the method export_psd(), which exports all canvases of the render target into the given file. For comparison, the canvases are additionally exported into individual PNG files.

example_psd_exporter.cpp

001 /******************************************************************************
002  * © 1986, 2016 NVIDIA Corporation. All rights reserved.
003  *****************************************************************************/
004 
005 // examples/example_psd_exporter.cpp
006 //
007 // Imports a scene file, renders the scene, and writes the image to disk as PSD file.
008 //
009 // The example expects the following command line arguments:
010 //
011 //   example_psd_exporter <scene_file> <mdl_path>
012 //
013 // scene_file       some scene file, e.g., main.mi
014 // mdl_path         path to the MDL modules, e.g., iray-<version>/mdl
015 //
016 // The rendered images are written to files named "example_psd_exporter_*.png".
017 
018 #include <cassert>
019 #include <cstdio>
020 #include <iostream>
021 #include <limits>
022 
023 #include <mi/neuraylib.h>
024 
025 // Include code shared by all examples.
026 #include "example_shared.h"
027 
028 // for htons, ntohs
029 #ifndef MI_PLATFORM_WINDOWS
030 #include <arpa/inet.h>
031 #else
032 #ifndef WIN32_LEAN_AND_MEAN
033 #define WIN32_LEAN_AND_MEAN 1
034 #endif
035 #include <winsock2.h>
036 #endif
037 
038 // The PSD file for export. Global variable to avoid passing it to every write function below.
039 FILE* g_file;
040 
041 // Writes the 8-bit integer \p data to \p g_file.
042 void write_int8( mi::Uint8 data)
043 {
044     fwrite( &data, 1, 1, g_file);
045 }
046 
047 // Writes the 16-bit integer \p data to \p g_file (converts data to network byte order).
048 void write_int16( mi::Uint16 data)
049 {
050     data = htons( data);
051     fwrite( &data, 2, 1, g_file);
052 }
053 
054 // Writes the 32-bit integer \p data to \p g_file (converts data to network byte order).
055 void write_int32( mi::Uint32 data)
056 {
057     data = htonl( data);
058     fwrite( &data, 4, 1, g_file);
059 }
060 
061 // Writes\p length bytes from buffer \p p to \p g_file.
062 void write_buf( const void* p, mi::Size length)
063 {
064     fwrite( p, length, 1, g_file);
065 }
066 
067 // Writes the string \p s to \p g_file.
068 void write_str( const char* s)
069 {
070     write_buf( s, strlen( s));
071 }
072 
073 // Writes the string \p s to \p g_file as Pascal string, using a multiple of \p padding bytes.
074 void write_str_pascal( const char* s, mi::Size padding)
075 {
076     mi::Size len = strlen( s);
077     if( len > 255)
078         len = 255;
079     write_int8( static_cast<mi::Uint8>( len));
080     write_buf( s, len);
081     for( mi::Size i = (len % padding) + 1; i < padding; ++i)
082         write_int8( 0);
083 }
084 
085 // Writes pixel data of a channel to \p g_file, starting at \p input (converts data to network byte
086 // order).
087 void write_channel(
088     const mi::Uint8* input,
089     mi::Uint32 width,
090     mi::Uint32 height,
091     mi::Uint32 bytes_per_channel,
092     mi::Uint32 stride)
093 {
094     mi::Uint8* output = new mi::Uint8[width * height * bytes_per_channel];
095     for( mi::Uint32 i = 0; i < width * height; ++i)
096         for( mi::Uint32 b = 0; b < bytes_per_channel; ++b)
097             output[i*bytes_per_channel + b] = input[i*stride + bytes_per_channel-1-b];
098     write_buf( output, width * height * bytes_per_channel);
099     delete[] output;
100 }
101 
102 // Returns the channel count used for the PSD file depending on the neuray pixel type.
103 // (Pixel types with only 1 or 2 channels are converted to types with 3 channels.)
104 mi::Uint32 get_channel_count( const char* pixel_type)
105 {
106     if( strcmp( pixel_type, "Float32<4>") == 0) return 4;
107     if( strcmp( pixel_type, "Rgba"      ) == 0) return 4;
108     if( strcmp( pixel_type, "Rgbea"     ) == 0) return 4;
109     if( strcmp( pixel_type, "Rgba_16"   ) == 0) return 4;
110     if( strcmp( pixel_type, "Color"     ) == 0) return 4;
111     return 3;
112 }
113 
114 // Returns the number of bytes per channel for the PSD file depending on the neuray pixel type.
115 mi::Uint32 get_bytes_per_channel( const char* pixel_type)
116 {
117     if( strcmp( pixel_type, "Sint8"  ) == 0) return 1;
118     if( strcmp( pixel_type, "Sint32" ) == 0) return 1;
119     if( strcmp( pixel_type, "Rgb"    ) == 0) return 1;
120     if( strcmp( pixel_type, "Rgba"   ) == 0) return 1;
121     return 2;
122 }
123 
124 // Returns the RGB(A) pixel type for given bytes per channel and number of channels.
125 const char* get_pixel_type( mi::Uint32 bytes_per_channel, mi::Uint32 channels)
126 {
127     assert( channels == 3 || channels == 4);
128 
129     switch( bytes_per_channel) {
130         case 1:  return channels == 3 ? "Rgb"    : "Rgba";
131         case 2:  return channels == 3 ? "Rgb_16" : "Rgba_16";
132         default: assert( false); return "Rgb";
133     }
134 }
135 
136 // Exports an array of canvases as PSD file.
137 //
138 // This method creates PSD files (version 1 only). It supports only 8 and 16 bits per channel
139 // (other pixel types converted accordingly). It does not support compression.
140 //
141 // \param canvases   The array of canvases to export.
142 // \param names      Optional array of canvas names.
143 // \param filename   The name of the file to export to.
144 // \param image_api  API component IImage_api (needed for pixel type conversion).
145 //
146 // \see "Adobe Photoshop File Formats Specification", July 2010 for file format details.
147 bool export_psd(
148     const mi::IArray* canvases,
149     const mi::IArray* names,
150     const char* filename,
151     mi::neuraylib::IImage_api* image_api)
152 {
153     if( !canvases || !filename || !image_api)
154         return false;
155 
156     // Check consistent array length
157     if( names && names->get_length() != canvases->get_length())
158         return false;
159 
160     // Compute maximum width, height, bytes per channel, and total number of layers
161     mi::Uint32 total_width = 0;
162     mi::Uint32 total_height = 0;
163     mi::Uint32 total_layers = 0;
164     mi::Uint32 bytes_per_channel = 0;
165     for( mi::Uint32 i = 0; i < canvases->get_length(); ++i) {
166         mi::base::Handle<const mi::neuraylib::ICanvas> canvas(
167             canvases->get_element<mi::neuraylib::ICanvas>( i));
168         if( !canvas.is_valid_interface())
169             return false;
170         total_width  = std::max( total_width,  canvas->get_resolution_x());
171         total_height = std::max( total_height, canvas->get_resolution_y());
172         total_layers += canvas->get_layers_size();
173         bytes_per_channel = std::max( bytes_per_channel,
174             get_bytes_per_channel( canvas->get_type()));
175     }
176 
177     // Reject canvases too large for PSD files version 1
178     if( total_width > 30000 || total_height > 30000)
179         return false;
180 
181     // Reject if too many layers in total
182     if( total_layers == 0 || total_layers > 56)
183         return false;
184 
185     // Check names
186     bool has_result = false;
187     for( mi::Uint32 i = 0; names && i < names->get_length(); ++i) {
188         mi::base::Handle<const mi::IString> name(
189             names->get_element<mi::IString>( i));
190         if( !name.is_valid_interface())
191             return false;
192         if( strcmp( name->get_c_str(), "result") == 0)
193             has_result = true;
194     }
195 
196     g_file = fopen( filename, "wb");
197     if( !g_file)
198         return false;
199 
200     // File header section
201 
202     write_str( "8BPS");                 // signature
203     write_int16( 1);                    // version
204     write_buf( "\0\0\0\0\0\0", 6);      // reserved
205     write_int16( 3);                    // number of channels
206     write_int32( total_height);         // height
207     write_int32( total_width);          // width
208     write_int16( static_cast<mi::Uint16>( 8*bytes_per_channel)); // bits per channel
209     write_int16( 3);                    // color mode (RGB)
210 
211     write_int32( 0);                    // length of color mode data section
212     write_int32( 0);                    // length of image resource section
213 
214     // Layer and mask information section
215 
216     write_int32( 0);                    // length of layer and mask information section (dummy)
217     long lamis_start = ftell( g_file);
218 
219     // Layer and mask information section: Layer info subsection
220 
221     write_int32( 0);                    // length of layer information section (dummy)
222     long lis_start = ftell( g_file);
223 
224     write_int16( static_cast<mi::Uint16>( total_layers)); // layer count
225 
226     // Layer records
227 
228     for( mi::Size i = 0; i < canvases->get_length(); ++i) {
229 
230         mi::base::Handle<const mi::neuraylib::ICanvas> canvas(
231             canvases->get_element<mi::neuraylib::ICanvas>( i));
232         mi::Uint32 width = canvas->get_resolution_x();
233 
234         const char* name = "";
235         if( names) {
236             mi::base::Handle<const mi::IString> s( names->get_element<mi::IString>( i));
237             name = s->get_c_str();
238         }
239         bool is_result = strcmp( name, "result") == 0;
240 
241         for( mi::Size j = 0; j < canvas->get_layers_size(); ++j) {
242 
243             mi::Uint32 channels = get_channel_count( canvas->get_type());
244 
245             write_int32( 0);                    // top
246             write_int32( 0);                    // left
247             write_int32( total_height);         // bottom
248             write_int32( width);                // right
249             write_int16( static_cast<mi::Uint16>( channels)); // number of channels
250             mi::Uint32 channel_size = width * total_height * bytes_per_channel + 2;
251             write_int16( 0);                    // channel ID red
252             write_int32( channel_size);         // byte count red
253             write_int16( 1);                    // channel ID green
254             write_int32( channel_size);         // byte count green
255             write_int16( 2);                    // channel ID blue
256             write_int32( channel_size);         // byte count blue
257             if( channels == 4) {
258                 write_int16( 0xFFFF);           // channel ID alpha
259                 write_int32( channel_size);     // byte count alpha
260             }
261 
262             write_str( "8BIM");                 // blend mode signature
263             write_str( "norm");                 // blend mode key
264             write_int8( 255);                   // opacity
265             write_int8( 0);                     // clipping
266             mi::Uint8 flags = static_cast<mi::Uint8>( !has_result || is_result ? 0 : 2);
267             write_int8( flags);                 // flags (invisible = 2)
268             write_int8( 0);                     // filler
269             write_int32( 0);                    // extra data length (dummy)
270             long extra_data_start = ftell( g_file);
271             write_int32( 0);                    // layer mask data length
272             write_int32( 0);                    // layer blending ranges length
273             write_str_pascal( name, 4);         // layer name
274 
275             // extra data length
276             long extra_data_end = ftell( g_file);
277             fseek( g_file, extra_data_start-4, SEEK_SET);
278             write_int32( static_cast<mi::Uint32>( extra_data_end-extra_data_start));
279             fseek( g_file, extra_data_end, SEEK_SET);
280         }
281     }
282 
283     // Channel image data
284 
285    for( mi::Size i = 0; i < canvases->get_length(); ++i) {
286 
287        mi::base::Handle<const mi::neuraylib::ICanvas> canvas(
288            canvases->get_element<mi::neuraylib::ICanvas>( i));
289        mi::Uint32 width  = canvas->get_resolution_x();
290        mi::Uint32 height = canvas->get_resolution_y();
291 
292        for( mi::Uint32 j = 0; j < canvas->get_layers_size(); ++j) {
293 
294            mi::Uint32 channels = get_channel_count( canvas->get_type());
295            mi::Uint8* buffer = new mi::Uint8[width * total_height * channels * bytes_per_channel];
296            memset( buffer, 0, width * total_height * channels * bytes_per_channel);
297            mi::Size offset = width * (total_height-height) * channels * bytes_per_channel;
298            const char* pixel_type = get_pixel_type( bytes_per_channel, channels);
299            image_api->read_raw_pixels(
300                width, height, canvas.get(), 0, 0, j, buffer + offset, true, pixel_type);
301 
302            for( mi::Uint32 channel = 0; channel < channels; ++channel) {
303                write_int16( 0);                // channel compression method (raw)
304                mi::Uint8* start = buffer + channel * bytes_per_channel;
305                mi::Uint32 stride = channels * bytes_per_channel;
306                write_channel( start, width, total_height, bytes_per_channel, stride);
307            }
308 
309            delete[] buffer;
310        }
311    }
312 
313     // length of layer information section
314     long lis_end = ftell( g_file);
315     fseek( g_file, lis_start-4, SEEK_SET);
316     write_int32( static_cast<mi::Uint32>( lis_end-lis_start));
317     fseek( g_file, lis_end, SEEK_SET);
318 
319     // length of global layer mask info
320     write_int32( 0);
321 
322     // length of layer and mask inform. section
323     long lamis_end = ftell( g_file);
324     fseek( g_file, lamis_start-4, SEEK_SET);
325     write_int32( static_cast<mi::Uint32>( lamis_end-lamis_start));
326     fseek( g_file, lamis_end, SEEK_SET);
327 
328     // Image data section
329 
330     mi::base::Handle<const mi::neuraylib::ICanvas> canvas(
331         canvases->get_element<mi::neuraylib::ICanvas>( 0));
332     mi::Uint32 width  = canvas->get_resolution_x();
333     mi::Uint32 height = canvas->get_resolution_y();
334 
335     for( mi::Uint32 j = 0; j < canvas->get_layers_size(); ++j) {
336 
337         mi::Uint32 channels = 3;
338         mi::Uint8* buffer
339             = new mi::Uint8[total_width * total_height * channels * bytes_per_channel];
340         memset( buffer, 0, total_width * total_height * channels * bytes_per_channel);
341         mi::Size offset = total_width * (total_height-height) * channels * bytes_per_channel;
342         mi::Uint32 padding = (total_width - width) * channels * bytes_per_channel;
343         const char* pixel_type = get_pixel_type( bytes_per_channel, channels);
344         image_api->read_raw_pixels(
345             width, height, canvas.get(), 0, 0, j, buffer + offset, true, pixel_type, padding);
346 
347         write_int16( 0);                    // compression method (raw)
348         for( mi::Uint32 channel = 0; channel < channels; ++channel) {
349             mi::Uint8* start = buffer + channel * bytes_per_channel;
350             mi::Uint32 stride = channels * bytes_per_channel;
351             write_channel( start, width, total_height, bytes_per_channel, stride);
352         }
353 
354         delete[] buffer;
355     }
356 
357     fclose( g_file);
358     return true;
359 }
360 
361 // Exports a render target as PSD file.
362 //
363 // This method creates PSD files (version 1 only). It supports only 8 and 16 bits per channel
364 // (other pixel types converted accordingly). It does not support compression.
365 //
366 // \param render_target   The render target to export.
367 // \param filename        The name of the file to export to.
368 // \param factory         API component IFactory (needed to create new data types).
369 // \param image_api       API component IImage_api (needed for pixel type conversion).
370 bool export_psd(
371     mi::neuraylib::IRender_target* render_target,
372     const char* filename,
373     mi::neuraylib::IFactory* factory,
374     mi::neuraylib::IImage_api* image_api)
375 {
376     if( !render_target || !filename || !image_api)
377         return false;
378 
379     mi::base::Handle<mi::IDynamic_array> canvases(
380         factory->create<mi::IDynamic_array>( "Interface[]"));
381     check_success( canvases.is_valid_interface());
382     mi::base::Handle<mi::IDynamic_array> names(
383         factory->create<mi::IDynamic_array>( "String[]"));
384     check_success( names.is_valid_interface());
385     for( mi::Uint32 i = 0; i < render_target->get_canvas_count(); ++i) {
386         mi::base::Handle<mi::neuraylib::ICanvas> canvas( render_target->get_canvas( i));
387         canvases->push_back( canvas.get());
388         mi::base::Handle<mi::IString> name( factory->create<mi::IString>( "String"));
389         name->set_c_str( render_target->get_canvas_name( i));
390         names->push_back( name.get());
391     }
392 
393     return export_psd( canvases.get(), names.get(), filename, image_api);
394 }
395 
396 void configuration( mi::base::Handle<mi::neuraylib::INeuray> neuray, const char* mdl_path)
397 {
398     // Configure the neuray library. Here we set the search path for .mdl files.
399     mi::base::Handle<mi::neuraylib::IRendering_configuration> rendering_configuration(
400         neuray->get_api_component<mi::neuraylib::IRendering_configuration>());
401     check_success( rendering_configuration.is_valid_interface());
402     check_success( rendering_configuration->add_mdl_path( mdl_path) == 0);
403 
404     // Load the FreeImage, Iray Photoreal, and .mi importer plugins.
405     mi::base::Handle<mi::neuraylib::IPlugin_configuration> plugin_configuration(
406         neuray->get_api_component<mi::neuraylib::IPlugin_configuration>());
407 #ifndef MI_PLATFORM_WINDOWS
408     check_success( plugin_configuration->load_plugin_library( "freeimage.so") == 0);
409     check_success( plugin_configuration->load_plugin_library( "libiray.so") == 0);
410     check_success( plugin_configuration->load_plugin_library( "mi_importer.so") == 0);
411 #else
412     check_success( plugin_configuration->load_plugin_library( "freeimage.dll") == 0);
413     check_success( plugin_configuration->load_plugin_library( "libiray.dll") == 0);
414     check_success( plugin_configuration->load_plugin_library( "mi_importer.dll") == 0);
415 #endif
416 }
417 
418 // A simple implementation of the IRender_target interface with three canvases, one for the
419 // rendering result, for the normals, and one for the z-buffer.
420 class Render_target : public mi::base::Interface_implement<mi::neuraylib::IRender_target>
421 {
422 public:
423     // Constructor. Creates a render target with three canvases.
424     Render_target( mi::Uint32 width, mi::Uint32 height, mi::neuraylib::IImage_api* image_api)
425     {
426         m_canvas[0] = image_api->create_canvas( "Color", width, height, width, height, 1);
427         m_canvas[1] = image_api->create_canvas( "Float32<3>", width, height, width, height, 1);
428         m_canvas[2] = image_api->create_canvas( "Float32", width, height, width, height, 1);
429     }
430 
431     // Implement the interface of mi::neuraylib::IRender_target.
432     mi::Uint32 get_canvas_count() const { return 3; }
433     const char* get_canvas_name( mi::Uint32 index) const
434     {
435         if( index == 0) return "result";
436         if( index == 1) return "normal";
437         if( index == 2) return "depth";
438         return 0;
439     }
440     const mi::neuraylib::ICanvas* get_canvas( mi::Uint32 index) const
441     {
442         if( index >= 3)
443             return 0;
444         m_canvas[index]->retain();
445         return m_canvas[index].get();
446     }
447     mi::neuraylib::ICanvas* get_canvas( mi::Uint32 index)
448     {
449         if( index >= 3)
450             return 0;
451         m_canvas[index]->retain();
452         return m_canvas[index].get();
453     }
454 
455     // Normalizes the contents of the second and third canvas.
456     void normalize() {
457 
458         // Map values in m_canvas[1] linearly from [-1,1] to [0,1].
459         mi::base::Handle<mi::neuraylib::ITile> tile( m_canvas[1]->get_tile( 0, 0));
460         mi::Size n_pixels = tile->get_resolution_x() * tile->get_resolution_y();
461         mi::Float32_3* data1 = static_cast<mi::Float32_3*>( tile->get_data());
462         for( mi::Size i = 0; i < n_pixels; ++i) {
463             data1[i].x = mi::math::clamp( 0.5f*data1[i].x + 0.5f, 0.0f, 1.0f);
464             data1[i].y = mi::math::clamp( 0.5f*data1[i].y + 0.5f, 0.0f, 1.0f);
465             data1[i].z = mi::math::clamp( 0.5f*data1[i].z + 0.5f, 0.0f, 1.0f);
466         }
467 
468         // Map values in m_canvas[2] (excluding huge values) linearly to [0,1].
469         tile = m_canvas[2]->get_tile( 0, 0);
470         n_pixels = tile->get_resolution_x() * tile->get_resolution_y();
471         mi::Float32* data2 = static_cast<mi::Float32*>( tile->get_data());
472         mi::Float32 min_value = std::numeric_limits<mi::Float32>::max();
473         mi::Float32 max_value = std::numeric_limits<mi::Float32>::min();
474         for( mi::Size i = 0; i < n_pixels; ++i) {
475             if (data2[i] < min_value) min_value = data2[i];
476             if (data2[i] > max_value && data2[i] < 1.0E38f) max_value = data2[i];
477         }
478         if( min_value == max_value)
479             min_value = max_value-1;
480         for( mi::Size i = 0; i < n_pixels; ++i)
481             if( data2[i] < 1.0E38f)
482                 data2[i] = 1.0f - (data2[i] - min_value) / (max_value - min_value);
483             else
484                 data2[i] = 0.0f;
485     }
486 
487 private:
488     // The three canvases of this render target.
489     mi::base::Handle<mi::neuraylib::ICanvas> m_canvas[3];
490 };
491 
492 void rendering( mi::base::Handle<mi::neuraylib::INeuray> neuray,
493                 const char* scene_file)
494 {
495     // Get the database, the global scope of the database, and create a transaction in the global
496     // scope for importing the scene file and storing the scene.
497     mi::base::Handle<mi::neuraylib::IDatabase> database(
498         neuray->get_api_component<mi::neuraylib::IDatabase>());
499     check_success( database.is_valid_interface());
500     mi::base::Handle<mi::neuraylib::IScope> scope(
501         database->get_global_scope());
502     mi::base::Handle<mi::neuraylib::ITransaction> transaction(
503         scope->create_transaction());
504     check_success( transaction.is_valid_interface());
505 
506     // Import the scene file
507     mi::base::Handle<mi::neuraylib::IImport_api> import_api(
508         neuray->get_api_component<mi::neuraylib::IImport_api>());
509     check_success( import_api.is_valid_interface());
510     mi::base::Handle<const mi::IString> uri( import_api->convert_filename_to_uri( scene_file));
511     mi::base::Handle<const mi::neuraylib::IImport_result> import_result(
512         import_api->import_elements( transaction.get(), uri->get_c_str()));
513     check_success( import_result->get_error_number() == 0);
514 
515     // Create the scene object
516     mi::base::Handle<mi::neuraylib::IScene> scene(
517         transaction->create<mi::neuraylib::IScene>( "Scene"));
518     scene->set_rootgroup(       import_result->get_rootgroup());
519     scene->set_options(         import_result->get_options());
520     scene->set_camera_instance( import_result->get_camera_inst());
521     transaction->store( scene.get(), "the_scene");
522 
523     // Create the render context using the Iray Photoreal render mode
524     scene = transaction->edit<mi::neuraylib::IScene>( "the_scene");
525     mi::base::Handle<mi::neuraylib::IRender_context> render_context(
526         scene->create_render_context( transaction.get(), "iray"));
527     check_success( render_context.is_valid_interface());
528     mi::base::Handle<mi::IString> scheduler_mode( transaction->create<mi::IString>());
529     scheduler_mode->set_c_str( "batch");
530     render_context->set_option( "scheduler_mode", scheduler_mode.get());
531     scene = 0;
532 
533     // Create the render target and render the scene
534     mi::base::Handle<mi::neuraylib::IImage_api> image_api(
535         neuray->get_api_component<mi::neuraylib::IImage_api>());
536     mi::base::Handle<Render_target> render_target( new Render_target( 512, 384, image_api.get()));
537     check_success(
538         render_context->render( transaction.get(), render_target.get(), 0) >= 0);
539 
540     // Write the image to disk (entire render target as one PSD file)
541     render_target->normalize();
542     mi::base::Handle<mi::neuraylib::IFactory> factory(
543         neuray->get_api_component<mi::neuraylib::IFactory>());
544     check_success( export_psd(
545         render_target.get(), "example_psd_exporter.psd", factory.get(), image_api.get()));
546 
547     // Write the image to disk (individual canvases as PNG files)
548     mi::base::Handle<mi::neuraylib::IExport_api> export_api(
549         neuray->get_api_component<mi::neuraylib::IExport_api>());
550     check_success( export_api.is_valid_interface());
551     mi::base::Handle<mi::neuraylib::ICanvas> canvas0( render_target->get_canvas( 0));
552     export_api->export_canvas( "file:example_psd_exporter_0.png", canvas0.get());
553     mi::base::Handle<mi::neuraylib::ICanvas> canvas1( render_target->get_canvas( 1));
554     export_api->export_canvas( "file:example_psd_exporter_1.png", canvas1.get());
555     mi::base::Handle<mi::neuraylib::ICanvas> canvas2( render_target->get_canvas( 2));
556     export_api->export_canvas( "file:example_psd_exporter_2.png", canvas2.get());
557 
558     transaction->commit();
559 }
560 
561 int main( int argc, char* argv[])
562 {
563     // Collect command line parameters
564     if( argc != 3) {
565         std::cerr << "Usage: example_psd_exporter <scene_file> <mdl_path>" << std::endl;
566         keep_console_open();
567         return EXIT_FAILURE;
568     }
569     const char* scene_file  = argv[1];
570     const char* mdl_path = argv[2];
571 
572     // Access the neuray library
573     mi::base::Handle<mi::neuraylib::INeuray> neuray( load_and_get_ineuray());
574     check_success( neuray.is_valid_interface());
575 
576     // Configure the neuray library
577     configuration( neuray, mdl_path);
578 
579     // Start the neuray library
580     mi::Sint32 result = neuray->start();
581     check_start_success( result);
582 
583     // Do the actual rendering
584     rendering( neuray, scene_file);
585 
586     // Shut down the neuray library
587     check_success( neuray->shutdown() == 0);
588     neuray = 0;
589 
590     // Unload the neuray library
591     check_success( unload());
592 
593     keep_console_open();
594     return EXIT_SUCCESS;
595 }