Goal
I am trying to replicate this effect algorithmically:
This is for a project I am working on where I need to make stylised visualisations of different kinds of pizza. The image shows a Margherita (tomato base, mozzarella and basil on top).
The rules I need to adhere to:
- each ‘layer’ consists of uniformly positioned objects with a stroke and filled with a pattern (that shares coordinates)
- the layers need to cover lower layers
Means
To achieve the uniform distribution, I use Poisson-Disc sampling.
I chose SVG because I need the result to be visible in the browser and generate this server-side.
For efficiency and simplicity – as the viewing size will be smaller – I decided to reference one object with <use>
elements and vary it only with rotations, as opposed to the sample.
Attempts
Every approach I tried reached a dead end:
Creating a
<clipPath>
filled with<use>
elements for clipping the background.This does not allow me to add a stroke around the clipped area, which I need. A workaround would be to use a feMorphology filter, but that seems like it’s going to be needlessly costly on the client. A second workaround seemed to be:
Grouping the elements and using that group twice: once in a
<clipPath>
for clipping the pattern background, once directly on the canvas with an added stroke.This does not work as
<g>
elements are unsupported in web browsers due to completely arbitrary reasons (it does work in Inkscape, however, which I used for the proof-of-concept). A workaround would be to use two copies of all the<use>
elements, but that would essentially double the file size.Grouping the elements and applying a fill with SVG patterns.
This does not work as since we create the distribution using
<use>
elements, the pattern looks identical in every instance. Moreover, I cannot rotate the objects, as the pattern would get rotated too. A workaround would be not to use<use>
, but that would create the same problem as in point 2.
CodePudding user response:
Those approaches won't work because patterns are affected by any transforms applied to the same shape.
In the solution below, we create a whole layer of an ingredient (id="basil-layer"
). Then use that layer to first draw the ingredient outlines (strokes). Then afterwards we use a mask, created from that same layer, to draw hatching on top of the outlines.
You'll need to duplicate this process for each of the ingredients.
More documentation of what's happening inside the code.
<svg width="600" height="400">
<defs>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" viewBox="0 0 4 4" width="16" height="16">
<rect fill="black" width="4" height="4"/>
<path d="M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2"
style="stroke:green; stroke-width:0.5" />
</pattern>
<!-- Definition for a leaf of basil -->
<ellipse id="basil" cx="0" cy="0" rx="60" ry="30"/>
<!-- A layer of N pieces of basil -->
<g id="basil-layer">
<use xlink:href="#basil" transform="translate(300,200)"/>
<use xlink:href="#basil" transform="translate(400,150) rotate(45)"/>
<use xlink:href="#basil" transform="translate(450,200) rotate(110)"/>
</g>
<!-- A mask that consists of all the pieces of basil -->
<!-- The fill is white to keep the *insides* of the basil shape.
And we stroke with black so that this mask doesn't hide any of the
green stroke outline of the leaf, when use this mask to lay down
the hatch pattern on top of the drawn basil leaves. -->
<mask id="basil-layer-mask">
<use xlink:href="#basil-layer" fill="white" stroke="black" stroke-width="2"/>
</mask>
</defs>
<!-- Fill SVG with a black background -->
<rect width="100%" height="100%" fill="black"/>
<!-- Draw all the basil pieces with a black fill and a green outline -->
<use xlink:href="#basil-layer" fill="black" stroke="green" stroke-width="2"/>
<!-- Finally draw the basil layer hatching.
This is a whole-SVG sized rectangle of hatching masked by the basil layer mask -->
<rect width="100%" height="100%" fill="url(#diagonalHatch)" mask="url(#basil-layer-mask)"/>
</svg>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>