Sample Code

windows driver samples/ Windows Image Acquisition (WIA) Driver Samples/ C++/ ProdScan/ Validate.cpp/

/**************************************************************************
*
*  Copyright � Microsoft Corporation
*
*  File Name:   Validate.cpp
*
*  Description: This file contains code for WIA property validation
*               performed by the Production Scanning Driver Sample
*               for special categories of properties such as "format"
*               and "scan region/document size" properties.
*
***************************************************************************/

#include "stdafx.h"

/**************************************************************************\
*
* Validates new current values for the following dependent WIA properties:
*
* WIA_IPA_DATATYPE (*)
* WIA_IPS_CUR_INTENT (*)
* WIA_IPA_DEPTH (*)
* WIA_IPA_FORMAT
* WIA_IPA_TYMED (skipped since its value cannot be changed for this driver)
* WIA_IPA_COMPRESSION
*
* The current values for the following properties could be changed by the driver
* when one of the above mentioned properties is changed:
*
* WIA_IPA_DATATYPE (*)
* WIA_IPS_CUR_INTENT (*)
* WIA_IPA_DEPTH (*)
* WIA_IPA_CHANNELS_PER_PIXEL (*)
* WIA_IPA_BITS_PER_CHANNEL (*)
* WIA_IPA_FORMAT
* WIA_IPA_TYMED (skipped since its value cannot be changed for this driver)
* WIA_IPA_FILENAME_EXTENSION
* WIA_IPA_COMPRESSION
*
* (*) - These properties are available only on the the Flatbed and Feeder items
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*    nDocumentHandlingSelect - FLAT or FEED (as defined in wiadef.h)
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise (E_INVALIDARG if
*    an invalid combination is attempted)
*
\**************************************************************************/

