[mapguide-commits] r10230 - in branches/4.0/MgDev: Common/Stylization Server/src/UnitTesting

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Fri Oct 23 15:11:41 PDT 2026


Author: jng
Date: 2026-10-23 15:11:40 -0700 (Fri, 23 Oct 2026)
New Revision: 10230

Modified:
   branches/4.0/MgDev/Common/Stylization/Band.cpp
   branches/4.0/MgDev/Common/Stylization/BandData.cpp
   branches/4.0/MgDev/Common/Stylization/GridData.cpp
   branches/4.0/MgDev/Server/src/UnitTesting/CMakeLists.txt
   branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp
   branches/4.0/MgDev/Server/src/UnitTesting/TestStylization.cpp
Log:
Add more tests to harden the Grid band I/O

Modified: branches/4.0/MgDev/Common/Stylization/Band.cpp
===================================================================
--- branches/4.0/MgDev/Common/Stylization/Band.cpp	2026-10-23 20:37:05 UTC (rev 10229)
+++ branches/4.0/MgDev/Common/Stylization/Band.cpp	2026-10-23 22:11:40 UTC (rev 10230)
@@ -164,7 +164,43 @@
         }
         break;
 #else
-        // Linux implementation here
+        // The ByteBandData native accessors are Windows only, so use the
+        // BandData virtual API, which copies the band element size in bytes.
+    case UnsignedInt32:
+    case Int32:
+        {
+            assert (Int32 == m_dataType || UnsignedInt32 == m_dataType);
+            success = m_pBandData->GetValue(i, j, *reinterpret_cast<INT32*>(pValue));
+        }
+        break;
+
+    case Double32:
+        {
+            assert (Double32 == m_dataType);
+            INT32 bits;
+            success = m_pBandData->GetValue(i, j, bits);
+            if (success)
+                memcpy(pValue, &bits, sizeof(float));
+        }
+        break;
+
+    case UnsignedInt64:
+    case Int64:
+        {
+            assert (Int64 == m_dataType || UnsignedInt64 == m_dataType);
+            success = m_pBandData->GetValue(i, j, *reinterpret_cast<INT64*>(pValue));
+        }
+        break;
+
+    case Double64:
+        {
+            assert (Double64 == m_dataType);
+            INT64 bits;
+            success = m_pBandData->GetValue(i, j, bits);
+            if (success)
+                memcpy(pValue, &bits, sizeof(double));
+        }
+        break;
 #endif
 
 
@@ -230,7 +266,41 @@
         }
         break;
 #else
-        // Linux implementation
+        // The ByteBandData native accessors are Windows only, so use the
+        // BandData virtual API, which copies the band element size in bytes.
+    case UnsignedInt32:
+    case Int32:
+        {
+            assert (Int32 == m_dataType || UnsignedInt32 == m_dataType);
+            success = m_pBandData->SetValue(i, j, reinterpret_cast<const INT32*>(pValue));
+        }
+        break;
+
+    case Double32:
+        {
+            assert (Double32 == m_dataType);
+            INT32 bits;
+            memcpy(&bits, pValue, sizeof(float));
+            success = m_pBandData->SetValue(i, j, &bits);
+        }
+        break;
+
+    case UnsignedInt64:
+    case Int64:
+        {
+            assert (Int64 == m_dataType || UnsignedInt64 == m_dataType);
+            success = m_pBandData->SetValue(i, j, reinterpret_cast<const INT64*>(pValue));
+        }
+        break;
+
+    case Double64:
+        {
+            assert (Double64 == m_dataType);
+            INT64 bits;
+            memcpy(&bits, pValue, sizeof(double));
+            success = m_pBandData->SetValue(i, j, &bits);
+        }
+        break;
 #endif
 
 
@@ -337,7 +407,71 @@
         }
         break;
 #else
-    // Linux implementation
+    // The ByteBandData native accessors are Windows only, so use the
+    // BandData virtual API, which copies the band element size in bytes.
+    case UnsignedInt32:
+        {
+            INT32 bits;
+            success = m_pBandData->GetValue(i, j, bits);
+            success = success && (memcmp(&bits, &m_nullValue, 4) != 0); //ensure it is not equal to the null value
+            dvalue = (double)(unsigned int)bits;
+        }
+        break;
+
+    case Int32:
+        {
+            INT32 bits;
+            success = m_pBandData->GetValue(i, j, bits);
+            success = success && (memcmp(&bits, &m_nullValue, 4) != 0); //ensure it is not equal to the null value
+            dvalue = (double)bits;
+        }
+        break;
+
+    case Double32:
+        {
+            INT32 bits;
+            float val = 0.0f;
+            success = m_pBandData->GetValue(i, j, bits);
+            if (success)
+            {
+                memcpy(&val, &bits, sizeof(float));
+                success = (memcmp(&val, &m_nullValue, sizeof(float)) != 0); //ensure it is not equal to the null value
+                dvalue = (double)val;
+            }
+        }
+        break;
+
+    case UnsignedInt64:
+        {
+            INT64 bits;
+            success = m_pBandData->GetValue(i, j, bits);
+            success = success && memcmp(&bits, &m_nullValue, 8) != 0; //ensure it is not equal to the null value
+            dvalue = (double)(UINT64)bits;
+        }
+        break;
+
+    case Int64:
+        {
+            INT64 bits;
+            success = m_pBandData->GetValue(i, j, bits);
+            success = success && (memcmp(&bits, &m_nullValue, 8) != 0); //ensure it is not equal to the null value
+            dvalue = (double)bits;
+        }
+        break;
+
+    case Double64:
+        {
+            INT64 bits;
+            double val = 0.0;
+            success = m_pBandData->GetValue(i, j, bits);
+            if (success)
+            {
+                memcpy(&val, &bits, sizeof(double));
+                success = (memcmp(&val, &m_nullValue, sizeof(double)) != 0); //ensure it is not equal to the null value
+                dvalue = val;
+            }
+        }
+        break;
 #endif
 
     default:
@@ -666,6 +800,7 @@
         return m_pOwnerGrid->GetXUnitDistance();
     }
 
+    // Ignore any C4723 warning about potenial divide by 0. It's a false positive
     return m_nXCount ? m_xExtent / m_nXCount : 0;
 }
 
@@ -842,11 +977,26 @@
         if (memcmp(&fbottom, &m_nullValue, 4) == 0)
             fbottom = fcenter;
 #else
-        // TODO: Linux implementation
-        fleft = 0.0f;
-        fright = 0.0f;
-        ftop = 0.0f;
-        fbottom = 0.0f;
+        // The ByteBandData native accessors are Windows only, so read the
+        // neighbours through GetValueAsDouble, which applies the same
+        // null-value check the Windows path does inline
+        double dcenter, dleft, dright, dtop, dbottom;
+        if (!GetValueAsDouble(i, j, dcenter))
+            return false;
+        if (!i || !GetValueAsDouble(i - 1, j, dleft))
+            dleft = dcenter;
+        if (i == m_nXCount - 1 || !GetValueAsDouble(i + 1, j, dright))
+            dright = dcenter;
+        if (!j || !GetValueAsDouble(i, j - 1, dtop))
+            dtop = dcenter;
+        if (j == m_nYCount - 1 || !GetValueAsDouble(i, j + 1, dbottom))
+            dbottom = dcenter;
+
+        fcenter = (float)dcenter;
+        fleft = (float)dleft;
+        fright = (float)dright;
+        ftop = (float)dtop;
+        fbottom = (float)dbottom;
 #endif
 
         left = fleft;
@@ -887,7 +1037,19 @@
         else
             bottom = ((ByteBandData*)m_pBandData)->GetNativeDouble64(index+m_nXCount);
 #else
