[mapguide-commits] r10259 - in branches/4.0/MgDev: Common/Renderers Server/src/UnitTesting

svn_mapguide at osgeo.org svn_mapguide at osgeo.org
Tue Nov 3 03:44:16 PST 2026


Author: jng
Date: 2026-11-03 03:44:14 -0800 (Tue, 03 Nov 2026)
New Revision: 10259

Modified:
   branches/4.0/MgDev/Common/Renderers/AGGRenderer.cpp
   branches/4.0/MgDev/Common/Renderers/agg_context.h
   branches/4.0/MgDev/Server/src/UnitTesting/TestRenderingService.cpp
Log:
Fix area clip settings not being honored.

Fixes #2572

Modified: branches/4.0/MgDev/Common/Renderers/AGGRenderer.cpp
===================================================================
--- branches/4.0/MgDev/Common/Renderers/AGGRenderer.cpp	2026-11-03 10:35:20 UTC (rev 10258)
+++ branches/4.0/MgDev/Common/Renderers/AGGRenderer.cpp	2026-11-03 11:44:14 UTC (rev 10259)
@@ -1475,6 +1475,53 @@
 
 
 //////////////////////////////////////////////////////////////////////////////
+// Draws the glyphs of a string using the supplied scanline renderer.
+template<class RendererType>
+static void DrawStringGlyphs(agg_context* cxt, unsigned int* p, double& x, double& y,
+                             agg::glyph_rendering glyph_type, RendererType& ren)
+{
+    if (glyph_type == agg::glyph_ren_agg_gray8)
+    {
+        while (*p)
+        {
+            const agg::glyph_cache* glyph = cxt->fman.glyph(*p++);
+            cxt->fman.add_kerning(&x, &y);
+            cxt->fman.init_embedded_adaptors(glyph, x, y);
+
+            agg::render_scanlines(cxt->fman.gray8_adaptor(), cxt->fman.gray8_scanline(), ren);
+
+            x += glyph->advance_x;
+            y += glyph->advance_y;
+        }
+    }
+    else
+    {
+        font_curve_type curves(cxt->fman.path_adaptor());
+        font_contour_type contour(curves);
+
+        cxt->ras.filling_rule(agg::fill_non_zero);
+
+        while (*p)
+        {
+            const agg::glyph_cache* glyph = cxt->fman.glyph(*p++);
+            cxt->fman.add_kerning(&x, &y);
+            cxt->fman.init_embedded_adaptors(glyph, x, y);
+
+            cxt->ras.reset();
+            cxt->ras.add_path(contour);
+
+            agg::render_scanlines(cxt->ras, cxt->sl, ren);
+
+            x += glyph->advance_x;
+            y += glyph->advance_y;
+        }
+
+        cxt->ras.filling_rule(agg::fill_even_odd);
+    }
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
 void AGGRenderer::DrawString(agg_context*     cxt,
                              const RS_String& s,
                              double           x,
@@ -1552,9 +1599,6 @@
         cxt->last_font_transform = mtx;
     }
 
-    mg_ren_aa_solid ren_aa_solid(cxt->ren);
-    ren_aa_solid.color(agg::argb8_packed(color.argb()));
-
     unsigned int* text;
 #ifdef _WIN32
     // TODO: check if we really need to convert UCS-2 to UCS-4 on Windows or
@@ -1566,47 +1610,20 @@
 #endif
 
     unsigned int* p = text;
-    if (glyph_type == agg::glyph_ren_agg_gray8)
+    if (cxt->bPolyClip)
     {
-        while (*p)
-        {
-            const agg::glyph_cache* glyph = cxt->fman.glyph(*p++);
-            cxt->fman.add_kerning(&x, &y);
-            cxt->fman.init_embedded_adaptors(glyph, x, y);
-
-            agg::render_scanlines(cxt->fman.gray8_adaptor(), cxt->fman.gray8_scanline(), ren_aa_solid);
-
-            x += glyph->advance_x;
-            y += glyph->advance_y;
-        }
+        mg_clip_mask_ren_aa_solid clip_ren_aa_solid(cxt->clip_rb);
+        clip_ren_aa_solid.color(agg::argb8_packed(color.argb()));
+        DrawStringGlyphs(cxt, p, x, y, glyph_type, clip_ren_aa_solid);
     }
     else
     {
-        font_curve_type curves(cxt->fman.path_adaptor());
-        font_contour_type contour(curves);
-
-        cxt->ras.filling_rule(agg::fill_non_zero);
-
-        while (*p)
-        {
-            const agg::glyph_cache* glyph = cxt->fman.glyph(*p++);
-            cxt->fman.add_kerning(&x, &y);
-            cxt->fman.init_embedded_adaptors(glyph, x, y);
-
-            cxt->ras.reset();
-            cxt->ras.add_path(contour);
-
-            agg::render_scanlines(cxt->ras, cxt->sl, ren_aa_solid);
-
-            x += glyph->advance_x;
-            y += glyph->advance_y;
-        }
-
-        cxt->ras.filling_rule(agg::fill_even_odd);
+        mg_ren_aa_solid ren_aa_solid(cxt->ren);
+        ren_aa_solid.color(agg::argb8_packed(color.argb()));
+        DrawStringGlyphs(cxt, p, x, y, glyph_type, ren_aa_solid);
     }
 }
 