HRESULT CWiaDriver::ValidateFormatProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext,
    UINT                          nDocumentHandlingSelect)
{
    HRESULT hr = S_OK;

    LONG lDataType = WIA_DATA_COLOR;
    LONG lIntent = WIA_INTENT_NONE;
    LONG lDepth = 24;
    LONG lChannelsPerPixel = 3;
    LONG lBitsPerChannel = 8;
    GUID guidFormat = WiaImgFmt_UNDEFINED;
    LONG lCompression = WIA_COMPRESSION_NONE;
    BSTR bstrFileExtension = NULL;
    BYTE bRawBitsPerChannel[3] = {};

    BOOL bDataTypeChanged = FALSE;
    BOOL bIntentChanged = FALSE;
    BOOL bImageTypeIntentChanged = FALSE;
    BOOL bDepthChanged = FALSE;
    BOOL bFormatChanged = FALSE;
    BOOL bCompressionChanged = FALSE;

    PROPSPEC ps[3] = {};
    ULONG nPropSpec = 0;

    if ((!pWiasContext) || (!pPropertyContext))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "Invalid parameters, hr = 0x%08X", hr));
    }

    if (SUCCEEDED(hr))
    {
        //
        // Check which color properties have been changed (ignore failures).
        // Note that the Auto item supports only format, tymed and compression:
        //
        if ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect))
        {
            wiasIsPropChanged(WIA_IPA_DATATYPE, pPropertyContext, &bDataTypeChanged);
            wiasIsPropChanged(WIA_IPS_CUR_INTENT, pPropertyContext, &bIntentChanged);
            wiasIsPropChanged(WIA_IPA_DEPTH, pPropertyContext, &bDepthChanged);
        }
        wiasIsPropChanged(WIA_IPA_FORMAT, pPropertyContext, &bFormatChanged);
        wiasIsPropChanged(WIA_IPA_COMPRESSION, pPropertyContext, &bCompressionChanged);

        if ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect))
        {
            //
            // Read the current WIA_IPS_CUR_INTENT value:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasReadPropLong(pWiasContext, WIA_IPS_CUR_INTENT, &lIntent, NULL, TRUE);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_CUR_INTENT, hr = 0x%08X", hr));
                }
            }

            //
            // When the intent is changed check if an image type intent flag is set:
            //
            if (SUCCEEDED(hr))
            {
                bImageTypeIntentChanged = (BOOL)(bIntentChanged && (WIA_INTENT_IMAGE_TYPE_MASK & lIntent));
            }
        }
    }

    //
    // Read the other current property values (no matter if each respective property was changed or not):
    //

    if (SUCCEEDED(hr) && (bDataTypeChanged || bIntentChanged || bDepthChanged) &&
        ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect)))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPA_DATATYPE, &lDataType, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPA_DATATYPE, hr = 0x%08X", hr));
        }

        if (SUCCEEDED(hr))
        {
            hr = wiasReadPropLong(pWiasContext, WIA_IPA_DEPTH, &lDepth, NULL, TRUE);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Error reading current WIA_IPA_DEPTH, hr = 0x%08X", hr));
            }
        }
    }

    if (SUCCEEDED(hr) && (bFormatChanged || bCompressionChanged))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPA_COMPRESSION, &lCompression, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPA_COMPRESSION, hr = 0x%08X", hr));
        }

        if (SUCCEEDED(hr))
        {
            hr = wiasReadPropGuid(pWiasContext, WIA_IPA_FORMAT, &guidFormat, NULL, TRUE);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Error reading current WIA_IPA_FORMAT, hr = 0x%08X", hr));
            }
        }
    }

    //
    // If the application changed the image type intent the driver must consider
    // that WIA_IPA_DATA_TYPE was changed to the value apropriate for the new
    // intent no matter if the application set WIA_IPA_DATA_TYPE and to what value:
    //
    if (SUCCEEDED(hr) && bImageTypeIntentChanged)
    {
        //
        // If multiple color intents are set at the same time consider
        // just one and give the highest priority to highest bitdepth:
        //
        if (WIA_INTENT_IMAGE_TYPE_COLOR & lIntent)
        {
            bDataTypeChanged = TRUE;
            lDataType = WIA_DATA_COLOR;
        }
        else if (WIA_INTENT_IMAGE_TYPE_GRAYSCALE & lIntent)
        {
            bDataTypeChanged = TRUE;
            lDataType = WIA_DATA_GRAYSCALE;
        }
    }

    //
    // Validate the new current values against the total supported values for
    // each of the changed properties with write access in this category:
    //
    // WIA_IPA_DATATYPE
    // WIA_IPS_CUR_INTENT
    // WIA_IPA_DEPTH
    // WIA_IPA_FORMAT
    // WIA_IPA_TYMED (skipped here since its value cannot be changed for this driver)
    // WIA_IPA_COMPRESSION
    //
    // This sample driver can validate color and format properties separately
    // since none of its color modes (WIA_IPA_DATATYPE and WIA_IPA_DEPTH
    // combinations) are dependent on format changes (WIA_IPA_FORMAT,
    // WIA_IPA_TYMED -ignored here- and WIA_IPA_COMPRESSION combinations).
    // If the driver would also support WIA_DATA_BW, WIA_COMPRESSION_G4
    // and WiaImgFmt_TIFF the driver would need to validate all the color
    // and format properties together (for example WIA_DATA_BW and
    // WIA_COMPRESSION_G4 do neither work with WiaImgFmt_EXIF).
    //
    if (SUCCEEDED(hr) && ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect)) &&
        (bDataTypeChanged || bIntentChanged || bDepthChanged))
    {
        nPropSpec = 0;

        if (bDataTypeChanged)
        {
            ps[nPropSpec].ulKind = PRSPEC_PROPID;
            ps[nPropSpec].propid = WIA_IPA_DATATYPE;
            nPropSpec++;
        }

        if (bIntentChanged)
        {
            ps[nPropSpec].ulKind = PRSPEC_PROPID;
            ps[nPropSpec].propid = WIA_IPS_CUR_INTENT;
            nPropSpec++;
        }

        if (bDepthChanged)
        {
            ps[nPropSpec].ulKind = PRSPEC_PROPID;
            ps[nPropSpec].propid = WIA_IPA_DEPTH;
            nPropSpec++;
        }

        hr = wiasValidateItemProperties(pWiasContext, nPropSpec, ps);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Invalid color property value(s) requested, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr) && (bFormatChanged || bCompressionChanged))
    {
        nPropSpec = 0;

        if (bFormatChanged)
        {
            ps[nPropSpec].ulKind = PRSPEC_PROPID;
            ps[nPropSpec].propid = WIA_IPA_FORMAT;
            nPropSpec++;
        }

        if (bCompressionChanged)
        {
            ps[nPropSpec].ulKind = PRSPEC_PROPID;
            ps[nPropSpec].propid = WIA_IPA_COMPRESSION;
            nPropSpec++;
        }

        hr = wiasValidateItemProperties(pWiasContext, nPropSpec, ps);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Invalid format property value(s) requested, hr = 0x%08X", hr));
        }
    }

    //
    // Additional validation for WIA_IPA_DATATYPE and WIA_IPA_DEPTH:
    //
    if (SUCCEEDED(hr) && ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect)) &&
        (bDataTypeChanged || bDepthChanged))
    {
        if (bDataTypeChanged && bDepthChanged)
        {
            if (((WIA_DATA_COLOR == lDataType) && (8 == lDepth)) ||
                ((WIA_DATA_GRAYSCALE == lDataType) && (24 == lDepth)))
            {
                hr = E_INVALIDARG;
                WIAEX_ERROR((g_hInst, "Unsupported data type (%u) - depth (%u) combination requested, hr = 0x%08X",
                    lDataType, lDepth, hr));
            }
        }
        else if (bDataTypeChanged && (!bDepthChanged))
        {
            if (WIA_DATA_COLOR == lDataType)
            {
                lDepth = 24;
            }
            else if (WIA_DATA_GRAYSCALE == lDataType)
            {
                lDepth = 8;
            }
            else if (WIA_DATA_AUTO == lDataType)
            {
                lDepth = WIA_DEPTH_AUTO;
            }
        }
        else if ((!bDataTypeChanged) && bDepthChanged)
        {
            if (8 == lDepth)
            {
                lDataType = WIA_DATA_GRAYSCALE;
            }
            else if (24 == lDepth)
            {
                lDataType = WIA_DATA_COLOR;
            }
            else if (WIA_DEPTH_AUTO == lDepth)
            {
                lDataType = WIA_DATA_AUTO;
            }
        }
    }

    //
    // Additional validation for WIA_IPA_FORMAT and WIA_IPA_COMPRESSION (skipped for the
    // sample non-image sources since those do not support compresssed data transfers):
    //
    if (SUCCEEDED(hr) && ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect) ||
        (AUTO_SOURCE == nDocumentHandlingSelect)) && (bFormatChanged || bCompressionChanged))
    {
        if (bFormatChanged && (!bCompressionChanged))
        {
            //
            // If WIA_IPA_FORMAT if changed alone, update WIA_IPA_COMPRESSION to match:
            //
            if (IsEqualGUID(guidFormat, WiaImgFmt_EXIF))
            {
                lCompression = WIA_COMPRESSION_JPEG;
            }
            else if (IsEqualGUID(guidFormat, WiaImgFmt_BMP) || IsEqualGUID(guidFormat, WiaImgFmt_RAW))
            {
                lCompression = WIA_COMPRESSION_NONE;
            }
        }
        else if ((!bFormatChanged) && bCompressionChanged)
        {
            //
            // If WIA_IPA_COMPRESSION if changed alone, update WIA_IPA_FORMAT to match:
            //
            if ((WIA_COMPRESSION_JPEG == lCompression) || (WIA_COMPRESSION_AUTO == lCompression))
            {
                guidFormat = WiaImgFmt_EXIF;
            }
            else if (WIA_COMPRESSION_NONE == lCompression)
            {
                guidFormat = WiaImgFmt_BMP;
            }
        }
        else if (bFormatChanged && bCompressionChanged)
        {
            //
            // If both WIA_IPA_FORMAT and WIA_IPA_COMPRESSION are changed, verify that their values work together:
            //
            if (((WIA_COMPRESSION_NONE == lCompression) && IsEqualGUID(guidFormat, WiaImgFmt_EXIF)) ||
                (((WIA_COMPRESSION_JPEG == lCompression) || (WIA_COMPRESSION_AUTO == lCompression)) &&
                    (IsEqualGUID(guidFormat, WiaImgFmt_BMP) || IsEqualGUID(guidFormat, WiaImgFmt_RAW))))
            {
                hr = E_INVALIDARG;
                WIAEX_ERROR((g_hInst, "Unsupported file format - compression mode combination, hr = 0x%08X", hr));
            }
        }
    }

    //
    // Update current values:
    //

    if (SUCCEEDED(hr) && ((FLAT == nDocumentHandlingSelect) || (FEED == nDocumentHandlingSelect)) &&
        (bDataTypeChanged || bIntentChanged || bDepthChanged))
    {
        //
        // WIA_IPA_DATATYPE:
        //
        hr = wiasWritePropLong(pWiasContext, WIA_IPA_DATATYPE, lDataType);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_DATATYPE, hr = 0x%08X", hr));
        }
        else
        {
            wiasSetPropChanged(WIA_IPA_DATATYPE, pPropertyContext, TRUE);
        }

        //
        // WIA_IPS_CUR_INTENT
        //
        // If an image type intent is set we must make sure it matches the current WIA_IPA_DATATYPE.
        // Don't do anything if the application changes any of the other intent flags.
        //
        if (SUCCEEDED(hr) && (lIntent & WIA_INTENT_IMAGE_TYPE_MASK))
        {
            //
            // Reset all current image type intent flags.
            //
            lIntent &= ~ WIA_INTENT_IMAGE_TYPE_MASK;

            //
            // .. and add just the one apropriate with the current WIA_IPA_DATATYPE value:
            //

            switch (lDataType)
            {
                case WIA_DATA_COLOR:
                    lIntent |= WIA_INTENT_IMAGE_TYPE_COLOR;
                    break;

                case WIA_DATA_GRAYSCALE:
                    lIntent |= WIA_INTENT_IMAGE_TYPE_GRAYSCALE;
            }

            hr = wiasWritePropLong(pWiasContext, WIA_IPS_CUR_INTENT, lIntent);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_CUR_INTENT, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPS_CUR_INTENT, pPropertyContext, TRUE);
            }
        }

        //
        // WIA_IPA_DEPTH:
        //
        if (SUCCEEDED(hr))
        {
            hr = wiasWritePropLong(pWiasContext, WIA_IPA_DEPTH, lDepth);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_DEPTH, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPA_DEPTH, pPropertyContext, TRUE);
            }
        }

        //
        // WIA_IPA_CHANNELS_PER_PIXEL:
        //
        if (SUCCEEDED(hr))
        {
            lChannelsPerPixel = (24 == lDepth) ? 3 : 1;

            hr = wiasWritePropLong(pWiasContext, WIA_IPA_CHANNELS_PER_PIXEL, lChannelsPerPixel);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_CHANNELS_PER_PIXEL, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPA_CHANNELS_PER_PIXEL, pPropertyContext, TRUE);
            }
        }

        //
        // WIA_IPA_BITS_PER_CHANNEL:
        //
        if (SUCCEEDED(hr))
        {
            lBitsPerChannel = 8;

            hr = wiasWritePropLong(pWiasContext, WIA_IPA_BITS_PER_CHANNEL, lBitsPerChannel);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_BITS_PER_CHANNEL, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPA_BITS_PER_CHANNEL, pPropertyContext, TRUE);
            }
        }

        //
        // WIA_IPA_RAW_BITS_PER_CHANNEL
        //
        if (SUCCEEDED(hr))
        {
            for (int i = 0; i < lChannelsPerPixel; i++)
            {
                bRawBitsPerChannel[i] = 8;
            }

            hr = wiasWritePropBin(pWiasContext, WIA_IPA_RAW_BITS_PER_CHANNEL, lChannelsPerPixel, bRawBitsPerChannel);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_RAW_BITS_PER_CHANNEL, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPA_RAW_BITS_PER_CHANNEL, pPropertyContext, TRUE);
            }
        }
    }

    if (SUCCEEDED(hr) && (bFormatChanged || bCompressionChanged))
    {
        //
        // WIA_IPA_COMPRESSION:
        //
        if (SUCCEEDED(hr))
        {
            hr = wiasWritePropLong(pWiasContext, WIA_IPA_COMPRESSION, lCompression);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_COMPRESSION, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPA_COMPRESSION, pPropertyContext, TRUE);
            }
        }

        //
        // WIA_IPA_FORMAT:
        //
        if (SUCCEEDED(hr))
        {
            hr = wiasWritePropGuid(pWiasContext, WIA_IPA_FORMAT, guidFormat);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_FORMAT, hr = 0x%08X", hr));
            }
            else
            {
                wiasSetPropChanged(WIA_IPA_COMPRESSION, pPropertyContext, TRUE);
            }
        }

        //
        // WIA_IPA_FILENAME_EXTENSION:
        //

        if (SUCCEEDED(hr))
        {
            if (IsEqualGUID(guidFormat, WiaImgFmt_BMP))
            {
                bstrFileExtension = SysAllocString(FILE_EXT_BMP);
            }
            else if (IsEqualGUID(guidFormat, WiaImgFmt_EXIF))
            {
                bstrFileExtension = SysAllocString(FILE_EXT_JPG);
            }
            else if (IsEqualGUID(guidFormat, WiaImgFmt_RAW) || IsEqualGUID(guidFormat, WiaImgFmt_RAWBAR) ||
                IsEqualGUID(guidFormat, WiaImgFmt_RAWPAT) || IsEqualGUID(guidFormat, WiaImgFmt_RAWMIC))
            {
                bstrFileExtension = SysAllocString(FILE_EXT_RAW);
            }
            else if (IsEqualGUID(guidFormat, WiaImgFmt_CSV))
            {
                bstrFileExtension = SysAllocString(FILE_EXT_CSV);
            }
            else if (IsEqualGUID(guidFormat, WiaImgFmt_TXT))
            {
                bstrFileExtension = SysAllocString(FILE_EXT_TXT);
            }
            else if (IsEqualGUID(guidFormat, WiaImgFmt_XMLBAR) || IsEqualGUID(guidFormat, WiaImgFmt_XMLPAT) ||
                IsEqualGUID(guidFormat, WiaImgFmt_XMLMIC))
            {
                bstrFileExtension = SysAllocString(FILE_EXT_XML);
            }
            else
            {
                hr = E_FAIL;
                WIAEX_ERROR((g_hInst, "Unsupported file format, hr = 0x%08X", hr));
            }
        }

        if (SUCCEEDED(hr) && (!bstrFileExtension))
        {
            hr = E_OUTOFMEMORY;
            WIAEX_ERROR((g_hInst, "Unable to allocate memory for new file extension, hr = 0x%08X", hr));
        }

        if (SUCCEEDED(hr))
        {
            hr = wiasWritePropStr(pWiasContext, WIA_IPA_FILENAME_EXTENSION, bstrFileExtension);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_FILENAME_EXTENSION, hr = 0x%08X", hr));
            }
        }
    }

    if (bstrFileExtension)
    {
        SysFreeString(bstrFileExtension);
    }

    return hr;
}