-        // Linux implementation
+        // The ByteBandData native accessors are Windows only, so read the
+        // neighbours through GetValueAsDouble, which applies the same
+        // null-value check the Windows path does inline
+        if (!GetValueAsDouble(i, j, center))
+            return false;
+        if (!i || !GetValueAsDouble(i - 1, j, left))
+            left = center;
+        if (i == m_nXCount - 1 || !GetValueAsDouble(i + 1, j, right))
+            right = center;
+        if (!j || !GetValueAsDouble(i, j - 1, top))
+            top = center;
+        if (j == m_nYCount - 1 || !GetValueAsDouble(i, j + 1, bottom))
+            bottom = center;
 #endif
 
         if (memcmp(&left, &m_nullValue, 8) == 0)

Modified: branches/4.0/MgDev/Common/Stylization/BandData.cpp
===================================================================
--- branches/4.0/MgDev/Common/Stylization/BandData.cpp	2026-10-23 20:37:05 UTC (rev 10229)
+++ branches/4.0/MgDev/Common/Stylization/BandData.cpp	2026-10-23 22:11:40 UTC (rev 10230)
@@ -441,7 +441,7 @@
     if (m_size == 8)
     {
         int pos = (x + y * m_width);
-        value = m_pData[pos*8];
+        memcpy(&value, &m_pData[pos*8], sizeof(INT64));
         return true;
     }
 

Modified: branches/4.0/MgDev/Common/Stylization/GridData.cpp
===================================================================
--- branches/4.0/MgDev/Common/Stylization/GridData.cpp	2026-10-23 20:37:05 UTC (rev 10229)
+++ branches/4.0/MgDev/Common/Stylization/GridData.cpp	2026-10-23 22:11:40 UTC (rev 10230)
@@ -21,6 +21,8 @@
 #include "Bounds.h"
 #include "RS_InputStream.h"
 
+#include <memory>
+
 #ifdef MG_MAX_PATH
 #undef MG_MAX_PATH
 #endif
@@ -56,9 +58,13 @@
         return false;
 }
 
