Examples

Wolfram LibraryLink allows dynamic libraries to be directly loaded into the Wolfram Language kernel so that functions in the libraries can be immediately called from the Wolfram Language. You can exchange not only C-like data types such as integers, reals, packed arrays, and strings, but also arbitrary Wolfram Language expressions. In addition, there are useful functions such as sending errors and calling back to the Wolfram Language.

A number of sample Wolfram Libraries are included with the documentation. These have a number of short functions that demonstrate various aspects of calling libraries from the Wolfram Language.

Using the Examples

The documentation contains the source for the sample libraries. You will need access to a C compiler to build the libraries, and you might find the CCompilerDriver package useful.

This shows that the demo example can be found for your platform.

In[2]:=
Click for copyable input
Out[2]=

This loads a function from the demo example library.

In[2]:=
Click for copyable input
Out[2]=

This calls the function.

In[3]:=
Click for copyable input
Out[3]=

Source

The source for the examples is found in the documentation paclet. You can find this by evaluating the following input.

In[3]:=
Click for copyable input
Out[3]=
demo.csample of basic examples
demo_shared.csample of basic examples of shared passing
demo_error.csample of error catching
demo_string.csample of using string arguments and results
demo_LinkObject.csample of using LinkObject as an argument and result specification
demo_managed.cxxsample of using CreateManagedLibraryExpression
demo_callback.csample of calling back a CompiledFunction from a library
demo_sparse.csample of using SparseArray
demo_image.cxxsample of using Image

Library examples source files.

demo

The demo example contains many functions; here is an example of using it.

In[5]:=
Click for copyable input
Out[6]=

demo_shared

The demo_shared example has an example of sharing a packed array with a library function. The following loads a number of functions.

In[7]:=
Click for copyable input

This creates a packed array and loads it into the library. Since shared memory passing was used, the array can be used in other function calls.

In[10]:=
Click for copyable input
Out[11]=

This gets the 10^(th) element, which is 10.

In[12]:=
Click for copyable input
Out[12]=

This unloads the array; after this the array cannot be used any more.

In[13]:=
Click for copyable input
Out[13]=

This shows the source of some of the functions.

