//*****************************************************************************
//
// File name: Mfunc.cs
// Location: See Matrox Example Launcher in the MIL Control Center
// 
//
// Synopsis:  This example shows the use of the MIL Function Developer Tool Kit and how 
//            MIL and custom code can be mixed to create a custom MIL function that 
//            accesses the data pointer of a MIL buffer directly in order to process it.
//
//            The example creates a Master MIL function that registers all the parameters 
//            to MIL and calls the Slave function. The Slave function retrieves all the 
//            parameters, gets the pointers to the MIL image buffers, uses them to access 
//            the data directly and adds a constant.
//             
//            Note: The images must be 8-bit unsigned and have a valid host address.
//
// Copyright (C) Matrox Electronic Systems Ltd., 1992-2020.
// All Rights Reserved
//*****************************************************************************
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;

using Matrox.MatroxImagingLibrary;

namespace MFunc
{
    class Program
    {
        // MIL function specifications.
        private const int FUNCTION_NB_PARAM = 4;
        private const int FUNCTION_OPCODE_ADD_CONSTANT = 1;
        private const int FUNCTION_PARAMETER_ERROR_CODE = 1;
        private const int FUNCTION_SUPPORTED_IMAGE_TYPE = (8 + MIL.M_UNSIGNED);

        // Target image file name.
        private const string IMAGE_FILE = MIL.M_IMAGE_PATH + "BoltsNutsWashers.mim";

        // Master MIL Function definition.
        // -------------------------------

        static MIL_INT AddConstant(MIL_ID SrcImageId, MIL_ID DstImageId, MIL_INT ConstantToAdd)
        {
            MIL_ID Func = MIL.M_NULL;
            MIL_INT SlaveReturnValue = 0;

            // Allocate a MIL function context that will be used to call a target 
            // Slave function locally on the Host to do the processing.
            MFUNCFCTPTR SlaveAddConstantDelegate = new MFUNCFCTPTR(SlaveAddConstant);
            MIL.MfuncAlloc("AddConstant",
                       FUNCTION_NB_PARAM,
                       SlaveAddConstantDelegate, MIL.M_NULL, MIL.M_NULL,
                       MIL.M_USER_MODULE_1 + FUNCTION_OPCODE_ADD_CONSTANT,
                       MIL.M_LOCAL + MIL.M_SYNCHRONOUS_FUNCTION,
                       ref Func);

            // Register the parameters.
            MIL.MfuncParamMilId(Func, 1, SrcImageId, MIL.M_IMAGE, MIL.M_IN);
            MIL.MfuncParamMilId(Func, 2, DstImageId, MIL.M_IMAGE, MIL.M_OUT);
            MIL.MfuncParamMilInt(Func, 3, ConstantToAdd);

            // To pass a pointer to MIL, we need to use a reference type such as an array
            // to be able to pin the object and prevent the garbage collector from moving the object.
            MIL_INT[] slaveReturnValueArray = new MIL_INT[1] { -1 };
            GCHandle slaveReturnValueArrayHandle = GCHandle.Alloc(slaveReturnValueArray, GCHandleType.Pinned);

            try
            {
                // Get the address of the pinned object, for an array, the address is pointing to the first element.
                IntPtr slaveReturnValuePtr = slaveReturnValueArrayHandle.AddrOfPinnedObject();
                MIL.MfuncParamDataPointer(Func, 4, slaveReturnValuePtr, MIL_INT.Size * 2, MIL.M_OUT);

                // Call the target Slave function.
                MIL.MfuncCall(Func);

                SlaveReturnValue = slaveReturnValueArray[0];

                // Make sure that the delegate survives garbage collection until the slave function is executed.
                GC.KeepAlive(SlaveAddConstantDelegate);
            }
            finally
            {
                // Free the allocated GCHandle to allow the array object to be garbage collected.
                if (slaveReturnValueArrayHandle.IsAllocated)
                {
                    slaveReturnValueArrayHandle.Free();
                }
            }

            // Free the MIL function context.
            MIL.MfuncFree(Func);

            return SlaveReturnValue;
        }

        // MIL Slave function definition.
        // ------------------------------

