Criando e desenhando em bitmaps SkiaSharp
Você viu como um aplicativo pode carregar bitmaps da Web, dos recursos do aplicativo e da biblioteca de fotos do usuário. Também é possível criar novos bitmaps em seu aplicativo. A abordagem mais simples envolve um dos construtores de SKBitmap
:
SKBitmap bitmap = new SKBitmap(width, height);
Os width
parâmetros e height
são inteiros e especificam as dimensões em pixels do bitmap. Este construtor cria um bitmap colorido com quatro bytes por pixel: um byte cada para os componentes vermelho, verde, azul e alfa (opacidade).
Depois de criar um novo bitmap, você precisa obter algo na superfície do bitmap. Você geralmente faz isso de duas maneiras:
- Desenhe no bitmap usando métodos de desenho padrão
Canvas
. - Acesse os bits de pixel diretamente.
Este artigo demonstra a primeira abordagem:
A segunda abordagem é discutida no artigo Acessando pixels de bitmap SkiaSharp.
Desenhando no bitmap
Desenhar na superfície de um bitmap é o mesmo que desenhar em uma exibição de vídeo. Para desenhar em uma exibição de vídeo, você obtém um SKCanvas
objeto dos PaintSurface
argumentos de evento. Para desenhar em um bitmap, crie um SKCanvas
objeto usando o SKCanvas
construtor:
SKCanvas canvas = new SKCanvas(bitmap);
Quando terminar de desenhar no bitmap, você poderá descartar o SKCanvas
objeto. Por esta razão, o SKCanvas
construtor é geralmente chamado em uma using
declaração:
using (SKCanvas canvas = new SKCanvas(bitmap))
{
··· // call drawing function
}
O bitmap pode então ser exibido. Posteriormente, o programa pode criar um novo SKCanvas
objeto com base nesse mesmo bitmap e desenhar nele um pouco mais.
A página Hello Bitmap no aplicativo de exemplo grava o texto "Hello, Bitmap!" em um bitmap e, em seguida, exibe esse bitmap várias vezes.
O construtor do HelloBitmapPage
começa criando um SKPaint
objeto para exibir texto. Ele determina as dimensões de uma cadeia de caracteres de texto e cria um bitmap com essas dimensões. Em seguida, ele cria um SKCanvas
objeto com base nesse bitmap, chama e Clear
, em seguida, chama DrawText
. É sempre uma boa ideia chamar Clear
com um novo bitmap porque um bitmap recém-criado pode conter dados aleatórios.
O construtor conclui criando um SKCanvasView
objeto para exibir o bitmap:
public partial class HelloBitmapPage : ContentPage
{
const string TEXT = "Hello, Bitmap!";
SKBitmap helloBitmap;
public HelloBitmapPage()
{
Title = TEXT;
// Create bitmap and draw on it
using (SKPaint textPaint = new SKPaint { TextSize = 48 })
{
SKRect bounds = new SKRect();
textPaint.MeasureText(TEXT, ref bounds);
helloBitmap = new SKBitmap((int)bounds.Right,
(int)bounds.Height);
using (SKCanvas bitmapCanvas = new SKCanvas(helloBitmap))
{
bitmapCanvas.Clear();
bitmapCanvas.DrawText(TEXT, 0, -bounds.Top, textPaint);
}
}
// Create SKCanvasView to view result
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear(SKColors.Aqua);
for (float y = 0; y < info.Height; y += helloBitmap.Height)
for (float x = 0; x < info.Width; x += helloBitmap.Width)
{
canvas.DrawBitmap(helloBitmap, x, y);
}
}
}
O PaintSurface
manipulador renderiza o bitmap várias vezes em linhas e colunas da exibição. Observe que o Clear
método no PaintSurface
manipulador tem um argumento de SKColors.Aqua
, que colore o plano de fundo da superfície de exibição:
A aparência do fundo aqua revela que o bitmap é transparente, exceto para o texto.
Compensação e transparência
A exibição da página Hello Bitmap demonstra que o bitmap que o programa criou é transparente, exceto para o texto preto. É por isso que a cor aqua da superfície da tela aparece.
A documentação dos Clear
métodos de SKCanvas
descreve-os com a instrução: "Substitui todos os pixels no clipe atual da tela". O uso da palavra "substitui" revela uma característica importante desses métodos: todos os métodos de desenho de SKCanvas
adicionar algo à superfície de exibição existente. Os Clear
métodos substituem o que já existe.
Clear
existe em duas versões diferentes:
O
Clear
método com umSKColor
parâmetro substitui os pixels da superfície de exibição por pixels dessa cor.O
Clear
método sem parâmetros substitui os pixels pelaSKColors.Empty
cor, que é uma cor na qual todos os componentes (vermelho, verde, azul e alfa) são definidos como zero. Essa cor às vezes é chamada de "preto transparente".
Chamar Clear
sem argumentos em um novo bitmap inicializa o bitmap inteiro para ser totalmente transparente. Qualquer coisa desenhada posteriormente no bitmap geralmente será opaca ou parcialmente opaca.
Aqui está algo para tentar: Na página Hello Bitmap , substitua o Clear
método aplicado ao bitmapCanvas
por este:
bitmapCanvas.Clear(new SKColor(255, 0, 0, 128));
A ordem dos SKColor
parâmetros do construtor é vermelho, verde, azul e alfa, onde cada valor pode variar de 0 a 255. Lembre-se de que um valor alfa de 0 é transparente, enquanto um valor alfa de 255 é opaco.
O valor (255, 0, 0, 128) limpa os pixels de bitmap para pixels vermelhos com uma opacidade de 50%. Isso significa que o plano de fundo do bitmap é semitransparente. O fundo vermelho semitransparente do bitmap combina com o plano de fundo aquático da superfície de exibição para criar um plano de fundo cinza.
Tente definir a cor do texto como preto transparente colocando a SKPaint
seguinte atribuição no inicializador:
Color = new SKColor(0, 0, 0, 0)
Você pode pensar que esse texto transparente criaria áreas totalmente transparentes do bitmap através das quais você veria o plano de fundo aquático da superfície de exibição. Mas não é assim. O texto é desenhado sobre o que já está no bitmap. O texto transparente não será visível.
Nenhum Draw
método torna um bitmap mais transparente. Só Clear
pode fazer isso.
Tipos de cores de bitmap
O construtor mais SKBitmap
simples permite que você especifique uma largura e altura de pixel inteiro para o bitmap. Outras SKBitmap
construtoras são mais complexas. Esses construtores exigem argumentos de dois tipos de enumeração: SKColorType
e SKAlphaType
. Outros construtores utilizam a SKImageInfo
estrutura, que consolida essas informações.
A SKColorType
enumeração tem 9 membros. Cada um desses membros descreve uma maneira particular de armazenar os pixels de bitmap:
Unknown
Alpha8
— cada pixel é de 8 bits, representando um valor alfa de totalmente transparente para totalmente opacoRgb565
— cada pixel é de 16 bits, 5 bits para vermelho e azul, e 6 para verdeArgb4444
— cada pixel é de 16 bits, 4 cada para alfa, vermelho, verde e azulRgba8888
— cada pixel é de 32 bits, 8 cada para vermelho, verde, azul e alfaBgra8888
— cada pixel é de 32 bits, 8 cada para azul, verde, vermelho e alfaIndex8
— cada pixel é de 8 bits e representa um índice em umSKColorTable
Gray8
— cada pixel é de 8 bits representando um tom de cinza do preto ao brancoRgbaF16
— cada pixel é de 64 bits, com vermelho, verde, azul e alfa em um formato de ponto flutuante de 16 bits
Os dois formatos em que cada pixel é de 32 pixels (4 bytes) são frequentemente chamados de formatos coloridos . Muitos dos outros formatos datam de uma época em que as próprias exibições de vídeo não eram capazes de colorir. Bitmaps de cores limitadas eram adequados para essas exibições e permitiam que bitmaps ocupassem menos espaço na memória.
Hoje em dia, os programadores quase sempre usam bitmaps coloridos e não se preocupam com outros formatos. A exceção é o RgbaF16
formato, que permite maior resolução de cores do que até mesmo os formatos coloridos. No entanto, esse formato é usado para fins especializados, como imagens médicas, e não faz muito sentido quando usado com monitores coloridos padrão.
Esta série de artigos se restringirá aos SKBitmap
formatos de cores usados por padrão quando nenhum SKColorType
membro for especificado. Esse formato padrão é baseado na plataforma subjacente. Para as plataformas suportadas pelo Xamarin.Forms, o tipo de cor padrão é:
Rgba8888
para iOS e AndroidBgra8888
para a UWP
A única diferença é a ordem dos 4 bytes na memória, e isso só se torna um problema quando você acessa diretamente os bits de pixel. Isso não se tornará importante até que você chegue ao artigo Acessando pixels de bitmap do SkiaSharp.
A SKAlphaType
enumeração tem quatro membros:
Unknown
Opaque
— o bitmap não tem transparênciaPremul
— os componentes de cor são pré-multiplicados pelo componente alfaUnpremul
— os componentes de cor não são pré-multiplicados pelo componente alfa
Aqui está um pixel de bitmap vermelho de 4 bytes com 50% de transparência, com os bytes mostrados na ordem vermelha, verde, azul, alfa:
0xFF 0x00 0x00 0x80
Quando um bitmap contendo pixels semitransparentes é renderizado em uma superfície de exibição, os componentes de cor de cada pixel de bitmap devem ser multiplicados pelo valor alfa desse pixel, e os componentes de cor do pixel correspondente da superfície de exibição devem ser multiplicados por 255 menos o valor alfa. Os dois pixels podem então ser combinados. O bitmap pode ser renderizado mais rapidamente se os componentes de cor nos pixels de bitmap já tiverem sido pré-multiplicados pelo valor alfa. Esse mesmo pixel vermelho seria armazenado assim em um formato pré-multiplicado:
0x80 0x00 0x00 0x80
Essa melhoria de desempenho é o motivo pelo qual SkiaSharp
os bitmaps por padrão são criados com um Premul
formato. Mas, novamente, torna-se necessário saber disso apenas quando você acessa e manipula pixel bits.
Desenhando em bitmaps existentes
Não é necessário criar um novo bitmap para desenhar nele. Você também pode desenhar em um bitmap existente.
A página Bigode do Macaco usa seu construtor para carregar a imagem MonkeyFace.png. Em seguida, ele cria um SKCanvas
objeto com base nesse bitmap e usa SKPaint
e SKPath
objetos para desenhar um bigode nele:
public partial class MonkeyMoustachePage : ContentPage
{
SKBitmap monkeyBitmap;
public MonkeyMoustachePage()
{
Title = "Monkey Moustache";
monkeyBitmap = BitmapExtensions.LoadBitmapResource(GetType(),
"SkiaSharpFormsDemos.Media.MonkeyFace.png");
// Create canvas based on bitmap
using (SKCanvas canvas = new SKCanvas(monkeyBitmap))
{
using (SKPaint paint = new SKPaint())
{
paint.Style = SKPaintStyle.Stroke;
paint.Color = SKColors.Black;
paint.StrokeWidth = 24;
paint.StrokeCap = SKStrokeCap.Round;
using (SKPath path = new SKPath())
{
path.MoveTo(380, 390);
path.CubicTo(560, 390, 560, 280, 500, 280);
path.MoveTo(320, 390);
path.CubicTo(140, 390, 140, 280, 200, 280);
canvas.DrawPath(path, paint);
}
}
}
// Create SKCanvasView to view result
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(monkeyBitmap, info.Rect, BitmapStretch.Uniform);
}
}
O construtor conclui criando um SKCanvasView
manipulador cujo PaintSurface
simplesmente exibe o resultado:
Copiando e modificando bitmaps
Os métodos SKCanvas
que você pode usar para desenhar em um bitmap incluem DrawBitmap
. Isso significa que você pode desenhar um bitmap em outro, geralmente modificando-o de alguma forma.
A maneira mais versátil de modificar um bitmap é acessando os bits de pixel reais, um assunto abordado no artigo Acessando pixels de bitmap SkiaSharp. Mas existem muitas outras técnicas para modificar bitmaps que não exigem o acesso aos bits de pixel.
O seguinte bitmap incluído com o aplicativo de exemplo tem 360 pixels de largura e 480 pixels de altura:
Suponha que você não tenha recebido permissão do macaco à esquerda para publicar esta fotografia. Uma solução é obscurecer o rosto do macaco usando uma técnica chamada pixelização. Os pixels do rosto são substituídos por blocos de cor para que você não possa distinguir as características. Os blocos de cor são geralmente derivados da imagem original pela média das cores dos pixels correspondentes a esses blocos. Mas você não precisa fazer essa média sozinho. Isso acontece automaticamente quando você copia um bitmap em uma dimensão de pixel menor.
A face do macaco esquerdo ocupa aproximadamente uma área quadrada de 72 pixels com um canto superior esquerdo no ponto (112, 238). Vamos substituir essa área quadrada de 72 pixels por uma matriz de 9 por 9 de blocos coloridos, cada um dos quais é quadrado de 8 por 8 pixels.
A página Pixelizar imagem é carregada nesse bitmap e primeiro cria um pequeno bitmap quadrado de 9 pixels chamado faceBitmap
. Este é um destino para copiar apenas o rosto do macaco. O retângulo de destino é apenas quadrado de 9 pixels, mas o retângulo de origem é quadrado de 72 pixels. Cada bloco 8 por 8 de pixels de origem é consolidado em apenas um pixel, calculando a média das cores.
A próxima etapa é copiar o bitmap original em um novo bitmap do mesmo tamanho chamado pixelizedBitmap
. O minúsculo faceBitmap
é então copiado em cima disso com um retângulo de destino quadrado de 72 pixels para que cada pixel de faceBitmap
seja expandido para 8 vezes seu tamanho:
public class PixelizedImagePage : ContentPage
{
SKBitmap pixelizedBitmap;
public PixelizedImagePage ()
{
Title = "Pixelize Image";
SKBitmap originalBitmap = BitmapExtensions.LoadBitmapResource(GetType(),
"SkiaSharpFormsDemos.Media.MountainClimbers.jpg");
// Create tiny bitmap for pixelized face
SKBitmap faceBitmap = new SKBitmap(9, 9);
// Copy subset of original bitmap to that
using (SKCanvas canvas = new SKCanvas(faceBitmap))
{
canvas.Clear();
canvas.DrawBitmap(originalBitmap,
new SKRect(112, 238, 184, 310), // source
new SKRect(0, 0, 9, 9)); // destination
}
// Create full-sized bitmap for copy
pixelizedBitmap = new SKBitmap(originalBitmap.Width, originalBitmap.Height);
using (SKCanvas canvas = new SKCanvas(pixelizedBitmap))
{
canvas.Clear();
// Draw original in full size
canvas.DrawBitmap(originalBitmap, new SKPoint());
// Draw tiny bitmap to cover face
canvas.DrawBitmap(faceBitmap,
new SKRect(112, 238, 184, 310)); // destination
}
// Create SKCanvasView to view result
SKCanvasView canvasView = new SKCanvasView();
canvasView.PaintSurface += OnCanvasViewPaintSurface;
Content = canvasView;
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(pixelizedBitmap, info.Rect, BitmapStretch.Uniform);
}
}
O construtor conclui criando um SKCanvasView
para exibir o resultado:
Rotação de bitmaps
Outra tarefa comum é girar bitmaps. Isso é particularmente útil ao recuperar bitmaps de uma biblioteca de fotos do iPhone ou iPad. A menos que o dispositivo tenha sido mantido em uma orientação específica quando a foto foi tirada, é provável que a foto esteja de cabeça para baixo ou de lado.
Virar um bitmap de cabeça para baixo requer a criação de outro bitmap do mesmo tamanho do primeiro e, em seguida, definir uma transformação para girar em 180 graus enquanto copia o primeiro para o segundo. Em todos os exemplos desta seção, bitmap
é o SKBitmap
objeto que você precisa girar:
SKBitmap rotatedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear();
canvas.RotateDegrees(180, bitmap.Width / 2, bitmap.Height / 2);
canvas.DrawBitmap(bitmap, new SKPoint());
}
Ao girar em 90 graus, você precisa criar um bitmap de tamanho diferente do original, trocando a altura e a largura. Por exemplo, se o bitmap original tiver 1200 pixels de largura e 800 pixels de altura, o bitmap girado terá 800 pixels de largura e 1200 pixels de largura. Defina a conversão e a rotação para que o bitmap seja girado em torno de seu canto superior esquerdo e, em seguida, deslocado para a exibição. (Lembre-se de que os Translate
métodos e RotateDegrees
são chamados na ordem oposta da maneira como são aplicados.) Aqui está o código para girar 90 graus no sentido horário:
SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear();
canvas.Translate(bitmap.Height, 0);
canvas.RotateDegrees(90);
canvas.DrawBitmap(bitmap, new SKPoint());
}
E aqui está uma função semelhante para girar 90 graus no sentido anti-horário:
SKBitmap rotatedBitmap = new SKBitmap(bitmap.Height, bitmap.Width);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear();
canvas.Translate(0, bitmap.Width);
canvas.RotateDegrees(-90);
canvas.DrawBitmap(bitmap, new SKPoint());
}
Esses dois métodos são usados nas páginas do Photo Puzzle descritas no artigo Recortando bitmaps SkiaSharp.
Um programa que permite ao usuário girar um bitmap em incrementos de 90 graus precisa implementar apenas uma função para girar em 90 graus. O usuário pode então girar em qualquer incremento de 90 graus pela execução repetida desta função.
Um programa também pode girar um bitmap em qualquer quantidade. Uma abordagem simples é modificar a função que gira em 180 graus, substituindo 180 por uma variável generalizada angle
:
SKBitmap rotatedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear();
canvas.RotateDegrees(angle, bitmap.Width / 2, bitmap.Height / 2);
canvas.DrawBitmap(bitmap, new SKPoint());
}
No entanto, no caso geral, essa lógica cortará os cantos do bitmap rotacionado. Uma abordagem melhor é calcular o tamanho do bitmap girado usando trigonometria para incluir esses cantos.
Essa trigonometria é mostrada na página Bitmap Rotator . O arquivo XAML instancia um SKCanvasView
e um Slider
que pode variar de 0 a 360 graus com um Label
mostrando o valor atual:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Bitmaps.BitmapRotatorPage"
Title="Bitmap Rotator">
<StackLayout>
<skia:SKCanvasView x:Name="canvasView"
VerticalOptions="FillAndExpand"
PaintSurface="OnCanvasViewPaintSurface" />
<Slider x:Name="slider"
Maximum="360"
Margin="10, 0"
ValueChanged="OnSliderValueChanged" />
<Label Text="{Binding Source={x:Reference slider},
Path=Value,
StringFormat='Rotate by {0:F0}°'}"
HorizontalTextAlignment="Center" />
</StackLayout>
</ContentPage>
O arquivo code-behind carrega um recurso de bitmap e o salva como um campo estático somente leitura chamado originalBitmap
. O bitmap exibido no PaintSurface
manipulador é rotatedBitmap
, que é inicialmente definido como originalBitmap
:
public partial class BitmapRotatorPage : ContentPage
{
static readonly SKBitmap originalBitmap =
BitmapExtensions.LoadBitmapResource(typeof(BitmapRotatorPage),
"SkiaSharpFormsDemos.Media.Banana.jpg");
SKBitmap rotatedBitmap = originalBitmap;
public BitmapRotatorPage ()
{
InitializeComponent ();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(rotatedBitmap, info.Rect, BitmapStretch.Uniform);
}
void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
double angle = args.NewValue;
double radians = Math.PI * angle / 180;
float sine = (float)Math.Abs(Math.Sin(radians));
float cosine = (float)Math.Abs(Math.Cos(radians));
int originalWidth = originalBitmap.Width;
int originalHeight = originalBitmap.Height;
int rotatedWidth = (int)(cosine * originalWidth + sine * originalHeight);
int rotatedHeight = (int)(cosine * originalHeight + sine * originalWidth);
rotatedBitmap = new SKBitmap(rotatedWidth, rotatedHeight);
using (SKCanvas canvas = new SKCanvas(rotatedBitmap))
{
canvas.Clear(SKColors.LightPink);
canvas.Translate(rotatedWidth / 2, rotatedHeight / 2);
canvas.RotateDegrees((float)angle);
canvas.Translate(-originalWidth / 2, -originalHeight / 2);
canvas.DrawBitmap(originalBitmap, new SKPoint());
}
canvasView.InvalidateSurface();
}
}
O ValueChanged
manipulador do executa as operações que criam um novo rotatedBitmap
com base no ângulo de Slider
rotação. A nova largura e altura são baseadas em valores absolutos de senos e cossenos das larguras e alturas originais. As transformações usadas para desenhar o bitmap original no bitmap girado movem o centro do bitmap original para a origem, depois o giram pelo número especificado de graus e, em seguida, convertem esse centro para o centro do bitmap rotacionado. (Os Translate
métodos e RotateDegrees
são chamados na ordem oposta à forma como são aplicados.)
Observe o Clear
uso do método para fazer o fundo de rotatedBitmap
um rosa claro. Isso é apenas para ilustrar o tamanho da rotatedBitmap
tela:
O bitmap girado é grande o suficiente para incluir todo o bitmap original, mas não maior.
Invertendo bitmaps
Outra operação comumente executada em bitmaps é chamada de inversão. Conceitualmente, o bitmap é girado em três dimensões em torno de um eixo vertical ou eixo horizontal através do centro do bitmap. A inversão vertical cria uma imagem espelhada.
A página Bitmap Flipper no aplicativo de exemplo demonstra esses processos. O arquivo XAML contém um SKCanvasView
e dois botões para inverter vertical e horizontalmente:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skia="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="SkiaSharpFormsDemos.Bitmaps.BitmapFlipperPage"
Title="Bitmap Flipper">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<skia:SKCanvasView x:Name="canvasView"
Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
PaintSurface="OnCanvasViewPaintSurface" />
<Button Text="Flip Vertical"
Grid.Row="1" Grid.Column="0"
Margin="0, 10"
Clicked="OnFlipVerticalClicked" />
<Button Text="Flip Horizontal"
Grid.Row="1" Grid.Column="1"
Margin="0, 10"
Clicked="OnFlipHorizontalClicked" />
</Grid>
</ContentPage>
O arquivo code-behind implementa essas duas operações nos Clicked
manipuladores dos botões:
public partial class BitmapFlipperPage : ContentPage
{
SKBitmap bitmap =
BitmapExtensions.LoadBitmapResource(typeof(BitmapRotatorPage),
"SkiaSharpFormsDemos.Media.SeatedMonkey.jpg");
public BitmapFlipperPage()
{
InitializeComponent();
}
void OnCanvasViewPaintSurface(object sender, SKPaintSurfaceEventArgs args)
{
SKImageInfo info = args.Info;
SKSurface surface = args.Surface;
SKCanvas canvas = surface.Canvas;
canvas.Clear();
canvas.DrawBitmap(bitmap, info.Rect, BitmapStretch.Uniform);
}
void OnFlipVerticalClicked(object sender, ValueChangedEventArgs args)
{
SKBitmap flippedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new SKCanvas(flippedBitmap))
{
canvas.Clear();
canvas.Scale(-1, 1, bitmap.Width / 2, 0);
canvas.DrawBitmap(bitmap, new SKPoint());
}
bitmap = flippedBitmap;
canvasView.InvalidateSurface();
}
void OnFlipHorizontalClicked(object sender, ValueChangedEventArgs args)
{
SKBitmap flippedBitmap = new SKBitmap(bitmap.Width, bitmap.Height);
using (SKCanvas canvas = new SKCanvas(flippedBitmap))
{
canvas.Clear();
canvas.Scale(1, -1, 0, bitmap.Height / 2);
canvas.DrawBitmap(bitmap, new SKPoint());
}
bitmap = flippedBitmap;
canvasView.InvalidateSurface();
}
}
A inversão vertical é realizada por uma transformação de escala com um fator de escala horizontal de –1. O centro de dimensionamento é o centro vertical do bitmap. A inversão horizontal é uma transformada de escala com um fator de escala vertical de –1.
Como você pode ver pelas letras invertidas na camisa do macaco, virar não é o mesmo que girar. Mas, como a captura de tela UWP à direita demonstra, inverter horizontal e verticalmente é o mesmo que girar 180 graus:
Outra tarefa comum que pode ser manipulada usando técnicas semelhantes é cortar um bitmap para um subconjunto retangular. Isso é descrito no próximo artigo Recortando bitmaps SkiaSharp.