Markup scheren

Begonnen von Manuela, Mittwoch, 5. April 2023, 18:56

« vorheriges - nächstes »

Manuela

Kann man mit Lilypond-Mitteln ein Markup verzerren? Etwa in der Art wie skewX für SVG.

Ist aber nur ein Luxus-Problem.
Danke für eure Hilfe
viele Grüße
-- Manuela

Arnold

Hmmm,

die 'Scherung' ist eine 'Geometrietransformation, algebraisch beschrieben durch die Multiplikation der Positionsvektoren mit einer nicht-orthogonalen Matrix' würde da wohl ein Mathematiklehrer sagen (oder so ähnlich).

Als Scheme-Funktionen fand ich ly:make-transform, ly:transform? und ly:transform->list, und als ähnliche Funktionen ly:make-rotation, ly:make-scaling und ly:make-translation.
Doch leider fand ich von all diesen Funktionen keine Verwendung in den Quellcode-Dateien lily/*.cc, scm/* und ly/*  :(

Also, eine »schiefe Transformation« läßt sich zwar mittels ly:make-transform erzeugen, doch fand ich keinen Hinweis, wie dann dieses ly:transform?-Objekt auf ein Markup angewandt werden könnte.

Sieht aus, als hätte jemand ein Markup-Kommando
  \transform-by-matrix (ly:transform? ly:markup?)
vergessen zu erstellen.

Arnold

Arnold

#2
    Hallo Manuela,

    heute morgen ist mir noch ein Lösungsweg eingefallen!

    Für die Transformation:
      x' = x + y * sin w
      y' = y * cos w
    drei aufeinanderfolgende Abbildungen berechnen:
    • Rotation um 45°: x-Vektor = (wurzel(0.5), wurzel(0.5)); y-Vektor = (-wurzel(0.5), wurzel(0.5))
    • Skalierung mit Y-Faktor = p * X-Faktor: \scale #'(1 . p) ergäbe einen neuen Winkel zwischen X- und Y-Vektor mit 2 * arctan(1/p) = 90° - w bzw. p = tan(0.5 * (90° + w)); der X-Vektor hat als neue Winkelrichtung arctan(p); die beiden Vektoren wären nun l = wurzel(0.5 + 0.5 * p²) lang; damit deren Länge aber gleich bleibt wird mit #(cons (/ 1 l) (/ p l)) skaliert.
    • Rotation zurück, damit der X-Vektor wieder horizontal liegt, also um -arctan(p) bzw. -0.5 * (90° + w)

Arnold

Manuela

Danke, dann werde ich mal versuchen, dies in Code umzuwandeln.
Danke für eure Hilfe
viele Grüße
-- Manuela

Manuela

#4
Ja, das klappt, danke.

Hier ein Codebeispiel:

\version "2.23.5"

aaa = \markup \filled-box #'(10 . 30) #'(10 . 20) #0

%% p = .4
%% l = sqrt(.5 + p2*.5) = sqrt(.58)=0.761577311

\markup \combine \aaa
\translate #'(-2.7 . 5.5)
\with-color #grey \scale #(cons (/ 1 .761577) (/ .4 .761577))\rotate #-21.8 \scale #'(1 . .4)
\rotate #45 \aaa
Danke für eure Hilfe
viele Grüße
-- Manuela

Manuela

Ich habe den Code in das LSR gestellt.
Danke für eure Hilfe
viele Grüße
-- Manuela

Arnold

Ja, Manuela,

vielleicht kannst Du gleich ein Update ins LSR stellen  :)

Zuerst einmal: Die beiden Skalierungsfaktoren können auch direkt aus dem Scherungswinkel w berechnet werden:
px = cos(w / 2) - sin(w / 2)
py = cos(w / 2) + sin(w / 2)

Für eine Scherung des Typs Parallelverschiebung (anstelle Rotation) fügt man zu Angang der Transformationen noch eine Skalierung um 1 / cos(w) in Y-Richtung hinzu.

Durch die gedrehten Zwischenschritte entsteht eine relativ große Bounding-Box:
\version "2.24.1" % mit Guile 2.2

#(define-public (degsin ang) (sin (* ang (/ (atan 1.0) 45.0))))
#(define-public (degcos ang) (cos (* ang (/ (atan 1.0) 45.0))))
#(define-public (ssp ang)
  (let* ((rad-ang (* ang (/ (atan 1.0) 45.0)))
        (cos-part (cos rad-ang))
        (sin-part (sin rad-ang)))
  (cons (- cos-part sin-part) (+ cos-part sin-part))))

Text = \markup \overlay {
        \with-color #darkgreen \path #0.1 #'((moveto 20.0 0.0) (lineto 0.0 0.0) (lineto 0.0 3.5))
        \with-color #black \fontsize #6 "Hallo Welt!"
      }

\markup \column {
    " "
    \line { \bold "Scherung des Typs »Rotation«,"
            \italic "mit verketteten Markup-Befehlen erstellt."
    }
    \italic "Darstellung mit Bounding-Box:"
  }
\markup {
  \override #'(box-padding . 0.0) \with-color #red \box
    \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #(- -45.0 10.0) \scale #(ssp 10.0) \rotate #45.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #(- -45.0 20.0) \scale #(ssp 20.0) \rotate #45.0 \Text
}

\markup \column {
    " "
    "Die Entwicklung der BoundingBox hierzu:"
  }
\markup {
  \override #'(box-padding . 0.0) \with-color #red \box
    \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #45.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \scale #(ssp 20.0) \rotate #45.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #(- -45.0 20.0) \scale #(ssp 20.0) \rotate #45.0 \Text
}

\markup \column {
    " "
    \line { \bold "Scherung des Typs »Parallelverschiebung«,"
            \italic "mit verketteten Markup-Befehlen erstellt."
    }
    \italic "Darstellung mit Bounding-Box:"
  }
\markup {
  \override #'(box-padding . 0.0) \with-color #red \box
    \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #(- -45.0 10.0) \scale #(ssp 10.0) \rotate #45.0 \scale #(cons 1.0 (/ (degcos 20.0))) \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #(- -45.0 20.0) \scale #(ssp 20.0) \rotate #45.0 \scale #(cons 1.0 (/ (degcos 40.0))) \Text
}

\markup \column {
    " "
    "Die Entwicklung der BoundingBox hierzu:"
  }
\markup {
  \override #'(box-padding . 0.0) \with-color #red \box
    \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \scale #(cons 1.0 (/ (degcos 40.0))) \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #45.0 \scale #(cons 1.0 (/ (degcos 40.0))) \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \scale #(ssp 20.0) \rotate #45.0 \scale #(cons 1.0 (/ (degcos 40.0))) \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \rotate #(- -45.0 20.0) \scale #(ssp 20.0) \rotate #45.0 \scale #(cons 1.0 (/ (degcos 40.0))) \Text
}

Das kann man natürlich mit einem in SCHEME selbst erstellten Markup-Befehl umgehen, indem man darin die finale Bounding-Box aus der Ursprungs-Boundig-Box und der Gesamttransformation berechnet:
\version "2.24.1" % mit Guile 2.2

#(define-markup-command (slant layout props paraltype ang arg)
  (string-or-symbol? number? markup?)
  (let* ((deg-rad-factor (/ (atan 1.0) 45.0))
        (par (equal? (if (symbol? paraltype) (symbol->string paraltype) paraltype) "par"))
        (half-ang (* 0.5 ang))
        (sin-part (sin (* deg-rad-factor half-ang)))
        (cos-part (cos (* deg-rad-factor half-ang)))
        (px (- cos-part sin-part))
        (py (+ cos-part sin-part))
        ; Overall Matrix: xx = 1.0    xy
        ;                 yx = 0.0    yy
        (xy (if par (tan (* deg-rad-factor ang))
                    (sin (* deg-rad-factor ang))))
        (yy (if par 1.0 (cos (* deg-rad-factor ang))))
        ; input markup's stencil and extent
        (stil (interpret-markup layout props arg))
        (ext-x (ly:stencil-extent stil X))
        (ext-y (ly:stencil-extent stil Y))
        (xo1 (car ext-x))
        (xo2 (cdr ext-x))
        (yo1 (car ext-y))
        (yo2 (cdr ext-y))
        ; four edge points transformed by that matrix
        (xp1 (+ xo1 (* xy yo1)))
        (yp13 (* yy yo1))
        (xp2 (+ xo1 (* xy yo2)))
        (yp24 (* yy yo2))
        (xp3 (+ xo2 (* xy yo1)))
        (xp4 (+ xo2 (* xy yo2)))
        ; new extent
        (xmin (min xp1 xp2 xp3 xp4))
        (xmax (max xp1 xp2 xp3 xp4))
        (ymin (min yp13 yp24))
        (ymax (max yp13 yp24))
        (stil-two
          (if par (ly:stencil-scale stil 1.0 (/ 1.0 (cos (* deg-rad-factor ang)))) stil))
        (stil-three (ly:stencil-rotate-absolute stil-two 45.0 0.0 0.0))
        (stil-four (ly:stencil-scale stil-three px py))
        (stil-five (ly:stencil-rotate-absolute stil-four (- -45.0 half-ang) 0.0 0.0)))
  (ly:stencil-outline stil-five
    (make-filled-box-stencil (cons xmin xmax) (cons ymin ymax)))))

Text = \markup \overlay {
        \with-color #darkgreen \path #0.1 #'((moveto 20.0 0.0) (lineto 0.0 0.0) (lineto 0.0 3.5))
        \with-color #black \fontsize #6 "Hallo Welt!"
      }

\markup \column {
    " "
    \bold "Scherung der Typen »Rotation« und »Parallelverschiebung«,"
    \line { \italic "mit selbst programmiertem Markup-Befehl" \bold "\\slant" \italic "erstellt." }
    \italic "Die Berechnung der Bouding-Box über die Zwischenschritte kann hier verworfen werden"
    \italic "und statt dessen die neue Bounding-Box anhand der Gesamt-Tranformation berechnet werden:"
  }
\markup {
  \override #'(box-padding . 0.0) \with-color #red \box
    \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \slant #'rot #20.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \slant #'rot #40.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \slant #'rot #-25.0 \Text
}

\markup " "

\markup {
  \override #'(box-padding . 0.0) \with-color #red \box
    \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \slant #'par #20.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \slant #'par #40.0 \Text
  \override #'(box-padding . 0.0) \with-color #red \box
    \slant #'par #-25.0 \Text
}

Viel Spaß damit,

Arnold



Manuela

Danke, Arnold.
Du hast damit meine nächste Frage vorweggenommen, nämlich wie ich die Abmessungen des erzeugten Markups anpasse, sodass der linke Rand wirklich der linke Rand ist. Bisher habe ich mir mit translate geholfen.

Zusammen sind wir genial!  ;)  ;)  ;)
Danke für eure Hilfe
viele Grüße
-- Manuela

harm6

Hallo Manuela, hallo Arnold,

wie wahrscheinlich bekannt bin ich einer der LSR-Editoren.
Wenn neue snippets eingestellt werden, diskutiere ich gerne Verbesserungsmöglichkeiten mit dem Autor, falls bekannt ;)

Insoweit:
@Manuela
Du hast jetzt schon ein zweites snippet hoch geladen, mit Verweis auf das erste.
Bitte sei ein bißchen nachsichtig mit einem alten Mann, ich muß mich erst da durch kämpfen...

Aber folgendes kann ich schon sagen:
Die description könnte schon etwas ausführlicher sein und auch das Problem Rotation vs Parallelverschiebung thematisieren.
Wenn Du einen link angibst, benutze bitte entsprechende html-tags:
<a href="whatever-url">mein-Text</a>
Gib bitte keine \version an.
Der code den Arnold hier vorgestellt hat ist dem aus #1166 überlegen.
Nun habe ich weiter unten noch einiges dazu anzumerken, aber ich würde immer den überlegenen code nehmen. Du solltest #1166 also upgraden, aber erst lese:

@Arnold
Im source code wird danach gestrebt grundsätzlich auf Gradzahlen zu setzen und Radians zu eliminieren.
Auch sind guiles trigonometrische Funktionen mit Vorsicht zu genießen. In Grenzbereichen sind sie zu ungenau (ich bin da schon mal übel reingefallen...). Das war der Grund warum ly:directed, ly:length und ly:angle überhaupt erst implementiert wurden. Und mit ly:directed kann man hier problemlos alles hinkriegen, s.u.
Darüberhinaus sollten mehr Kommentare im Code stehen (warum passiert jetzt dies oder das). Nicht jeder ist ja ein kundiger Progammierer.
Auch fehlt dem markup-command ein doc-string.
Sollte `paraltype' nicht besser ein property sein?

Es gibt kein Sicherheitsnetz wenn der user den Winkel mit (Vielfachen von) 90 angibt.

Hier sind schon mal ein paar Sachen implementiert (mit zum Teil weiteren Anmerkungen):

\version "2.24.1" % mit Guile 2.2

%% TODO Discuss "Parallelverschiebung" vs "Rotation"
%% If a link pointing to a proper explanation can be given, a very short summary
%% may be enough...

#(define-markup-command (slant layout props paraltype ang arg)
  (string-or-symbol? number? markup?)
  "TODO DOCME"
  ;; TODO make `paraltype' a property?
  (let* (
        (par (equal? (if (symbol? paraltype) (symbol->string paraltype) paraltype) "par"))
        (half-ang (* 0.5 ang))
        ;; (ly:directed <angle>) returns a number-pair representing cosinus and
        ;; sinus of <angle> for a magnitude of length 1 (the default).
        (ang-coords (ly:directed ang))
        (ang-sin (cdr ang-coords))
        (ang-cos (car ang-coords))
        (half-ang-coords (ly:directed half-ang))
        (half-ang-sin (cdr half-ang-coords))
        (half-ang-cos (car half-ang-coords))
        ;; Values for the second scaling
        (px (- half-ang-cos half-ang-sin))
        (py (+ half-ang-cos half-ang-sin))
        (stil (interpret-markup layout props arg))
        (stil-first-scaled
          (if par
              (ly:stencil-scale stil 1.0 (/ 1.0 ang-cos))
              stil))
        (stil-first-rotated
          (ly:stencil-rotate-absolute stil-first-scaled 45.0 0.0 0.0))
        (stil-second-scaled (ly:stencil-scale stil-first-rotated px py))
        (stil-second-rotated
          (ly:stencil-rotate-absolute
            stil-second-scaled (- -45.0 half-ang) 0.0 0.0))
        ;; Doing multiple rotations lead to blown up extent, calculate proper
        ;; values.
        ;;
        ;; Overall Matrix: xx = 1.0    xy
        ;;                 yx = 0.0    yy
        (xy (if par (/ ang-sin ang-cos) ang-sin))
        (yy (if par 1.0 ang-cos))
        ;; input markup's stencil and extent
        (ext-x (ly:stencil-extent stil X))
        (ext-y (ly:stencil-extent stil Y))
        (xo1 (car ext-x))
        (xo2 (cdr ext-x))
        (yo1 (car ext-y))
        (yo2 (cdr ext-y))
        ;; four edge points transformed by that matrix
        (xp1 (+ xo1 (* xy yo1)))
        (yp13 (* yy yo1))
        (xp2 (+ xo1 (* xy yo2)))
        (yp24 (* yy yo2))
        (xp3 (+ xo2 (* xy yo1)))
        (xp4 (+ xo2 (* xy yo2)))
        ; new extent
        (xmin (min xp1 xp2 xp3 xp4))
        (xmax (max xp1 xp2 xp3 xp4))
        (ymin (min yp13 yp24))
        (ymax (max yp13 yp24)))
 
  (ly:stencil-outline stil-second-rotated
    ;; TODO Cheaper than using make-filled-box-stencil?
    (ly:make-stencil '() (cons xmin xmax) (cons ymin ymax)))))

Text = \markup \overlay {
        \with-color #darkgreen \path #0.1 #'((moveto 20.0 0.0) (lineto 0.0 0.0) (lineto 0.0 3.5))
        \with-color #black \fontsize #6 "Hallo Welt!"
      }

\markup \column {
    " "
    \bold "Scherung der Typen »Rotation« und »Parallelverschiebung«,"
    \line { \italic "mit selbst programmiertem Markup-Befehl" \bold "\\slant" \italic "erstellt." }
    \italic "Die Berechnung der Bouding-Box über die Zwischenschritte kann hier verworfen werden"
    \italic "und statt dessen die neue Bounding-Box anhand der Gesamt-Tranformation berechnet werden:"
  }
 
\markup
  \override #'(box-padding . 0.0)
  \with-color #red
  \box {
    \Text
    \slant #'rot #20.0 \Text
    \slant #'rot #40.0 \Text
    \slant #'rot #-25.0 \Text
}

\markup " "

\markup
  \override #'(box-padding . 0.0)
  \with-color #red
  \box {
    \Text
    \slant #'par #20.0 \Text
    \slant #'par #40.0 \Text
    \slant #'par #-25.0 \Text
}

Als Ausblick: Kann man auch Scherung in y-axis programmieren bzw eine Kombination?

So weit erstmal ...

Gruß,
  Harm

Manuela

Zitat von: harm6 am Samstag,  8. April 2023, 15:20@Manuela
Du hast jetzt schon ein zweites snippet hoch geladen, mit Verweis auf das erste.
Bitte sei ein bißchen nachsichtig mit einem alten Mann, ich muß mich erst da durch kämpfen...

Sei ein bisschen nachsichtig mit einer übereiligen alten Frau  ;)
Zitat von: harm6 am Samstag,  8. April 2023, 15:20Die description könnte schon etwas ausführlicher sein und auch das Problem Rotation vs Parallelverschiebung thematisieren.
Wenn Du einen link angibst, benutze bitte entsprechende html-tags:
<a href="whatever-url">mein-Text</a>
So wie ich das verstanden habe, werden derartige Tags aus dem Text entfernt, deswegen habe ich keinen Link eingebaut.

Zitat von: harm6 am Samstag,  8. April 2023, 15:20Der code den Arnold hier vorgestellt hat ist dem aus #1166 überlegen.

Auf jeden Fall, aber die erste Version ist so einfach und leicht zu verstehen, dass ich sie nicht überschreiben mochte.

Hier eine von mir bearbeitete Version für das LSR (die Version kommt dann weg). Ich habe außerdem die Anordnung der Parameter geändert und statt eines Symbols ein Boolean verwendet. Weiters habe ich mich bemüht, den Ablauf zu erläutern (und hoffe, dass ich keinen Unsinn verzapfe).

\version "2.23.5"

% LSR workaround:
#(set! paper-alist (cons '("snippet" . (cons (* 220 mm) (* 75 mm))) paper-alist))
\paper {
  #(set-paper-size "snippet")
  tagline = ##f
  #(include-special-characters)
  indent = #0
  top-margin = #20
  ragged-right = ##t
}

#(define-markup-command (skew layout props arg ang par)
   (markup? number? boolean?)
   "@var{function} applies horizontal skewing to markup @var{arg} with angle @var{ang}
in analogy to the SVG transformation skewX https://www.w3.org/wiki/CSS3_2D_Transforms#Skew
@var{par} = true preserves height of @var{arg} otherwise the height decreases with cos(@var{ang})
the transforation requires several steps
I)   if @var{par} true multiply height of @var{arg} with scaling factor 1/cos @var{ang}
     therefore you should not apply 90 degrees with 'true'; this will cause a crash of the procedure
     stil-first-scaled
II)  rotation of markup @var{arg} with angle 45 deg
     stil-first-rotated
III) scale rotated markup with some sin and cos of @var{ang}/2 to get the skewing
     stil-second-scaled
IV)  rotate back with @var{ang}/2 plus 45 deg to align the markup in the original direction
     stil-second-rotated
V)   calculate extent of result markup and return a markup with the calculated dimension
"
   ;; TODO make `type' a property?
   (let* (
           (half-ang (* 0.5 ang))
           ;; (ly:directed <angle>) returns a number-pair representing cosinus and
           ;; sinus of <angle> for a magnitude of length 1 (the default).
           (ang-coords (ly:directed ang))
           (ang-sin (cdr ang-coords))
           (ang-cos (car ang-coords))
           (half-ang-coords (ly:directed half-ang))
           (half-ang-sin (cdr half-ang-coords))
           (half-ang-cos (car half-ang-coords))
           ;; Values for the second scaling
           (px (- half-ang-cos half-ang-sin))
           (py (+ half-ang-cos half-ang-sin))
           (stil (interpret-markup layout props arg))
           (stil-first-scaled
            (if par
                (ly:stencil-scale stil 1.0 (/ 1.0 ang-cos))
                stil))
           (stil-first-rotated
            (ly:stencil-rotate-absolute stil-first-scaled 45.0 0.0 0.0))
           (stil-second-scaled (ly:stencil-scale stil-first-rotated px py))
           (stil-second-rotated
            (ly:stencil-rotate-absolute
             stil-second-scaled (- -45.0 half-ang) 0.0 0.0))
           ;; Doing multiple rotations lead to blown up extent, calculate proper
           ;; values.
           ;;
           ;; Overall Matrix: xx = 1.0    xy
           ;;                 yx = 0.0    yy
           (xy (if par (/ ang-sin ang-cos) ang-sin))
           (yy (if par 1.0 ang-cos))
           ;; input markup's stencil and extent
           (ext-x (ly:stencil-extent stil X))
           (ext-y (ly:stencil-extent stil Y))
           (xo1 (car ext-x))
           (xo2 (cdr ext-x))
           (yo1 (car ext-y))
           (yo2 (cdr ext-y))
           ;; four edge points transformed by that matrix
           (xp1 (+ xo1 (* xy yo1)))
           (yp13 (* yy yo1))
           (xp2 (+ xo1 (* xy yo2)))
           (yp24 (* yy yo2))
           (xp3 (+ xo2 (* xy yo1)))
           (xp4 (+ xo2 (* xy yo2)))
           ; new extent
           (xmin (min xp1 xp2 xp3 xp4))
           (xmax (max xp1 xp2 xp3 xp4))
           (ymin (min yp13 yp24))
           (ymax (max yp13 yp24)))
     (ly:stencil-outline stil-second-rotated
                         ;; TODO Cheaper than using make-filled-box-stencil?
                         (ly:make-stencil '() (cons xmin xmax) (cons ymin ymax)))))

Text = \markup \override #'(box-padding . 0.0) \with-color #darkgreen \box
\with-color #black \fontsize #6 "Hello world!"

\markup
\override #'(box-padding . 0.0)
\with-color #red
\box {
  \Text
  \skew \Text #20 ##f
  \skew \Text #40 ##f
  \skew \Text #-30 ##f
}
\markup \vspace #1
\markup
\override #'(box-padding . 0.0)
\with-color #red
\box {
  \Text
  \skew \Text #20 ##t
  \skew \Text #60 ##t
  \skew \Text #-30 ##t
}

Danke für eure Hilfe
viele Grüße
-- Manuela

Arnold

Hallo Manuela und Harm,

ich glaube, ihr beide macht das gut, und ich brauche nicht mehr viel dazu zu beizutragen  ;)

ly:directed hatte ich noch gar nicht entdeckt.

Zum Kommentar im Codebeispiel:
    ;; TODO Cheaper than using make-filled-box-stencil?
    (ly:make-stencil '() (cons xmin xmax) (cons ymin ymax)))))

Hatte ich aus der Markup-Definition von with-dimensions in Datei scm/define-markup-commands.scm kopiert (etwa Zeile 2537 in Version 2.25.1). Harm, solltest Du dann die Verbesserung (ly:make-stencil '() statt make-filled-box-stencil) auch nach scm/define-markup-commands.scm rückübertragen?

Zitat(Harm) Als Ausblick: Kann man auch Scherung in y-axis programmieren bzw eine Kombination?
Ich kenne nur Beispiele, bei denen die X-Achse stehen bleibt. Die Geometrie vorher um 90° zu drehen, dann die Scherung durchzuführen, und das Ergebnis wieder um 90° zurückzudrehen entspräche ja einer Scherung mit konstanter Y-Achse - falls sich bei den normalen Markup-Anweisungen (wo man keinen Ursprung angibt) kein Versatz einstellt. Die Outline-Box vergrößert sich bei 90°-Drehungen ja nicht.
Ansonsten könnte man natürlich auch einen anzugebenden Winkel für die Achsrichtung der »konstanten Gerade« einbauen. Diesen aber wirklich bevorzugt nur als Property mit Defaultwert 0°.
  • Vordrehung um minus__Achsrichtung_der_konstanten-Gerade
  • für par: Vorskalierung Y
  • Drehung 45°, u.s.w.
  • Schlußdrehung um diesen Winkel vergrößert
Die Berechnung der Gesamtabbildungsmatrix wird etwas aufwändiger. Gibt es auch eine Funktion um die ly:transform?-Objekte zu verketten?

Die erste Rotation um 45° orientiert das Ursprungskoordinatensystem zu Diagonalen eines Quadrats. Durch die folgende anisotrope Skalierung wird eine symmetrische Scherbewegung erzeugt - die Größen so gewählt, daß die Vektorlängen des Ursprungskoordinatensystem gleich lang bleiben. Daher war auch mein Ausgangspunkt die »rotatorische Form der Scherung«.
Da ich mich nicht entscheiden kann, ob 'parallel' oder 'rotatorisch' als Default anzusehen ist, kann ich mich nicht für einen (quasi versteckten) Parameter entscheiden, denn dann müßte ich eine der beiden Arten bevorzugen. Aufgrund der gewählten Berechungsart wäre aber 'rotatorisch' der zugrundegelegte Fall, 'parallel' eine Ableitung davon.
Bei der »parallelverschiebenden Form« kommt man bei einer Winkelangabe um 90° + n * 180° immer in Unendlichkeits-Konflikte. Ich würde, wenn par und Betrag von ang-cos < 0.02 einen Programm-Error ausgeben (auf den folgt ein "cross fingers" bzw. "Daumendrücken") - Entweder es klappt doch noch, oder der Anwender liest hoffentlich, wo da Problem lag.

Arnold

harm6

Zitat von: Manuela am Samstag,  8. April 2023, 16:39
Zitat von: harm6 am Samstag,  8. April 2023, 15:20Die description könnte schon etwas ausführlicher sein und auch das Problem Rotation vs Parallelverschiebung thematisieren.
Wenn Du einen link angibst, benutze bitte entsprechende html-tags:
<a href="whatever-url">mein-Text</a>
So wie ich das verstanden habe, werden derartige Tags aus dem Text entfernt, deswegen habe ich keinen Link eingebaut.
In der description des snippets sind sie ok.

Zitat von: Manuela am Samstag,  8. April 2023, 16:39
Zitat von: harm6 am Samstag,  8. April 2023, 15:20Der code den Arnold hier vorgestellt hat ist dem aus #1166 überlegen.

Auf jeden Fall, aber die erste Version ist so einfach und leicht zu verstehen, dass ich sie nicht überschreiben mochte.
Wenn ich als LSR-Editor zwei snippets zum selben Thema sehe, bei dem eines den code des anderen verbessert verwendet, so lösche ich das andere. ;)

Zitat von: Manuela am Samstag,  8. April 2023, 16:39Hier eine von mir bearbeitete Version für das LSR (die Version kommt dann weg). Ich habe außerdem die Anordnung der Parameter geändert und statt eines Symbols ein Boolean verwendet. Weiters habe ich mich bemüht, den Ablauf zu erläutern (und hoffe, dass ich keinen Unsinn verzapfe).


\version "2.23.5"

% LSR workaround:
#(set! paper-alist (cons '("snippet" . (cons (* 220 mm) (* 75 mm))) paper-alist))
\paper {
  #(set-paper-size "snippet")
  tagline = ##f
  #(include-special-characters)
  indent = #0
  top-margin = #20
  ragged-right = ##t
}

#(define-markup-command (skew layout props arg ang par)
   (markup? number? boolean?)
   "@var{function} applies horizontal skewing to markup @var{arg} with angle @var{ang}
in analogy to the SVG transformation skewX https://www.w3.org/wiki/CSS3_2D_Transforms#Skew
@var{par} = true preserves height of @var{arg} otherwise the height decreases with cos(@var{ang})
the transforation requires several steps
I)   if @var{par} true multiply height of @var{arg} with scaling factor 1/cos @var{ang}
     therefore you should not apply 90 degrees with 'true'; this will cause a crash of the procedure
     stil-first-scaled
II)  rotation of markup @var{arg} with angle 45 deg
     stil-first-rotated
III) scale rotated markup with some sin and cos of @var{ang}/2 to get the skewing
     stil-second-scaled
IV)  rotate back with @var{ang}/2 plus 45 deg to align the markup in the original direction
     stil-second-rotated
V)   calculate extent of result markup and return a markup with the calculated dimension
"
[...]

Der LSR-workaround ist nicht mehr nötig, auch sehe ich keinen Sinn in den anderen \paper-Setzungen.
Den doc-string würde ich anders angehen. Was und vor allem warum etwas gemacht wird gehört imho in inline Kommentare des Codings.

Ich antworte noch auf Arnold's letzten post, da werde ich auch meine neuste Verion des Codes einstellen. Schau doch mal da rein.
Möglicherweise verbessert es auch das Problem in
https://lilypondforum.de/index.php/topic,1218.0.html

Gruß,
  Harm

harm6

Zitat von: Arnold am Sonntag,  9. April 2023, 17:28
Zitat(Harm) Als Ausblick: Kann man auch Scherung in y-axis programmieren bzw eine Kombination?
Ich kenne nur Beispiele, bei denen die X-Achse stehen bleibt. Die Geometrie vorher um 90° zu drehen, dann die Scherung durchzuführen, und das Ergebnis wieder um 90° zurückzudrehen entspräche ja einer Scherung mit konstanter Y-Achse - falls sich bei den normalen Markup-Anweisungen (wo man keinen Ursprung angibt) kein Versatz einstellt. Die Outline-Box vergrößert sich bei 90°-Drehungen ja nicht.
Ansonsten könnte man natürlich auch einen anzugebenden Winkel für die Achsrichtung der »konstanten Gerade« einbauen. Diesen aber wirklich bevorzugt nur als Property mit Defaultwert 0°.
  • Vordrehung um minus__Achsrichtung_der_konstanten-Gerade
  • für par: Vorskalierung Y
  • Drehung 45°, u.s.w.
  • Schlußdrehung um diesen Winkel vergrößert
Die Berechnung der Gesamtabbildungsmatrix wird etwas aufwändiger. Gibt es auch eine Funktion um die ly:transform?-Objekte zu verketten?

Die erste Rotation um 45° orientiert das Ursprungskoordinatensystem zu Diagonalen eines Quadrats. Durch die folgende anisotrope Skalierung wird eine symmetrische Scherbewegung erzeugt - die Größen so gewählt, daß die Vektorlängen des Ursprungskoordinatensystem gleich lang bleiben. Daher war auch mein Ausgangspunkt die »rotatorische Form der Scherung«.

Ich habe die gesamten diesbezüglichen Berechnungen raus genommen und stattdessen `stencil-true-extent' verwendet. Das funktioniert via skylines, nicht über boxes.
Scherung in Y-Richtung habe ich mit 90-Grad-Drehungen kodiert.
Das gesamt Coding weiter unten.
Ich bin aber noch nicht ganz glücklich. Insbesondere wenn die Ausdehnung des Ausgangs-markup irgendwo in den negativen Bereich geht.

Zitat von: Arnold am Sonntag,  9. April 2023, 17:28Da ich mich nicht entscheiden kann, ob 'parallel' oder 'rotatorisch' als Default anzusehen ist, kann ich mich nicht für einen (quasi versteckten) Parameter entscheiden, denn dann müßte ich eine der beiden Arten bevorzugen. Aufgrund der gewählten Berechungsart wäre aber 'rotatorisch' der zugrundegelegte Fall, 'parallel' eine Ableitung davon.
Bei der »parallelverschiebenden Form« kommt man bei einer Winkelangabe um 90° + n * 180° immer in Unendlichkeits-Konflikte. Ich würde, wenn par und Betrag von ang-cos < 0.02 einen Programm-Error ausgeben (auf den folgt ein "cross fingers" bzw. "Daumendrücken") - Entweder es klappt doch noch, oder der Anwender liest hoffentlich, wo da Problem lag.

Arnold

Tatsächlich kann ich mich auch noch nicht recht zwischen 'parallel und 'rotated entscheiden, hab' aber für den Moment 'parallel als default-property genommen.  Nichtsdestotrotz hatte ich den Gedanken, ob man da nicht auch Mittelwege einbauen kann: 'parallel führt ja zum Erhalt der Höhe, mit 'rotated ist die Höhe dynamisch abhängig vom winkel.  Vielleicht geht ja auch etwas wie immer die halbe Höhe (oder ein beliebiges Vielfaches/Bruchteil der Höhe) anzugeben.
Für das 90-Grad Problem habe ich einen workaround eingebaut.

Im Moment brauch ich aber eine Pause. Hier noch der Code in meiner letzten Fassung:
\version "2.24.1" % mit Guile 2.2

#(define-markup-command (skew-x layout props ang arg)
  (number? markup?)
  #:properties ((type  'parallel))
  "Applies horizontal skewing to @var{arg} with angle @var{ang}, in analogy to
the SVG transformation skewX https://www.w3.org/wiki/CSS3_2D_Transforms#Skew

The transformation is done by a sequence of scaling and rotating.

With @code{type} set @code{'parallel} the height is preserved, though this
would crash if @var{ang} is of type @code{(+ 90 (* n 180))} degrees.  In this
case a warning is issued and type @code{'rotated} is used instead.
Ofcourse angles between @code{90} and @code{270} make no sense if a constant
height is wished.

Angles of type @code{(+ 90 (* n 180))} degrees are problematic generally,
because they would lead to scaling by zero.  As a workaround 0.0001 is used,
though even then there may be no visible output."

  (when (and (eq? type 'parallel) (= (cyclic-base-value ang 180.0) 90))
    (ly:warning
      "Preserving height, i.e. type 'parallel, is not possible with angel ~a,
using type 'rotated instead."
      ang)
      (set! type 'rotated))
     
  (let* ((preserve-height?
           (not (and (integer? (/ ang 90.0)) (odd? (/ ang 90.0)))))
         (half-ang (* 0.5 ang))
         ;; (ly:directed <angle>) returns a number-pair representing cosinus and
         ;; sinus of <angle> for a magnitude of length 1 (the default).
         (ang-cos (car (ly:directed ang)))
         (half-ang-coords (ly:directed half-ang))
         (half-ang-sin (cdr half-ang-coords))
         (half-ang-cos (car half-ang-coords))
         ;; Values for the second scaling
         (raw-px (- half-ang-cos half-ang-sin))
         (raw-py (+ half-ang-cos half-ang-sin))
         ;; Clumsy workaround: to avoid scaling by zero, take 0.0001 instead
         (px (if (zero? raw-px) 0.0001 raw-px))
         (py (if (zero? raw-py) 0.0001 raw-py))
         (stil (interpret-markup layout props arg))
         ;; To keep the height, scale `stil' in vertical direction.
         (stil-first-scaled
           (if (equal? type 'parallel)
               (ly:stencil-scale stil 1.0 (/ 1.0 ang-cos))
               stil))
         ;; Rotate the stencil 45 degrees (and ensure proper extents), in order
         ;; to apply the final scaling
         (raw-stil-first-rotated
           (ly:stencil-rotate-absolute stil-first-scaled 45.0 0.0 0.0))
         ;; Don't use ly:stencil-outline here and below, we want the extents
         ;; like skylines not boxes.
         (stil-first-rotated
           (ly:make-stencil
             (ly:stencil-expr raw-stil-first-rotated)
             (stencil-true-extent raw-stil-first-rotated X)
             (stencil-true-extent raw-stil-first-rotated Y)))
         ;; Apply the final scaling
         (stil-second-scaled (ly:stencil-scale stil-first-rotated px py))
         ;; Rotate back the stencil
         (raw-stil-second-rotated
           (ly:stencil-rotate-absolute
             stil-second-scaled (- -45.0 half-ang) 0.0 0.0)))

    ;; Return the ready stencil with proper extents.
    (ly:make-stencil
      (ly:stencil-expr raw-stil-second-rotated)
      (stencil-true-extent raw-stil-second-rotated X)
      (stencil-true-extent raw-stil-second-rotated Y))))
   
#(define-markup-command (skew-y layout props ang arg)
  (number? markup?)
  #:properties (skew-x-markup)
  "Same as @code{skew-x-markup}, but for Y-axis"
  (interpret-markup layout props
    (make-rotate-markup -90
      (make-skew-x-markup ang
        (make-rotate-markup 90 arg)))))  
       
polygon-I =
  \markup
    \override #'(filled . #f)
    \polygon #'((0 . 2) (10 . 0) (10 . 20) (0 . 22))
   
polygon-II =
  \markup
    \override #'(filled . #f)
    \polygon #'((0 . 0) (10 . 2) (10 . 22) (0 . 20))
   
polygon-III =
  \markup
    \override #'(filled . #f)
    \polygon #'((0 . 0) (10 . 0) (10 . 20) (0 . 20))


%{
\markup \fill-line {\overlay
#(map
    (lambda (n)
     (make-override-markup '(type . rotated)
      (make-skew-x-markup n polygon-I)))
    (iota 12 0 30))
}

\markup \fill-line {\overlay
#(map
    (lambda (n)
     (make-override-markup '(type . rotated)
      (make-skew-x-markup n polygon-II)))
    (iota 12 0 30))
}
%}

%%%%%%%%%%%%%%%%%%
%% skew-x parallel
%%%%%%%%%%%%%%%%%%

\markup
  \rounded-box {
    \fill-line {
      "Testing \\skew-x"
      "preserving height, i.e. type is 'parallel, the default"
      ""
    }
  }
 
\markup
  \fill-line {
    \overlay
      #(map
          (lambda (n)
            (make-skew-x-markup n polygon-III))
          '(-60 -30 0 30 60))
  }
 
%%%%%%%%%%%%%%%%%
%% skew-x rotated
%%%%%%%%%%%%%%%%%
\markup
  \rounded-box {
    \fill-line {
      "Testing \\skew-x"
      "with type 'rotated"
      ""
    }
  }

\markup
  \fill-line {
    \overlay
      #(map
          (lambda (n)
           (make-override-markup '(type . rotated)
            (make-skew-x-markup n polygon-III)))
          (iota 12 0 30))
  }
 
%%%%%%%%%%%%%%%%%%
%% skew-y rotated
%%%%%%%%%%%%%%%%%%

\markup
  \rounded-box {
    \fill-line {
      "Testing \\skew-y"
      "with type 'rotated"
      ""
    }
  }

\markup
  \fill-line \override #'(type . rotated) {
    \skew-y #-60 \polygon-III
    \skew-y #-30 \polygon-III
    \skew-y #0 \polygon-III
    \skew-y #30 \polygon-III
    \skew-y #60 \polygon-III
  }

%%%%%%%%%%%%%%%%%%
%% skew-y parallel
%%%%%%%%%%%%%%%%%%


\markup
  \rounded-box {
    \fill-line {
      "Testing \\skew-y"
      "with type 'parallel"
      ""
    }
  }

\markup
  \fill-line {
    \skew-y #-60 \polygon-III
    \skew-y #-30 \polygon-III
    \skew-y #0 \polygon-III
    \skew-y #30 \polygon-III
    \skew-y #60 \polygon-III
  }
 
%%%%%%%%%%%%%%%%%%
%% other tests
%%%%%%%%%%%%%%%%%%

\markup
  \fontsize #8
  \overlay {
      \with-color #'(0.6 0.6 0.6)
      \override #'(type . rotated)
      \skew-x #60 "Hello World!"
      "Hello World!"
  }

Gruß,
  Harm

Arnold

Hallo Harm und Manuela,

ich hätte übrigens sogar noch eine Variante zum überschreiben alternativer Stencil-Abmessungen:
(ly:make-stencil (ly:stencil-expr stil-second-rotated) (cons xmin xmax) (cons ymin ymax))
Ich könnte mich heransetzen und eine Beschreibung aufsetzen, schließlich stammt die Realisierungs-Methode aus meinem Kopf. Ob ich immer die richtigen englischsprachigen Fachbegriffe dann finde, sei einmal dahingestellt. Und die Beschreibung dürfte eher ausladender als knapp und bündig werden:
1. Begriffsklärung der Scherung
   a) veranschaulicht als Parallelverschiebung, Verschiebung proportional zum Abstand von der Affinitätsachse
   b) veranschaulicht über außer-Lot-Drehung des Ursprungskoordinatensystem
   c) Transformationmatrizen beider Arten und Umwandlung zueinander
2. Realisierung
   a) Um ein ly:transform?-Objekt auf einen stencil anzuwenden, steht leider keine SCHEME-Funktion zur Verfügung
   b) Anschauliche Beschreibung mittels u-v-Vektoren (Ursprungskoordinatensystem) über eine Sequenz aus Rotationen und anisotroper Skalierung um diese Scherungs-Transformation nachzubilden.

Übrigens:
Wer einmal technisches Zeichnen (in der Schule?) gelernt hat, kennt wohl noch die einfachen isometrischen und dimetrischen Darstellungen eines Quaders. Die sichtbaren Oberflächen sind als solch eine Scherung (radialen Typs) abzubilden - aber meistens sollte noch eine Rotation (um den absoluten Nullpunkt) angefügt werden, um diese Elemente anschließend an die gewünschte Position zu verschieben.

Ich könnte mir ein Titelblatt mit in solcher Methodik gestalteten »beschrifteten Bauklötzchen« vorstellen (auch mit Noten beschriftet!).

Dazu würde ich wohl nur noch noch ein paar kleine und einfache Hilfsroutinen benötigen:
\rotate mit der Möglichkeit, den Rotationsnullpunkt absolut anzugeben.
ein translate-edge-of-bounding-box-to-origin (wo die absolute Nullpunkt für Folgeoperationen liegen wird)
und eine einfache 3D-zu-2D-Positionsumrechnung (Isometrisch [30°/30°] und Dimetrisch [7°/42°])

Arnold


Arnold

So zusammen,

nun eine wenig Beschreibungstext, sicher noch mit genügend Schreibfehlern:

A geometric transformation which may be described in two ways:
a) Shear Mapping: Cut your image in small stripes parallel to your
    base axis (e.g. X axis), then move them parallel to your base axis,
    the distance is proportional to the distance from the base axis.
b) Skew Coordinates: See your input image as a geometric list by the
    cartesian coordiate system relative to your base axes (e.g. X axis
    and perpendicular the Y axis) - name these axes U resp. V. Now the
    V axis will be rotated to manipulate your image. Your U-V coordiante
    system is no longer cartesian, because it's no longer pependicular.
Tranformation matrix for an angle @var{w}, default direction, type a:
    xx = 1.0    xy = tan(w)
    yx = 0.0    yy = 1.0
Tranformation matrix for an angle @var{w}, default direction, type b:
    xx = 1.0    xy = sin(w)
    yx = 0.0    yy = cos(w)
Relation from type b to type a: the original Y axis (V axis) is
scaled by 1/cos(w)

Technically there is no SCHEME function available to apply a
ly:transform? object to a stencil. Therefore roation and scale
statements are used to fullfill the task.
Once the U and V axis are properly scaled, the stencil is rotated so
the U axis directs 45° up to the right and the V axis directs 45° up to
the left. A properly choosen scale factor pair will rotate both axes
together (or away from each other) by a specified angle without scaling
these axes. A final back rotation completes the sequence.


Arnold.