-#define READELEVATIONDATA(elevationd_band_type, elevation_fdo_type, function_name) \
-            FdoPtr<elevation_fdo_type> val = static_cast<elevation_fdo_type*>(nullValue); \
-            elevationd_band_type fdoNullValue= val->function_name(); \
+// null_data_type is the FdoDataType the macro's cast expects. If the raster has
+// no null value, or reports one of a different type, every sample is kept.
+#define READELEVATIONDATA(elevationd_band_type, elevation_fdo_type, function_name, null_data_type) \
+            elevation_fdo_type* val = (nullValue.p && nullValue->GetDataType() == (null_data_type))? \
+                                        static_cast<elevation_fdo_type*>(nullValue.p) : NULL; \
+            const bool bHasNullValue = (val != NULL); \
+            elevationd_band_type fdoNullValue = bHasNullValue? val->function_name() : (elevationd_band_type)0; \
             elevationd_band_type curVal; \
             for (unsigned long y = 0; y < actualRows; ++y) \
             { \
@@ -66,10 +72,10 @@
                 { \
                     pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 ); \
                     curVal = (*(reinterpret_cast<elevationd_band_type *>(pCurPos))); \
-                    if (curVal != fdoNullValue) \
+                    if (!bHasNullValue || curVal != fdoNullValue) \
                     { \
-                        float val = (float)curVal; \
-                        pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::Double32, &val); \
+                        float fValue = (float)curVal; \
+                        pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::Double32, &fValue); \
                     } \
                     else \
                     { \
@@ -337,373 +343,359 @@
 )
 {
     Band* pGisBand = NULL;
-    RS_InputStream* reader = NULL;
-    FdoByte* pRasterData = NULL;
-    FdoByte* pPaletteBuf = NULL;
     wchar_t pUniqueBandName[MG_MAX_PATH] = { 0 };
-    try
+
+    //FdoPtr<FdoRasterDataModel> dataModel = pRaster->GetDataModel();
+    //FdoPtr<FdoByteArray> byteArray = pRaster->GetBounds();
+    //FdoPtr<FdoFgfGeometryFactory> pAwkbFactory = FdoFgfGeometryFactory::GetInstance();
+    //FdoPtr<FdoIGeometry> pGeometry = pAwkbFactory->CreateGeometryFromFgf(byteArray);
+    //FdoPtr<FdoIEnvelope> envelope = pGeometry->GetEnvelope();
+    FdoInt32 bitPerPixel = pRaster->GetBitsPerPixel();
+    FdoRasterDataModelType dataModelType = (FdoRasterDataModelType)pRaster->GetDataModelType();
+    RS_Bounds env(pRaster->GetExtent());
+    // Compute the acutal pixel size of returned raster
+    unsigned long actualCols = (unsigned long) (((env.maxx - env.minx) / (xMax - xMin)) * cols + 0.5);
+    unsigned long actualRows = (unsigned long) (((env.maxy - env.miny) / (yMax - yMin)) * rows + 0.5);
+    unsigned long colStartPos = (unsigned long) (((env.minx - xMin) / (xMax - xMin)) * cols + 0.5);
+    unsigned long rowStartPos = (unsigned long) (((yMax - env.maxy) / (yMax - yMin)) * rows + 0.5);
+
+    //pRaster->SetImageXSize(actualCols);
+    //pRaster->SetImageYSize(actualRows);
+    std::unique_ptr<RS_InputStream> reader(pRaster->GetStream(/*this value is ignored*/RS_ImageFormat_ABGR, actualCols, actualRows));
+
+    if (reader)
     {
-        //FdoPtr<FdoRasterDataModel> dataModel = pRaster->GetDataModel();
-        //FdoPtr<FdoByteArray> byteArray = pRaster->GetBounds();
-        //FdoPtr<FdoFgfGeometryFactory> pAwkbFactory = FdoFgfGeometryFactory::GetInstance();
-        //FdoPtr<FdoIGeometry> pGeometry = pAwkbFactory->CreateGeometryFromFgf(byteArray);
-        //FdoPtr<FdoIEnvelope> envelope = pGeometry->GetEnvelope();
-        FdoInt32 bitPerPixel = pRaster->GetBitsPerPixel();
-        FdoRasterDataModelType dataModelType = (FdoRasterDataModelType)pRaster->GetDataModelType();
-        RS_Bounds env(pRaster->GetExtent());
-        // Compute the acutal pixel size of returned raster
-        unsigned long actualCols = (unsigned long) (((env.maxx - env.minx) / (xMax - xMin)) * cols + 0.5);
-        unsigned long actualRows = (unsigned long) (((env.maxy - env.miny) / (yMax - yMin)) * rows + 0.5);
-        unsigned long colStartPos = (unsigned long) (((env.minx - xMin) / (xMax - xMin)) * cols + 0.5);
-        unsigned long rowStartPos = (unsigned long) (((yMax - env.maxy) / (yMax - yMin)) * rows + 0.5);
+        //FireOnStepQuery();
+        // Compute the row length of image. If bAlignment is true, bits size of each row must be a multiple of 8 byte.
+        unsigned long rowBitSize = (actualCols)*bitPerPixel;
+        unsigned long rowLength;
+        if (bAlignment)
+            rowLength = (rowBitSize%64)? ((rowBitSize + 64 - rowBitSize%64) / 8):(rowBitSize/8);
+        else
+            rowLength = rowBitSize/8;
+        unsigned long bufferLength = rowLength * actualRows;
 
-        //pRaster->SetImageXSize(actualCols);
-        //pRaster->SetImageYSize(actualRows);
-        reader = pRaster->GetStream(/*this value is ignored*/RS_ImageFormat_ABGR, actualCols, actualRows);
+        if (reader->available() != bufferLength)
+            return;
 
-        if (reader)
+        //Create an buffer to hold data
+        FdoByte* pRasterData = new FdoByte[bufferLength];
+        std::unique_ptr<FdoByte[]> rasterData(pRasterData);
+        if (NULL == pRasterData)
         {
-            //FireOnStepQuery();
-            // Compute the row length of image. If bAlignment is true, bits size of each row must be a multiple of 8 byte.
-            unsigned long rowBitSize = (actualCols)*bitPerPixel;
-            unsigned long rowLength;
-            if (bAlignment)
-                rowLength = (rowBitSize%64)? ((rowBitSize + 64 - rowBitSize%64) / 8):(rowBitSize/8);
-            else
-                rowLength = rowBitSize/8;
-            unsigned long bufferLength = rowLength * actualRows;
+            throw FdoException::Create(FdoException::NLSGetMessage(FDO_NLSID(CLNT_5_OUTOFMEMORY)));
+        }
 
-            if (reader->available() != bufferLength)
-            {
-                delete reader;
-                return;
-            }
+        reader->read(pRasterData, bufferLength);
 
-            //Create an buffer to hold data
-            pRasterData = new FdoByte[bufferLength];
-            if (NULL == pRasterData)
-            {
-                throw FdoException::Create(FdoException::NLSGetMessage(FDO_NLSID(CLNT_5_OUTOFMEMORY)));
-            }
+        // Get grid data type
+        Band::BandDataType gridDataType = GetGridDataType(pRaster);
+        GetUniqueBandName(pUniqueBandName, this,  pBandName);
 
-            reader->read(pRasterData, bufferLength);
+        pGisBand = CreateBand(gridDataType, pUniqueBandName);
+        FdoString* pVerticalUnit = pRaster->GetVerticalUnits().c_str();
+        pGisBand->SetUnitName(pVerticalUnit);
 
-            // Get grid data type
-            Band::BandDataType gridDataType = GetGridDataType(pRaster);
-            GetUniqueBandName(pUniqueBandName, this,  pBandName);
-
-            pGisBand = CreateBand(gridDataType, pUniqueBandName);
-            FdoString* pVerticalUnit = pRaster->GetVerticalUnits().c_str();
-            pGisBand->SetUnitName(pVerticalUnit);
-
-            // Set NULL value for Band
-            if (dataModelType == FdoRasterDataModelType_Data)
+        // Set NULL value for Band
+        if (dataModelType == FdoRasterDataModelType_Data)
+        {
+            //TODO: Are we guaranteed
+            //that FLT_MAX will always indicate the NODATA value for all grids?
+            if (gridDataType == Band::Double32)
             {
-                //TODO: Are we guaranteed
-                //that FLT_MAX will always indicate the NODATA value for all grids?
-                if (gridDataType == Band::Double32)
-                {
-                    pGisBand->SetNullValue(Band::Double32, (void*)&MG_NODATA_VALUE);
-                }
-                else
-                {
-                    double nullval = DBL_MAX;
-                    pGisBand->SetNullValue(Band::Double64, (void*)&nullval);
-                }
+                pGisBand->SetNullValue(Band::Double32, (void*)&MG_NODATA_VALUE);
             }
-            else if ((dataModelType == FdoRasterDataModelType_RGB)
-                || (dataModelType == FdoRasterDataModelType_RGBA))
-            {
-                unsigned int nullValue = 0x00000000;
-                pGisBand->SetAllToValue(gridDataType, (char*)&nullValue);
-            }
             else
             {
-                unsigned long nullValue(0);
-                pGisBand->SetAllToValue(gridDataType, (char*)(&nullValue));
+                double nullval = DBL_MAX;
+                pGisBand->SetNullValue(Band::Double64, (void*)&nullval);
             }
+        }
+        else if ((dataModelType == FdoRasterDataModelType_RGB)
+            || (dataModelType == FdoRasterDataModelType_RGBA))
+        {
+            unsigned int nullValue = 0x00000000;
+            pGisBand->SetAllToValue(gridDataType, (char*)&nullValue);
+        }
+        else
+        {
+            unsigned long nullValue(0);
+            pGisBand->SetAllToValue(gridDataType, (char*)(&nullValue));
+        }
 
-            unsigned char* pCurPos;
-            FdoRasterDataType rasterDataType = (FdoRasterDataType)pRaster->GetDataType();
-            if ((dataModelType == FdoRasterDataModelType_RGB)
-                || (dataModelType == FdoRasterDataModelType_RGBA))
+        unsigned char* pCurPos;
+        FdoRasterDataType rasterDataType = (FdoRasterDataType)pRaster->GetDataType();
+        if ((dataModelType == FdoRasterDataModelType_RGB)
+            || (dataModelType == FdoRasterDataModelType_RGBA))
+        {
+            unsigned int color;
+            if (24 == bitPerPixel)
             {
-                unsigned int color;
-                if (24 == bitPerPixel)
+                for (unsigned long y = 0; y < actualRows; ++y)
                 {
-                    for (unsigned long y = 0; y < actualRows; ++y)
+                    //FireOnStepQuery();
+                    for (unsigned long x = 0; x < actualCols; ++x)
                     {
-                        //FireOnStepQuery();
-                        for (unsigned long x = 0; x < actualCols; ++x)
-                        {
-                            pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 );
-                            color = ( 0xFF000000 | ((*(pCurPos)) << 16) | ((*(pCurPos + 1) )<< 8) | (*(pCurPos + 2)));
-                            pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::UnsignedInt32, &color );
-                        }
+                        pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 );
+                        color = ( 0xFF000000 | ((*(pCurPos)) << 16) | ((*(pCurPos + 1) )<< 8) | (*(pCurPos + 2)));
+                        pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::UnsignedInt32, &color );
                     }
                 }
-                else if (48 == bitPerPixel) //BUG: 48 bpp should not be using a 32 bit grid size!
+            }
+            else if (48 == bitPerPixel) //BUG: 48 bpp should not be using a 32 bit grid size!
+            {
+                for (unsigned long y = 0; y < actualRows; ++y)
                 {
-                    for (unsigned long y = 0; y < actualRows; ++y)
+                    //FireOnStepQuery();
+                    for (unsigned long x = 0; x < actualCols; ++x)
                     {
-                        //FireOnStepQuery();
-                        for (unsigned long x = 0; x < actualCols; ++x)
-                        {
-                            pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 );
-                            color = ( 0xFF000000 | ((*(pCurPos)) << 32) | ((*(pCurPos + 2)) << 16) | (*(pCurPos + 4)) << 8 );
-                            pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::UnsignedInt32, &color );//BUG: 48 bpp should not be using a 32 bit grid size!
-                        }
+                        pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 );
+                        //The provider hands us 16 bit samples in native byte order, so read
+                        //each RGB channel as a 16 bit value and reduce it to its high byte
+                        UINT16 red = *(reinterpret_cast<UINT16*>(pCurPos));
+                        UINT16 green = *(reinterpret_cast<UINT16*>(pCurPos + 2));
+                        UINT16 blue = *(reinterpret_cast<UINT16*>(pCurPos + 4));
+                        color = ( 0xFF000000 | ((red >> 8) << 16) | ((green >> 8) << 8) | (blue >> 8) );
+                        pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::UnsignedInt32, &color );//BUG: 48 bpp should not be using a 32 bit grid size!
                     }
                 }