/**************************************************************************\
*
* Helper for IWiaMinIDrv::drvValidateItemProperties.
*
* Updates the following "image information" properties:
*
* WIA_IPA_PIXELS_PER_LINE
* WIA_IPA_NUMBER_OF_LINES
* WIA_IPA_BYTES_PER_LINE
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*    nDocumentHandlingSelect - FLAT or FEED (as defined in wiadef.h)
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidateImageInfoProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext,
    UINT                          nDocumentHandlingSelect)
{
    HRESULT hr = S_OK;
    LONG lXExtent = 0;
    LONG lYExtent = 0;
    LONG lPixelsPerLine = 0;
    LONG lNumberOfLines = 0;
    LONG lDepth = 0;
    LONG lBytesPerLine = 0;

    if ((!pWiasContext) || (!pPropertyContext) || (AUTO_SOURCE == nDocumentHandlingSelect))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "Invalid parameters, hr = 0x%08X", hr));
    }

    //
    // Read the current extent and bit depth values:
    //

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPA_DEPTH, &lDepth, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPA_DEPTH, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_XEXTENT, &lXExtent, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_XEXTENT, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_YEXTENT, &lYExtent, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_YEXTENT, hr = 0x%08X", hr));
        }
    }

    //
    // WIA_IPA_PIXELS_PER_LINE - the image width, in pixels, for the final image
    // WIA_IPA_NUMBER_OF_LINES - the image length, in pixels, for the final image
    // WIA_IPA_BYTES_PER_LINE - line width in bytes matching WIA_IPA_PIXELS_PER_LINE and WIA_IPA_DEPTH
    //

    lPixelsPerLine = lXExtent;
    lNumberOfLines = lYExtent;
    lBytesPerLine = BytesPerLine(lPixelsPerLine, lDepth);

    if (SUCCEEDED(hr))
    {
        hr = wiasWritePropLong(pWiasContext, WIA_IPA_PIXELS_PER_LINE, lPixelsPerLine);

        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_PIXELS_PER_LINE, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasWritePropLong(pWiasContext, WIA_IPA_NUMBER_OF_LINES, lNumberOfLines);

        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_NUMBER_OF_LINES, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasWritePropLong(pWiasContext, WIA_IPA_BYTES_PER_LINE, lBytesPerLine);

        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Failed to update WIA_IPA_BYTES_PER_LINE, hr = 0x%08X", hr));
        }
    }

    return hr;
}

/**************************************************************************\
*
* Validates new current values for the following dependent WIA properties:
*
* WIA_IPS_PAGE_SIZE
* WIA_IPS_ORIENTATION
* WIA_IPS_PAGE_WIDTH
* WIA_IPS_PAGE_HEIGHT
* WIA_IPS_XPOS
* WIA_IPS_YPOS
* WIA_IPS_XEXTENT
* WIA_IPS_YEXTENT
* WIA_IPS_XRES
* WIA_IPS_YRES
* WIA_IPS_XSCALING
* WIA_IPS_YSCALING
* WIA_IPS_LONG_DOCUMENT
*
* If the application changes the document size and orientation the driver
* should update the current and valid origin and extent properties to match
* at the current resolution the current full document dimensions.
*
* Resolution changes should also cause updated origins and extents (valid and current).
*
* Orientation changes should result in updated valid page size lists and
* updated current and valid origin and extents values.
*
* The driver is going to consider the origins and extents to configure
* the scan area (crop region) and the current page size to configure
* the physical document size, if needed.
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*    nDocumentHandlingSelect - FLAT or FEED (as defined in wiadef.h)
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise (E_INVALIDARG if
*    an invalid combination is attempted)
*
\**************************************************************************/

