Io image raw

From The Foundry MODO SDK wiki
Revision as of 16:30, 11 September 2013 by Adissid (Talk | contribs)

Jump to: navigation, search

Io_image_raw is a basic example plugin. This wiki page is intended as a walkthrough of the code in order to help you better understand the SDK.

When installed, the Io_image_raw plugin adds a saver/loader functionality for RAW image files.

Code Walkthrough

Class Declarations

We want to be able to examine the image files, so here we declare an Interface for opaque image access called CRawImage that inherits from CLxImpl_Image. Of note is a pointer back to the active RAW loader. We use this pointer in all of our basic methods. In our ing_Size function, which retrieves the size of the image, we have the loader load the image in question and then retrieve its width and height objects. We do the same for our img_Format function, except this time with the file format of the image. The loader is again used in img_GetPixel to provide important information regarding the scan line size and channels that helps us perform the purpose of the function of the function, which is to access one pixel. We also use the loader in ing_GetLine to load a scanline from the image directly into the buffer.

class CRawImage : public CLxImpl_Image
{
public:
       CRawLoader		*rawLoader;
       ILxUnknownID		 inst_ifc;
       FILE			*open_file;
       CLxUser_ThreadService	 threadSvc;

       virtual			~CRawImage();

       virtual void		 img_Size (
                                       unsigned int	*w,
                                       unsigned int	*h);

       virtual LXtPixelFormat	 img_Format (void);

       virtual LxResult	 img_GetPixel (
                                       unsigned int	 x,
                                       unsigned int	 y,
                                       LXtPixelFormat	 type,
                                       void		*pixel);

       virtual const void *	 img_GetLine (
                                       unsigned int	 y,
                                       LXtPixelFormat	 type,
                                       void		*buf);
};

We want to create a loader, so we inherit from [[1]]. Inside the class, we declare a factory. Factories are objects that allow attributes to be read and new server instances to be spawned. In this case, we declare it to be able to create an image interface for opaque image I/O. The remaining functions, except for the last one, simply retrieve information about the target file or actually load the file. Inside the class,

class CRawLoader : public CLxImpl_Loader1
{
        LXtImageTarget1		 img_target;
        FILE			*open_file;

        /*
         * Factory to create image interface for opaque image I/O.
         */
        CLxPolymorph<CRawImage>	 raw_factory;

        /*
         * Import options from the Image I/O panel.
         */
        unsigned		 width;
        unsigned		 height;
        unsigned		 channels;
        bool			 interleaved;
        unsigned		 depth;
        bool			 byteOrderPC;
        unsigned		 headerSize;

        void			 GetImportOptions ();

        size_t			 scanlineSize;

    public:
                                 CRawLoader ();

        LxResult		 load_Recognize  (LXtLoadAccess1 *) LXx_OVERRIDE;

        const LXtImageTarget1*	 load_GetImageTarget () const
        {
                return &img_target;
        }

        FILE *			 load_GetOpenFile () const
        {
                return open_file;
        }

        unsigned		 load_GetChannels () const
        {
                return channels;
        }

        unsigned		 load_GetDepth () const
        {
                return depth;
        }

        bool			 load_GetByteOrderPC () const
        {
                return byteOrderPC;
        }

        unsigned		 load_GetHeaderSize () const
        {
                return headerSize;
        }

        size_t			 load_GetScanlineSize () const
        {
                return scanlineSize;
        }

        LxResult		 load_LoadInstance (
                                        LXtLoadAccess1	*load,
                                        void		**ppvObj) LXx_OVERRIDE;

        LxResult		 load_LoadObject (
                                        LXtLoadAccess1	*load,
                                        ILxUnknownID	 dest) LXx_OVERRIDE;

        LxResult		 load_Cleanup    (LXtLoadAccess1 *load) LXx_OVERRIDE;

        static LXtTagInfoDesc	 descInfo[];
};

We want to create a saver object, so we have this class inherit from CLxImpl_Saver. Inside the class we have the main function, which performs the meat of saving the file, followed by the server tags.

class CRawSaver : public CLxImpl_Saver
{
   public:
       virtual LxResult	 sav_Save (
                                       ILxUnknownID	 source,
                                       const char	*filename,
                                       ILxUnknownID	 monitor) LXx_OVERRIDE;