-                else //32 bpp
+            }
+            else //32 bpp
+            {
+                //allocate a row where we will do pixel conversion
+                std::unique_ptr<unsigned char[]> row(new unsigned char[actualCols * 4]);
+
+                for (unsigned long y=0; y<actualRows; ++y)
                 {
-                    //allocate a row where we will do pixel conversion
-                    unsigned char* row = new unsigned char[actualCols * 4];
+                    //FireOnStepQuery();
 
-                    for (unsigned long y=0; y<actualRows; ++y)
+                    //convert from RGBA to BGRA
+                    pCurPos = pRasterData + y*rowLength; //source pixel pointer
+                    unsigned char* pix = row.get(); //destination pixel pointer
+
+                    for (unsigned int x=0; x<actualCols; ++x)
                     {
-                        //FireOnStepQuery();
+                        pix[1] = pCurPos[1];
+                        pix[3] = pCurPos[3];
+                        pix[2] = pCurPos[0];
+                        pix[0] = pCurPos[2];
 
-                        //convert from RGBA to BGRA
-                        pCurPos = pRasterData + y*rowLength; //source pixel pointer
-                        unsigned char* pix = row; //destination pixel pointer
-
-                        for (unsigned int x=0; x<actualCols; ++x)
-                        {
-                            pix[1] = pCurPos[1];
-                            pix[3] = pCurPos[3];
-                            pix[2] = pCurPos[0];
-                            pix[0] = pCurPos[2];
-
-                            pCurPos += 4;
-                            pix += 4;
-                        }
-
-                        //set the whole row into the grid
-                        pGisBand->SetRowValue(0, y, Band::UnsignedInt32, (char*)row, actualCols);
+                        pCurPos += 4;
+                        pix += 4;
                     }
 
-                    delete [] row;
+                    //set the whole row into the grid
+                    pGisBand->SetRowValue(0, y, Band::UnsignedInt32, (char*)row.get(), actualCols);
                 }
             }
-            else if (dataModelType == FdoRasterDataModelType_Palette)
+        }
+        else if (dataModelType == FdoRasterDataModelType_Palette)
+        {
+            //FdoPtr<FdoIRasterPropertyDictionary> propDict = pRaster->GetAuxiliaryProperties();
+            //FdoPtr<FdoDataValue> pal = propDict->GetProperty(L"Palette");
+            //FdoLOBValue* palLOB = static_cast<FdoLOBValue*>(pal.p);
+            struct RgbColor
             {
-                //FdoPtr<FdoIRasterPropertyDictionary> propDict = pRaster->GetAuxiliaryProperties();
-                //FdoPtr<FdoDataValue> pal = propDict->GetProperty(L"Palette");
-                //FdoLOBValue* palLOB = static_cast<FdoLOBValue*>(pal.p);
-                struct RgbColor
-                {
-                    union {
-                        struct { FdoByte red; FdoByte green; FdoByte blue; FdoByte alpha; } rgba;
-                        unsigned int packed;
-                    };
+                union {
+                    struct { FdoByte red; FdoByte green; FdoByte blue; FdoByte alpha; } rgba;
+                    unsigned int packed;
                 };
+            };
 
-                unsigned int color;
-                unsigned int pos;
-                if (bitPerPixel >= 8)
+            unsigned int color;
+            unsigned int pos;
+            if (bitPerPixel >= 8)
+            {
+                std::unique_ptr<RS_InputStream> paletteStream(pRaster->GetPalette());
+                FdoByte* pPaletteBuf = new FdoByte[paletteStream->available()];
+                std::unique_ptr<FdoByte[]> paletteBuf(pPaletteBuf);
+                if (NULL == pPaletteBuf)
                 {
-                    RS_InputStream* pStream = pRaster->GetPalette();
-                    pPaletteBuf = new FdoByte[pStream->available()];
-                    if (NULL == pPaletteBuf)
-                    {
-                        throw FdoException::Create(FdoException::NLSGetMessage(FDO_NLSID(CLNT_5_OUTOFMEMORY)));
-                    }
+                    throw FdoException::Create(FdoException::NLSGetMessage(FDO_NLSID(CLNT_5_OUTOFMEMORY)));
+                }
 
-                    pStream->read(pPaletteBuf, pStream->available());
-                    RgbColor* palette = reinterpret_cast<RgbColor*>(pPaletteBuf);
+                paletteStream->read(pPaletteBuf, paletteStream->available());
+                RgbColor* palette = reinterpret_cast<RgbColor*>(pPaletteBuf);
 
-                    for (unsigned long y = 0; y < actualRows; ++y)
+                for (unsigned long y = 0; y < actualRows; ++y)
+                {
+                    //FireOnStepQuery();
+                    for (unsigned long x = 0; x< actualCols; ++x)
                     {
-                        //FireOnStepQuery();
-                        for (unsigned long x = 0; x< actualCols; ++x)
+                        pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 );
+                        switch(bitPerPixel)
                         {
-                            pCurPos = (unsigned char*)(pRasterData + y*rowLength + x*bitPerPixel/8 );
-                            switch(bitPerPixel)
-                            {
-                                case 8:
-                                    pos = *(reinterpret_cast<UINT8*>(pCurPos));
-                                    break;
-                                case 16:
-                                    pos = *(reinterpret_cast<UINT16*>(pCurPos));
-                                    break;
-                                case 32:
-                                    pos = *(reinterpret_cast<UINT32*>(pCurPos));
-                                    break;
-                                default:
-                                    pos = 0;
-                                    break;
-                            }
-                            color = 0xFF000000 | palette[pos].rgba.red << 16 | palette[pos].rgba.green << 8 | palette[pos].rgba.blue;
-                            pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::UnsignedInt32, &color );
+                            case 8:
+                                pos = *(reinterpret_cast<UINT8*>(pCurPos));
+                                break;
+                            case 16:
+                                pos = *(reinterpret_cast<UINT16*>(pCurPos));
+                                break;
+                            case 32:
+                                pos = *(reinterpret_cast<UINT32*>(pCurPos));
+                                break;
+                            default:
+                                pos = 0;
+                                break;
                         }
+                        color = 0xFF000000 | palette[pos].rgba.red << 16 | palette[pos].rgba.green << 8 | palette[pos].rgba.blue;
+                        pGisBand->SetValue(colStartPos + x, rowStartPos + y, Band::UnsignedInt32, &color );
                     }
                 }
-                else
+            }
+            else
+            {
+                char value, bitNum, temp;
+                unsigned long byteNum;
+
+                for (unsigned long y = 0; y < actualRows; ++y)
                 {
-                    char value, bitNum, temp;
-                    unsigned long byteNum;
-
-                    for (unsigned long y = 0; y < actualRows; ++y)
+                    bitNum = 0;
+                    value = 0;
+                    temp = 0;
+                    byteNum = 0;
+                    //FireOnStepQuery();
+                    for (unsigned long x = 0; x < actualCols; ++x)
                     {
-                        bitNum = 0;
-                        value = 0;
-                        temp = 0;
-                        byteNum = 0;
-                        //FireOnStepQuery();
-                        for (unsigned long x = 0; x < actualCols; ++x)
+                        if (bitNum >= 8)
                         {
-                            if (bitNum >= 8)
-                            {
-                                bitNum = 0;
-                                byteNum++;
-                            }
-                            if (bitNum == 0)
-                                temp = (*((char*)(pRasterData + y*rowLength + byteNum)));
+                            bitNum = 0;
+                            byteNum++;
+                        }
+                        if (bitNum == 0)
+                            temp = (*((char*)(pRasterData + y*rowLength + byteNum)));
 
-                            value = (char)((temp << (8 - bitNum - bitPerPixel)) >> (8 - bitPerPixel));
-                            pGisBand->SetValue(x, y, gridDataType, &value);
-                            bitNum += bitPerPixel;
-                        }
+                        value = (char)((temp << (8 - bitNum - bitPerPixel)) >> (8 - bitPerPixel));
+                        pGisBand->SetValue(x, y, gridDataType, &value);
+                        bitNum += bitPerPixel;
                     }
                 }
             }