-
 //////////////////////////////////////////////////////////////////////////////
 void AGGRenderer::MeasureString(const RS_String& s,
                                 double           height,
@@ -1818,6 +1835,7 @@
         // this is important because we will only clear that section of the alpha mask
         // buffer and the rest may be dirty
         c()->clip_rb.clip_box(iminx, iminy, imaxx, imaxy);
+        c()->clip_rb_pre.clip_box(iminx, iminy, imaxx, imaxy);
 
         // transform to screen coords and transfer to AGG storage
         unsigned int* pathids = (unsigned int*)alloca(polygon->geom_count() * sizeof(unsigned int));
@@ -2671,6 +2689,18 @@
 //////////////////////////////////////////////////////////////////////////////
 // Renders the source image to the destination context, using the specified
 // affine transformation matrix.
+template<class RendererType, class SpanAllocatorType, class SpanGeneratorType>
+static void RenderImageSpans(agg_context* cxt, RendererType& ren,
+                             SpanAllocatorType& sa, SpanGeneratorType& sg,
+                             bool antiAlias)
+{
+    if (antiAlias)
+        agg::render_scanlines_aa(cxt->ras, cxt->sl, ren, sa, sg);
+    else
+        agg::render_scanlines_bin(cxt->ras, cxt->sl, ren, sa, sg);
+}
+
+
 void AGGRenderer::RenderWithTransform(mg_rendering_buffer& src, agg_context* cxt,
                                       agg::trans_affine& img_mtx, RS_ImageFormat format, bool antiAlias)
 {
@@ -2690,10 +2720,10 @@
 
         agg::span_allocator<mg_pixfmt_type::color_type> sa;
 
-        if (antiAlias)
-            agg::render_scanlines_aa(cxt->ras, cxt->sl, cxt->ren_pre, sa, sg);
+        if (cxt->bPolyClip)
+            RenderImageSpans(cxt, cxt->clip_rb_pre, sa, sg, antiAlias);
         else
-            agg::render_scanlines_bin(cxt->ras, cxt->sl, cxt->ren_pre, sa, sg);
+            RenderImageSpans(cxt, cxt->ren_pre, sa, sg, antiAlias);
     }
     else if (format == RS_ImageFormat_ARGB)
     {
@@ -2712,10 +2742,10 @@
         agg::span_allocator<mg_pixfmt_type::color_type> sa;
 
         // we are using the alpha premultiplied renderer since the source image is premultiplied
-        if (antiAlias)
-            agg::render_scanlines_aa(cxt->ras, cxt->sl, cxt->ren_pre, sa, sg);
+        if (cxt->bPolyClip)
+            RenderImageSpans(cxt, cxt->clip_rb_pre, sa, sg, antiAlias);
         else
-            agg::render_scanlines_bin(cxt->ras, cxt->sl, cxt->ren_pre, sa, sg);
+            RenderImageSpans(cxt, cxt->ren_pre, sa, sg, antiAlias);
     }
     else if (format == RS_ImageFormat_NATIVE)
     {
@@ -2734,10 +2764,10 @@
 
         agg::span_allocator<mg_pixfmt_type::color_type> sa;
 
-        if (antiAlias)
-            agg::render_scanlines_aa(cxt->ras, cxt->sl, cxt->ren_pre, sa, sg);
+        if (cxt->bPolyClip)
+            RenderImageSpans(cxt, cxt->clip_rb_pre, sa, sg, antiAlias);
         else
-            agg::render_scanlines_bin(cxt->ras, cxt->sl, cxt->ren_pre, sa, sg);
+            RenderImageSpans(cxt, cxt->ren_pre, sa, sg, antiAlias);
     }
 }
 

Modified: branches/4.0/MgDev/Common/Renderers/agg_context.h
===================================================================
--- branches/4.0/MgDev/Common/Renderers/agg_context.h	2026-11-03 10:35:20 UTC (rev 10258)
+++ branches/4.0/MgDev/Common/Renderers/agg_context.h	2026-11-03 11:44:14 UTC (rev 10259)
@@ -103,6 +103,9 @@
 typedef agg::renderer_base<mg_pixfmt_clip_mask_type>                    mg_clip_mask_ren_base;
 typedef agg::renderer_scanline_aa_solid<mg_clip_mask_ren_base>          mg_clip_mask_ren_aa_solid;
 
+// premultiplied variant, used to clip images and other premultiplied spans
+typedef agg::pixfmt_amask_adaptor<mg_pixfmt_type_pre, mg_alpha_mask_type> mg_pixfmt_clip_mask_type_pre;
+typedef agg::renderer_base<mg_pixfmt_clip_mask_type_pre>                  mg_clip_mask_ren_base_pre;
 
 struct RS_Font;
 
@@ -154,6 +157,9 @@
         clip_mask = new mg_pixfmt_clip_mask_type(clip_pixf, mask_mask);
         clip_rb.attach(*clip_mask);
         clip_ren.attach(clip_rb);
+
+        clip_mask_pre = new mg_pixfmt_clip_mask_type_pre(pf_pre, mask_mask);
+        clip_rb_pre.attach(*clip_mask_pre);
     }
 
     ~agg_context()
@@ -161,6 +167,7 @@
         if (ownrows)
             delete [] m_rows;
 
+        delete clip_mask_pre;
         delete clip_mask;
         delete mask_pixf;
         delete [] mask_buf;
@@ -193,6 +200,10 @@
     mg_pixfmt_type              clip_pixf;
     mg_pixfmt_clip_mask_type*   clip_mask;
 
+    // premultiplied alpha-masked renderer, for image spans
+    mg_clip_mask_ren_base_pre   clip_rb_pre;
+    mg_pixfmt_clip_mask_type_pre* clip_mask_pre;
+
     // font engine state caching
     font_engine_type feng;
     font_manager_type fman;

Modified: branches/4.0/MgDev/Server/src/UnitTesting/TestRenderingService.cpp
===================================================================
--- branches/4.0/MgDev/Server/src/UnitTesting/TestRenderingService.cpp	2026-11-03 10:35:20 UTC (rev 10258)
+++ branches/4.0/MgDev/Server/src/UnitTesting/TestRenderingService.cpp	2026-11-03 11:44:14 UTC (rev 10259)
@@ -24,10 +24,13 @@
 //#include "AGGRenderer.h"
 #include "FoundationDefs.h"
 #include "SE_Renderer.h"
+#include "AGGImageIO.h"
 
 #include "TestServiceFactory.h"
 #include "catch.hpp"
 