        static void SlaveAddConstant(MIL_ID Func)
        {
            MIL_ID SrcImageId = MIL.M_NULL;
            MIL_ID DstImageId = MIL.M_NULL;
            MIL_INT ConstantToAdd = 0;
            MIL_INT TempValue = 0;
            MIL_INT SrcImageDataPtr = MIL.M_NULL;
            MIL_INT DstImageDataPtr = MIL.M_NULL;
            MIL_INT SrcImageSizeX = 0;
            MIL_INT SrcImageSizeY = 0;
            MIL_INT SrcImageType = 0;
            MIL_INT SrcImagePitchByte = 0;
            MIL_INT DstImageSizeX = 0;
            MIL_INT DstImageSizeY = 0;
            MIL_INT DstImageType = 0;
            MIL_INT DstImagePitchByte = 0;
            int x = 0;
            int y = 0;
            IntPtr SlaveReturnValuePtr = IntPtr.Zero;

            // Read the parameters.
            MIL.MfuncParamValue(Func, 1, ref SrcImageId);
            MIL.MfuncParamValue(Func, 2, ref DstImageId);
            MIL.MfuncParamValue(Func, 3, ref ConstantToAdd);
            MIL.MfuncParamValue(Func, 4, ref SlaveReturnValuePtr);

            // Lock buffers for direct access.
            MIL.MbufControl(SrcImageId, MIL.M_LOCK, MIL.M_DEFAULT);
            MIL.MbufControl(DstImageId, MIL.M_LOCK, MIL.M_DEFAULT);

            // Read image information.
            MIL.MbufInquire(SrcImageId, MIL.M_HOST_ADDRESS, ref SrcImageDataPtr);
            MIL.MbufInquire(SrcImageId, MIL.M_SIZE_X, ref SrcImageSizeX);
            MIL.MbufInquire(SrcImageId, MIL.M_SIZE_Y, ref SrcImageSizeY);
            MIL.MbufInquire(SrcImageId, MIL.M_TYPE, ref SrcImageType);
            MIL.MbufInquire(SrcImageId, MIL.M_PITCH_BYTE, ref SrcImagePitchByte);
            MIL.MbufInquire(DstImageId, MIL.M_HOST_ADDRESS, ref DstImageDataPtr);
            MIL.MbufInquire(DstImageId, MIL.M_SIZE_X, ref DstImageSizeX);
            MIL.MbufInquire(DstImageId, MIL.M_SIZE_Y, ref DstImageSizeY);
            MIL.MbufInquire(DstImageId, MIL.M_TYPE, ref DstImageType);
            MIL.MbufInquire(DstImageId, MIL.M_PITCH_BYTE, ref DstImagePitchByte);

            // Reduce the destination area to process if necessary.
            if (SrcImageSizeX < DstImageSizeX) DstImageSizeX = SrcImageSizeX;
            if (SrcImageSizeY < DstImageSizeY) DstImageSizeY = SrcImageSizeY;

            unsafe // Unsafe code block to allow manipulating memory addresses
            {
                // If images have the proper depth, execute the operation using a custom code.
                if ((SrcImageType == DstImageType) && (SrcImageType == FUNCTION_SUPPORTED_IMAGE_TYPE) &&
                    (SrcImageDataPtr != MIL.M_NULL) && (DstImageDataPtr != MIL.M_NULL))
                {
                    // Convert the address inquired from MIL to a fixed size pointer.
                    // The MIL_INT type cannot be converted to a byte * so use the
                    // IntPtr portable type 
                    // 
                    IntPtr SrcImageDataPtrIntPtr = SrcImageDataPtr;
                    IntPtr DstImageDataPtrIntPtr = DstImageDataPtr;
                    byte* SrcImageDataAddr = (byte*)SrcImageDataPtrIntPtr;
                    byte* DstImageDataAddr = (byte*)DstImageDataPtrIntPtr;

                    // Add the constant to the image.
                    for (y = 0; y < DstImageSizeY; y++)
                    {
                        for (x = 0; x < DstImageSizeX; x++)
                        {
                            // Calculate the value to write.
                            TempValue = (MIL_INT)SrcImageDataAddr[x] + (MIL_INT)ConstantToAdd;

                            // Write the value if no overflow, else saturate.
                            if (TempValue <= 0xff)
                            {
                                DstImageDataAddr[x] = (byte)TempValue;
                            }
                            else
                            {
                                DstImageDataAddr[x] = 0xff;
                            }
                        }

                        // Move pointer to the next line taking into account the image's pitch.
                        SrcImageDataAddr += SrcImagePitchByte;
                        DstImageDataAddr += DstImagePitchByte;
                    }

                    // Return a null error code to the Master function.
                    MIL_INT.WriteMilInt(SlaveReturnValuePtr, MIL.M_NULL);
                }
                else
                {
                    // Buffer cannot be processed. Report an error.
                    MIL.MfuncErrorReport(Func, MIL.M_FUNC_ERROR + FUNCTION_PARAMETER_ERROR_CODE,
                                     "Invalid parameter.",
                                     "Image type not supported or invalid target system.",
                                     "Image depth must be 8-bit and have a valid host address.",
                                     MIL.M_NULL);

                    // Return an error code to the Master function.
                    MIL_INT.WriteMilInt(SlaveReturnValuePtr, MIL.M_FUNC_ERROR + FUNCTION_PARAMETER_ERROR_CODE);
                }
            }

            // Unlock buffers.
            MIL.MbufControl(SrcImageId, MIL.M_UNLOCK, MIL.M_DEFAULT);
            MIL.MbufControl(DstImageId, MIL.M_UNLOCK, MIL.M_DEFAULT);

            // Signal to MIL that the destination buffer was modified.
            MIL.MbufControl(DstImageId, MIL.M_MODIFIED, MIL.M_DEFAULT);
        }