-            else if ((dataModelType == FdoRasterDataModelType_Data) &&
-                     ((rasterDataType == FdoRasterDataType_Integer) ||
-                      (rasterDataType == FdoRasterDataType_UnsignedInteger)))
+        }
+        else if ((dataModelType == FdoRasterDataModelType_Data) &&
+                 ((rasterDataType == FdoRasterDataType_Integer) ||
+                  (rasterDataType == FdoRasterDataType_UnsignedInteger)))
+        {
+            Band::BandDataType bandDataType = GetGridDataType(pRaster, false);
+            FdoDataType type = (FdoDataType)pRaster->GetNullValueType();
+            FdoPtr<FdoDataValue> nullValue;
+            switch(type)
             {
-                Band::BandDataType bandDataType = GetGridDataType(pRaster, false);
-                FdoDataType type = (FdoDataType)pRaster->GetNullValueType();
-                FdoDataValue* nullValue = NULL;
-                switch(type)
+            case FdoDataType_Byte:
+                nullValue = FdoByteValue::Create((FdoByte)pRaster->GetNullValueData());
+                break;
+            case FdoDataType_Double:
+                nullValue = FdoDoubleValue::Create((double)pRaster->GetNullValueData());
+                break;
+            case FdoDataType_Single:
+                nullValue = FdoSingleValue::Create((float)pRaster->GetNullValueData());
+                break;
+            case FdoDataType_Int16:
+                nullValue = FdoInt16Value::Create((FdoInt16)pRaster->GetNullValueData());
+                break;
+            case FdoDataType_Int32:
+                nullValue = FdoInt32Value::Create((FdoInt32)pRaster->GetNullValueData());
+                break;
+            case FdoDataType_Int64:
+                nullValue = FdoInt64Value::Create(pRaster->GetNullValueData());
+                break;
+            default: break;
+            }
+
+            switch (bandDataType)
+            {
+            case Band::UnsignedInt8:
                 {
-                case FdoDataType_Double:
-                    nullValue = FdoDoubleValue::Create((double)pRaster->GetNullValueData());
+                    READELEVATIONDATA(UINT8, FdoByteValue, GetByte, FdoDataType_Byte);
                     break;
-                case FdoDataType_Single:
-                    nullValue = FdoSingleValue::Create((float)pRaster->GetNullValueData());
+                }
+            case Band::UnsignedInt16:
+                {
+                    READELEVATIONDATA(UINT16, FdoInt16Value, GetInt16, FdoDataType_Int16);
                     break;
-                case FdoDataType_Int16:
-                    nullValue = FdoInt16Value::Create((FdoInt16)pRaster->GetNullValueData());
+                }
+            case Band::UnsignedInt32:
+                {
+                    READELEVATIONDATA(UINT32, FdoInt32Value, GetInt32, FdoDataType_Int32);
                     break;
-                case FdoDataType_Int32:
-                    nullValue = FdoInt32Value::Create((FdoInt32)pRaster->GetNullValueData());
+                }
+            case Band::UnsignedInt64:
+                {
+                    READELEVATIONDATA(UINT64, FdoInt64Value, GetInt64, FdoDataType_Int64);
                     break;
-                case FdoDataType_Int64:
-                    nullValue = FdoInt64Value::Create(pRaster->GetNullValueData());
+                }
+            case Band::Int8:
+                {
+                    READELEVATIONDATA(INT8, FdoByteValue, GetByte, FdoDataType_Byte);
                     break;
-                default: break;
                 }
-
-                switch (bandDataType)
+            case Band::Int16:
                 {
-                case Band::UnsignedInt8:
-                    {
-                        READELEVATIONDATA(UINT8, FdoByteValue, GetByte);
-                        break;
-                    }
-                case Band::UnsignedInt16:
-                    {
-                        READELEVATIONDATA(UINT16, FdoInt16Value, GetInt16);
-                        break;
-                    }
-                case Band::UnsignedInt32:
-                    {
-                        READELEVATIONDATA(UINT32, FdoInt32Value, GetInt32);
-                        break;
-                    }
-                case Band::UnsignedInt64:
-                    {
-                        READELEVATIONDATA(UINT64, FdoInt64Value, GetInt64);
-                        break;
-                    }
-                case Band::Int8:
-                    {
-                        READELEVATIONDATA(INT8, FdoByteValue, GetByte);
-                        break;
-                    }
-                case Band::Int16:
-                    {
-                        READELEVATIONDATA(INT16, FdoInt16Value, GetInt16);
-                        break;
-                    }
-                case Band::Int32:
-                    {
-                        READELEVATIONDATA(INT32, FdoInt32Value, GetInt32);
-                        break;
-                    }
-                case Band::Int64:
-                    {
-                        READELEVATIONDATA(INT64, FdoInt64Value, GetInt64);
-                        break;
-                    }
-                case Band::Double32:
-                    {
-                        READELEVATIONDATA(float, FdoSingleValue, GetSingle);
-                        break;
-                    }
-                case Band::Double64:
-                    {
-                        READELEVATIONDATA(double, FdoSingleValue, GetSingle);
-                        break;
-                    }
+                    READELEVATIONDATA(INT16, FdoInt16Value, GetInt16, FdoDataType_Int16);
+                    break;
                 }
-                if(nullValue)
-                    nullValue->Release();
-            }
-            else
-            {
-                for (unsigned long y = 0; y < actualRows; ++y)
+            case Band::Int32:
                 {
-                    //FireOnStepQuery();
-                    pGisBand->SetRowValue(colStartPos, rowStartPos + y, gridDataType,
-                                            (char*)(pRasterData + y*rowLength), actualCols);
+                    READELEVATIONDATA(INT32, FdoInt32Value, GetInt32, FdoDataType_Int32);
+                    break;
                 }
+            case Band::Int64:
+                {
+                    READELEVATIONDATA(INT64, FdoInt64Value, GetInt64, FdoDataType_Int64);
+                    break;
+                }
+            case Band::Double32:
+                {
+                    READELEVATIONDATA(float, FdoSingleValue, GetSingle, FdoDataType_Single);
+                    break;
+                }
+            case Band::Double64:
+                {
+                    READELEVATIONDATA(double, FdoSingleValue, GetSingle, FdoDataType_Single);
+                    break;
+                }
             }
+        }
+        else
+        {
+            for (unsigned long y = 0; y < actualRows; ++y)
+            {
+                //FireOnStepQuery();
+                pGisBand->SetRowValue(colStartPos, rowStartPos + y, gridDataType,
+                                        (char*)(pRasterData + y*rowLength), actualCols);
+            }
+        }
 
