Trasformazione di ridimensionamento

Download Sample Scaricare l'esempio

Individuare la trasformazione della scala SkiaSharp per ridimensionare gli oggetti in varie dimensioni

Come si è visto nell'articolo Traduzione trasformazione , la trasformazione traduci può spostare un oggetto grafico da una posizione a un'altra. Al contrario, la trasformazione della scala modifica le dimensioni dell'oggetto grafico:

A tall word scaled in size

La trasformazione della scala spesso comporta lo spostamento delle coordinate grafiche man mano che vengono rese più grandi.

In precedenza sono state illustrate due formule di trasformazione che descrivono gli effetti dei fattori di traslazione di dx e dy:

x' = x + dx

y' = y + dy

I fattori di scala di sx e sy sono moltiplicativi anziché additivi:

x' = sx · X

y' = sy · Y

I valori predefiniti dei fattori di conversione sono 0; i valori predefiniti dei fattori di scala sono 1.

La SKCanvas classe definisce quattro Scale metodi. Il primo Scale metodo è per i casi in cui si desidera lo stesso fattore di ridimensionamento orizzontale e verticale:

public void Scale (Single s)

Questo è noto come ridimensionamento isotropico - ridimensionamento che è lo stesso in entrambe le direzioni. La scalabilità isotropica mantiene le proporzioni dell'oggetto.

Il secondo Scale metodo consente di specificare valori diversi per la scalabilità orizzontale e verticale:

public void Scale (Single sx, Single sy)

Questo comporta un ridimensionamento anisotropico . Il terzo Scale metodo combina i due fattori di ridimensionamento in un singolo SKPoint valore:

public void Scale (SKPoint size)

Il quarto Scale metodo verrà descritto a breve.

La pagina Scala di base illustra il Scale metodo . Il file BasicScalePage.xaml contiene due Slider elementi che consentono di selezionare fattori di ridimensionamento orizzontale e verticale compresi tra 0 e 10. Il file code-behind BasicScalePage.xaml.cs usa tali valori da chiamare Scale prima di visualizzare un rettangolo arrotondato con una linea tratteggiata e ridimensionato per adattare un testo nell'angolo superiore sinistro dell'area di disegno:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] {  7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        canvas.Scale((float)xScaleSlider.Value,
                     (float)yScaleSlider.Value);

        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);

        float margin = 10;
        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

Ci si potrebbe chiedere: in che modo i fattori di ridimensionamento influiscono sul valore restituito dal MeasureText metodo di SKPaint? La risposta è: Non affatto. Scale è un metodo di SKCanvas. Non influisce su qualsiasi operazione eseguita con un SKPaint oggetto fino a quando non si usa tale oggetto per eseguire il rendering di un elemento nell'area di disegno.

Come si può notare, tutto ciò che viene disegnato dopo la Scale chiamata aumenta proporzionalmente:

Triple screenshot of the Basic Scale page

Il testo, la larghezza della linea tratteggiata, la lunghezza dei trattini in tale linea, l'arrotondamento degli angoli e il margine di 10 pixel tra i bordi sinistro e superiore dell'area di disegno e il rettangolo arrotondato sono tutti soggetti agli stessi fattori di ridimensionamento.

Importante

Il piattaforma UWP (Universal Windows Platform) non esegue correttamente il rendering di testo a scalabilità anisotropica.

Il ridimensionamento anisotropico fa sì che la larghezza del tratto diventi diversa per le linee allineate agli assi orizzontali e verticali. Questo è evidente anche dalla prima immagine in questa pagina. Se non si vuole che la larghezza del tratto sia influenzata dai fattori di ridimensionamento, impostarla su 0 e sarà sempre un pixel largo indipendentemente dall'impostazione Scale .

Il ridimensionamento è relativo all'angolo superiore sinistro dell'area di disegno. Questo potrebbe essere esattamente quello che vuoi, ma potrebbe non essere. Si supponga di voler posizionare il testo e il rettangolo in un'altra posizione nell'area di disegno e di ridimensionarlo rispetto al centro. In tal caso è possibile usare la quarta versione del Scale metodo , che include due parametri aggiuntivi per specificare il centro del ridimensionamento:

public void Scale (Single sx, Single sy, Single px, Single py)

I px parametri e py definiscono un punto a volte denominato centro di ridimensionamento, ma nella documentazione skiaSharp viene definito punto pivot. Si tratta di un punto relativo all'angolo superiore sinistro dell'area di disegno che non è interessato dal ridimensionamento. Tutto il ridimensionamento si verifica in relazione al centro.

La pagina Scala allineata al centro mostra come funziona. Il PaintSurface gestore è simile al programma Di base, ad eccezione del fatto che il margin valore viene calcolato al centro del testo orizzontalmente, il che implica che il programma funziona meglio in modalità verticale:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear(SKColors.SkyBlue);

    using (SKPaint strokePaint = new SKPaint
    {
        Style = SKPaintStyle.Stroke,
        Color = SKColors.Red,
        StrokeWidth = 3,
        PathEffect = SKPathEffect.CreateDash(new float[] { 7, 7 }, 0)
    })
    using (SKPaint textPaint = new SKPaint
    {
        Style = SKPaintStyle.Fill,
        Color = SKColors.Blue,
        TextSize = 50
    })
    {
        SKRect textBounds = new SKRect();
        textPaint.MeasureText(Title, ref textBounds);
        float margin = (info.Width - textBounds.Width) / 2;

        float sx = (float)xScaleSlider.Value;
        float sy = (float)yScaleSlider.Value;
        float px = margin + textBounds.Width / 2;
        float py = margin + textBounds.Height / 2;

        canvas.Scale(sx, sy, px, py);

        SKRect borderRect = SKRect.Create(new SKPoint(margin, margin), textBounds.Size);
        canvas.DrawRoundRect(borderRect, 20, 20, strokePaint);
        canvas.DrawText(Title, margin, -textBounds.Top + margin, textPaint);
    }
}

L'angolo superiore sinistro del rettangolo arrotondato è posizionato in margin pixel a sinistra dell'area di disegno e margin dei pixel dall'alto. Gli ultimi due argomenti del Scale metodo vengono impostati su tali valori più la larghezza e l'altezza del testo, che è anche la larghezza e l'altezza del rettangolo arrotondato. Ciò significa che tutto il ridimensionamento è relativo al centro del rettangolo:

Triple screenshot of the Centered Scale page

Gli Slider elementi di questo programma hanno un intervallo compreso tra -10 e 10. Come si può notare, i valori negativi della scalabilità verticale (ad esempio sullo schermo Android al centro) causano il capovolgimento degli oggetti intorno all'asse orizzontale che passa attraverso il centro della scalabilità. I valori negativi della scalabilità orizzontale (ad esempio nella schermata UWP a destra) causano lo scorrimento degli oggetti attorno all'asse verticale che passa attraverso il centro del ridimensionamento.

La versione del Scale metodo con punti pivot è un collegamento per una serie di tre Translate chiamate e Scale . È possibile visualizzare il funzionamento di questo metodo sostituendo il Scale metodo nella pagina Scala al centro con quanto segue:

canvas.Translate(-px, -py);

Si tratta dei negativi delle coordinate del punto pivot.

Eseguire nuovamente il programma. Si noterà che il rettangolo e il testo vengono spostati in modo che il centro si trovi nell'angolo superiore sinistro dell'area di disegno. È possibile vederlo a malapena. I dispositivi di scorrimento non funzionano naturalmente perché ora il programma non viene ridimensionato affatto.

Aggiungere ora la chiamata di base Scale (senza un centro di scalabilità) prima di tale Translate chiamata:

canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

Se hai familiarità con questo esercizio in altri sistemi di programmazione grafica, potresti pensare che sia sbagliato, ma non lo è. Skia gestisce le chiamate di trasformazione successive in modo leggermente diverso da quello con cui si potrebbe avere familiarità.

Con le chiamate e Translate successiveScale, il centro del rettangolo arrotondato è ancora nell'angolo superiore sinistro, ma è ora possibile ridimensionarlo rispetto all'angolo superiore sinistro dell'area di disegno, che è anche il centro del rettangolo arrotondato.