HRESULT CWiaDriver::ValidateRegionProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext,
    UINT                          nDocumentHandlingSelect)
{
    HRESULT hr = S_OK;

    if ((!pWiasContext) || (!pPropertyContext) || (AUTO_SOURCE == nDocumentHandlingSelect))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "Invalid parameters, hr = 0x%08X", hr));
    }

    LONG lXRes = 0;
    LONG lYRes = 0;
    LONG lMinXExtent = (MIN_SCAN_AREA_WIDTH * OPTICAL_RESOLUTION) / 1000;
    LONG lMinYExtent = (MIN_SCAN_AREA_HEIGHT * OPTICAL_RESOLUTION) / 1000;
    LONG lMaxXExtent = (MAX_SCAN_AREA_WIDTH * OPTICAL_RESOLUTION) / 1000;
    LONG lMaxYExtent = (MAX_SCAN_AREA_HEIGHT * OPTICAL_RESOLUTION) / 1000;

    LONG lXPos = 0;
    LONG lYPos = 0;
    LONG lXExtent = lMaxXExtent;
    LONG lYExtent = lMaxYExtent;

    LONG lPageWidth = MAX_SCAN_AREA_WIDTH;
    LONG lPageHeight = MAX_SCAN_AREA_HEIGHT;
    LONG lPageSize = WIA_PAGE_LETTER;
    LONG lOrientation = PORTRAIT;
    LONG lMaxWidth = MAX_SCAN_AREA_WIDTH;
    LONG lMaxHeight = MAX_SCAN_AREA_HEIGHT;
    LONG lLongDocument = WIA_LONG_DOCUMENT_DISABLED;

    BOOL bPageSizeChanged = FALSE;
    BOOL bOrientationChanged = FALSE;
    BOOL bLongDocumentChanged = FALSE;

    //
    // Read the current property values (no matter if they were changed or not):
    //

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_XPOS, &lXPos, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_XPOS, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_YPOS, &lYPos, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_YPOS, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_XEXTENT, &lXExtent, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_XEXTENT, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_YEXTENT, &lYExtent, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_YEXTENT, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_XRES, &lXRes, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_XRES, hr = 0x%08X", hr));
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_YRES, &lYRes, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_YRES, hr = 0x%08X", hr));
        }
    }

    //
    // In order to validate scan frame or resolution changes we must know
    // the maximum scan area size. For Flatbed this is the total bed size.
    // For Feeder this is the size of the currently selected document size.
    //
    if (SUCCEEDED(hr) &&(FEED == nDocumentHandlingSelect))
    {
        wiasIsPropChanged(WIA_IPS_PAGE_SIZE, pPropertyContext, &bPageSizeChanged);
        wiasIsPropChanged(WIA_IPS_ORIENTATION, pPropertyContext, &bOrientationChanged);
        wiasIsPropChanged(WIA_IPS_LONG_DOCUMENT, pPropertyContext, &bLongDocumentChanged);

        if (SUCCEEDED(hr))
        {
            hr = wiasReadPropLong(pWiasContext, WIA_IPS_ORIENTATION, &lOrientation, NULL, TRUE);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_ORIENTATION, hr = 0x%08X", hr));
            }
        }

        if (SUCCEEDED(hr))
        {
            hr = wiasReadPropLong(pWiasContext, WIA_IPS_PAGE_SIZE, &lPageSize, NULL, TRUE);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_PAGE_SIZE, hr = 0x%08X", hr));
            }
        }

        if (SUCCEEDED(hr))
        {
            hr = wiasReadPropLong(pWiasContext, WIA_IPS_LONG_DOCUMENT, &lLongDocument, NULL, TRUE);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_LONG_DOCUMENT, hr = 0x%08X", hr));
            }
        }

        //
        // When WIA_LONG_DOCUMENT_ENABLED is set, if the application is not doing this update,
        // the driver must update itself the current WIA_IPS_PAGE_SIZE property value to WIA_PAGE_AUTO.
        // If WIA_LONG_DOCUMENT_ENABLED is set  and the application changes WIA_IPS_PAGE_SIZE to
        // another value than WIA_PAGE_AUTO, the driver must self-update the WIA_IPS_LONG_DOCUMENT
        // property to WIA_LONG_DOCUMENT_DISABLED.
        //
        if (SUCCEEDED(hr) && (bPageSizeChanged || bLongDocumentChanged))
        {
            if ((WIA_PAGE_AUTO != lPageSize) && (WIA_LONG_DOCUMENT_ENABLED == lLongDocument))
            {
                if (bLongDocumentChanged && bPageSizeChanged)
                {
                    hr = E_INVALIDARG;
                    WIAEX_ERROR((g_hInst, "Invalid WIA_IPS_PAGE_SIZE value for WIA_LONG_DOCUMENT_ENABLED, hr = 0x%08X", hr));
                }
                else if (bLongDocumentChanged && (!bPageSizeChanged))
                {
                    lPageSize = WIA_PAGE_AUTO;
                    bPageSizeChanged = TRUE;
                }
                else if ((!bLongDocumentChanged) && bPageSizeChanged)
                {
                    lLongDocument = WIA_LONG_DOCUMENT_DISABLED;
                    bLongDocumentChanged = TRUE;
                }
            }
        }

        //
        // Validate the current WIA_IPS_PAGE_SIZE against the valid values
        // apropriate with the current WIA_IPS_ORIENTATION:
        //
        if (SUCCEEDED(hr) && (bPageSizeChanged || bOrientationChanged))
        {
            CBasicDynamicArray<LONG> &lPageSizesArray = (PORTRAIT == lOrientation) ? m_lPortraitSizesArray : m_lLandscapeSizesArray;

            if (-1 == lPageSizesArray.Find(lPageSize))
            {
                if (bPageSizeChanged)
                {
                    hr = E_INVALIDARG;
                    WIAEX_ERROR((g_hInst, "Invalid WIA_IPS_PAGE_SIZE value, hr = 0x%08X", hr));
                }
                else if (WIA_PAGE_AUTO != lPageSize)
                {
                    //
                    // This means that the application did not request the page size
                    // to be changed, just changed the orientation and the current
                    // page size is not supported in the new orientation so we should
                    // quietly select WIA_PAGE_CUSTOM:
                    //
                    lPageSize = WIA_PAGE_CUSTOM;
                    lPageWidth = lMaxWidth;
                    lPageHeight = lMaxHeight;
                    bPageSizeChanged = TRUE;
                }
            }
            else
            {
                //
                // For WIA_PAGE_CUSTOM the page dimensions are the maximum dimensions of the scan area,
                // same for WIA_PAGE_AUTO (real page dimensions being detected here only after the
                // document has been scanned and reflected in the final image layout and dimensions):
                //
                if ((WIA_PAGE_CUSTOM == lPageSize) || (WIA_PAGE_AUTO == lPageSize))
                {
                    lPageWidth = lMaxWidth;
                    lPageHeight = lMaxHeight;
                }
                else
                {
                    //
                    // The page sizes from the current array are guaranteed to fit in
                    // the total scan acquisition area limits, minimum and maximum:
                    //
                    hr = GetPageDimensions(lPageSize, (BOOL)(PORTRAIT == lOrientation), lPageWidth, lPageHeight);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Unable to retrieve dimensions for the current document size, hr = 0x%08X", hr));
                    }
                }
            }

            //
            // For Feeder the actual maximum dimensions for the scan area
            // are dictated by the current document size selected:
            //
            if (SUCCEEDED(hr))
            {
                lMaxWidth = lPageWidth;
                lMaxHeight = lPageHeight;
            }
        }
    }

    //
    // Use wiasUpdateScanRect to validate the current extents and
    // resolutions and update all their dependent properties:
    //
    if (SUCCEEDED(hr))
    {
        hr = wiasUpdateScanRect(pWiasContext, pPropertyContext, lMaxWidth, lMaxHeight);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "wiasUpdateScanRect(max width = %u, max height = %u) failed, hr = 0x%08X", lMaxWidth, lMaxHeight, hr));
        }
    }

    //
    // For feeder special validation must be performed for WIA_IPS_ORIENTATION and WIA_IPS_PAGE_SIZE changes:
    //
    if (FEED == nDocumentHandlingSelect)
    {
        //
        // Update valid list of values for WIA_IPS_PAGE_SIZE according with the new set WIA_IPS_ORIENTATION:
        //
        if (SUCCEEDED(hr) && bOrientationChanged)
        {
            LONG lNumPageSizes = 0;
            CBasicDynamicArray<LONG> &lPageSizesArray = (PORTRAIT == lOrientation) ? m_lPortraitSizesArray : m_lLandscapeSizesArray;
            lNumPageSizes = lPageSizesArray.Size();

            hr = wiasSetValidListLong(pWiasContext, WIA_IPS_PAGE_SIZE, (ULONG)lNumPageSizes,
                lPageSize, (LONG *)lPageSizesArray.Array());
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update valid WIA_IPS_PAGE_SIZE values, hr = 0x%08X", hr));
            }
        }

        //
        // If the current WIA_IPS_PAGE_SIZE is changed updated all the dependent properties:
        //
        if (SUCCEEDED(hr) && bPageSizeChanged)
        {
            //
            // Update current WIA_IPS_PAGE_SIZE value:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasWritePropLong(pWiasContext, WIA_IPS_PAGE_SIZE, lPageSize);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_PAGE_SIZE, hr = 0x%08X", hr));
                }
            }

            //
            // Update current WIA_IPS_PAGE_WIDTH:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasWritePropLong(pWiasContext, WIA_IPS_PAGE_WIDTH, lPageWidth);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_PAGE_WIDTH, hr = 0x%08X", hr));
                }
            }

            //
            // Update current WIA_IPS_LONG_DOCUMENT:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasWritePropLong(pWiasContext, WIA_IPS_LONG_DOCUMENT, lLongDocument);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_PAGE_HEIGHT, hr = 0x%08X", hr));
                }
            }

            //
            // Update current WIA_IPS_PAGE_HEIGHT:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasWritePropLong(pWiasContext, WIA_IPS_PAGE_HEIGHT, lPageHeight);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_PAGE_HEIGHT, hr = 0x%08X", hr));
                }
            }

            //
            // If the current page size is changed always update the current and valid
            // WIA_IPS_XPOS, WIA_IPS_YPOS, WIA_IPS_XEXTENT and WIA_IPS_YEXTENT to match
            // the entire area of the currently selected document size, overwriting any
            // direct change for any of these properties requested at the same time with
            // the change for WIA_IPS_ORIENTATION or WIA_IPS_PAGESIZE.
            //

            //
            // Read current WIA_IPS_XRES:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasReadPropLong(pWiasContext, WIA_IPS_XRES, &lXRes, NULL, TRUE);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_XRES, hr = 0x%08X", hr));
                }
            }

            //
            // Read current WIA_IPS_YRES:
            //
            if (SUCCEEDED(hr))
            {
                hr = wiasReadPropLong(pWiasContext, WIA_IPS_YRES, &lYRes, NULL, TRUE);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_YRES, hr = 0x%08X", hr));
                }
            }

            //
            // Compute the new extent limits:
            //
            if (SUCCEEDED(hr))
            {
                lMinXExtent = (MIN_SCAN_AREA_WIDTH * lXRes) / 1000;
                if (!lMinXExtent)
                {
                    lMinXExtent = 1;
                }
                lMinYExtent = (MIN_SCAN_AREA_HEIGHT * lYRes) / 1000;
                if (!lMinYExtent)
                {
                    lMinYExtent = 1;
                }
                lMaxXExtent = (lPageWidth * lXRes) / 1000;
                lMaxYExtent = (lPageHeight * lYRes) / 1000;

                if ((lMaxXExtent < 1) || (lMaxYExtent < 1) || (lMinXExtent > lMaxXExtent) || (lMinYExtent > lMaxYExtent))
                {
                    hr = E_FAIL;
                    WIAEX_ERROR((g_hInst, "Unable to update the extent limits to match the new document size, hr = 0x%08X", hr));
                }
            }

            if (SUCCEEDED(hr))
            {
                //
                // The new current scan region, covering the entire document size:
                //
                lXPos = 0;
                lYPos = 0;
                lXExtent = lMaxXExtent;
                lYExtent = lMaxYExtent;

                //
                // Set new valid values for WIA_IPS_XPOS:
                //
                hr = wiasSetValidRangeLong(pWiasContext, WIA_IPS_XPOS, 0, lXPos, lMaxXExtent - lMinXExtent, 1);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Failed to update valid WIA_IPS_XPOS, hr = 0x%08X", hr));
                }

                //
                // Set new valid values for WIA_IPS_YPOS:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasSetValidRangeLong(pWiasContext, WIA_IPS_YPOS, 0, lYPos, lMaxYExtent - lMinYExtent, 1);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update valid WIA_IPS_YPOS, hr = 0x%08X", hr));
                    }
                }

                //
                // Set new valid values for WIA_IPS_XEXTENT:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasSetValidRangeLong(pWiasContext, WIA_IPS_XEXTENT, lMinXExtent, lXExtent, lMaxXExtent - lXPos, 1);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update valid WIA_IPS_XEXTENT, hr = 0x%08X", hr));
                    }
                }

                //
                // Set new valid values for WIA_IPS_XEXTENT:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasSetValidRangeLong(pWiasContext, WIA_IPS_YEXTENT, lMinYExtent, lYExtent, lMaxYExtent - lYPos, 1);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update valid WIA_IPS_YEXTENT, hr = 0x%08X", hr));
                    }
                }

                //
                // Set new current value for WIA_IPS_XPOS:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasWritePropLong(pWiasContext, WIA_IPS_XPOS, lXPos);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_XPOS, hr = 0x%08X", hr));
                    }
                }

                //
                // Set new current value for WIA_IPS_YPOS:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasWritePropLong(pWiasContext, WIA_IPS_YPOS, lYPos);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_YPOS, hr = 0x%08X", hr));
                    }
                }

                //
                // Set new current value for WIA_IPS_XEXTENT:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasWritePropLong(pWiasContext, WIA_IPS_XEXTENT, lXExtent);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_XEXTENT, hr = 0x%08X", hr));
                    }
                }

                //
                // Set new current value for WIA_IPS_YEXTENT:
                //
                if (SUCCEEDED(hr))
                {
                    hr = wiasWritePropLong(pWiasContext, WIA_IPS_YEXTENT, lYExtent);
                    if (FAILED(hr))
                    {
                        WIAEX_ERROR((g_hInst, "Failed to update WIA_IPS_YEXTENT, hr = 0x%08X", hr));
                    }
                }
            }
        }
    }

    return hr;
}