-            // TODO:  When we get past the Mako crunch, we should revisit and probably remove this code.
-            //        Leaf, Brett and I agree that we should not completely remove this code at this
-            //        time.  I changed it from 25000 to 30000, so we can map anything above the earth's
-            //        surface in feet (Mt Everest is 28717 feet in elevation).  - lap - 3/10/06
-            // When new version of FDO RFP, which modifies resampling algorithm for dem file to BSpline, is avaiable,
-            // we will remove code to smooth elevation and use the following code to recover modified NULL value to its original value.
-            // NOTE:  The smoothing code has been removed.
-            if (dataModelType == FdoRasterDataModelType_Data)
+        // TODO:  When we get past the Mako crunch, we should revisit and probably remove this code.
+        //        Leaf, Brett and I agree that we should not completely remove this code at this
+        //        time.  I changed it from 25000 to 30000, so we can map anything above the earth's
+        //        surface in feet (Mt Everest is 28717 feet in elevation).  - lap - 3/10/06
+        // When new version of FDO RFP, which modifies resampling algorithm for dem file to BSpline, is avaiable,
+        // we will remove code to smooth elevation and use the following code to recover modified NULL value to its original value.
+        // NOTE:  The smoothing code has been removed.
+        if (dataModelType == FdoRasterDataModelType_Data)
+        {
+            float fNullValue = MG_NODATA_VALUE;
+            double dCurValue(0.0);
+            for (unsigned long y = rowStartPos; (y < actualRows + rowStartPos); ++y)
             {
-                float fNullValue = MG_NODATA_VALUE;
-                double dCurValue(0.0);
-                for (unsigned long y = rowStartPos; (y < actualRows + rowStartPos); ++y)
+                //FireOnStepQuery();
+                for (unsigned long x = colStartPos; (x < actualCols + colStartPos); ++x)
                 {
-                    //FireOnStepQuery();
-                    for (unsigned long x = colStartPos; (x < actualCols + colStartPos); ++x)
+                    // If elevation value is greater than 30000, we will consider it a NULL value
+                    bool ret = pGisBand->GetValueAsDouble(x, y, dCurValue);
+                    if (!ret || (dCurValue > 30000.0)||(dCurValue < -30000.0))
                     {
-                        // If elevation value is greater than 30000, we will consider it a NULL value
-                        bool ret = pGisBand->GetValueAsDouble(x, y, dCurValue);
-                        if (!ret || (dCurValue > 30000.0)||(dCurValue < -30000.0))
-                        {
-                            pGisBand->SetValue(x, y, gridDataType, (char*)(&fNullValue));
-                        }
+                        pGisBand->SetValue(x, y, gridDataType, (char*)(&fNullValue));
                     }
                 }
             }
-            else if (dataModelType == FdoRasterDataModelType_Bitonal)
-            {
-                //Fix DID 885517
-            }
         }
+        else if (dataModelType == FdoRasterDataModelType_Bitonal)
+        {
+            //Fix DID 885517
+        }
     }
-    catch(...)
-    {
-        delete [] pRasterData;
-        delete [] pPaletteBuf;
-        delete reader;
-
-        throw;
-    }
-
-    delete [] pRasterData;
-    delete [] pPaletteBuf;
-    delete reader;
 }
 
 Band::BandDataType

Modified: branches/4.0/MgDev/Server/src/UnitTesting/CMakeLists.txt
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/CMakeLists.txt	2026-10-23 20:37:05 UTC (rev 10229)
+++ branches/4.0/MgDev/Server/src/UnitTesting/CMakeLists.txt	2026-10-23 22:11:40 UTC (rev 10230)
@@ -20,7 +20,6 @@
     ${MG_SERVER_SERVICES_DIR}/Kml
     ${MG_SERVER_SERVICES_DIR}/Tile
     ${FDO_INCLUDE_DIR}
-    ${FDO_INCLUDE_DIR}/ExpressionEngine
 )
 
 set(MgUnitTesting_SRCS

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp	2026-10-23 20:37:05 UTC (rev 10229)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestRasterStylization.cpp	2026-10-23 22:11:40 UTC (rev 10230)
@@ -119,22 +119,16 @@
 
 const Fixture FIXTURES[] =
 {
-    // These fixtures already render correctly from unmodified source on both
-    // Windows and Linux: they only use code paths that are shared between the
-    // platforms (SetRowValue plus the colour handler's memcpy fast path), so
-    // they define the behavioural baseline the band fixes must not disturb.
-    //
-    // The 24/48 bpp RGB and palette fixtures are deliberately absent: their
-    // bands are only filled by the 32 bit Band::SetValue path, which is a no-op
-    // on Linux before the fixes. They are added, with their expected colours,
-    // in the change that fixes those paths.
-    //
-    // The 64 bpp float fixture is absent for a different reason: before the
-    // fixes its behaviour depends on the platform. Windows reads one byte of
-    // the eight byte sample and paints an opaque black band, while Linux does
-    // not style the band at all and the background shows through. It cannot be
-    // part of a baseline that must pass on both, and is added by the change
-    // that fixes the 8 byte BandData accessor.
+    // The 24/48 bpp branches and the palette branch pack an opaque ARGB value
+    // per pixel, so these fixtures render exactly the colour in the file.
+    { "RasterStylization_rgb24.tif",       "24 bpp RGB",
+      { 0x12569A, 0xFF0000, 0x00FF01, 0x804010 } },
+    { "RasterStylization_rgb48.tif",       "48 bpp RGB, high byte of each 16 bit sample",
+      { 0x12569A, 0xFF0000, 0x00FF01, 0x804010 } },
+    { "RasterStylization_rgb48_int16.tif", "48 bpp signed RGB, same bit patterns",
+      { 0x12569A, 0xFF0000, 0x00FF01, 0x804010 } },
+    { "RasterStylization_palette8.tif",    "8 bpp palette",
+      { 0x12569A, 0x0000FF, 0x00FF01, 0x804010 } },
 
     // The 32 bpp branch copies the source pixels through unchanged, so the
     // source alpha survives and the renderer blends the colour over the white
@@ -160,6 +154,10 @@
     // pattern happens to contain (0x45, 0x46, 0x46, 0x40).
     { "RasterStylization_gray32f.tif",     "32 bpp float single band, samples read as ARGB",
       { 0xE1E5B9, 0xE8FAB8, 0xBFECB7, 0xDDBBBB } },
+    // Doubles are read as unsigned 64 bit samples, which is far outside the
+    // range a packed ARGB value can hold, so no pixel is coloured.
+    { "RasterStylization_gray64f.tif",     "64 bpp float single band, not styled",
+      { 0xFFFFFF, 0xFFFFFF, 0xFFFFFF, 0xFFFFFF } },
 };
 
 // Real rasters derived from the FDO GDAL provider test data
@@ -173,10 +171,6 @@
 // multiple of 64 falls through to RasterAdapter's legacy decoder instead, which
 // is not the code under test here - and which has no 16 bpp case at all.
 //
-// The palette sheet is absent for the same reason as the palette fixture above:
-// its colour band is only filled through the 32 bit Band::SetValue path, so it
-// renders blank from unmodified source on Linux and is added with the fixes.
-//
 // Nothing here has an expected colour: the point is that genuine imagery and
 // elevation data survive the pipeline unaltered, so each fixture is rendered to
 // ../UnitTestFiles/Results/RealRaster_<name>.png for a human to look at, and the
@@ -214,6 +208,8 @@
       StyleGrayBands, 0, 0 },
     { "RealRaster_spif83.tif",      "8 bit grey, NAD83 / California zone 6, ~195 m",
       StyleGrayBands, 0, 0 },
+    { "RealRaster_cm13ct.tif",      "8 bit palette scanned map, UTM 13N",
+      StyleBand, 0, 0 },
     // The DEM holds metres (nodata is -32767), so it needs scaling to become a
     // visible grey ramp; 0-3000 m covers the whole tile.
     { "RealRaster_mtjefferson.tif", "Int16 USGS DEM as a 0-3000 m elevation ramp",

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestStylization.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestStylization.cpp	2026-10-23 20:37:05 UTC (rev 10229)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestStylization.cpp	2026-10-23 22:11:40 UTC (rev 10230)
@@ -163,15 +163,65 @@
     return band->GetValue(0, 0, Band::UnsignedInt32, &color);
 }
 