A questo punto, prima della Scale chiamata, aggiungere un'altra Translate chiamata con i valori centrati:

canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);

In questo modo il risultato ridimensionato viene spostato nella posizione originale. Queste tre chiamate sono equivalenti a:

canvas.Scale(sx, sy, px, py);

Le singole trasformazioni vengono composte in modo che la formula di trasformazione totale sia:

x' = sx · (x – px) + px

y' = sy · (y – py) + py

Tenere presente che i valori predefiniti di sx e sy sono 1. È facile convincere se stessi che il punto pivot (px, py) non è trasformato da queste formule. Rimane nella stessa posizione rispetto all'area di disegno.

Quando si combinano Translate e Scale si chiama, l'ordine è importante. Se viene Translate dopo , Scalei fattori di traslazione vengono effettivamente ridimensionati dai fattori di ridimensionamento. Se precede TranslateScale, i fattori di conversione non vengono ridimensionati. Questo processo diventa un po 'più chiaro (anche se più matematico) quando viene introdotto il soggetto delle matrici di trasformazione.

La SKPath classe definisce una proprietà di sola Bounds lettura che restituisce un oggetto SKRect che definisce l'extent delle coordinate nel percorso. Ad esempio, quando la Bounds proprietà viene ottenuta dal percorso hendecagram creato in precedenza, le Left proprietà e Top del rettangolo sono circa -100, le Right proprietà e Bottom sono circa 100 e le Width proprietà e Height sono circa 200. La maggior parte dei valori effettivi è leggermente inferiore perché i punti delle stelle sono definiti da un cerchio con un raggio di 100, ma solo il punto superiore è parallelo con gli assi orizzontali o verticali.

La disponibilità di queste informazioni implica che dovrebbe essere possibile derivare la scala e tradurre i fattori adatti per ridimensionare un percorso alle dimensioni dell'area di disegno. La pagina Scala anisotropica dimostra questo con la stella a 11 punte. Una scala anisotropica significa che è diverso nelle direzioni orizzontali e verticali, il che significa che la stella non manterrà le proporzioni originali. Ecco il codice pertinente nel PaintSurface gestore:

SKPath path = HendecagramPage.HendecagramPath;
SKRect pathBounds = path.Bounds;

using (SKPaint fillPaint = new SKPaint
{
    Style = SKPaintStyle.Fill,
    Color = SKColors.Pink
})
using (SKPaint strokePaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 3,
    StrokeJoin = SKStrokeJoin.Round
})
{
    canvas.Scale(info.Width / pathBounds.Width,
                 info.Height / pathBounds.Height);
    canvas.Translate(-pathBounds.Left, -pathBounds.Top);

    canvas.DrawPath(path, fillPaint);
    canvas.DrawPath(path, strokePaint);
}

Il pathBounds rettangolo viene ottenuto nella parte superiore di questo codice e quindi usato in seguito con la larghezza e l'altezza dell'area di disegno nella Scale chiamata. Tale chiamata in modo autonomo ridimensiona le coordinate del percorso quando viene eseguito il DrawPath rendering dalla chiamata, ma la stella verrà allineata al centro nell'angolo superiore destro dell'area di disegno. Deve essere spostato verso il basso e verso sinistra. Questo è il processo della Translate chiamata. Queste due proprietà di sono circa -100, quindi i fattori di pathBounds traslazione sono circa 100. Poiché la Translate chiamata è dopo la Scale chiamata, tali valori vengono ridimensionati in modo efficace in base ai fattori di ridimensionamento, in modo da spostare il centro della stella al centro dell'area di disegno:

Triple screenshot of the Anisotropic Scaling page

Un altro modo per pensare alle Scale chiamate e Translate consiste nel determinare l'effetto nella sequenza inversa: la Translate chiamata sposta il percorso in modo che diventi completamente visibile ma orientato nell'angolo superiore sinistro dell'area di disegno. Il Scale metodo rende quindi la stella più grande rispetto all'angolo superiore sinistro.