DLLEXPORT int loadArray(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {

    tensor = MArgument_getMTensor(Args[0]);
    MArgument_setInteger(Res, 0);
    return LIBRARY_NO_ERROR;
}

DLLEXPORT int getElementVector(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {
    mint pos;
    double value;
    
    pos = MArgument_getInteger(Args[0]);
    value = libData->MTensorVector_getReal( tensor, pos);
    
    MArgument_setReal(Res, value);
    return LIBRARY_NO_ERROR;
}

DLLEXPORT int unloadArray(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {

    libData->MTensor_disown( tensor);
    MArgument_setInteger(Res, 0);
    return LIBRARY_NO_ERROR;
}

demo_error

The demo_error example has examples of calling functions that trigger errors. The following loads a function from the library.

In[14]:=
Click for copyable input

This is the source of the errordemo1 function. It takes an MTensor argument and then tries to get the real data.

DLLEXPORT int errordemo1(WolframLibraryData libData,
            mint Argc, MArgument *Args, MArgument Res) {
    MTensor T0, T1;
    mint I0, I1, res;
    mint pos[2];
    double *data;

    T0 = MArgument_getMTensor(Args[0]);
    I0 = MArgument_getInteger(Args[1]);
    
    data = libData->MTensor_getRealData(T0);
    MArgument_setReal(Res, data[I0]);
    return LIBRARY_NO_ERROR;
}

The example takes an integer MTensor. The function calls MTensor_getRealData which results in an error and returns a LibraryFunctionError expression.

In[15]:=
Click for copyable input
Out[15]=

demo_string

The demo_string example shows some ways that you can use string arguments and results with the required memory management. The following loads a function that does a simple shift cipher for an ASCII string.

In[17]:=
Click for copyable input

This is the source of the encoding function. The arguments are the string to encode and the integer shift to apply.

DLLEXPORT int encodeString(WolframLibraryData libData,
        mint Argc, MArgument *Args, MArgument Res)
{
    mint i = 0, shift;
    
    if (string)
        libData->String_disown(string);

    string = MArgument_getUTF8String(Args[0]);
    shift = MArgument_getInteger(Args[1]);

    /* Find shift mod 127 so we only
     deal with positive numbers below */
    shift = shift % 127;
    if (shift < 0)
        shift += 127;

    shift -= 1;
        
    while (string[i]) {
        mint c = (mint) string[i];
        /* Error for non ASCII string */
        if (c & 128) return LIBRARY_FUNCTION_ERROR;
        c = ((c + shift) % 127) + 1;
        string[i++] = (char) c;
    }
    MArgument_setUTF8String(Res, string);
    return LIBRARY_NO_ERROR;
}

Here is an example.

In[18]:=
Click for copyable input
Out[18]=

Note that the string reference is stored in a variable with scope outside of the function. This reference is disowned when either the function is called again (and a new string is referenced) or when the library is unloaded (in the function WolframLibrary_uninitialize). Since the string argument memory is owned entirely by the library functions, the encoding can be done in place in that memory, saving the need to allocate another string.

demo_LinkObject

The demo_WSTP example shows using LinkObject as an argument and result specification. The following loads a function from the library.

In[19]:=
Click for copyable input

This is the source of the reverseString function. It takes a WSLINK argument and uses the Wolfram Symbolic Transfer Protocol (WSTP) API to read the arguments that come in a list. After it has generated the result, this is written onto the link.

DLLEXPORT int reverseString( WolframLibraryData libData, WLINK mlp)
{
    int res = LIBRARY_FUNCTION_ERROR;
    long len;
    const char *inStr = NULL;
    char* outStr = NULL;
    
    if ( !WSTestHead( mlp, "List", &len))
        goto retPt;
    if ( len != 1)
        goto retPt;

    if(! WSGetString(mlp, &inStr))
        goto retPt;

    if ( ! WSNewPacket(mlp) )
        goto retPt;

    outStr = reverseStringImpl(inStr);
    
    if (!WSPutString( mlp,outStr))
        goto retPt;
    res = 0;
retPt:
    if ( inStr != NULL)
        WSReleaseString(mlp, inStr);
    if ( outStr != NULL)
        free( (void*) outStr);
    return res;
}

Here a string is passed in. The result is read from the link and displayed.

In[20]:=
Click for copyable input
Out[20]=

demo_managed

The demo_managed example shows how you can use managed library expressions to keep separate instances of a class or objects associated with Wolfram Language expressions.

The functions in the example implement a very simple (and not very random or particularly efficiently implemented) linear congruential generator that allows multiple instances with different states (or even parameters) simultaneously.

A key part of the code is in the initialization and unitialization functions where registerLibraryExpressionManager and unregisterLibraryExpressionManager are used.

/* Initialize Library */
EXTERN_C DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData)
{
    return (*libData->registerLibraryExpressionManager)("LCG", manage_instance);
}

/* Uninitialize Library */
EXTERN_C DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData)
{
    int err = (*libData->unregisterLibraryExpressionManager)("LCG");
}

The code in the file demo_managed.cxx uses C++ only for the hashmap class. This is a convenient way of mapping IDs to instances. The function manage_instance is defined to add the ID to the hashmap when mode is 0 (when CreateManagedLibraryExpression is used) and to free the MTensor and erase the ID from the hashmap when mode is 1.

DLLEXPORT void manage_instance(WolframLibraryData libData, mbool mode, mint id)
{
    if (mode == 0) {
        MTensor *T = new(MTensor);
        map[id] = T;
        *T = 0;
    } else {
        MTensor *T = map[id];
        if (T != 0) {
            if (*T != 0) (*libData->MTensor_free)(*T);
            map.erase(id);
        }
    }
}

The following loads the library (the registration of the manager with name "LCG" is done when the library is first loaded) and defines several LibraryFunctions that manipulate instances.

In[1]:=
Click for copyable input

The following makes several definitions to set up an expression type with head LCG that will be used as a handle to an instance.

In[5]:=
Click for copyable input

Finally, this defines the "random" number generator.

In[9]:=
Click for copyable input

This sets up an instance (the parameters come from Numerical Recipes (1992)), tests it, and generates a number from it.

In[12]:=
Click for copyable input
Out[12]=
In[13]:=
Click for copyable input
Out[13]=
In[14]:=
Click for copyable input
Out[14]=

This sets up another instance and generates two numbers from it. Note that the ID is unique.

In[15]:=
Click for copyable input
Out[15]=
In[16]:=
Click for copyable input
Out[16]=

This shows all the instances with their states.

In[17]:=
Click for copyable input
Out[17]=

After generating one more number, the first and second instances have identical states.

In[18]:=
Click for copyable input
Out[18]=
In[19]:=
Click for copyable input
Out[19]=

The following sets up a third instance, but being careful to be sure that the assignment is the only reference to the instance expression and use it to generate a matrix.

In[20]:=
Click for copyable input
Out[20]=

The following releases the second instance.

In[21]:=
Click for copyable input
Out[21]=

Unsetting the value of g2 takes away all references to the third instance, and the manage_instance function is automatically called, removing the instance from the hashmap.

In[22]:=
Click for copyable input
Out[22]=

When the library is unloaded, the remaining instances will be removed so g is no longer a managed library expression.

In[23]:=
Click for copyable input
Out[23]=
In[24]:=
Click for copyable input
Out[24]=

demo_callback

The demo_callback example shows how you can call back from a library to a CompiledFunction in the Wolfram Language.

A important part of the code is in the initialization and unitialization functions where registerLibraryCallbackManager and unregisterLibraryCallbackManager are used.

/* Initialize Library */
DLLEXPORT int WolframLibrary_initialize( WolframLibraryData libData)
{
    call_id = 0;
    call_nargs = 0;
    return (*libData->registerLibraryCallbackManager)("demo", manage_callback);
}

/* Uninitialize Library */
DLLEXPORT void WolframLibrary_uninitialize( WolframLibraryData libData)
{
    (*libData->unregisterLibraryCallbackManager)("demo");
}

The callback manager function, manage_callback, is very simple in this example, allowing only one connected function at a time. When the function is called, if there is already a positive ID (all IDs generated by the system will be positive), then the function associated with that ID is released and the new ID is stored. With more elaborate code to store multiple IDs, you could have multiple functions connected at once.

DLLEXPORT mbool manage_callback(WolframLibraryData libData, mint id, MTensor argtypes)
{
    mint i;
    mint *dims;
    mint *typerank;
    if (call_id) {
        (*libData->releaseLibraryCallbackFunction)(call_id);
        call_id = 0;
        free(cbArgs);
        free(tdata);
    }
    call_id = id;
    dims = (*libData->MTensor_getDimensions)(argtypes);
    call_nargs = dims[0] - 1;
    if (call_nargs == 0) {
        call_id = 0;
        return False;
    }
    typerank = (*libData->MTensor_getIntegerData)(argtypes);
    /* Check that the arguments and result (thus i <= call_nargs loop control) are scalar mreal */
    for (i = 0; i <= call_nargs; i++) {
        /* Each row is {type, rank} */
        if ((typerank[0] != MType_Real) || (typerank[1] != 0)) {
            call_id = 0;
            call_nargs = 0;
            return False;
        }
        typerank += 2;
    }
    cbArgs = (MArgument *) malloc((call_nargs + 1)*sizeof(MArgument));
    tdata = (mreal **) malloc(call_nargs*sizeof(mreal *));
    return True;
}

The following loads the library (the registration of the manager with name "demo_callback_manager" is done when the library is first loaded) and defines a LibraryFunction that will call the callback function for each element of an array of type mreal.

In[2]:=
Click for copyable input

The following compiles the sine function and connects the CompiledFunction to the library.

In[3]:=
Click for copyable input
Out[3]=
In[4]:=
Click for copyable input
Out[4]=

This calls the LibraryFunction on an array of random reals.

In[5]:=
Click for copyable input
Out[6]=

The result is the same as evaluating Sin directly.

In[7]:=
Click for copyable input
Out[7]=

To make a comparison of the timing for using the callback, a function that directly uses the standard C sin() function was defined.

In[8]:=
Click for copyable input
In[9]:=
Click for copyable input
In[10]:=
Click for copyable input
Out[10]=
In[11]:=
Click for copyable input
Out[11]=

The values are the same.

In[12]:=
Click for copyable input
Out[12]=

Of course, using the Sin function in the Wolfram Language can be quite a bit faster yet, since it may use parallel evaluation.

In[13]:=
Click for copyable input
Out[13]=

In this case there may be small differences, because the parallel evaluation uses functions different from the standard C library.

In[14]:=
Click for copyable input
Out[14]=

If you compile the function to C using CompilationTarget->"C", the overhead is quite a bit smaller.

In[15]:=
Click for copyable input
In[16]:=
Click for copyable input
Out[16]=
In[17]:=
Click for copyable input
Out[17]=

The mechanism still works even when the CompiledFunction needs to use the Wolfram Language evaluator.

In[18]:=
Click for copyable input
Out[18]=
In[19]:=
Click for copyable input
Out[19]=
In[20]:=
Click for copyable input
Out[21]=

However, the function in this case can be changed without recompiling.

In[22]:=
Click for copyable input
Out[23]=

Note the management function rejects the connection if the CompiledFunction results and arguments are not all scalar reals, and so ConnectLibraryCallbackFunction returns False.

In[24]:=
Click for copyable input
Out[24]=
In[25]:=
Click for copyable input
Out[25]=

Now trying to apply the callback will give an error.

In[26]:=
Click for copyable input
Out[26]=

The code allows for multiple arguments. Here is an example that iterates the logistic map 1000 times.

In[35]:=
Click for copyable input
Out[35]=
In[36]:=
Click for copyable input
Out[36]=

To use this, a LibraryFunction overload that uses two arguments is needed.

In[43]:=
Click for copyable input
In[45]:=
Click for copyable input
Out[45]=

demo_sparse

The demo_sparse example shows how you can use MSparseArray arguments and results with SparseArray objects in the Wolfram Language.

The function sparse_properties from the demo enables an exploration of how the compressed sparse row (CSR) data structure works. The following loads this function from the library.

(P10.0) In[1]:=
Click for copyable input

This is the source for the sparse_properties functions. The arguments are a string to identify the desired property and an MSparseArray, allowed to be of any type and rank. The use of the "Constant" passing is appropriate since the function is only using read access of the MSparseArray argument.

DLLEXPORT int sparse_properties( WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) 
{
    int err = LIBRARY_NO_ERROR;
    char *what;
    mint *data;
    MSparseArray S;
    MTensor *T, Tres = 0;
    WolframSparseLibrary_Functions sparseFuns = libData->sparseLibraryFunctions;

    if (Argc != 2) return LIBRARY_FUNCTION_ERROR;
        
    what = MArgument_getUTF8String(Args[0]);
    S = MArgument_getMSparseArray(Args[1]);

    if (!strcmp(what, "ImplicitValue")) {
        T = (*(sparseFuns->MSparseArray_getImplicitValue))(S);
    } else if (!strcmp(what, "ExplicitValues")) {
        T = (*(sparseFuns->MSparseArray_getExplicitValues))(S);
    } else if (!strcmp(what, "RowPointers")) {
        T = (*(sparseFuns->MSparseArray_getRowPointers))(S);
    } else if (!strcmp(what, "ColumnIndices")) {
        T = (*(sparseFuns->MSparseArray_getColumnIndices))(S);
    } else if (!strcmp(what, "ExplicitPositions")) {
        err = (*(sparseFuns->MSparseArray_getExplicitPositions))(S, &Tres);
    } else if (!strcmp(what, "Normal")) {
        err = (*(sparseFuns->MSparseArray_toMTensor))(S, &Tres);
    } else {
        err = LIBRARY_FUNCTION_ERROR;
    }
    if (err) return err;
    if (!Tres) (*(libData->MTensor_clone))(*T, &Tres);

    MArgument_setMTensor(Res, Tres);
    return err;
}

One thing to note in the source is that the MTensor references returned for the CSR data by MSparseArray_getImplicitValue, MSparseArray_getImplicitValue, MSparseArray_getColumnIndices, and MSparseArray_getRowPointers belong to the MSparseArray, so to return them, it is necessary to make a copy so that the MTensors in the MSparseArray data do not get unintentionally freed.

Below are a few examples that show some of the properties for different sparse arrays.

In[32]:=
Click for copyable input
In[33]:=
Click for copyable input
Out[33]=

This makes a table of the CSR data structures and also shows the explicit positions and the normal array.

In[34]:=
Click for copyable input
In[35]:=
Click for copyable input
Out[35]//TableForm=

When either the implicit value or any of the values have machine precision, all of the values get coerced in the coercion stage of converting from SparseArray to MSparseArray.

In[36]:=
Click for copyable input
Out[37]//TableForm=

Using Normal in the Wolfram Language on the SparseArray with mixed type keeps the exact values, but is not a packed array.

In[38]:=
Click for copyable input
Out[38]=

A sparse vector is represented in the CSR as a 1-row matrix.

In[39]:=
Click for copyable input
Out[39]//TableForm=

A rank-4 array has three column indices per entry.

In[40]:=
Click for copyable input
Out[40]//TableForm=

This loads a function that allows you to change the values of a SparseArray in place.

In[41]:=
Click for copyable input
Out[41]=

This is the source of the sparse_modify_values function.

DLLEXPORT int sparse_modify_values( WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res)
{
    char *what;
    int err = 0;
    mbool resparsify;
    mint i, nz;
    mreal *t, *v;
    MSparseArray S = 0, Sout = 0;
    MTensor T = 0, *Vp = 0;
    WolframSparseLibrary_Functions sparseFuns = libData->sparseLibraryFunctions;

    if (Argc != 2) return LIBRARY_FUNCTION_ERROR;

    S = MArgument_getMSparseArray(Args[0]);
    Vp = (*sparseFuns->MSparseArray_getExplicitValues)(S);
    if ((*libData->MTensor_getType)(*Vp) != MType_Real) return LIBRARY_TYPE_ERROR;
    nz = (*libData->MTensor_getFlattenedLength)(*Vp);
    v = (*libData->MTensor_getRealData)(*Vp);

    T = MArgument_getMTensor(Args[1]);
    if ((*libData->MTensor_getType)(T) != MType_Real) return LIBRARY_TYPE_ERROR;
    if ((*libData->MTensor_getFlattenedLength)(T) != nz) return LIBRARY_DIMENSION_ERROR;
    t = (*libData->MTensor_getRealData)(T);

    for (i = 0; i < nz; i++) v[i] = t[i];

    /* Recompute explicit positions */
    err = (*sparseFuns->MSparseArray_resetImplicitValue)(S, NULL, &Sout);

    (*sparseFuns->MSparseArray_disown)(S);

    if (!err)
        MArgument_setMSparseArray(Res, Sout);

    return err;
}

The sparse array returned is created by recomputing the explicit positions. This is roughly equivalent to what happens if you use SparseArray[s], where s is a SparseArray. Note that to modify the values in place, the data is copied into the existing MTensor for the values that are owned by the MSparseArray. For the SparseArray in the kernel to be affected, the passing needs to be "Shared".

In[42]:=
Click for copyable input
Out[42]=
In[43]:=
Click for copyable input
Out[43]=

Because one of the new values was zero, that position no longer needs to be represented explicitly, so the structure for is more compact.

In[44]:=
Click for copyable input
Out[44]//TableForm=
In[45]:=
Click for copyable input
Out[45]//TableForm=

demo_image

The demo_image example shows how you can use MImage arguments and results with Image and Image3D objects in the Wolfram Language.

The function color_negate from the demo shows how to obtain the negative of Image or Image3D objects in which all colors have been negated. The following loads this function from the library.

In[6]:=
Click for copyable input

This is the source for the color_negate function. The argument is an MImage to be negated.

This code goes through all pixels present in an image and negates them, which is the fastest implementation. However, this fails to correctly negate images with alpha channels since the alpha channel is also negated.


template <typename T> static T maxValue() {
return -1; // ERROR
}

template <> char maxValue<char>() { return 1; }

template <> raw_t_ubit8 maxValue<raw_t_ubit8>() { return 255; }

template <> raw_t_ubit16 maxValue<raw_t_ubit16>() { return 65535; }

template <> raw_t_real32 maxValue<raw_t_real32>() { return 1.0f; }

template <> raw_t_real64 maxValue<raw_t_real64>() { return 1.0; }

template <typename T>
static void icolor_negate(void *out0, const void *in0, mint length) {
mint ii;
T *out = reinterpret_cast<T *>(out0);
const T *in = reinterpret_cast<const T *>(in0);
for (ii = 0; ii < length; ii++) {
out[ii] = maxValue<T>() - in[ii];
}
}

/* Negate image colors */
EXTERN_C DLLEXPORT int color_negate(WolframLibraryData libData, mint Argc,
MArgument *Args, MArgument res) {
mint length;
MImage image_in, image_out = 0;
void *data_in, *data_out;
int err = LIBRARY_FUNCTION_ERROR;
imagedata_t type;
WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

if (Argc < 1) {
return err;
}

image_in = MArgument_getMImage(Args[0]);

err = imgFuns->MImage_clone(image_in, &image_out);
if (err)
return err;

type = imgFuns->MImage_getDataType(image_in);
length = imgFuns->MImage_getFlattenedLength(image_in);

data_in = imgFuns->MImage_getRawData(image_in);
data_out = imgFuns->MImage_getRawData(image_out);
if (data_in == NULL || data_out == NULL)
goto cleanup;

switch (type) {
case MImage_Type_Bit:
icolor_negate<char>(data_out, data_in, length);
break;
case MImage_Type_Bit8:
icolor_negate<raw_t_ubit8>(data_out, data_in, length);
break;
case MImage_Type_Bit16:
icolor_negate<raw_t_ubit16>(data_out, data_in, length);
break;
case MImage_Type_Real32:
icolor_negate<raw_t_real32>(data_out, data_in, length);
break;
case MImage_Type_Real:
icolor_negate<raw_t_real64>(data_out, data_in, length);
break;
default:
goto cleanup;
}

MArgument_setMImage(res, image_out);
return err;

cleanup:
imgFuns->MImage_free(image_out);
return err;
}

Below are a few examples that show how to negate colors in images.

In[7]:=
Click for copyable input
Out[7]=
In[8]:=
Click for copyable input
Out[8]=

Another example shows how to convert an RGB image to grayscale. This loads the function rgb_to_gray.

In[13]:=
Click for copyable input
Out[13]=

This is the source of the rgb_to_gray function.

template <typename T>
static void irgb_to_gray(void *out0, const void *in0, mint rows, mint cols,
mbool alphaQ) {
mint row;
mint col;
T r, g, b;

T *out = reinterpret_cast<T *>(out0);
const T *in = reinterpret_cast<const T *>(in0);

if (alphaQ) {
for (row = 0; row < rows; row++) {
for (col = 0; col < cols; col++) {
mint idx = row * cols + col;
r = in[4 * idx];
g = in[4 * idx + 1];
b = in[4 * idx + 2];
out[2 * idx] = (T)(.299 * r + .587 * g + .114 * b);
out[2 * idx + 1] = in[4 * idx + 3];
}
}
} else {
for (row = 0; row < rows; row++) {
for (col = 0; col < cols; col++) {
mint idx = row * cols + col;
r = in[3 * idx];
g = in[3 * idx + 1];
b = in[3 * idx + 2];
out[idx] = (T)(.299 * r + .587 * g + .114 * b);
}
}
}
}

/* Convert RGB image to grayscale */
EXTERN_C DLLEXPORT int rgb_to_gray(WolframLibraryData libData, mint Argc,
MArgument *Args, MArgument res) {
mint rows, columns;
mbool alphaQ;
int err = 0;
imagedata_t type;
MImage image_in, image_out;
void *data_in, *data_out;
WolframImageLibrary_Functions imgFuns = libData->imageLibraryFunctions;

if (Argc < 1) {
return LIBRARY_FUNCTION_ERROR;
}

image_in = MArgument_getMImage(Args[0]);
if (imgFuns->MImage_getColorSpace(image_in) != MImage_CS_RGB)
return LIBRARY_FUNCTION_ERROR;

/*This function accepts only 2D images, but can be easily extended to work
* with Image3D.*/
if (imgFuns->MImage_getRank(image_in) == 3)
return LIBRARY_FUNCTION_ERROR;

type = imgFuns->MImage_getDataType(image_in);
rows = imgFuns->MImage_getRowCount(image_in);
columns = imgFuns->MImage_getColumnCount(image_in);
alphaQ = imgFuns->MImage_alphaChannelQ(image_in);

err = imgFuns->MImage_new2D(columns, rows, alphaQ ? 2 : 1, type,
MImage_CS_Gray, True, &image_out);
if (err)
return LIBRARY_FUNCTION_ERROR;

data_in = imgFuns->MImage_getRawData(image_in);
data_out = imgFuns->MImage_getRawData(image_out);
if (data_in == NULL || data_out == NULL)
return LIBRARY_FUNCTION_ERROR;

switch (type) {
case MImage_Type_Bit:
// RGB binary images are not allowed
imgFuns->MImage_free(image_out);
return LIBRARY_FUNCTION_ERROR;
case MImage_Type_Bit8:
irgb_to_gray<raw_t_ubit8>(data_out, data_in, rows, columns, alphaQ);
break;
case MImage_Type_Bit16:
irgb_to_gray<raw_t_ubit16>(data_out, data_in, rows, columns, alphaQ);
break;
case MImage_Type_Real32:
irgb_to_gray<raw_t_real32>(data_out, data_in, rows, columns, alphaQ);
break;
case MImage_Type_Real:
irgb_to_gray<raw_t_real64>(data_out, data_in, rows, columns, alphaQ);
break;
default:
imgFuns->MImage_free(image_out);
return LIBRARY_FUNCTION_ERROR;
}
MArgument_setMImage(res, image_out);
return LIBRARY_NO_ERROR;
}

Notice that this function accepts only 2D images but can be easily extended to work with Image3D.

In[28]:=
Click for copyable input
Out[28]=

Helper functions such as MImage_getByte and MImage_setByte can be used to avoid index arithmetic in C. These functions use the same numbering scheme as the Wolfram Language parts, i.e. the first element is 1.