/**************************************************************************\
*
* Validates new current values for the following dependent WIA properties:
*
* WIA_IPS_DOCUMENT_HANDLING_SELECT: either DUPLEX or FRONT_ONLY can be set
*
* WIA_IPS_PAGES: the valid range and current value are updated to match
* the current WIA_IPS_DOCUMENT_HANDLING_SELECT
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise (E_INVALIDARG if
*    an invalid combination is attempted)
*
\**************************************************************************/

HRESULT CWiaDriver::ValidateFeedProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext)
{
    HRESULT hr = S_OK;
    BOOL bHandlingSelectChanged = TRUE;
    LONG lFeederHandlingSelect = FRONT_ONLY;
    BOOL bPagesChanged = TRUE;
    LONG lPages = 1;
    LONG lMaxPages = 0x7FFFFFFF; //maximum value for a signed 32-bit integer
    LONG lMinPages = 0; //ALL_PAGES
    LONG lStepPages = 1;
    WIAS_CHANGED_VALUE_INFO wiasValInfo = {};

    if ((!pWiasContext) || (!pPropertyContext))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "Invalid parameters, hr = 0x%08X", hr));
    }

    //
    // Check if WIA_IPS_DOCUMENT_HANDLING_SELECT was changed:
    //
    if (SUCCEEDED(hr))
    {
        if (SUCCEEDED(wiasGetChangedValueLong(pWiasContext, pPropertyContext, TRUE, WIA_IPS_DOCUMENT_HANDLING_SELECT, &wiasValInfo)))
        {
            lFeederHandlingSelect = wiasValInfo.Current.lVal;
            bHandlingSelectChanged = wiasValInfo.bChanged;
        }
        else
        {
            bHandlingSelectChanged = FALSE;
        }
    }

    //
    // Check if WIA_IPS_PAGES was changed and read its current value:
    //
    if (SUCCEEDED(hr))
    {
        if (SUCCEEDED(wiasGetChangedValueLong(pWiasContext, pPropertyContext, TRUE, WIA_IPS_PAGES, &wiasValInfo)))
        {
            lPages = wiasValInfo.Current.lVal;
            bPagesChanged = wiasValInfo.bChanged;
        }
        else
        {
            bPagesChanged = FALSE;

            if (bHandlingSelectChanged)
            {
                hr = wiasReadPropLong(pWiasContext, WIA_IPS_PAGES, &lPages, NULL, TRUE);
                if (FAILED(hr))
                {
                    WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_PAGES on the Feeder item, hr = 0x%08X", hr));
                }
            }
        }
    }

    //
    // If WIA_IPS_DOCUMENT_HANDLING_SELECT is changed verify that only FRONT_ONLY
    // or DUPLEX is requested to be set, only one at a time:
    //
    if (SUCCEEDED(hr) && bHandlingSelectChanged)
    {
        if ((DUPLEX != lFeederHandlingSelect) && (FRONT_ONLY != lFeederHandlingSelect))
        {
            hr = E_INVALIDARG;
            WIAEX_ERROR((g_hInst,
                "WIA_IPS_DOCUMENT_HANDLING_SELECT validation failed, only FRONT_ONLY and DUPLEX are valid, only one at a time, hr = 0x%08X", hr));
        }
    }

    //
    // Read the current WIA_IPS_DOCUMENT_HANDLING_SELECT value if not changed and WIA_IPS_PAGES is changed:
    //
    if (SUCCEEDED(hr) && (!bHandlingSelectChanged) && bPagesChanged)
    {
        hr = wiasReadPropLong(pWiasContext, WIA_IPS_DOCUMENT_HANDLING_SELECT, &lFeederHandlingSelect, NULL, TRUE);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Error reading current WIA_IPS_DOCUMENT_HANDLING_SELECT on the Feeder item, hr = 0x%08X", hr));
        }
    }

    //
    // If WIA_IPS_DOCUMENT_HANDLING_SELECT is changed and WIA_IPS_PAGES is not, update the valid and,
    // if needed, current values for WIA_IPS_PAGES. Note that the step WIA_IPS_PAGES value must remain
    // 1 for duplex in order to allow a legacy WIA 1.0 application to indirectly disable duplex by
    // settings WIA_DPS_PAGES to 1:
    //
    if (SUCCEEDED(hr) && bHandlingSelectChanged && (!bPagesChanged))
    {
        if (DUPLEX == lFeederHandlingSelect)
        {
            lMaxPages = 0x7FFFFFFE; //maximum even value for a signed 32-bit integer
            lMinPages = 0; //ALL_PAGES
            lStepPages = 1; //not 2

            //
            // Round up the current WIA_IPS_PAGES value to the nearest even number:
            //
            if ((lPages > 0) && (lPages % 2))
            {
                if (lPages <= (lMaxPages - 1))
                {
                    lPages += 1;
                }
                else if (lPages >= (lMinPages + 2))
                {
                    lPages -= 1;
                }
                else
                {
                    lPages = 2;
                }
            }
        }
        else
        {
            lMaxPages = 0x7FFFFFFF; //maximum value for a signed 32-bit integer
            lMinPages = 0; //ALL_PAGES
            lStepPages = 1;
        }

        //
        // Update the range of valid WIA_IPS_PAGES values:
        //
        hr = wiasSetValidRangeLong(pWiasContext, WIA_IPS_PAGES, lMinPages, lPages, lMaxPages, lStepPages);
        if (FAILED(hr))
        {
            WIAEX_ERROR((g_hInst, "Failed to update valid WIA_IPS_PAGES, hr = 0x%08X", hr));
        }

        //
        // Update the current WIA_IPS_PAGES value:
        //
        if (SUCCEEDED(hr))
        {
            hr = wiasWritePropLong(pWiasContext, WIA_IPS_PAGES, lPages);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update current WIA_IPS_PAGES, hr = 0x%08X", hr));
            }
        }
    }

    //
    // If WIA_IPS_PAGES is changed and WIA_IPS_DOCUMENT_HANDLING_SELECT is not, check if
    // we need to disable duplex as result of a WIA_IPS_PAGES current value change to 1.
    // We'll leave duplex enabled if the application changes WIA_IPS_PAGE to another
    // odd value, the application being responsible in this case to decide itself if
    // these pages are to be scanned duplex (and the last side discarded) or simplex:
    //
    if (SUCCEEDED(hr) && (!bHandlingSelectChanged) && bPagesChanged)
    {
        if ((DUPLEX == lFeederHandlingSelect) && (1 == lPages))
        {
            lFeederHandlingSelect = FRONT_ONLY;

            hr = wiasWritePropLong(pWiasContext, WIA_IPS_DOCUMENT_HANDLING_SELECT, lFeederHandlingSelect);
            if (FAILED(hr))
            {
                WIAEX_ERROR((g_hInst, "Failed to update current WIA_IPS_DOCUMENT_HANDLING_SELECT, hr = 0x%08X", hr));
            }
        }
    }

    //
    // If both WIA_IPS_PAGES and WIA_IPS_DOCUMENT_HANDLING_SELECT are changed at the
    // same time, check if DUPLEX and an odd WIA_IPS_PAGES value are set. We'll allow
    // odd values other than 1 while DUPLEX is set but not 1:
    //
    if (SUCCEEDED(hr) && bHandlingSelectChanged && bPagesChanged)
    {
        if ((DUPLEX == lFeederHandlingSelect) && (1 == lPages))
        {
            hr = E_INVALIDARG;
            WIAEX_ERROR((g_hInst, "WIA_IPS_PAGES cannot be set to 1 while setting WIA_IPS_DOCUMENT_HADLING_SELECT to DUPLEX, hr = 0x%08X", hr));
        }
    }

    return hr;
}