       static LXtTagInfoDesc	 descInfo[];
};

Server Tags

The tags, here corresponding to the loader, indicate that the server can load images and provide a file pattern.

LXtTagInfoDesc	 CRawLoader::descInfo[] = {
       { LXsLOD_CLASSLIST,	LXa_IMAGE	},
       { LXsLOD_DOSPATTERN,	"*.raw"		},
       { LXsSRV_USERNAME,	"Raw Image"	},
       { 0 }
};

Here, the tags indicate that the saver class above saves objects of the image class with the file extension RAW.

LXtTagInfoDesc	 CRawSaver::descInfo[] = {
       { LXsSAV_OUTCLASS,	LXa_IMAGE	},
       { LXsSAV_DOSTYPE,	"RAW"		},
       { LXsSRV_USERNAME,	"Raw Image"	},
       { 0 }
};

Initialization

Intialize is called when we add the plugin to modo, and is the utility that exports the server. The LXx_ADD_SERVER method is simply a wrapper that is identical to normal method of adding a server, with the arguments being (interface_to_be_added, class_you_depend_on, server_name).

Here we indicate that we intend to add two servers to modo. One of them is of the type saver, depends on the class CRawSaver, and is called raw_RGB. The other does the same, but with Loader1, CRawLoader, and raw_RGB respectively.

       void
initialize ()
{
       LXx_ADD_SERVER (Saver,  CRawSaver,  "raw_RGB");
       LXx_ADD_SERVER (Loader1, CRawLoader, "raw_RGB");
}

Helper Functions

This function queries the user for an int value and returns that value.

       static int
GetUserInt (const char *prefKey, int defaultValue = 0)
{
       int	value = defaultValue;
       CLxReadUserValue ruvUser;
       if (ruvUser.Query (prefKey)) {
               value = ruvUser.GetInt ();
       }

       return value;
}

Implementations

This method retrieves the options we will be using to import the file

       void
CRawLoader::GetImportOptions ()
{
       width = GetUserInt ("ImageIO.PRAW.width", 1024);
       height = GetUserInt ("ImageIO.PRAW.height", 1024);

       int channelsIndex = GetUserInt ("ImageIO.PRAW.channels.index", 1);
       switch (channelsIndex) {
               case 0:
                       channels = 1;
                       break;
               case 1:
                       channels = 3;
                       break;
               case 2:
                       channels = 4;
                       break;
       }

       interleaved = GetUserInt ("ImageIO.PRAW.channels.index", true) ? true : false;

       int depthIndex = GetUserInt ("ImageIO.PRAW.depth.index", 0);
       switch (depthIndex) {
               case 0:
                       depth = 8;
                       break;
               case 1:
                       depth = 16;
                       break;
               case 2:
                       depth = 32;
                       break;
       }

       byteOrderPC = GetUserInt ("ImageIO.PRAW.byte.order.index", true) ? true : false;

       headerSize = GetUserInt ("ImageIO.PRAW.header.size", 0);
}

The constructor indicates that the loader will be using the image interface.

CRawLoader::CRawLoader ()
{
       raw_factory.AddInterface (new CLxIfc_Image<CRawImage>);
}

Recognize method scans the file to see if it finds the data it can understand. In our case we look for a simple header string.

        LxResult
CRawLoader::load_Recognize (
        LXtLoadAccess1		*load)
{
        LxResult		 result = LXe_FAILED;

        open_file = fopen (load->filename, "rb");
        if (open_file) {
                /*
                 * This format is a bit unusual, in that there is no known data
                 * signature we can compare against, so we instead check that
                 * the file size is equal to the image size and depth specified
                 * in the RAW import options, plus the header size.
                 */
                GetImportOptions ();

                /*
                 * Fetch the file size.
                 */
                FSEEK (open_file, 0, SEEK_END);
                size_t length = FTELL (open_file);
                FSEEK (open_file, 0, SEEK_SET);

                /*
                 * Compare against the image size specified by the options.
                 */
                scanlineSize = width * channels * (depth / 8);
                size_t optionSize = height * scanlineSize;
                if (length == optionSize) {
                        /*
                         * Determine the specified image format.
                         */
                        switch (channels) {
                                case 1: {
                                        img_target.type = (depth == 8) ?
                                                LXiIMP_GREY8 : LXiIMP_GREYFP;
                                        break;
                                }

                                case 3: {
                                        img_target.type = (depth == 8) ?
                                                LXiIMP_RGB24 : LXiIMP_RGBFP;
                                        break;
                                }

                                case 4: {
                                        img_target.type = (depth == 8) ?
                                                LXiIMP_RGBA32 : LXiIMP_RGBAFP;
                                        break;
                                }
                        }

                        /*
                         * Use the option width and height.
                         */
                        img_target.w      = width;
                        img_target.h      = height;
                        img_target.ncolor = 0;

                        load->found  = lx::LookupGUID (LXa_IMAGE);
                        /*
                         * Use the opaque image APIs for large images.
                         * (Greater than 8K x 8K)
                         */
                        load->opaque = width * height > 512 * 512;
                        load->target = &img_target;

                        result = LXe_OK;
                }
        }

        return result;
}

