A transformação de escala
Descubra a transformação de escala SkiaSharp para dimensionar objetos para vários tamanhos
Como você viu no artigo The Translate Transform , a transformação de tradução pode mover um objeto gráfico de um local para outro. Por outro lado, a transformação de escala altera o tamanho do objeto gráfico:
A transformação em escala também geralmente faz com que as coordenadas gráficas se movam à medida que são aumentadas.
Anteriormente, você viu duas fórmulas de transformação que descrevem os efeitos dos fatores de tradução de dx
e dy
:
x' = x + dx
y' = y + dy
Fatores de escala de sx
e sy
são multiplicativos em vez de aditivos:
x' = sx · x
y' = sy · y
Os valores padrão dos fatores de conversão são 0; Os valores padrão dos fatores de escala são 1.
A SKCanvas
classe define quatro Scale
métodos. O primeiro Scale
método é para casos em que você deseja o mesmo fator de dimensionamento horizontal e vertical:
public void Scale (Single s)
Isso é conhecido como escala isotrópica — escala que é a mesma em ambas as direções. A escala isotrópica preserva a proporção do objeto.
O segundo Scale
método permite especificar valores diferentes para dimensionamento horizontal e vertical:
public void Scale (Single sx, Single sy)
Isso resulta em descamação anisotrópica .
O terceiro Scale
método combina os dois fatores de dimensionamento em um único SKPoint
valor:
public void Scale (SKPoint size)
O quarto Scale
método será descrito em breve.
A página Escala Básica demonstra o Scale
método. O arquivo BasicScalePage.xaml contém dois Slider
elementos que permitem selecionar fatores de dimensionamento horizontal e vertical entre 0 e 10. O arquivo code-behind BasicScalePage.xaml.cs usa esses valores para chamar Scale
antes de exibir um retângulo arredondado traçado com uma linha tracejada e dimensionado para caber algum texto no canto superior esquerdo da tela:
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);
}
}
Você pode se perguntar: Como os fatores de dimensionamento afetam o valor retornado do MeasureText
método de SKPaint
? A resposta é: não. Scale
é um método de SKCanvas
. Ele não afeta nada que você faz com um SKPaint
objeto até que você use esse objeto para renderizar algo na tela.
Como você pode ver, tudo sorteado após a Scale
chamada aumenta proporcionalmente:
O texto, a largura da linha tracejada, o comprimento dos traços nessa linha, o arredondamento dos cantos e a margem de 10 pixels entre as bordas esquerda e superior da tela e o retângulo arredondado estão sujeitos aos mesmos fatores de escala.
Importante
A Plataforma Universal do Windows não processa corretamente texto dimensionado anisotrópico.
A escala anisotrópica faz com que a largura do curso se torne diferente para linhas alinhadas com os eixos horizontal e vertical. (Isso também fica evidente na primeira imagem desta página.) Se você não quiser que a largura do traçado seja afetada pelos fatores de escala, defina-a como 0 e ela sempre terá um pixel de largura, independentemente da Scale
configuração.
O dimensionamento é relativo ao canto superior esquerdo da tela. Isso pode ser exatamente o que você quer, mas pode não ser. Suponha que você queira posicionar o texto e o retângulo em outro lugar na tela e queira dimensioná-lo em relação ao seu centro. Nesse caso, você pode usar a Scale
quarta versão do método, que inclui dois parâmetros adicionais para especificar o centro de dimensionamento:
public void Scale (Single sx, Single sy, Single px, Single py)
Os px
parâmetros e py
definem um ponto que às vezes é chamado de centro de dimensionamento, mas na documentação do SkiaSharp é chamado de ponto de pivô. Esse é um ponto relativo ao canto superior esquerdo da tela que não é afetado pelo dimensionamento. Todo o dimensionamento ocorre em relação a esse centro.
A página Escala Centrada mostra como isso funciona. O PaintSurface
manipulador é semelhante ao programa Basic Scale , exceto que o margin
valor é calculado para centralizar o texto horizontalmente, o que implica que o programa funciona melhor no modo retrato:
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);
}
}
O canto superior esquerdo do retângulo arredondado é posicionado margin
pixels a partir da esquerda da tela e margin
pixels a partir da parte superior. Os dois últimos argumentos para o Scale
método são definidos para esses valores mais a largura e altura do texto, que também é a largura e altura do retângulo arredondado. Isso significa que todo o dimensionamento é relativo ao centro desse retângulo:
Os Slider
elementos neste programa têm um intervalo de –10 a 10. Como você pode ver, os valores negativos do dimensionamento vertical (como na tela do Android no centro) fazem com que os objetos girem em torno do eixo horizontal que passa pelo centro do dimensionamento. Valores negativos de dimensionamento horizontal (como na tela UWP à direita) fazem com que os objetos invertam o eixo vertical que passa pelo centro do dimensionamento.
A versão do Scale
método com pontos de pivô é um atalho para uma série de três Translate
e Scale
chamadas. Talvez você queira ver como isso funciona substituindo o Scale
método na página Escala Centrada pelo seguinte:
canvas.Translate(-px, -py);
Estes são os pontos negativos das coordenadas do ponto de pivô.
Agora, execute o programa novamente. Você verá que o retângulo e o texto são deslocados para que o centro fique no canto superior esquerdo da tela. Você mal consegue vê-lo. Os controles deslizantes não funcionam, é claro, porque agora o programa não escala nada.
Agora adicione a chamada básica Scale
(sem um centro de dimensionamento) antes dessa Translate
chamada:
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);
Se você está familiarizado com este exercício em outros sistemas de programação gráfica, você pode pensar que está errado, mas não está. O Skia lida com chamadas de transformação sucessivas de forma um pouco diferente do que você pode estar familiarizado.
Com as sucessivas Scale
e Translate
chamadas, o centro do retângulo arredondado ainda está no canto superior esquerdo, mas agora você pode dimensioná-lo em relação ao canto superior esquerdo da tela, que também é o centro do retângulo arredondado.
Agora, antes dessa Scale
chamada, adicione outra Translate
chamada com os valores de centralização:
canvas.Translate(px, py);
canvas.Scale(sx, sy);
canvas.Translate(–px, –py);
Isso move o resultado reduzido de volta para a posição original. Essas três chamadas equivalem a:
canvas.Scale(sx, sy, px, py);
As transformações individuais são compostas de modo que a fórmula de transformação total seja:
x' = sx · (x – px) + px
y' = sy · (y – py) + py
Lembre-se de que os valores padrão de sx
e sy
são 1. É fácil se convencer de que o ponto de pivô (px, py) não é transformado por essas fórmulas. Ele permanece no mesmo local em relação à tela.
Quando você combina Translate
e Scale
liga, a ordem é importante. Se o Translate
vem depois do Scale
, os fatores de tradução são efetivamente dimensionados pelos fatores de escala. Se o Translate
vem antes do Scale
, os fatores de tradução não são dimensionados. Esse processo torna-se um pouco mais claro (embora mais matemático) quando o assunto das matrizes transformadoras é introduzido.
A SKPath
classe define uma propriedade somente Bounds
leitura que retorna uma SKRect
definição da extensão das coordenadas no caminho. Por exemplo, quando a Bounds
propriedade é obtida do caminho hendecagram criado anteriormente, as Left
propriedades e Top
do retângulo são aproximadamente –100, as Right
propriedades e Bottom
são aproximadamente 100 e as Width
propriedades e Height
são aproximadamente 200. (A maioria dos valores reais são um pouco menores porque os pontos das estrelas são definidos por um círculo com um raio de 100, mas apenas o ponto superior é paralelo com os eixos horizontal ou vertical.)
A disponibilidade dessas informações implica que deve ser possível derivar escala e traduzir fatores adequados para dimensionar um caminho para o tamanho da tela. A página Anisotropic Scaling demonstra isso com a estrela de 11 pontas. Uma escala anisotrópica significa que ela é desigual nas direções horizontal e vertical, o que significa que a estrela não manterá sua proporção original. Aqui está o código relevante no PaintSurface
manipulador:
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);
}
O pathBounds
retângulo é obtido perto da parte superior desse código e, em seguida, usado posteriormente com a largura e a altura da tela na Scale
chamada. Essa chamada por si só dimensionará as coordenadas do caminho quando for renderizada pela DrawPath
chamada, mas a estrela será centralizada no canto superior direito da tela. Precisa ser deslocado para baixo e para a esquerda. Esse é o trabalho da Translate
chamada. Essas duas propriedades de são aproximadamente –100, então os fatores de tradução são cerca de pathBounds
100. Como a Translate
chamada é posterior à Scale
chamada, esses valores são efetivamente dimensionados pelos fatores de dimensionamento, de modo que movem o centro da estrela para o centro da tela:
Outra maneira de pensar sobre as Scale
chamadas e Translate
é determinar o efeito na sequência inversa: a Translate
chamada desloca o caminho para que ele se torne totalmente visível, mas orientado no canto superior esquerdo da tela. O Scale
método, então, torna essa estrela maior em relação ao canto superior esquerdo.
Na verdade, parece que a estrela é um pouco maior do que a tela. O problema é a largura do traçado. A Bounds
propriedade de SKPath
indica as dimensões das coordenadas codificadas no caminho, e é isso que o programa usa para dimensioná-lo. Quando o caminho é renderizado com uma largura de traçado específica, o caminho renderizado é maior do que a tela.
Para corrigir esse problema, você precisa compensar isso. Uma abordagem fácil neste programa é adicionar a seguinte instrução antes da Scale
chamada:
pathBounds.Inflate(strokePaint.StrokeWidth / 2,
strokePaint.StrokeWidth / 2);
Isso aumenta o pathBounds
retângulo em 1,5 unidades nos quatro lados. Esta é uma solução razoável apenas quando a junção do traçado é arredondada. Uma junção de esquadria pode ser mais longa e é difícil de calcular.
Você também pode usar uma técnica semelhante com texto, como demonstra a página Texto anisotrópico. Aqui está a parte relevante do PaintSurface
manipulador da 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);
}
É uma lógica semelhante, e o texto se expande para o tamanho da página com base no retângulo de limites de texto retornado de MeasureText
(que é um pouco maior do que o texto real):
Se você precisar preservar a proporção dos objetos gráficos, convém usar o dimensionamento isotrópico. A página Isotropic Scaling demonstra isso para a estrela de 11 pontas. Conceitualmente, as etapas para exibir um objeto gráfico no centro da página com escala isotrópica são:
- Traduza o centro do objeto gráfico para o canto superior esquerdo.
- Dimensione o objeto com base no mínimo das dimensões horizontais e verticais da página divididas pelas dimensões do objeto gráfico.
- Traduza o centro do objeto dimensionado para o centro da página.
O IsotropicScalingPage
executa estas etapas na ordem inversa antes de exibir a estrela:
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;
}
}
}
O código também exibe a estrela mais 10 vezes, cada vez diminuindo o fator de escala em 10% e mudando progressivamente a cor de vermelho para azul: