15
$\begingroup$

What is the best way to clip a graphic to a certain region?

Here's a very simple implementation to show what I mean:

Show[
 CountryData["World", "Shape"],
 Graphics[{White, 
   FilledCurve[{{Line[{{-200, -100}, {200, -100}, {200, 100}, {-200, 
         100}}]}, {Line[{{-10.181224213835492, 65.15571149065886}, 
              {-4.290845998237188, 
         79.14535975270482}, {14.116585925507536, 
                63.683116936759234}, {16.32547775635689, 
         43.80309045911497}, 
              {-0.6093596134882375, 
         35.70382041266731}, {-16.071602429433796, 
                40.85790135131583}, {-13.126413321634658, 
         57.79273872116096}}]}}]}],
 PlotRange -> {{-18, 18}, {35, 82}}, Background -> LightBlue
 ]

Mathematica graphics

My aim is to be able to clip arbitrary vector graphics to an arbitrary vector region. The region can be anything, and can have curved boundaries (but approximation with line segments is okay).

The problem with the approach I showed here (i.e. simply overlaying a rectangle with a hole in it) is that it is cumbersome and it does not allow arbitrary layering of objects: imagine that I need this clipped graphic to be in the foreground and cover parts of the background objects.

Note: It is possible to clip raster graphics using Texture, as shown here. This question is about clipping arbitrary vector graphics. If it is not possible to do this in the current version of Mathematica, I accept that.

$\endgroup$
  • $\begingroup$ Related: mathematica.stackexchange.com/q/1332/121 $\endgroup$ – Mr.Wizard Feb 19 '12 at 16:43
  • $\begingroup$ @Mr.Wizard The difference is that here I'd like to clip vector graphics, if this is possible at all. Rasterization worsens the quality and increases the file size. $\endgroup$ – Szabolcs Feb 19 '12 at 16:45
  • 2
    $\begingroup$ Absolutely. This question is what I initially thought that one was, and IMHO should have been. Unfortunately I still don't have an answer. I think to do this properly WRI needs to add a ClippingPath function. Here's hoping there is already some undocumented function that does this. $\endgroup$ – Mr.Wizard Feb 19 '12 at 16:48
  • 1
    $\begingroup$ @Szabolcs I don't think so. I used << Version5``Graphics`` and as far as I can tell, the PostScript engine can only deal with rectangular clipping regions. $\endgroup$ – Lev Bishop Feb 20 '12 at 4:24
  • 2
    $\begingroup$ For current versions of Mathematica this is not possible AFAIK. You can do it the hard way by intersecting each edge of the to-be-clipped polygons with the mask polygon edges and deleting resulting outlier elements. This is a fair bit of work though. I have some hopes (and the urgent desire) of this being adressed in future versions at least for 2D cases. $\endgroup$ – Yves Klett Feb 20 '12 at 8:50
11
$\begingroup$

I'm pretty sure this can't be done. As evidence of this I put forward Import[] of .EPS and .PDF with such clipping in it: mathematica imports the shapes unclipped. If there would be some undocumented function to do this clipping, I would assume that Import[] would make use of it.

$\endgroup$
2
$\begingroup$

You can use region functionality (RegionIntersection) to clip primitives, although it will be a bit slow. There are two issues, though.

  1. The output if not always a graphics primitive. Sometimes the output of RegionIntersection is a BooleanRegion object, and these objects don't render inside of Graphics. This can be fixed by using BoundaryDiscretizeRegion to convert to a BoundaryMeshRegion that does render inside of Graphics (in M12). If you are using earlier versions of Mathematica, you can use my answer to Make MeshRegion/BoundaryMeshRegion work as a graphics primitive in M11 to enable them to be rendered in earlier versions of Mathematica as well.

  2. 2 dimensional multi-primitives (e.g., Polygon) lose the edges where the polygons overlap. This can be fixed by converting multi-primitives to lists of single-primitives.

Here is some code that does this:

ClippedPrimitives[prims_, clip_] := prims /. r_?RegionQ :> clipPrimitives[r, clip]

clipPrimitives[p:Polygon[{__?MatrixQ}], clip_] := ClippedPrimitives[Thread[p], clip]

clipPrimitives[prim_, clip_] := Which[
    RegionDisjoint[clip, prim],
    Nothing,

    RegionWithin[clip, prim],
    prim,

    True,
    Replace[
        RegionIntersection[prim, clip],
        b_BooleanRegion :> BoundaryDiscretizeRegion[b]
    ]
]

For your example (which will be slow because the world multi-polygon consists of 5809 polygons):

Graphics[{
    Green, Rectangle[{-18, 35}, {18, 82}],
    Red, Line[{
        {-10.181224213835492,65.15571149065886},
        {-4.290845998237188,79.14535975270482},
        {14.116585925507536,63.683116936759234},
        {16.32547775635689,43.80309045911497},
        {-0.6093596134882375,35.70382041266731},
        {-16.071602429433796,40.85790135131583},
        {-13.126413321634658,57.79273872116096},
        {-10.181224213835492,65.15571149065886}
    }],
    ClippedPrimitives[
        First @ CountryData["World", "Shape"],
        Polygon[{
            {-10.181224213835492,65.15571149065886},
            {-4.290845998237188,79.14535975270482},
            {14.116585925507536,63.683116936759234},
            {16.32547775635689,43.80309045911497},
            {-0.6093596134882375,35.70382041266731},
            {-16.071602429433796,40.85790135131583},
            {-13.126413321634658,57.79273872116096}
        }]
    ]
}]

enter image description here

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.