        // Main to test the custom function. //
        // --------------------------------- //

        static void Main(string[] args)
        {
            MIL_ID MilApplication = MIL.M_NULL;     // Application identifier.
            MIL_ID MilSystem = MIL.M_NULL;          // System identifier.
            MIL_ID MilDisplay = MIL.M_NULL;         // Display identifier.
            MIL_ID MilImage = MIL.M_NULL;           // Image buffer identifier.
            MIL_INT ReturnValue = 0;                // Return value of the function.

            // Allocate default application, system, display and image.
            MIL.MappAllocDefault(MIL.M_DEFAULT, ref MilApplication, ref MilSystem, ref MilDisplay, MIL.M_NULL, MIL.M_NULL);

            // Load source image into a Host memory image buffer.
            MIL.MbufAlloc2d(MilSystem,
                        MIL.MbufDiskInquire(IMAGE_FILE, MIL.M_SIZE_X),
                        MIL.MbufDiskInquire(IMAGE_FILE, MIL.M_SIZE_Y),
                        8 + MIL.M_UNSIGNED,
                        MIL.M_IMAGE + MIL.M_DISP + MIL.M_HOST_MEMORY,
                        ref MilImage);
            MIL.MbufLoad(IMAGE_FILE, MilImage);

            // Display the image.
            MIL.MdispSelect(MilDisplay, MilImage);

            // Pause.
            Console.WriteLine();
            Console.WriteLine("MIL FUNCTION DEVELOPER'S TOOLKIT:");
            Console.WriteLine("---------------------------------");
            Console.WriteLine();
            Console.WriteLine("This example creates a custom MIL function that processes");
            Console.WriteLine("an image using its data pointer directly.");
            Console.WriteLine();
            Console.WriteLine("Target image was loaded.");
            Console.WriteLine("Press a key to continue.");
            Console.WriteLine();
            Console.ReadKey();

            // Run the custom function only if the target system's memory is local and accessible.
            if (MIL.MsysInquire(MilSystem, MIL.M_LOCATION, MIL.M_NULL) == MIL.M_LOCAL)
            {
                // Process the image directly with the custom MIL function.
                ReturnValue = AddConstant(MilImage, MilImage, 0x40);

                // Print a conclusion message.
                if (ReturnValue == MIL.M_NULL)
                {
                    Console.WriteLine("The white level of the image was augmented.");
                }
                else
                {
                    Console.WriteLine("An error was returned by the Slave function.");
                }
            }
            else
            {
                // Print that the example don't run remotely.
                Console.WriteLine("This example doesn't run with Distributed MIL.");
            }

            // Wait for a key to terminate.
            Console.WriteLine("Press a key to terminate.");
            Console.WriteLine();
            Console.ReadKey();

            // Free all allocations.
            MIL.MbufFree(MilImage);
            MIL.MappFreeDefault(MilApplication, MilSystem, MilDisplay, MIL.M_NULL, MIL.M_NULL);
        }
    }
}