-// The read path rounds each row up to a 64 bit boundary when it is told the
-// provider pads its rows, and gives up when the stream is shorter than that.
-// This is behavioural baseline: it holds on Windows and Linux, before and after
-// the band accessor work.
-TEST_CASE("Grid colour band row alignment", "[Stylization]")
+// Runs a 16 bpp single component Data raster through ReadRaster and reads back
+// pixel (0, 0) of the elevation band. Returns false when no Double32 band was
+// created.
+bool ReadElevationBandPixel(const std::vector<unsigned char>& bytes,
+                            int nullValueType,
+                            int64_t nullValueData,
+                            double& value)
 {
     const std::vector<unsigned char> noPalette;
+    GridData grid(Point2D(0.0, 0.0), 4.0, 4.0, 4, 4);
+    TestRaster raster(16, FdoRasterDataModelType_Data, FdoRasterDataType_UnsignedInteger,
+                      bytes, noPalette, 4, 4, nullValueType, nullValueData);
+
+    grid.ReadRaster(&raster, L"1", 4, 4, 0.0, 0.0, 4.0, 4.0, false);
+
+    Band* band = grid.GetBand((unsigned int)0);
+    if (NULL == band || Band::Double32 != band->GetDataType())
+        return false;
+
+    // GetValue, not GetValueAsDouble: a Data model raster sets the band's null
+    // value to MG_NODATA, so MG_NODATA samples read back as "no value".
+    float sample = 0.0f;
+    if (!band->GetValue(0, 0, Band::Double32, &sample))
+        return false;
+
+    value = (double)sample;
+    return true;
+}
+
+}
+
+TEST_CASE("Grid colour band packing", "[Stylization]")
+{
+    const std::vector<unsigned char> noPalette;
     unsigned int color = 0;
 
+    SECTION("48 bpp RGB reduces 16 bit samples to their high byte")
+    {
+        // R = 0x1234, G = 0x5678, B = 0x9ABC, little endian
+        const unsigned char pixel[] = { 0x34, 0x12, 0x78, 0x56, 0xBC, 0x9A };
+
+        REQUIRE(ReadColourBandPixel(RepeatPixel(pixel, 6, 16), 48, FdoRasterDataModelType_RGB,
+                                    4, 4, false, noPalette, color));
+        REQUIRE(0xFF12569Au == color);
+    }
+
+    SECTION("48 bpp RGB discards the low byte of each channel")
+    {
+        const unsigned char white[] = { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 }; // 0x00FF per channel
+        REQUIRE(ReadColourBandPixel(RepeatPixel(white, 6, 16), 48, FdoRasterDataModelType_RGB,
+                                    4, 4, false, noPalette, color));
+        REQUIRE(0xFF000000u == color);
+
+        const unsigned char almostBlack[] = { 0x00, 0x01, 0x00, 0x01, 0x00, 0x01 }; // 0x0100 per channel
+        REQUIRE(ReadColourBandPixel(RepeatPixel(almostBlack, 6, 16), 48, FdoRasterDataModelType_RGB,
+                                    4, 4, false, noPalette, color));
+        REQUIRE(0xFF010101u == color);
+    }
+
     SECTION("48 bpp RGB with a row length the provider does not pad produces no band")
     {
         // 6 pixels * 6 bytes = 36 bytes per row, but bAlignment rounds the
@@ -181,144 +231,175 @@
         REQUIRE_FALSE(ReadColourBandPixel(RepeatPixel(pixel, 6, 24), 48, FdoRasterDataModelType_RGB,
                                           6, 4, true, noPalette, color));
     }
-}
 
-// Band creation and the narrow (8 and 16 bit) accessors are implemented the
-// same way on both platforms, so they are baseline behaviour. The 32 and 64 bit
-// accessors are covered by the change that implements them on Linux.
-TEST_CASE("Band creation", "[Stylization]")
-{
-    GridData grid(Point2D(0.0, 0.0), 3.0, 3.0, 3, 3);
+    SECTION("24 bpp RGB")
+    {
+        const unsigned char pixel[] = { 0x11, 0x22, 0x33 };
 
-    SECTION("UnsignedInt32") { REQUIRE(NULL != grid.CreateBand(Band::UnsignedInt32, L"u32")); }
-    SECTION("Int32")         { REQUIRE(NULL != grid.CreateBand(Band::Int32, L"i32")); }
-    SECTION("Double32")      { REQUIRE(NULL != grid.CreateBand(Band::Double32, L"f32")); }
-    SECTION("UnsignedInt64") { REQUIRE(NULL != grid.CreateBand(Band::UnsignedInt64, L"u64")); }
-    SECTION("Int64")         { REQUIRE(NULL != grid.CreateBand(Band::Int64, L"i64")); }
-    SECTION("Double64")      { REQUIRE(NULL != grid.CreateBand(Band::Double64, L"f64")); }
-    SECTION("Boolean")       { REQUIRE(NULL != grid.CreateBand(Band::Boolean, L"b")); }
-    SECTION("Bit2")          { REQUIRE(NULL != grid.CreateBand(Band::Bit2, L"b2")); }
-    SECTION("Bit4")          { REQUIRE(NULL != grid.CreateBand(Band::Bit4, L"b4")); }
+        REQUIRE(ReadColourBandPixel(RepeatPixel(pixel, 3, 16), 24, FdoRasterDataModelType_RGB,
+                                    4, 4, false, noPalette, color));
+        REQUIRE(0xFF112233u == color);
+    }
+
+    SECTION("32 bpp RGBA keeps the source alpha")
+    {
+        const unsigned char pixel[] = { 0x11, 0x22, 0x33, 0x80 };
+
+        REQUIRE(ReadColourBandPixel(RepeatPixel(pixel, 4, 16), 32, FdoRasterDataModelType_RGBA,
+                                    4, 4, false, noPalette, color));
+        REQUIRE(0x80112233u == color);
+    }
+
+    SECTION("8 bpp palette")
+    {
+        const std::vector<unsigned char> palette =
+        {
+            255,   0,   0, 255,   // 0 red
+              0, 255,   0, 255,   // 1 green
+              0,   0, 255, 255,   // 2 blue
+            255, 255, 255, 255    // 3 white
+        };
+        const unsigned char index = 2;
+
+        REQUIRE(ReadColourBandPixel(RepeatPixel(&index, 1, 16), 8, FdoRasterDataModelType_Palette,
+                                    4, 4, false, palette, color));
+        REQUIRE(0xFF0000FFu == color);
+    }
 }
 
-TEST_CASE("Band numeric accessors for narrow types", "[Stylization]")
+TEST_CASE("Grid elevation band", "[Stylization]")
 {
-    GridData grid(Point2D(0.0, 0.0), 3.0, 3.0, 3, 3);
-
-    SECTION("UnsignedInt8")
+    // 16 bpp single component Data model: exercises the READELEVATIONDATA path,
+    // including ownership and validation of the null value it is given.
+    std::vector<unsigned char> bytes;
+    for (int i = 0; i < 16; ++i)
     {
-        Band* band = grid.CreateBand(Band::UnsignedInt8, L"u8");
-        REQUIRE(NULL != band);
+        bytes.push_back(0x34);
+        bytes.push_back(0x12); // 0x1234, little endian
+    }
 
-        INT8 value = 100;
-        REQUIRE(band->SetValue(1, 1, Band::UnsignedInt8, &value));
+    double value = 0.0;
 
-        INT8 readBack = 0;
-        REQUIRE(band->GetValue(1, 1, Band::UnsignedInt8, &readBack));
-        REQUIRE(value == readBack);
-        REQUIRE(100.0 == band->GetValueAsDouble(1, 1));
+    SECTION("supported null value type")
+    {
+        REQUIRE(ReadElevationBandPixel(bytes, FdoDataType_Int16, 0, value));
+        REQUIRE(4660.0 == value);
     }
 
-    SECTION("UnsignedInt8 above 0x7F reads back negative")
+    SECTION("sample equal to the null value becomes MG_NODATA")
     {
-        // Pre-existing behaviour, recorded so a change to it is visible: the
-        // unsigned 8 bit case in Band::GetValueAsDouble loads the sample into an
-        // INT8, so anything from 0x80 up is sign extended. This is why paletted
-        // and grey rasters with high sample values behave oddly.
-        Band* band = grid.CreateBand(Band::UnsignedInt8, L"u8high");
-        REQUIRE(NULL != band);
+        REQUIRE(ReadElevationBandPixel(bytes, FdoDataType_Int16, 4660, value));
+        REQUIRE(FLT_MAX == (float)value);
+    }
 
-        INT8 value = (INT8)200;
-        REQUIRE(band->SetValue(1, 1, Band::UnsignedInt8, &value));
+    SECTION("null value of an unsupported type keeps every sample")
+    {
+        REQUIRE(ReadElevationBandPixel(bytes, FdoDataType_DateTime, 0, value));
+        REQUIRE(4660.0 == value);
+    }
 
-        INT8 readBack = 0;
-        REQUIRE(band->GetValue(1, 1, Band::UnsignedInt8, &readBack));
-        REQUIRE(value == readBack);
-        REQUIRE(-56.0 == band->GetValueAsDouble(1, 1));
+    SECTION("null value type that does not match the reader keeps every sample")
+    {
+        REQUIRE(ReadElevationBandPixel(bytes, FdoDataType_Double, 0, value));
+        REQUIRE(4660.0 == value);
     }
+}
 
