Search code examples
svgmatrixrotationtransformscale

Reversing the flip and rotation of an image fill in a path that is flipped and rotated


I am needing some help understanding how to "unflip/unrotate" and image fill in SVG for a path. When I fill a path with an image and then rotate and fill the path with an image, the image also flips and rotates. But I'd like to keep the image upright and non-flipped, regardless of the rotation and flipping. The size of the picture is the bounding box of the rotated shape.

So, for example, say I have this path and this picture:

path image

If the path is only rotated (in this case, 315 degrees), it's easy to unrotate the image by just reversing the angle in the pattern that is used for a fill (i.e. 45 degrees).

    <svg name="rotate only" x="0" y="0" width="100.08" height="200" overflow="visible" fill="url(#fillImages0sp15)" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25">
    <defs>
      <image id="bgImage" preserveAspectRatio="none" width="159.113" height="159.113" xlink:href="THE IMAGE URL"></image>
      <pattern id="fillImages0sp15" x="-38.362" y="11.598" width="159.113" height="159.113" patternTransform="rotate(45,50.04,100)" patternUnits="userSpaceOnUse">
        <use xlink:href="#bgImages0sp15"></use>
      </pattern>
    </defs>
    <path d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z " transform="rotate(315,50.04,100)"></path>
  </svg>

But if there any kind of flip on the path (horizontal, vertical, or both), it doesn't work by just reversing the transformation on the pattern used for the image fill. For example, if the image is rotated 315 degrees and flipped vertical, the path has transform="rotate(45,50.04,100) translate(0,200), scale(1,-1)" for flipping vertically. That works. But the image fill needs to get reset back to be upright and not flipped. So the patternTransform should just be the same transformation. But this isn't working. This is the result I get.

 <svg name="flipV" x="0" y="0" width="100.08" height="200" overflow="visible" fill="url(#fillImages0sp14)" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25">
        <defs>
          <image id="bgImages" preserveAspectRatio="none" width="159.113" height="159.113" xlink:href="THE IMAGE URL"></image>
          <pattern id="fillImages0sp14" x="-20.671" y="-370.711" width="159.113" height="159.113" patternTransform="rotate(45,50.04,100) translate(0,200) scale(1,-1)" patternUnits="userSpaceOnUse">
            <use xlink:href="#bgImages"></use>
          </pattern>
        </defs>
        <path d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z " transform="rotate(45,50.04,100) translate(0,200) scale(1,-1)"></path>
      </svg>

Notice the path has transform="rotate(45,50.04,100) translate(0,200) scale(1,-1) and the fill pattern with the image has patternTransform="rotate(45,50.04,100) translate(0,200) scale(1,-1). This produces the wrong results.

In fact, here's all of it. This is what I'm hoping to achieve: FULL VIEW

Does anyone know how to set the patternTransform so that it can "unflip/unrotate" the filled image? Is it that the translate in the patternTransform needs to be calculated differently?


Solution

  • Rather than filling the arrow, <svg ... fill="url(#fillImages0sp14)", transforming it and then trying to somehow separate it from its fill, untransform that, and then refill it, I'd just display the image, but masked by the transformed arrow.

    I don't understand why the orange border still shows up. I've made the black rectangle overly large (which helped), and I've changed the overflow="...", but neither made it disappear.

    Edit: Your global stroke attributes were messing up the mask. Moving them to the displayed arrow (the only thing using that stroke) fixed the orange border issue.

    P.S. xlink is deprecated. Just use href.

    P.P.S. I had to add a final translate to center the transformed arrow over the image. It's easier and more accurate to move the center of the arrow to the center of the image first, and then do your transformations about that center.

    <svg name="transformed" x="0" y="0" width="159.113" height="159.113" overflow="visible" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <path id="Arrow" d="M0,149.96 25.02,149.96 25.02,0 75.06,0 75.06,149.96 100.08,149.96 50.04,200 Z" />
        <use id="TransformedArrow" href="#Arrow" transform="translate(38.3625,-29.2893) rotate(45,50.04,100) translate(0,200) scale(1,-1)" />
        <mask id="ArrowMask">
          <!-- Everything under black will be invisible -->
          <rect x="0" y="0" width="100%" height="100%" fill="black" />
          <!-- Everything under white will be visible -->
          <use href="#TransformedArrow" fill="white" />
        </mask>
      </defs>
      <image width="159.113" height="159.113" mask="url(#ArrowMask)" href="https://i.sstatic.net/yDcGi.png" />
      <use href="#TransformedArrow" fill="none" stroke="#4472C4" stroke-miterlimit="8" stroke-width="2.25" />
    </svg>