/**************************************************************************\
*
* Helper for IWiaMinIDrv::drvValidateItemProperties.
*
* Executes additional validation for the imprinter/endorser specific properties.
* The only imprinter/enorser property that this sample driver needs to validate
* here is WIA_IPS_PRINTER_ENDORSER_STRING - the characters submitted by the
* application must match WIA_IPS_PRINTER_ENDORSER_VALID_CHARACTERS. Unsupported
* WIA_IPS_PRINTER_ENDORSER_VALID_FORMAT_SPECIFIERS are quietly ignored by the
* driver (pretended to be printed/endorsed as-is).
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*    nDocumentHandlingSelect - IMPRINTER or ENDORSER (defined in wiadef.h)
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidateImprinterEndorserProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext,
    UINT                          nDocumentHandlingSelect)
{
    HRESULT hr = S_OK;
    BOOL bStringChanged = TRUE;
    BSTR bstrNewString = NULL;
    BSTR bstrOldString = NULL;
    WIAS_CHANGED_VALUE_INFO wiasValInfo = {};

    if ((!pWiasContext) || (!pPropertyContext) ||
        ((IMPRINTER != nDocumentHandlingSelect) && (ENDORSER != nDocumentHandlingSelect)))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "CWiaDriver::ValidateImprinterEndorserProperties failed, invalid parameter, hr = 0x%08X", hr));
    }

    //
    // Check if WIA_IPS_PRINTER_ENDORSER_STRING is changed by the application:
    //
    if (SUCCEEDED(hr))
    {
        if (SUCCEEDED(wiasGetChangedValueStr(pWiasContext, pPropertyContext, TRUE, WIA_IPS_PRINTER_ENDORSER_STRING, &wiasValInfo)))
        {
            //
            // wiasGetChangedValueStr allocates both Current and Old BSTRs, read them both
            // even if not using the Old value, we'll need to free both of them when done:
            //
            bstrNewString = wiasValInfo.Current.bstrVal;
            bstrOldString = wiasValInfo.Old.bstrVal;
            bStringChanged = wiasValInfo.bChanged;
        }
        else
        {
            bStringChanged = FALSE;
        }
    }

    //
    // If WIA_IPS_PRINTER_ENDORSER_STRING is changed, check if all characters are valid:
    //
    if (SUCCEEDED(hr) && bStringChanged && bstrNewString)
    {
        PWCHAR szValidChars = (IMPRINTER == nDocumentHandlingSelect) ? SAMPLE_IMPRINTER_VALID_CHARS : SAMPLE_ENDORSER_VALID_CHARS;
        ULONG ulValidChars = ((IMPRINTER == nDocumentHandlingSelect) ? ARRAYSIZE(SAMPLE_IMPRINTER_VALID_CHARS) : ARRAYSIZE(SAMPLE_ENDORSER_VALID_CHARS)) - 1;
        ULONG i = 0;
        BOOL bFound = FALSE;

        while (bstrNewString[i] != NULL)
        {
            bFound = FALSE;

            for (ULONG j = 0; j < ulValidChars; j++)
            {
                if (bstrNewString[i] == szValidChars[j])
                {
                    bFound = TRUE;
                    i++;
                    break;
                }
            }

            if (!bFound)
            {
                hr = E_INVALIDARG;
                WIAEX_ERROR((g_hInst, "Invalid character for WIA_IPS_PRINTER_ENDORSER_STRING: 0x%X (%wc), hr = 0x%08X",
                    bstrNewString[i], bstrNewString[i], hr));
                break;
            }
        }
    }

    //
    // This sample driver supports only one line of text for its Imprinter/Endorser.
    // Check that the new WIA_IPS_PRINTER_ENDORSER_STRING value does not contain any
    // special '$N$'sequences ('new line'). This sample driver will ignore other
    // formatting sequences that may be contained by the new string:
    //
    if (SUCCEEDED(hr) && bStringChanged && bstrNewString)
    {
        WCHAR szNewLine[] = L"$N$";

        if (wcsstr(bstrNewString, szNewLine))
        {
            hr = E_INVALIDARG;
            WIAEX_ERROR((g_hInst, "Invalid new line ($N$) format sequence for WIA_IPS_PRINTER_ENDORSER_STRING, only one line of text supported, submitted value: %ws, hr = 0x%08X",
                bstrNewString, hr));
        }
    }

    //
    // Important: both the WIAS_CHANGED_VALUE_INFO::Current.bstrVal and
    // WIAS_CHANGED_VALUE_INFO::Old.bstrVal BSTRs must be freed.
    //
    if (bstrNewString)
    {
        SysFreeString(bstrNewString);
    }
    if (bstrOldString)
    {
        SysFreeString(bstrOldString);
    }

    return hr;
}

/**************************************************************************\
*
* Helper for IWiaMinIDrv::drvValidateItemProperties.
*
* Validates WIA_IPS_ENABLED_BARCODE_TYPES
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidateBarcodeReaderProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext)
{
    HRESULT hr = S_OK;
    BOOL bBarcodeTypesChanged = TRUE;

    if ((!pWiasContext) || (!pPropertyContext))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "CWiaDriver::ValidateImprinterEndorserProperties failed, invalid parameter, hr = 0x%08X", hr));
    }

    //
    // Check if WIA_IPS_ENABLED_BARCODE_TYPES is changed by the application:
    //
    if (SUCCEEDED(hr))
    {
        //
        // wiasIsPropChanged fails if the property is not changed, do not fail ValidateBarcodeReaderProperties:
        //
        wiasIsPropChanged(WIA_IPS_ENABLED_BARCODE_TYPES, pPropertyContext, &bBarcodeTypesChanged);
    }

    //
    // If WIA_IPS_ENABLED_BARCODE_TYPES is changed, read the new (vector) array of values and validate against the valid values:
    //
    if (SUCCEEDED(hr) && bBarcodeTypesChanged)
    {
        PROPSPEC ps = {};
        PROPVARIANT pv = {};

        ps.ulKind = PRSPEC_PROPID;
        ps.propid = WIA_IPS_ENABLED_BARCODE_TYPES;

        PropVariantInit(&pv);

        hr = wiasReadMultiple(pWiasContext, 1, &ps, &pv, NULL);
        if (SUCCEEDED(hr))
        {
            if ((VT_VECTOR | VT_I4) == pv.vt)
            {
                ULONG ulValidValues = ARRAYSIZE(g_lSupportedBarcodeTypes);
                BOOL bFound = FALSE;

                for (ULONG i = 0; i < pv.cal.cElems; i++)
                {
                    bFound = FALSE;

                    for (ULONG j = 0; j < ulValidValues; j++)
                    {
                        if (g_lSupportedBarcodeTypes[j] == pv.cal.pElems[i])
                        {
                            bFound = TRUE;
                            break;
                        }
                    }

                    if (!bFound)
                    {
                        hr = E_INVALIDARG;
                        WIAEX_ERROR((g_hInst, "Unsupported barcode type: %u, hr = 0x%08X", pv.cal.pElems[i], hr));
                        break;
                    }
                }

            }
            else
            {
                hr = E_INVALIDARG;
                WIAEX_ERROR((g_hInst, "Invalid value type for WIA_IPS_ENABLED_BARCODE_TYPES, expected VT_VECTOR | VT_I4 (%u), got %u, hr = 0x%08X",
                    VT_VECTOR | VT_I4, pv.vt, hr));
            }
        }
        else
        {
            WIAEX_ERROR((g_hInst, "wiasReadMultiple(WIA_IPS_ENABLED_BARCODE_TYPES) failed, hr = 0x%08X", hr));
        }

        PropVariantClear(&pv);
    }

    return hr;
}

/**************************************************************************\
*
* Helper for IWiaMinIDrv::drvValidateItemProperties.
*
* Validates WIA_IPS_ENABLED_PATCH_CODE_TYPES
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidatePatchCodeReaderProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext)
{
    HRESULT hr = S_OK;
    BOOL bPatchCodeTypesChanged = TRUE;

    if ((!pWiasContext) || (!pPropertyContext))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "CWiaDriver::ValidateImprinterEndorserProperties failed, invalid parameter, hr = 0x%08X", hr));
    }

    //
    // Check if WIA_IPS_ENABLED_PATCH_CODE_TYPES is changed by the application:
    //
    if (SUCCEEDED(hr))
    {
        //
        // wiasIsPropChanged fails if the property is not changed, do not fail ValidatePatchCodeReaderProperties:
        //
        wiasIsPropChanged(WIA_IPS_ENABLED_PATCH_CODE_TYPES, pPropertyContext, &bPatchCodeTypesChanged);
    }

    //
    // If WIA_IPS_ENABLED_PATCH_CODE_TYPES is changed, read the new (vector) array of values and validate against the valid values:
    //
    if (SUCCEEDED(hr) && bPatchCodeTypesChanged)
    {
        PROPSPEC ps = {};
        PROPVARIANT pv = {};

        ps.ulKind = PRSPEC_PROPID;
        ps.propid = WIA_IPS_ENABLED_PATCH_CODE_TYPES;

        PropVariantInit(&pv);

        hr = wiasReadMultiple(pWiasContext, 1, &ps, &pv, NULL);
        if (SUCCEEDED(hr))
        {
            if ((VT_VECTOR | VT_I4) == pv.vt)
            {
                ULONG ulValidValues = ARRAYSIZE(g_lSupportedPatchCodeTypes);
                BOOL bFound = FALSE;

                for (ULONG i = 0; i < pv.cal.cElems; i++)
                {
                    bFound = FALSE;

                    for (ULONG j = 0; j < ulValidValues; j++)
                    {
                        if (g_lSupportedPatchCodeTypes[j] == pv.cal.pElems[i])
                        {
                            bFound = TRUE;
                            break;
                        }
                    }

                    if (!bFound)
                    {
                        hr = E_INVALIDARG;
                        WIAEX_ERROR((g_hInst, "Unsupported patch code type: %u, hr = 0x%08X", pv.cal.pElems[i], hr));
                        break;
                    }
                }

            }
            else
            {
                hr = E_INVALIDARG;
                WIAEX_ERROR((g_hInst, "Invalid value type for WIA_IPS_ENABLED_PATCH_CODE_TYPES, expected VT_VECTOR | VT_I4 (%u), got %u, hr = 0x%08X",
                    VT_VECTOR | VT_I4, pv.vt, hr));
            }
        }
        else
        {
            WIAEX_ERROR((g_hInst, "wiasReadMultiple(WIA_IPS_ENABLED_PATCH_CODE_TYPES) failed, hr = 0x%08X", hr));
        }

        PropVariantClear(&pv);
    }

    return hr;
}

/**************************************************************************\
*
* Helper for IWiaMinIDrv::drvValidateItemProperties.
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidateMicrReaderProperties(
    _In_ BYTE*                    pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*    pPropertyContext)
{
    HRESULT hr = S_OK;

    if ((!pWiasContext) || (!pPropertyContext))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "CWiaDriver::ValidateMicrReaderProperties failed, invalid parameter, hr = 0x%08X", hr));
    }

    //
    // Nothing special to validate here
    //

    return hr;
}

/**************************************************************************\
*
* Helper for CWiaDriver::ValidateColorDropProperties.
*
* Validates a RGB color drop property WIA_IPS_COLOR_DROP_RED, WIA_IPS_COLOR_DROP_GREEN and
* WIA_IPS_COLOR_DROP_BLUE
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*    nChannel                - WIA_COLOR_DROP_RED, WIA_COLOR_DROP_GREEN or
*                              WIA_COLOR_DROP_BLUE (as defined in wiadef.h)
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidateColorDropProperty(
    _In_ BYTE*                     pWiasContext,
    _In_ WIA_PROPERTY_CONTEXT*     pPropertyContext,
    UINT                           nChannel)
{
    HRESULT hr = S_OK;
    BOOL bChanged = TRUE;
    UINT nProp = 0;

    if ((!pWiasContext) || (!pPropertyContext) ||
        ((WIA_COLOR_DROP_RED != nChannel) && (WIA_COLOR_DROP_GREEN != nChannel) && (WIA_COLOR_DROP_BLUE != nChannel)))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "CWiaDriver::ValidateColorDropProperty failed, invalid parameter, hr = 0x%08X", hr));
    }

    switch (nChannel)
    {
        case WIA_COLOR_DROP_RED:
            nProp = WIA_IPS_COLOR_DROP_RED;
            break;

        case WIA_COLOR_DROP_GREEN:
            nProp = WIA_IPS_COLOR_DROP_GREEN;
            break;

        case WIA_COLOR_DROP_BLUE:
            nProp = WIA_IPS_COLOR_DROP_BLUE;
    }

    //
    // Check if this WIA_IPS_COLOR_DROP_* property is changed by the application:
    //
    if (SUCCEEDED(hr))
    {
        //
        // wiasIsPropChanged fails if the property is not changed, do not fail validation because of this:
        //
        wiasIsPropChanged(nProp, pPropertyContext, &bChanged);
    }

    //
    // If this WIA_IPS_COLOR_DROP_* property is changed, read the new (vector) array of values and validate
    // both against WIA_IPS_COLOR_DROP_MULTI and the valid range of values (from 0 and 100, inclusive):
    //
    if (SUCCEEDED(hr) && bChanged)
    {
        PROPSPEC ps = {};
        PROPVARIANT pv = {};

        ps.ulKind = PRSPEC_PROPID;
        ps.propid = nProp;

        PropVariantInit(&pv);

        hr = wiasReadMultiple(pWiasContext, 1, &ps, &pv, NULL);
        if (SUCCEEDED(hr))
        {
            if ((VT_VECTOR | VT_I4) == pv.vt)
            {
                if (pv.cal.cElems > g_lMaxDropColors)
                {
                    hr = E_INVALIDARG;
                    WIAEX_ERROR((g_hInst, "Unsupported number of drop out entries for property %u: %u, supported up to: %u, hr = 0x%08X",
                        nProp, pv.cal.cElems, g_lMaxDropColors, hr));
                }
                else
                {
                    for (ULONG i = 0; i < pv.cal.cElems; i++)
                    {
                        if ((pv.cal.pElems[i] < 0) || (pv.cal.pElems[i] > 100))
                        {
                            hr = E_INVALIDARG;
                            WIAEX_ERROR((g_hInst,
                                "Unsupported color drop value for property %u at vector position %u: %u, valid range is from 0 to 100 inclusive, hr = 0x%08X",
                                nProp, i, pv.cal.pElems[i], hr));
                            break;
                        }
                     }
                }
            }
            else
            {
                hr = E_INVALIDARG;
                WIAEX_ERROR((g_hInst, "Invalid value type for property %u, expected VT_VECTOR | VT_I4 (%u), got %u, hr = 0x%08X",
                    nProp, VT_VECTOR | VT_I4, pv.vt, hr));
            }
        }
        else
        {
            WIAEX_ERROR((g_hInst, "wiasReadMultiple(property %u) failed, hr = 0x%08X", nProp, hr));
        }

        PropVariantClear(&pv);
    }

    return hr;
}

/**************************************************************************\
*
* Helper for IWiaMinIDrv::drvValidateItemProperties.
*
* Validates WIA_IPS_COLOR_DROP_RED, WIA_IPS_COLOR_DROP_GREEN and
* WIA_IPS_COLOR_DROP_BLUE
*
* Parameters:
*
*    pWiasContext            - pointer to the item context
*    pPropertyContext        - pointer to the property context which
*                              indicates which properties are being written
*    nDocumentHandlingSelect - FLAT or FEED (as defined in wiadef.h)
*
* Return Value:
*
*    S_OK if successful, an error HRESULT otherwise
*
\**************************************************************************/
HRESULT CWiaDriver::ValidateColorDropProperties(
        _In_ BYTE*                 pWiasContext,
        _In_ WIA_PROPERTY_CONTEXT* pPropertyContext,
        UINT                       nDocumentHandlingSelect)
{
    HRESULT hr = S_OK;

    if ((!pWiasContext) || (!pPropertyContext) ||
        ((FLAT != nDocumentHandlingSelect) && (FEED != nDocumentHandlingSelect)))
    {
        hr = E_INVALIDARG;
        WIAEX_ERROR((g_hInst, "CWiaDriver::ValidateColorDropProperties failed, invalid parameter, hr = 0x%08X", hr));
    }

    //
    // Validate WIA_IPS_COLOR_DROP_RED (error logging is covered by ValidateColorDropProperty):
    //
    if (SUCCEEDED(hr))
    {
        hr = ValidateColorDropProperty(pWiasContext, pPropertyContext, WIA_COLOR_DROP_RED);
    }

    //
    // Validate WIA_IPS_COLOR_DROP_GREEN (error logging is covered by ValidateColorDropProperty):
    //
    if (SUCCEEDED(hr))
    {
        hr = ValidateColorDropProperty(pWiasContext, pPropertyContext, WIA_COLOR_DROP_GREEN);
    }

    //
    // Validate WIA_IPS_COLOR_DROP_BLUE (error logging is covered by ValidateColorDropProperty):
    //
    if (SUCCEEDED(hr))
    {
        hr = ValidateColorDropProperty(pWiasContext, pPropertyContext, WIA_COLOR_DROP_BLUE);
    }

    return hr;
}

Our Services

  • What our customers say about us?

© 2011-2025 All Rights Reserved. Joya Systems. 4425 South Mopac Building II Suite 101 Austin, TX 78735 Tel: 800-DEV-KERNEL

Privacy Policy. Terms of use. Valid XHTML & CSS