Scale and mirror SVG object

SvgMatrixScaleFlip

Svg Problem Overview


How do I most easily first scale an object, say 2 * times it's current size and then flip it vertically and horizontally, or both?

As of now, I can either set "scale(2,2)" for it to become 2 times as big as it's width and height but I can't flip it at the same with scale(-1, 1) for vertical flip.

I'm creating SVG objects programmatically, as a format to export to.

Svg Solutions


Solution 1 - Svg

To apply both scale and flip, just list both in your transform:

transform="scale(2,2) scale(-1,1)"

Or simply combine the values:

transform="scale(-2,2)"

Of course, the issue you have with negative scales is that the objects get flipped across the origin (top left) of the SVG, so they can go off the edge of the document. You need to correct this by adding a translate as well.

So, for example, imagine we had a document that is 100×100.

<svg width="100" height="100">
    <polygon points="100,0,100,100,0,100"/>
</svg>

To flip this vertically, you do:

<polygon points="100,0,100,100,0,100" transform="scale(2,-2)"/>

And to correct the movement off-screen, you can either...

(option 1) Shift it negative before the flip (so it gets flipped back on screen):

<polygon points="100,0,100,100,0,100" transform="scale(2,-2) translate(0,-100)"/>

(The translate is listed second here because transform lists are effectively applied right to left)

(option 2) Or, you can shift it positive (by the scaled size) afterwards:

<polygon points="100,0,100,100,0,100" transform="translate(0,200) scale(-2,2)"/>

Here is a demo showing vertical flip, horizontal flip and both flips

Update

To flip (in position) an already existing object that is somewhere on screen. First determine its bounding box (minX, minY, maxX, maxY), or centreX,centreY if you already know that instead.

Then prepend the following to its transform:

translate(<minX+maxX>,0) scale(-1, 1)   // for flip X
translate(0,<minY+maxY>) scale(1, -1)   // for flip Y

or if you have the centre you can use

translate(<2 * centreX>,0) scale(-1, 1)   // for flip X

So in your example:

<rect x="75" y="75" width="50" height="50"  transform="translate(-100, -100) scale(2, 2) scale(1, 1) rotate(45, 100, 100)" />

The minX+maxX comes to 200. So to flip horizontally, we prepend:

translate(200,0) scale(-1, 1)

So the final object becomes:

<rect x="75" y="75" width="50" height="50"  transform="translate(200,0) scale(-1, 1) translate(-100, -100) scale(2, 2) scale(1, 1) rotate(45, 100, 100)" />

Demo here

Solution 2 - Svg

simply add below attributes into path tag in svg

transform="scale (-1, 1)" transform-origin="center"

Eg: <path transform="scale (-1, 1)" transform-origin="center" ......./>

Solution 3 - Svg

Meet "Tux" the pinguin. For the sake of this exercise I painted the letters "L" and "R" on his feet.

Tux the pinguin

For starters, let's paint Tux in the center of our canvas. If the canvas is size 500x500, and if Tux has a size of 100x100 we have to position him at (200,200). (i.e. the center minus half its size.)

  <svg width="500" height="500">
    <!-- marking our border and a cross through the center -->
    <rect x="0" y="0" width="500" height="500" stroke-width="2" stroke="red" fill="none"></rect>
    <line x1="0" y1="0" x2="500" y2="500" stroke="red" stroke-width="2"></line>
    <line x1="500" y1="0" x2="0" y2="500" stroke="red" stroke-width="2"></line>

    <!-- our pinguin in the center -->
    <image x="200" y="200" width="100" height="100" href="assets/pinguin.png"></image>
  </svg>

The result

Now, if we want to mirror our pinguin horizontally (switching left and right) it is tempting to just use a transform with scale(-1 1). However, our pinguin just dissappears when we try that.

  <svg width="500" height="500">
    ...
    <image ... transform="scale(-1 1)"></image>
  </svg>

pinguin is gone