This function opens the instance we choose.

       LxResult
CRawLoader::load_LoadInstance (LXtLoadAccess1 *load, void **ppvObj)
{
       LxResult		 result = LXe_OK;

       ILxUnknownID		 inst = raw_factory.Spawn (0);
       if (inst) {
               open_file = fopen (load->filename, "rb");
               if (open_file) {
                       CRawImage	*rawImage = LXCWxOBJ(inst, CRawImage);

                       rawImage->rawLoader = this;
                       rawImage->inst_ifc = inst;
                       rawImage->open_file = open_file;

                       /*
                        * Lose our reference to the open file,
                        * since it now persists with the opaque image.
                        */
                       open_file = 0;

                       ppvObj[0] = inst;
               }
       }

       return result;
}

The load-object method gets an object to fill with data, in this case an image. Using a user wrapper for the image we proceed to fill it from the file line by line. An optional monitor is used to check for user abort.

       LxResult
CRawLoader::load_LoadObject (
       LXtLoadAccess1		*load,
       ILxUnknownID		 dest)
{
       CLxUser_ImageWrite	 wimg (dest);
       CLxUser_Monitor		 mon;
       LXtImageByte		*buf;
       unsigned int		 y, w, h;
       LxResult		 rc, rd;

       wimg.Size (&w, &h);
       buf = new LXtImageByte[w * channels];
       if (!buf)
               return LXe_OUTOFMEMORY;

       if (load->monitor) {
               mon.set (load->monitor);
               mon.Init (h);
       }

       rc = LXe_OK;
       for (y = 0; y < h && LXx_OK (rc); y++) {
               if (fread (buf, 1, w * channels, open_file) != w * channels)
                       rc = LXe_FAILED;

               else {
                       rd = wimg.SetLine (y, wimg.Format (), buf);
                       if (LXx_FAIL (rd))
                               rc = rd;
                       else if (mon.Step ())
                               rc = LXe_ABORT;
               }
       }

       delete[] buf;
       return rc;
}

Cleanup is called after a failed recognize or after load-object completes, regardless of the outcome.

       LxResult
CRawLoader::load_Cleanup (
       LXtLoadAccess1		*load)
{
       if (open_file) {
               fclose (open_file);
               open_file = 0;
       }

       return LXe_OK;
}

These functions are utilities that retrieve information that deal with the image in question

        void
CRawImage::img_Size (unsigned int *w, unsigned int *h)
{
        *w = rawLoader->load_GetImageTarget ()->w;
        *h = rawLoader->load_GetImageTarget ()->h;
}

        LXtPixelFormat
CRawImage::img_Format (void)
{
        return rawLoader->load_GetImageTarget ()->type;
}

        LxResult