In realtà, sembra che la stella sia un po ' più grande della tela. Il problema è la larghezza del tratto. La Bounds proprietà di SKPath indica le dimensioni delle coordinate codificate nel percorso ed è ciò che il programma usa per ridimensionarlo. Quando viene eseguito il rendering del percorso con una particolare larghezza del tratto, il percorso sottoposto a rendering è maggiore dell'area di disegno.

Per risolvere questo problema, è necessario compensare questo problema. Un approccio semplice in questo programma consiste nell'aggiungere l'istruzione seguente subito prima della Scale chiamata:

pathBounds.Inflate(strokePaint.StrokeWidth / 2,
                   strokePaint.StrokeWidth / 2);

In questo modo il pathBounds rettangolo viene a 1,5 unità su tutti e quattro i lati. Si tratta di una soluzione ragionevole solo quando il tratto join viene arrotondato. Un miter join può essere più lungo ed è difficile da calcolare.

È anche possibile utilizzare una tecnica simile con il testo, come illustrato nella pagina Anisotropic Text . Ecco la parte pertinente del PaintSurface gestore della AnisotropicTextPage classe :

using (SKPaint textPaint = new SKPaint
{
    Style = SKPaintStyle.Stroke,
    Color = SKColors.Blue,
    StrokeWidth = 0.1f,
    StrokeJoin = SKStrokeJoin.Round
})
{
    SKRect textBounds = new SKRect();
    textPaint.MeasureText("HELLO", ref textBounds);

    // Inflate bounds by the stroke width
    textBounds.Inflate(textPaint.StrokeWidth / 2,
                       textPaint.StrokeWidth / 2);

    canvas.Scale(info.Width / textBounds.Width,
                 info.Height / textBounds.Height);
    canvas.Translate(-textBounds.Left, -textBounds.Top);

    canvas.DrawText("HELLO", 0, 0, textPaint);
}

È una logica simile e il testo si espande fino alla dimensione della pagina in base al rettangolo dei limiti di testo restituito ( MeasureText che è leggermente più grande del testo effettivo):

Triple screenshot of the Anisotropic Test page

Se è necessario mantenere le proporzioni degli oggetti grafici, è consigliabile usare il ridimensionamento isotropico. La pagina Isotropic Scaling illustra questo valore per la stella a 11 punte. Concettualmente, i passaggi per la visualizzazione di un oggetto grafico al centro della pagina con ridimensionamento isotropico sono:

  • Convertire il centro dell'oggetto grafico nell'angolo superiore sinistro.
  • Scalare l'oggetto in base alle dimensioni minime delle pagine orizzontali e verticali divise per le dimensioni dell'oggetto grafico.
  • Convertire il centro dell'oggetto ridimensionato al centro della pagina.

IsotropicScalingPage Esegue questi passaggi in ordine inverso prima di visualizzare la stella:

void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
    SKImageInfo info = args.Info;
    SKSurface surface = args.Surface;
    SKCanvas canvas = surface.Canvas;

    canvas.Clear();

    SKPath path = HendecagramArrayPage.HendecagramPath;
    SKRect pathBounds = path.Bounds;

    using (SKPaint fillPaint = new SKPaint())
    {
        fillPaint.Style = SKPaintStyle.Fill;

        float scale = Math.Min(info.Width / pathBounds.Width,
                               info.Height / pathBounds.Height);

        for (int i = 0; i <= 10; i++)
        {
            fillPaint.Color = new SKColor((byte)(255 * (10 - i) / 10),
                                          0,
                                          (byte)(255 * i / 10));
            canvas.Save();
            canvas.Translate(info.Width / 2, info.Height / 2);
            canvas.Scale(scale);
            canvas.Translate(-pathBounds.MidX, -pathBounds.MidY);
            canvas.DrawPath(path, fillPaint);
            canvas.Restore();

            scale *= 0.9f;
        }
    }
}

Il codice visualizza anche la stella 10 volte più volte, ogni volta che diminuisce il fattore di ridimensionamento del 10% e cambiando progressivamente il colore da rosso a blu:

Triple screenshot of the Isotropic Scaling page