The reason, is that the default point of reflection (the so-called "transform-origin") for our transform is not in the center of our image, but is actually still at the (0,0) point.

The most obvious solution is to move the point of reflection to the central point of the image (250,250). (in this case, the center of our canvas).

  <svg width="500" height="500">
    ...
    <image ... transform="scale(-1 1)" transform-origin="250 250"></image>
  </svg>

Change the transform origin

And resizing works exactly the same. You can do it in 2 scales or combine them in 1 scale.

  <svg width="500" height="500">
    <!-- use 2 scales -->
    <image x="200" y="200" width="100" height="100" href="assets/pinguin.png"
           transform="scale(-1 1) scale(2 2)" transform-origin="250 250">
    </image>    
    <!-- or just multiply the values of both scales -->
    <image x="200" y="200" width="100" height="100" href="assets/pinguin.png"
           transform="scale(-2 2)" transform-origin="250 250">
    </image>
  </svg>

still 2 options

Solution 4 - Svg

Here is the Livescript-ish code snippet how you can horizontally flip and scale by any factor:

    # scale is 1 by default

    if mirror or scale isnt 1
        [minx, miny, width, height] = svg.attributes.viewBox |> split-by ',' |> cast-each-as (Number)

        s = scale
        # container is the root `g` node 
        container.attributes.transform = if mirror
            "translate(#{s * (width + minx) + minx}, #{-miny * (s-1)}) scale(#{-s},#{s})"
        else
            "translate(#{-minx * (s-1)}, #{-miny * (s-1)}) scale(#{s},#{s})"

        if scale isnt 1
            svg.attributes
                ..viewBox = "#{minx},#{miny},#{width * scale},#{height * scale}"
                ..width = "#{width * scale}"
                ..height = "#{height * scale}"

Solution 5 - Svg

No solution worked for me, I'll post what I discovered:

You can use either matrix or css-like transformations. And they behave different.

Take a look at this very basic example with an original shape and different ways to flip it horizontally. Notice that depending on the translation (you may want to keep it in the same x-axis) and the type of transformation you are using, you will need to set a different x-axis transformation.

Observation:

  • Matrix

    • Same place (light green): translate with positive width size.
    • X translation (dark green): Expected behavior (same as light green).
  • CSS-like

    • Same place (light blue): translate with negative width size and placed after scale. The opposite order is out of viewBox (note pink shape).
    • X translation (dark blue): translate with negative width size plus positive translation and placed before scale. The opposite order is out of viewBox (note orange shape).

<svg 
  viewBox="0 0 15 30" 
  width="150" 
  height="300" 
  xmlns="http://www.w3.org/2000/svg"
>
  <defs>
    <path 
      id="triangle"
      d="M0,5 l5,5 V0z" 
    />
  </defs>
  <use 
    href="#triangle"
    fill="red" 
  />
  <use
    y="5"
    href="#triangle"
    transform="scale(-1, 1) translate(-5, 0)"
    fill="lightBlue" 
  />
  <use
    y="5"
    href="#triangle"
    transform="translate(-5, 0) scale(-1, 1)"
    fill="pink" 
  />
  <use
    y="15"
    href="#triangle"
    transform="matrix(-1 0 0 1 5 0)"
    fill="lightGreen" 
  />
  <use
    href="#triangle"
    transform="translate(10, 0) scale(-1, 1)"
    fill="darkBlue" 
  />
  <use
    href="#triangle"
    transform="scale(-1, 1) translate(10, 0)"
    fill="orange" 
  />
  <use
    href="#triangle"
    transform="matrix(-1 0 0 1 15 0)"
    fill="darkGreen" 
  />
</svg>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDeukalionView Question on Stackoverflow
Solution 1 - SvgPaul LeBeauView Answer on Stackoverflow
Solution 2 - SvgRayees PkView Answer on Stackoverflow
Solution 3 - SvgbvdbView Answer on Stackoverflow
Solution 4 - SvgceremcemView Answer on Stackoverflow
Solution 5 - SvgllobetView Answer on Stackoverflow