CRawImage::img_GetPixel (
        unsigned int	 x,
        unsigned int	 y,
        LXtPixelFormat	 type,
        void		*pixel)
{
        CLxLoc_ThreadMutex mux;
        if (threadSvc.NewMutex (mux)) {
                size_t scanlineSize = rawLoader->load_GetScanlineSize ();

                mux.Enter ();
                FSEEK (open_file, scanlineSize * y, SEEK_SET);
                FSEEK (open_file, x * rawLoader->load_GetChannels (), SEEK_CUR);

                if (type == rawLoader->load_GetImageTarget ()->type) {
                        fread (pixel, 1, rawLoader->load_GetChannels (), open_file);
                }
                else {
                        unsigned char r, g, b;
                        fread (&r, 1, 1, open_file);
                        fread (&g, 1, 1, open_file);
                        fread (&b, 1, 1, open_file);

                        LXtImageByte *dstPixel = reinterpret_cast<LXtImageByte*>(pixel);
                        switch (type) {
                                case LXiIMP_RGB24:
                                        *dstPixel++ = r;
                                        *dstPixel++ = g;
                                        *dstPixel++ = b;
                                        break;

                                case LXiIMP_RGBA32:
                                        *dstPixel++ = r;
                                        *dstPixel++ = g;
                                        *dstPixel++ = b;
                                        *dstPixel = 255;
                                        break;

                                case LXiIMP_RGBFP: {
                                        LXtImageFloat *dstPixelF =
                                                reinterpret_cast<LXtImageFloat*>(pixel);
                                        *dstPixelF++ = r / 255.0f;
                                        *dstPixelF++ = g / 255.0f;
                                        *dstPixelF++ = b / 255.0f;
                                        break;
                                }

                                case LXiIMP_RGBAFP: {
                                        LXtImageFloat *dstPixelF =
                                                reinterpret_cast<LXtImageFloat*>(pixel);
                                        *dstPixelF++ = r / 255.0f;
                                        *dstPixelF++ = g / 255.0f;
                                        *dstPixelF++ = b / 255.0f;
                                        *dstPixelF = 1.0;
                                        break;
                                }
                        }
                }
                mux.Leave ();
        }

        return LXe_OK;
}

        const void *
CRawImage::img_GetLine (
        unsigned int	 y,
        LXtPixelFormat	 type,
        void		*buf)
{
        CLxLoc_ThreadMutex mux;
        if (threadSvc.NewMutex (mux)) {
                size_t scanlineSize = rawLoader->load_GetScanlineSize ();
                mux.Enter ();
                FSEEK (open_file, scanlineSize * y, SEEK_SET);

                /*
                 * Read the scanline directly into the buffer.
                 */
                if (type == rawLoader->load_GetImageTarget ()->type) {
                        fread (buf, 1, scanlineSize, open_file);
                        mux.Leave ();
                }
                else if (type == LXiIMP_RGBA32) {
                        LXtImageByte *nativeBuf = new LXtImageByte[scanlineSize];
                        fread (nativeBuf, 1, scanlineSize, open_file);
                        mux.Leave ();

                        LXtImageByte *srcIter = nativeBuf;
                        LXtImageByte *dstIter = reinterpret_cast<LXtImageByte*>(buf);
                        for (unsigned x = 0; x < rawLoader->load_GetImageTarget ()->w; ++x) {
                                *dstIter++ = *srcIter++;
                                *dstIter++ = *srcIter++;
                                *dstIter++ = *srcIter++;
                                *dstIter++ = 255;
                        }
                        delete [] nativeBuf;
                }
                else {
                        mux.Leave ();
                }
        }

        return buf;
}

This is very much the reverse of the load-object method. We extract pixels from the image (just to be different, although by line would be faster) and write them to the file.

        LxResult
CRawSaver::sav_Save (
        ILxUnknownID		 source,
        const char		*filename,
        ILxUnknownID		 monitor)
{
        CLxUser_Image		 image (source);
        CLxUser_Monitor		 mon;
        FILE			*fp;
        unsigned char		 buf[4];
        unsigned int		 x, y, w, h;

        fp = fopen (filename, "wb");
        if (!fp)
                return LXe_FAILED;

        if (fwrite ("DEMO Raw RGB", 1, 12, fp) != 12)
                return LXe_FAILED;

        image.Size (&w, &h);
        buf[0] = w / 256;
        buf[1] = w % 256;
        buf[2] = h / 256;
        buf[3] = h % 256;
        if (fwrite (buf, 1, 4, fp) != 4)
                return LXe_FAILED;

        if (monitor) {
                mon.set (monitor);
                mon.Init (h);
        }

        for (y = 0; y < h; y++) {
                for (x = 0; x < w; x++) {
                        image.GetPixel (x, y, LXiIMP_RGBA32, buf);
                        if (fwrite (buf, 1, 3, fp) != 3)
                                return LXe_FAILED;
                }

                if (mon.Step ())
                        return LXe_ABORT;
        }

        fclose (fp);
        return LXe_OK;
}