+#include <memory>
+
 static MgMap* CreateTestMapWithWatermark()
 {
     Ptr<MgServerSiteService> svcSite = TestServiceFactory::CreateSiteService();
@@ -2057,4 +2060,293 @@
     {
         throw;
     }
+}
+
+
+///----------------------------------------------------------------------------
+/// Test Case Description:
+///
+/// Renders a polygon whose area style is a custom symbol that tiles an image and
+/// a text primitive across the polygon's bounding box. The symbol's clipping
+/// control is Clip, so nothing the symbol draws may appear outside the polygon.
+/// The polygon covers the left half of the rendered image, which leaves the
+/// right half free of it; the test asserts the right half contains neither the
+/// image color nor the text color.
+///
+/// The ticket this covers is
+/// https://trac.osgeo.org/mapguide/ticket/2572 - an area usage symbol with an
+/// image used to draw an unclipped rectangle over the map.
+///----------------------------------------------------------------------------
+TEST_CASE("RenderAreaSymbolClipping", "[RenderingService]")
+{
+    try
+    {
+        // Set the user information for the current thread to be administrator.
+        Ptr<MgUserInformation> adminUserInfo = new MgUserInformation(MgUser::Administrator, L"");
+        MgUserInformation::SetCurrentUserInfo(adminUserInfo);
+
+        MgServiceManager* serviceManager = MgServiceManager::GetInstance();
+        if (serviceManager == NULL)
+            throw new MgException(MgExceptionCodes::MgNullReferenceException, L"TestRenderingService.RenderAreaSymbolClipping", __LINE__, __WFILE__, NULL, L"", NULL);
+
+        Ptr<MgFeatureService> svcFeature = dynamic_cast<MgFeatureService*>(serviceManager->RequestService(MgServiceType::FeatureService));
+        if (svcFeature == NULL)
+            throw new MgException(MgExceptionCodes::MgServiceNotAvailableException, L"TestRenderingService.RenderAreaSymbolClipping", __LINE__, __WFILE__, NULL, L"", NULL);
+
+        Ptr<MgResourceService> svcResource = TestServiceFactory::CreateResourceService();
+
+        Ptr<MgResourceIdentifier> fsId = new MgResourceIdentifier(L"Library://UnitTests/Data/AreaSymbolClippingTest.FeatureSource");
+        Ptr<MgResourceIdentifier> ldfId = new MgResourceIdentifier(L"Library://UnitTests/Layers/AreaSymbolClippingTest.LayerDefinition");
+        Ptr<MgResourceIdentifier> mdfId = new MgResourceIdentifier(L"Library://UnitTests/Maps/AreaSymbolClippingTest.MapDefinition");
+
+        // Drop anything an earlier run left behind.
+        if (svcResource->ResourceExists(mdfId)) svcResource->DeleteResource(mdfId);
+        if (svcResource->ResourceExists(ldfId)) svcResource->DeleteResource(ldfId);
+        if (svcResource->ResourceExists(fsId)) svcResource->DeleteResource(fsId);
+
+        Ptr<MgCoordinateSystemFactory> csFactory = new MgCoordinateSystemFactory();
+        STRING csWkt = csFactory->ConvertCoordinateSystemCodeToWkt(L"LL84");
+        std::string csWktMb = MgUtil::WideCharToMultiByte(csWkt);
+
+        STRING scName = L"Default";
+
+        Ptr<MgFeatureSchema> schema = new MgFeatureSchema(L"Default", L"Area symbol clipping test schema");
+        Ptr<MgClassDefinition> klass = new MgClassDefinition();
+        klass->SetName(L"TestClass");
+
+        Ptr<MgPropertyDefinitionCollection> clsProps = klass->GetProperties();
+        Ptr<MgPropertyDefinitionCollection> clsIdProps = klass->GetIdentityProperties();
+
+        Ptr<MgDataPropertyDefinition> id = new MgDataPropertyDefinition(L"ID");
+        id->SetDataType(MgPropertyType::Int32);
+        id->SetAutoGeneration(true);
+        clsProps->Add(id);
+        clsIdProps->Add(id);
+
+        Ptr<MgGeometricPropertyDefinition> geom = new MgGeometricPropertyDefinition(L"Geom");
+        geom->SetGeometryTypes(MgFeatureGeometricType::Surface);
+        geom->SetSpatialContextAssociation(scName);
+        clsProps->Add(geom);
+
+        klass->SetDefaultGeometryPropertyName(L"Geom");
+
+        Ptr<MgClassDefinitionCollection> classes = schema->GetClasses();
+        classes->Add(klass);
+
+        Ptr<MgFileFeatureSourceParams> fsParams = new MgFileFeatureSourceParams(L"OSGeo.SDF", scName, csWkt, schema);
+        svcFeature->CreateFeatureSource(fsId, fsParams);
+
+        // The left half of the rendered image. Everything to the right of x = 0
+        // is outside the polygon.
+        MgGeometryFactory geomFactory;
+
+        Ptr<MgCoordinateCollection> ringCoords = new MgCoordinateCollection();
+        Ptr<MgCoordinate> pt = geomFactory.CreateCoordinateXY(-1.0, -1.0);
+        ringCoords->Add(pt);
+        pt = geomFactory.CreateCoordinateXY(0.0, -1.0);
+        ringCoords->Add(pt);
+        pt = geomFactory.CreateCoordinateXY(0.0, 1.0);
+        ringCoords->Add(pt);
+        pt = geomFactory.CreateCoordinateXY(-1.0, 1.0);
+        ringCoords->Add(pt);
+        pt = geomFactory.CreateCoordinateXY(-1.0, -1.0);
+        ringCoords->Add(pt);
+
+        Ptr<MgLinearRing> ring = geomFactory.CreateLinearRing(ringCoords);
+        Ptr<MgGeometry> polygon = geomFactory.CreatePolygon(ring, nullptr);
+
+        Ptr<MgAgfReaderWriter> agfRw = new MgAgfReaderWriter();
+        Ptr<MgByteReader> agf = agfRw->Write(polygon);
+
+        Ptr<MgPropertyCollection> propVals = new MgPropertyCollection();
+        Ptr<MgGeometryProperty> geomVal = new MgGeometryProperty(L"Geom", agf);
+        propVals->Add(geomVal);
+
+        Ptr<MgFeatureReader> fr = svcFeature->InsertFeatures(fsId, L"Default:TestClass", propVals);
+        fr->Close();
+
+        // An 8x8 opaque red PNG, embedded in the symbol definition. It is drawn
+        // 100 mm wide, which is large enough that an unclipped copy covers a
+        // substantial part of the rendered image.
+        const char* pngBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAUSURBVChTY/jPwPAfHx4RChj+AwDC13+BykaqugAAAABJRU5ErkJggg==";
+
+        std::string ldfXml;
+        ldfXml += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+        ldfXml += "<LayerDefinition xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" version=\"2.4.0\" xsi:noNamespaceSchemaLocation=\"LayerDefinition-2.4.0.xsd\">";
+        ldfXml += "<VectorLayerDefinition>";
+        ldfXml += "<ResourceId>Library://UnitTests/Data/AreaSymbolClippingTest.FeatureSource</ResourceId>";
+        ldfXml += "<Watermarks/>";
+        ldfXml += "<FeatureName>Default:TestClass</FeatureName>";
+        ldfXml += "<FeatureNameType>FeatureClass</FeatureNameType>";
+        ldfXml += "<Geometry>Geom</Geometry>";
+        ldfXml += "<VectorScaleRange>";
+        ldfXml += "<CompositeTypeStyle>";
+        ldfXml += "<CompositeRule>";
+        ldfXml += "<LegendLabel/>";
+        ldfXml += "<CompositeSymbolization>";
+        ldfXml += "<SymbolInstance>";
+        ldfXml += "<SimpleSymbolDefinition>";
+        ldfXml += "<Name>Area Clipping Symbol</Name>";
+        ldfXml += "<Graphics>";
+        ldfXml += "<Image>";
+        ldfXml += "<Content>";
+        ldfXml += pngBase64;
+        ldfXml += "</Content>";
+        ldfXml += "<SizeX>100</SizeX>";
+        ldfXml += "<SizeY>100</SizeY>";
+        ldfXml += "<SizeScalable>false</SizeScalable>";
+        ldfXml += "</Image>";
+        ldfXml += "<Text>";
+        ldfXml += "<Content>'CLIP'</Content>";
+        ldfXml += "<FontName>'Arial'</FontName>";
+        ldfXml += "<Height>30</Height>";
+        ldfXml += "<HeightScalable>false</HeightScalable>";
+        ldfXml += "<TextColor>ff00ff00</TextColor>";
+        ldfXml += "</Text>";
+        ldfXml += "</Graphics>";
+        ldfXml += "<AreaUsage>";
+        ldfXml += "<AngleControl>'FromAngle'</AngleControl>";
+        ldfXml += "<OriginControl>'Global'</OriginControl>";
+        ldfXml += "<ClippingControl>'Clip'</ClippingControl>";
+        ldfXml += "<Angle>0</Angle>";
+        ldfXml += "<OriginX>0</OriginX>";
+        ldfXml += "<OriginY>0</OriginY>";
+        ldfXml += "<RepeatX>100</RepeatX>";
+        ldfXml += "<RepeatY>100</RepeatY>";
+        ldfXml += "</AreaUsage>";
+        ldfXml += "<ParameterDefinition/>";
+        ldfXml += "</SimpleSymbolDefinition>";
+        ldfXml += "<ParameterOverrides/>";
+        ldfXml += "<SizeContext>DeviceUnits</SizeContext>";
+        ldfXml += "<GeometryContext>Polygon</GeometryContext>";
+        ldfXml += "</SymbolInstance>";
+        ldfXml += "</CompositeSymbolization>";
+        ldfXml += "</CompositeRule>";
+        ldfXml += "</CompositeTypeStyle>";
+        ldfXml += "</VectorScaleRange>";
+        ldfXml += "</VectorLayerDefinition>";
+        ldfXml += "</LayerDefinition>";
+
+        Ptr<MgByteSource> ldfBs = new MgByteSource((BYTE_ARRAY_IN)ldfXml.c_str(), (INT32)ldfXml.length());
+        Ptr<MgByteReader> ldfContent = ldfBs->GetReader();
+        svcResource->SetResource(ldfId, ldfContent, nullptr);
+
+        std::string mdfXml;
+        mdfXml += "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+        mdfXml += "<MapDefinition xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" version=\"3.0.0\" xsi:noNamespaceSchemaLocation=\"MapDefinition-3.0.0.xsd\">";
+        mdfXml += "<Name>AreaSymbolClippingTest</Name>";
+        mdfXml += "<CoordinateSystem>";
+        mdfXml += csWktMb;
+        mdfXml += "</CoordinateSystem>";
+        mdfXml += "<Extents><MinX>-1</MinX><MaxX>1</MaxX><MinY>-1</MinY><MaxY>1</MaxY></Extents>";
+        mdfXml += "<BackgroundColor>ffffffff</BackgroundColor>";
+        mdfXml += "<MapLayer>";
+        mdfXml += "<Name>AreaSymbolClipping</Name>";
+        mdfXml += "<ResourceId>Library://UnitTests/Layers/AreaSymbolClippingTest.LayerDefinition</ResourceId>";
+        mdfXml += "<Selectable>true</Selectable>";
+        mdfXml += "<ShowInLegend>true</ShowInLegend>";
+        mdfXml += "<LegendLabel>AreaSymbolClipping</LegendLabel>";
+        mdfXml += "<ExpandInLegend>true</ExpandInLegend>";
+        mdfXml += "<Visible>true</Visible>";
+        mdfXml += "<Group />";
+        mdfXml += "</MapLayer>";
+        mdfXml += "<Watermarks />";
+        mdfXml += "</MapDefinition>";
+
+        Ptr<MgByteSource> mdfBs = new MgByteSource((BYTE_ARRAY_IN)mdfXml.c_str(), (INT32)mdfXml.length());
+        Ptr<MgByteReader> mdfContent = mdfBs->GetReader();
+        svcResource->SetResource(mdfId, mdfContent, nullptr);
+
+        Ptr<MgServerSiteService> svcSite = TestServiceFactory::CreateSiteService();
+        Ptr<MgSiteConnection> siteConnection = TestServiceFactory::CreateSiteConnection(svcSite);
+
+        Ptr<MgMap> map = new MgMap(siteConnection);
+        map->Create(mdfId, mdfId->GetName());
+        map->SetDisplayDpi(96);
+        map->SetDisplayWidth(512);
+        map->SetDisplayHeight(512);
+
+        // Render exactly the extents the polygon is defined against, so it
+        // covers the left half of the image.
+        const INT32 imageSize = 512;
+        Ptr<MgEnvelope> extents = new MgEnvelope(-1.0, -1.0, 1.0, 1.0);
+        Ptr<MgColor> bgColor = new MgColor(255, 255, 255, 255);
+
+        Ptr<MgRenderingService> svcRendering = TestServiceFactory::CreateRenderingService();
+        Ptr<MgByteReader> png = svcRendering->RenderMap(map, nullptr, extents, imageSize, imageSize, bgColor, MgImageFormats::Png);
+        png->ToFile(L"../UnitTestFiles/Results/RenderAreaSymbolClipping.png");
+
+        Ptr<MgByteSink> sink = new MgByteSink(png);
+        Ptr<MgByte> bytes = sink->ToBuffer();
+        REQUIRE(bytes->GetLength() > 0);
+
+        int width = 0;
+        int height = 0;
+        unsigned int* decoded = AGGImageIO::DecodePNG(bytes->Bytes(), (size_t)bytes->GetLength(), width, height);
+        REQUIRE(decoded != NULL);
+        // DecodePNG hands back a buffer that has to be released with delete[].
+        std::unique_ptr<unsigned int[]> pixels(decoded);
+        REQUIRE(width == imageSize);
+        REQUIRE(height == imageSize);
+
+        // Count the image and text colors inside and to the right of the polygon.
+        // DecodePNG returns the image bottom up.
+        int redInside = 0, redOutside = 0;
+        int greenInside = 0, greenOutside = 0;
+        for (int y = 0; y < height; ++y)
+        {
+            for (int x = 0; x < width; ++x)
+            {
+                unsigned int argb = decoded[(height - 1 - y) * width + x];
+                int r = (int)((argb >> 16) & 0xFF);
+                int g = (int)((argb >> 8) & 0xFF);
+                int b = (int)(argb & 0xFF);
+
+                bool isRed   = (r > 200 && g < 60 && b < 60);
+                bool isGreen = (g > 200 && r < 60 && b < 60);
+
+                // The polygon's right edge is at x = width / 2. Look a quarter
+                // of the image further right to stay clear of the boundary's
+                // antialiasing.
+                bool outside = (x > width * 3 / 4);
+
+                if (isRed)
+                    outside ? ++redOutside : ++redInside;
+                if (isGreen)
+                    outside ? ++greenOutside : ++greenInside;
+            }
+        }
+
+        INFO("red pixels inside=" << redInside << " outside=" << redOutside
+             << ", green pixels inside=" << greenInside << " outside=" << greenOutside);
+
+        // The symbol must actually be drawing, otherwise the clipping checks
+        // below would pass for the wrong reason.
+        CHECK(redInside > 1000);
+        CHECK(greenInside > 0);
+
+        // Nothing the area symbol draws may leak outside the polygon.
+        CHECK(redOutside == 0);
+        CHECK(greenOutside == 0);
+
+        // Leave the repository as we found it.
+        svcResource->DeleteResource(mdfId);
+        svcResource->DeleteResource(ldfId);
+        svcResource->DeleteResource(fsId);
+    }
+    catch (MgException* e)
+    {
+        STRING message = e->GetDetails(TestServiceFactory::TEST_LOCALE);
+        SAFE_RELEASE(e);
+        FAIL(MG_WCHAR_TO_CHAR(message.c_str()));
+    }
+    catch (FdoException* e)
+    {
+        FDO_SAFE_RELEASE(e);
+        FAIL("FdoException occurred");
+    }
+    catch (...)
+    {
+        throw;
+    }
 }
\ No newline at end of file



More information about the mapguide-commits mailing list