-    SECTION("Int8")
+TEST_CASE("Band numeric accessors", "[Stylization]")
+{
+    GridData grid(Point2D(0.0, 0.0), 3.0, 3.0, 3, 3);
+
+    SECTION("UnsignedInt32")
     {
-        Band* band = grid.CreateBand(Band::Int8, L"i8");
+        Band* band = grid.CreateBand(Band::UnsignedInt32, L"color");
         REQUIRE(NULL != band);
 
-        INT8 value = -100;
-        REQUIRE(band->SetValue(2, 0, Band::Int8, &value));
+        unsigned int value = 0xFF12569A;
+        REQUIRE(band->SetValue(1, 1, Band::UnsignedInt32, &value));
 
-        INT8 readBack = 0;
-        REQUIRE(band->GetValue(2, 0, Band::Int8, &readBack));
+        unsigned int readBack = 0;
+        REQUIRE(band->GetValue(1, 1, Band::UnsignedInt32, &readBack));
         REQUIRE(value == readBack);
-        REQUIRE(-100.0 == band->GetValueAsDouble(2, 0));
+        REQUIRE(4279391898.0 == band->GetValueAsDouble(1, 1));
     }
 
-    SECTION("UnsignedInt16")
+    SECTION("Int32")
     {
-        Band* band = grid.CreateBand(Band::UnsignedInt16, L"u16");
+        Band* band = grid.CreateBand(Band::Int32, L"int");
         REQUIRE(NULL != band);
 
-        INT16 value = (INT16)60000;
-        REQUIRE(band->SetValue(0, 2, Band::UnsignedInt16, &value));
+        int value = -123456;
+        REQUIRE(band->SetValue(2, 0, Band::Int32, &value));
 
-        INT16 readBack = 0;
-        REQUIRE(band->GetValue(0, 2, Band::UnsignedInt16, &readBack));
+        int readBack = 0;
+        REQUIRE(band->GetValue(2, 0, Band::Int32, &readBack));
         REQUIRE(value == readBack);
-        REQUIRE(60000.0 == band->GetValueAsDouble(0, 2));
+        REQUIRE(-123456.0 == band->GetValueAsDouble(2, 0));
     }
 
-    SECTION("Int16")
+    SECTION("Double32")
     {
-        Band* band = grid.CreateBand(Band::Int16, L"i16");
+        Band* band = grid.CreateBand(Band::Double32, L"float");
         REQUIRE(NULL != band);
 
-        INT16 value = -12345;
-        REQUIRE(band->SetValue(1, 0, Band::Int16, &value));
+        float value = 1234.5f;
+        REQUIRE(band->SetValue(0, 2, Band::Double32, &value));
 
-        INT16 readBack = 0;
-        REQUIRE(band->GetValue(1, 0, Band::Int16, &readBack));
+        float readBack = 0.0f;
+        REQUIRE(band->GetValue(0, 2, Band::Double32, &readBack));
         REQUIRE(value == readBack);
-        REQUIRE(-12345.0 == band->GetValueAsDouble(1, 0));
+        REQUIRE(1234.5 == band->GetValueAsDouble(0, 2));
     }
 
-    SECTION("Boolean")
+    SECTION("Double64")
     {
-        Band* band = grid.CreateBand(Band::Boolean, L"bool");
+        Band* band = grid.CreateBand(Band::Double64, L"double");
         REQUIRE(NULL != band);
 
-        INT8 value = 1;
-        REQUIRE(band->SetValue(0, 0, Band::Boolean, &value));
+        double value = 1.0 / 3.0;
+        REQUIRE(band->SetValue(1, 0, Band::Double64, &value));
 
-        INT8 readBack = 0;
-        REQUIRE(band->GetValue(0, 0, Band::Boolean, &readBack));
+        double readBack = 0.0;
+        REQUIRE(band->GetValue(1, 0, Band::Double64, &readBack));
         REQUIRE(value == readBack);
-        REQUIRE(1.0 == band->GetValueAsDouble(0, 0));
+        REQUIRE(value == band->GetValueAsDouble(1, 0));
     }
 
-    SECTION("Bit2")
+    SECTION("Int64")
     {
-        Band* band = grid.CreateBand(Band::Bit2, L"bit2");
+        Band* band = grid.CreateBand(Band::Int64, L"long");
         REQUIRE(NULL != band);
 
-        INT8 value = 3;
-        REQUIRE(band->SetValue(2, 2, Band::Bit2, &value));
+        INT64 value = 0x0102030405060708LL;
+        REQUIRE(band->SetValue(0, 1, Band::Int64, &value));
 
-        INT8 readBack = 0;
-        REQUIRE(band->GetValue(2, 2, Band::Bit2, &readBack));
+        INT64 readBack = 0;
+        REQUIRE(band->GetValue(0, 1, Band::Int64, &readBack));
         REQUIRE(value == readBack);
-        REQUIRE(3.0 == band->GetValueAsDouble(2, 2));
     }
+}
 
-    SECTION("Bit4")
+TEST_CASE("Band slope from neighbouring values", "[Stylization]")
+{
+    GridData grid(Point2D(0.0, 0.0), 3.0, 3.0, 3, 3);
+    Band* elevation = grid.CreateBand(Band::Double32, L"elevation");
+    REQUIRE(NULL != elevation);
+
+    // A ramp of one unit per cell: at the centre pixel the neighbours are
+    // left = 0, right = 2, top = bottom = 1, so the slope is 45 degrees.
+    for (unsigned int y = 0; y < 3; ++y)
     {
-        Band* band = grid.CreateBand(Band::Bit4, L"bit4");
-        REQUIRE(NULL != band);
+        for (unsigned int x = 0; x < 3; ++x)
+        {
+            float z = (float)x;
+            REQUIRE(elevation->SetValue(x, y, Band::Double32, &z));
+        }
+    }
 
-        INT8 value = 9;
-        REQUIRE(band->SetValue(1, 2, Band::Bit4, &value));
+    const Band* slope = elevation->GetSlopeBand();
+    REQUIRE(NULL != slope);
 
-        INT8 readBack = 0;
-        REQUIRE(band->GetValue(1, 2, Band::Bit4, &readBack));
-        REQUIRE(value == readBack);
-        REQUIRE(9.0 == band->GetValueAsDouble(1, 2));
-    }
+    double angle = slope->GetValueAsDouble(1, 1);
+    REQUIRE(44.9 < angle);
+    REQUIRE(45.1 > angle);
 }
-}



More information about the mapguide-commits mailing list