This was our initial foray into the world of on-chain layered SVG. We just wanted to know if it would be possible, and if so, how we would go about it. The contracts were simple, and the unit tests simpler. In fact for the unit tests, we only combined a star and a circle into an SVG 'sandwich'.
Beyond the POC expressed in the contracts and unit tests, we expanded our thinking about layering below. We had no artists on-board yet, so the art you see below is the best that I could come up with in 5 minutes to flesh out how an actual layered Avastar would actually be composed. We keep this POC folder in the project for posterity. What we actually developed is a bit different, but the basic concept is the same.
-=Cliff>
The native size of an Avastar is 500x500 (1000x1000 now, but examples below are built to a 500x500 size). This is
an arbitrary size obviously, since SVG scales, but we have to have a baseline so all the layers line up.
Each trait, such as hair, facial expression, hat, etc., will be chosen
at random and placed into an SVG 'sandwich' by the smart contract. Therefore
each layer should be considered a fragment, and not contain <svg>
open
and close tags.
Human trait layers will be composed in the following order (from back to front):
- Background
- Skin Color
- Hair Color
- Eye Color
- Facial Expression
- Facial Markings
- Facial Hair
- Hair
- Hat
- Glasses
- Earrings
- Mouthpiece
Certain traits (Hair Color, Eye Color, and Skin Color) will require the color gradients be defined separately from the shapes that use them in order to be layered properly. This will keep us from having to have separate facial expression definitions for every combination of expression, eye color, and skin color, or for every combination of hairstyle and hair color.
- In all cases,
id
must beeye_color
- Should not be wrapped in
<svg>
element, this is a fragment to be layered into the final Avastar
<!-- BLUE IRIS GRADIENT -->
<radialGradient id="eye_color">
<stop offset="0.025" stop-color="#000000"/>
<stop offset="1" stop-color="#56aaff"/>
</radialGradient>
- In all cases,
id
must beskin_color
- Should not be wrapped in
<svg>
element, this is a fragment to be layered into the final avastar
<!-- TAN SKIN GRADIENT -->
<linearGradient id="skin_color" x2="1" y2="1">
<stop offset="0" stop-color="#FFEEEE"/>
<stop offset="0.99927" stop-color="#bf5f00" stop-opacity="0.99609"/>
<stop offset="1" stop-color="#001111"/>
</linearGradient>
- In all cases,
id
must behair_color
- Should not be wrapped in
<svg>
element, this is a fragment to be layered into the final avastar
<!-- RED HAIR GRADIENT -->
<radialGradient id="hair_color">
<stop offset="0" stop-color="#none"/>
<stop offset="1" stop-color="#7f0000" stop-opacity="0.99609"/>
</radialGradient>
- In all cases,
id
must behair
and hair-colored paths should be filled withurl(#hair_color)
- Should not be wrapped in
<svg>
element, this is a fragment to be layered into the final avastar
<!-- HAIR LAYER -->
<g id="hair">
<path d="m121.5,134.5l62.5,-79.5l98,-12l97,26l32,56l39,58l22,64l-27,-47l24,113l-35,-58l1,114l-9,-51l-13,-85l-18,-48l-57,-62l-72,-22l-70,13c-24.83333,6.83333 -49.66667,13.66667 -74.5,20.5z"
fill="url(#hair_color)"/>
</g>
- In all cases,
id
must befacial_expression
- Individual subcomponents do not need an
id
- Uses eye color and skin color definitions as well as local fill colors for eye whites and mouth
- Should not be wrapped in
<svg>
element, this is a fragment to be layered into the final avastar
<!-- SIDEWAYS FACIAL EXPRESSION LAYER -->
<g id="facial_expression">
<!-- SKIN -->
<ellipse fill="url(#skin_color)"
cx="250.99999" cy="254.00001"
rx="166.5" ry="190.5"/>
<!-- EYE WHITES -->
<ellipse fill="#ffffff"
cx="175.5" cy="212"
rx="55" ry="19.5"/>
<ellipse fill="#ffffff"
cx="328.5" cy="211.5"
rx="55" ry="19.5"/>
<!-- EYE IRISES -->
<circle fill="url(#eye_color)"
cx="146.5" cy="213.5"
r="12.72792"/>
<circle fill="url(#eye_color)"
cx="299.5" cy="213"r="12.72792"
r="12.72792"/>
<!-- MOUTH SMIRK -->
<path d="m164.5,349.5c-30,-12 102,-2 113.5,-10.5c11.5,-8.5 35.5,-17.5 47,-23c11.5,-5.5 -14.5,42.5 -33,41c-18.5,-1.5 -97.5,4.5 -127.5,-7.5z"
fill="#ffffff"/>
</g>
<svg width="500" height="500"
xmlns="http://www.w3.org/2000/svg">
<!-- EYE COLOR GRADIENT -->
<radialGradient id="eye_color">
<stop offset="0.025" stop-color="#000000"/>
<stop offset="1" stop-color="#56aaff"/>
</radialGradient>
<!-- SKIN COLOR GRADIENT -->
<linearGradient id="skin_color" x2="1" y2="1">
<stop offset="0" stop-color="#FFEEEE"/>
<stop offset="0.99927" stop-color="#bf5f00" stop-opacity="0.99609"/>
<stop offset="1" stop-color="#001111"/>
</linearGradient>
<!-- HAIR COLOR GRADIENT -->
<radialGradient id="hair_color">
<stop offset="0" stop-color="#none"/>
<stop offset="1" stop-color="#7f0000" stop-opacity="0.99609"/>
</radialGradient>
<!-- SIDEWAYS FACIAL EXPRESSION -->
<g id="facial_expression">
<!-- SKIN -->
<ellipse fill="url(#skin_color)"
cx="250.99999" cy="254.00001"
rx="166.5" ry="190.5"/>
<!-- EYE WHITES -->
<ellipse fill="#ffffff"
cx="175.5" cy="212"
rx="55" ry="19.5"/>
<ellipse fill="#ffffff"
cx="328.5" cy="211.5"
rx="55" ry="19.5"/>
<!-- EYE IRISES -->
<circle fill="url(#eye_color)"
cx="146.5" cy="213.5"
r="12.72792"/>
<circle fill="url(#eye_color)"
cx="299.5" cy="213"r="12.72792"
r="12.72792"/>
<!-- MOUTH SMIRK -->
<path d="m164.5,349.5c-30,-12 102,-2 113.5,-10.5c11.5,-8.5 35.5,-17.5 47,-23c11.5,-5.5 -14.5,42.5 -33,41c-18.5,-1.5 -97.5,4.5 -127.5,-7.5z"
fill="#ffffff"/>
</g>
<!-- HAIR -->
<g id="hair">
<path d="m121.5,134.5l62.5,-79.5l98,-12l97,26l32,56l39,58l22,64l-27,-47l24,113l-35,-58l1,114l-9,-51l-13,-85l-18,-48l-57,-62l-72,-22l-70,13c-24.83333,6.83333 -49.66667,13.66667 -74.5,20.5z"
fill="url(#hair_color)"/>
</g>
</svg>