tdewolff / canvas

Cairo in Go: vector to raster, SVG, PDF, EPS, WASM, OpenGL, Gio, etc.
MIT License
1.51k stars 103 forks source link

[question] How to rotate an element? #203

Closed p1usign closed 1 year ago

p1usign commented 1 year ago

I would like to rotate an element 90 degrees, some like: image

by the way, in this case I set coordsystem to be IV:

ctx.SetCoordSystem(canvas.CartesianIV)

now the question is, how can I write the code? could u plz give me more information? thx a lot!

p1usign commented 1 year ago

When I use ctx.Rotate to rotate the canvas, every coordinate is messed up, how can i get the correct coordinate?

p1usign commented 1 year ago

also, I found there have difference between rotate path, text and image..

tdewolff commented 1 year ago

I've pushed a commit that changes how the View (modified by e.g. Rotate) and CoordView/CoordSystem work together. The coordinate is now independent of the view. That is, setting the CoordSystem to CartesianIV will draw elements at the given coordinates without surprises, even if the canvas has been rotated.

I've not seen any divergence in behaviour over paths/text/images, which differences have you observed? See the following example to show how drawing works:

package main

import (
    "bytes"
    "fmt"
    "io/ioutil"
    "time"

    "github.com/tdewolff/canvas"
    "github.com/tdewolff/canvas/renderers"
)

func main() {
    fontLatin := canvas.NewFontFamily("latin")
    if err := fontLatin.LoadFontFile("/usr/share/fonts/TTF/DejaVuSerif.ttf", canvas.FontRegular); err != nil {
        panic(err)
    }

    W := 80.0
    H := 80.0

    c := canvas.New(W, H)
    ctx := canvas.NewContext(c)
    ctx.SetFillColor(canvas.White)
    ctx.DrawPath(0, 0, canvas.Rectangle(W, H))

    // Rotate
    ctx.SetCoordSystem(canvas.CartesianIV)
    ctx.Rotate(90.0)

    // Text
    face := fontLatin.Face(12.0, canvas.Black, canvas.FontRegular, canvas.FontNormal)
    text := canvas.NewTextLine(face, "Hello World!", canvas.Left)
    ctx.DrawText(30.0, 40.0, text)

    // Path
    ctx.SetFillColor(canvas.Black)
    ctx.DrawPath(40.0, 40.0, canvas.StarPolygon(5, 5.0, 2.0, true))

    // Image
    lenna, err := ioutil.ReadFile("../resources/lenna.png")
    if err != nil {
        panic(err)
    }
    img, err := canvas.NewPNGImage(bytes.NewReader(lenna))
    if err != nil {
        panic(err)
    }
    ctx.DrawImage(50.0, 40.0, img, 40.0)

    // Render and save
    start := time.Now()
    renderers.Write("rotate.png", c, canvas.DPMM(10.0))
    fmt.Printf("%s to save PNG\n", time.Since(start))
}

rotate

p1usign commented 1 year ago

I've pushed a commit that changes how the View (modified by e.g. Rotate) and CoordView/CoordSystem work together. The coordinate is now independent of the view. That is, setting the CoordSystem to CartesianIV will draw elements at the given coordinates without surprises, even if the canvas has been rotated.

I've not seen any divergence in behaviour over paths/text/images, which differences have you observed? See the following example to show how drawing works:

package main

import (
  "bytes"
  "fmt"
  "io/ioutil"
  "time"

  "github.com/tdewolff/canvas"
  "github.com/tdewolff/canvas/renderers"
)

func main() {
  fontLatin := canvas.NewFontFamily("latin")
  if err := fontLatin.LoadFontFile("/usr/share/fonts/TTF/DejaVuSerif.ttf", canvas.FontRegular); err != nil {
      panic(err)
  }

  W := 80.0
  H := 80.0

  c := canvas.New(W, H)
  ctx := canvas.NewContext(c)
  ctx.SetFillColor(canvas.White)
  ctx.DrawPath(0, 0, canvas.Rectangle(W, H))

  // Rotate
  ctx.SetCoordSystem(canvas.CartesianIV)
  ctx.Rotate(90.0)

  // Text
  face := fontLatin.Face(12.0, canvas.Black, canvas.FontRegular, canvas.FontNormal)
  text := canvas.NewTextLine(face, "Hello World!", canvas.Left)
  ctx.DrawText(30.0, 40.0, text)

  // Path
  ctx.SetFillColor(canvas.Black)
  ctx.DrawPath(40.0, 40.0, canvas.StarPolygon(5, 5.0, 2.0, true))

  // Image
  lenna, err := ioutil.ReadFile("../resources/lenna.png")
  if err != nil {
      panic(err)
  }
  img, err := canvas.NewPNGImage(bytes.NewReader(lenna))
  if err != nil {
      panic(err)
  }
  ctx.DrawImage(50.0, 40.0, img, 40.0)

  // Render and save
  start := time.Now()
  renderers.Write("rotate.png", c, canvas.DPMM(10.0))
  fmt.Printf("%s to save PNG\n", time.Since(start))
}

rotate

Thanks for your answer

Now after I pull the latest code, I found that now all the element's original point always at the left bottom, even i set coordsystem IV, is it correct?

package main

import (
    "github.com/tdewolff/canvas"
    "github.com/tdewolff/canvas/renderers"
)

func main() {
    fontLatin := canvas.NewFontFamily("latin")
    if err := fontLatin.LoadLocalFont("DejaVuSerif.ttf", canvas.FontRegular); err != nil {
        panic(err)
    }

    W := 80.0
    H := 80.0

    c := canvas.New(W, H)
    ctx := canvas.NewContext(c)
    ctx.SetFillColor(canvas.White)
    ctx.DrawPath(0, 0, canvas.Rectangle(W, H))
    ctx.SetCoordSystem(canvas.CartesianIV)

    ctx.SetFillColor(canvas.Black)
    ctx.DrawPath(0, 20, canvas.Rectangle(20, 20))
    renderers.Write("test.png", c, canvas.DPMM(10.0))
}

test

p1usign commented 1 year ago

Before this, the setting of the coordinate system will affect the origin, this change is huge

p1usign commented 1 year ago

Also, the performance of translate is now becoming incomprehensible

Maybe you can explain the relationship between coordsystem/coordview and view, and how translate and rotate work

tdewolff commented 1 year ago

The origin of the path is where ever the path starts (i.e. its first MoveTo command). The canvas.Rectangle gives a path that starts at the bottom-left and moves counter-clockwise, this has nothing to do with the coordinate system and hasn't changed. You could use canvas.Rectangle(20, -20) to start at the top-left and go clockwise.

The coordview and coordsystem affect the way that coordinates (i.e. the x and y in DrawText(x,y, ...)) are transformed. First the coordsystem is applied (one of the four Cartesian quadrants) so that coordinates origin in the bottom-left (the default for all functions in Canvas and in mathematics in general). Then the optional coordview is applied in case needed (not in your case). That defines the coordinate at which an object is drawn. Separately, the object gets transformed by the view before drawing at the previously established coordinate. As you see, the coordview and view are independent of each other after the two commits above!

p1usign commented 1 year ago

I understand, thank you for your reply and help, issue has been resolved. Thank you very much!