Este artigo foi traduzido por máquina.

Windows com C++

Desenhando com Direct2D

Kenny Kerr

Na edição de junho da MSDN Magazine (msdn.microsoft.com/en-us/magazine/dd861344.aspx), apresentei Direct2D, um gráfico 2D totalmente novo API projetado para oferecer suporte os aplicativos da área de trabalho mais exigentes e visualmente avançados com o melhor desempenho possível. Esse artigo, descrevi onde Direct2D se encaixa entre os vários gráficos APIs no Windows, sua arquitetura e princípios. Em particular, eu descritos detalhadamente os conceitos básicos do uso Direct2D confiável e eficiente para processamento dentro de uma janela. Isso incluiu a criação de recursos específicos do dispositivo, bem como recursos independente de dispositivo e suas respectivos ciclos de vida. Se você ainda não tiver feito isso, seria recomendo que você leia esse artigo antes de continuar aqui, como este artigo baseia muito na base definido por aí.

Processamento e controle

É útil pensar Direct2D como um processamento de 2-D acelerada por hardware API. Obviamente, oferece suporte a fallback de software, mas o ponto aqui é que Direct2D sobre processamento. Ao contrário de outros elementos gráficos APIs no Windows, Direct2D adota uma abordagem modular para elementos gráficos. Ele não fornece sua própria APIs para codificar e decodificar bitmaps, layout de texto, gerenciamento de fonte, animação, 3D e assim por diante. Em vez disso, ele se concentra em processamento e controle sobre a unidade de processamento de elementos gráficos (GPU) fornecendo primeiro classe conecta outros APIs que se concentrem em coisas como layout de texto e imagem. Direct2D no entanto, faz, fornecer primitivos para representar diferentes tipos de pincéis, bem como formas simples e complexas, os blocos de construção para qualquer aplicativo de elementos gráficos 2D.

Neste artigo, vou mostrar como desenhar com Direct2D. Eu será Comece apresentando estrutura de cor da Direct2D e, em seguida, mostrarei como criar vários tipos de pincéis. Ao contrário da maioria dos outros gráficos APIs no Windows, Direct2D não fornecer "uma caneta"primitivos, portanto, pincéis são muito importantes eles são usados para todas as tarefas da estrutura de tópicos e preenchimento. Com isso fora do caminho, mostrarei como desenhar formas primitivas.

Cores

Direct2D usa uma estrutura simples que representa as cores com componentes de cor de ponto flutuante. O tipo de D2D1_COLOR_F é realmente um typedef para a estrutura D3DCOLORVALUE usada pelo Direct3D para descrever valores de cor. Ela inclui valores de ponto flutuante individuais para os canais vermelhos, verdes, azuis e alfa. Valores variam de 0,0 a 1,0 com 0,0 sendo preto para os canais de cores e completamente transparente para o canal alfa.

Eis o que ele se parece com:

struct D3DCOLORVALUE
{
FLOAT r;
FLOAT g;
FLOAT b;
FLOAT a;
};

Direct2D fornece a classe do auxiliar ColorF no namespace D2D1 que herda D2D1_COLOR_F e define algumas constantes de cores comum, mas fornece alguns construtores útil que inicializar a estrutura D2D1_COLOR_F mais importante. Por exemplo, você pode, definir vermelho da seguinte maneira:

const D2D1_COLOR_F red = D2D1::ColorF(1.0f, 0.0f, 0.0f);

Outro construtor aceita um valor RGB compactado e converte os canais de cor individual. Eis vermelho novamente:

D2D1::ColorF(0xFF0000)

Isso equivale exatamente a usando o seguinte valor de enum:

D2D1::ColorF(D2D1::ColorF::Red)

Embora concisa, usando a representação de RGB compactada comer até alguns ciclos da CPU mais os canais de cor diferente precisam ser extraídos e convertido em seus flutuante aponte equivalentes, portanto, use-o com cuidado.

Todos os os construtores aceitam um valor de alfa opcional que assumirá como padrão 1.0, ou totalmente opaco. Portanto, você pode expressar azul semi-transparente da seguinte maneira:

D2D1::ColorF(0.0f, 0.0f, 1.0f, 0.5f)

Além de Limpar área de desenho do destino de processamento, entretanto, há pouco que você pode fazer com uma cor diretamente. O que você precisa são pincéis.

Pincéis

Ao contrário das estruturas de cores simples, pincéis são recursos expostos por meio de interfaces. Elas são usadas para desenhar linhas, para desenhar e preencher as formas e para desenhar texto. Interfaces que derivam de ID2D1Brush representam os diferentes tipos de pincéis fornecidos pelo Direct2D. A própria interface ID2D1Brush permite que você controle opacidade de um pincel como um todo, em vez de alterar o canal alfa de cores usado pelo Pincel. Isso pode ser particularmente útil com alguns dos tipos de pincéis a mais interessantes.

Eis como você pode definir a opacidade do pincel para 50 por cento:

CComPtr<ID2D1Brush> brush;
// Create brush here...
brush->SetOpacity(0.5f);

ID2D1Brush também permite que você controle a transformação aplicada ao Pincel já que, ao contrário do Windows Presentation Foundation (WPF), pincéis adotam o sistema de coordenadas de alvo processado em vez de qualquer forma específica pode ser desenho.

Os diversos métodos destino render para criar todos os pincéis aceitam uma estrutura D2D1_BRUSH_PROPERTIES opcional que pode ser usada para definir a opacidade inicial e a transformação.

Todos os pincéis fornecidos pelo Direct2D mutáveis e com eficiência podem mudar suas características, portanto, você não precisará criar novos pincéis com características diferentes. Tenha isso em mente como projetar os aplicativos. Pincéis também são recursos dependentes de dispositivo, eles estão ligados pela vida útil do destino de processamento que os criou. Em outras palavras, você pode reutilizar um pincel específico para, desde que sua meta de processamento é válida mas você deve liberá-lo quando o destino de processamento for lançado.

Como seu nome sugere, a interface ID2D1SolidColorBrush representa um pincel de cor sólida. Ele adiciona métodos para controlar a cor usada pelo Pincel. Um pincel de cor sólida é criado com CreateSolidColorBrush método de um destino processamento:

CComPtr<ID2D1RenderTarget> m_target;
// Create render target here...
const D2D1_COLOR_F color = D2D1::ColorF(D2D1::ColorF::Red);
CComPtr<ID2D1SolidColorBrush> m_brush;
HR(m_target->CreateSolidColorBrush(color, &m_brush));
This brush's initial color can also easily be changed using the SetColor method:
m_brush->SetColor(differentColor);

Vou deixar uma discussão completa sobre formas para a próxima seção, mas para fins de ter algo a ver, irá preencher apenas destino de processamento de uma janela usando um retângulo com base no tamanho alvo processado. Tenha em mente que Direct2D usa pixels independentes de dispositivo (DIPs). Como resultado, o tamanho informado por um dispositivo específico, como área de cliente da janela da área de trabalho, pode não corresponder ao tamanho do destino render. Felizmente, é muito fácil obter o tamanho do destino render em DIPs usando o método GetSize. GetSize retorna um D2D1_SIZE_F estrutura que usa Direct2D para representar tamanhos com dois flutuante aponte valores denominados largura e altura. Em seguida, pode fornecer uma variável D2D1_RECT_F para descrever a área para preencher usando a função de auxiliar RectF e conectar o tamanho informado pelo alvo processado. Finalmente, Posso usar FillRectangle método do destino processamento para fazer o desenho real.

Eis o código é semelhante:

const D2D1_SIZE_F size = m_target->GetSize();
const D2D1_RECT_F rect = D2D1::RectF(0, 0, size.width, size.height);
m_target->FillRectangle(rect, m_brush);

A Figura 1 mostra a aparência da janela com um pincel sólido verde. Muito interessantes na verdade!

Direct2D também fornece dois tipos de pincéis gradientes. Um pincel em dégradé é aquele que preenche uma área com cores combinadas ao longo de um eixo. Um pincel em dégradé linear define o eixo como uma linha reta com um inicial e um ponto de extremidade. Um pincel em dégradé radial define o eixo como uma elipse, onde as cores radiam para fora de algum ponto em relação ao centro da elipse.

Um gradiente é definido como uma série de posições relativas de 0,0 a 1,0. Cada um tem sua própria cor e é chamada uma parada de gradiente. É possível usar posições fora desse intervalo para produzir os diversos efeitos. Para criar um pincel em dégradé, crie primeiro uma coleção de parada gradiente. Comece definindo uma matriz de estruturas D2D1_GRADIENT_STOP.

Veja um exemplo:

const D2D1_GRADIENT_STOP gradientStops[] =
{
{ 0.0f, color1 },
{ 0.2f, color2 },
{ 0.3f, color3 },
{ 1.0f, color4 }
};

Em seguida, chame CreateGradientStopCollection método do destino processamento para criar um objeto de coleção com base na matriz de paradas de gradação, da seguinte maneira:

CComPtr<ID2D1GradientStopCollection> gradientStopsCollection;

HR(m_target->CreateGradientStopCollection(gradientStops,
_countof(gradientStops),
&gradientStopsCollection));

O primeiro parâmetro é um ponteiro para a matriz e o segundo parâmetro fornece o tamanho da matriz. Aqui Estou apenas usando a macro _countof padrão. O método CreateGradientStopCollection retorna a nova coleção. Para criar um pincel em dégradé linear, você também precisará fornecer uma estrutura D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES para indicar o início e o ponto final do eixo do gradiente. Diferentemente dos posições parada gradiente, esses pontos são no espaço de coordenadas do pincel que geralmente é que o processamento de destino, a menos que uma transformação é definida no Pincel. Por exemplo, você pode criar o pincel com um eixo é executado do canto superior esquerdo do alvo processado para o canto inferior direito da seguinte maneira:

const D2D1_SIZE_F size = m_target->GetSize();
const D2D1_POINT_2F start = D2D1::Point2F(0.0f, 0.0f);
const D2D1_POINT_2F end = D2D1::Point2F(size.width, size.height);
const D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES properties = D2D1::LinearGradientBrushProperties(start, end);
HR(m_target->CreateLinearGradientBrush(properties,
gradientStopsCollection,
&m_brush));

LinearGradientBrushProperties é outra função de auxiliar fornecida pelo Direct2D ao inicializar uma estrutura D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES. O método CreateLinearGradientBrush aceita isso junto com a coleção de parada gradiente mostrada anteriormente e retorna um ponteiro de interface ID2D1LinearGradientBrush que representa o pincel de novo.

Obviamente, o pincel não saberá se o tamanho do destino processamento é alterado. Se desejar que o ponto final do eixo para alterar como uma janela é redimensionada, você poderia fazer isso facilmente com SetEndPoint método do pincel de. Da mesma forma, você pode alterar o eixoInicie ponto com o método SetStartPoint.

Eis a aparência do desenho código:

const D2D1_SIZE_F size = m_target->GetSize();
const D2D1_RECT_F rect = D2D1::RectF(0, 0, size.width, size.height);

m_brush->SetEndPoint(D2D1::Point2F(size.width, size.height));
m_target->FillRectangle(rect, m_brush);

Figura 2 mostra a aparência da janela com o pincel em dégradé linear.

Para criar um pincel em dégradé radial, você precisará fornecer uma estrutura D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES. Essa estrutura define a elipse, bem como um deslocamento relativo para o centro da elipse que representa a origem do qual o Pincel "irradia"cor com base na coleção parada gradiente. O exemplo a seguir produz uma elipse que é centralizada na alvo processado, tem um raio X e Y para coincidir com o destino de processamento e uma origem meio caminho em direção ao canto inferior direito:

const D2D1_SIZE_F size = m_target->GetSize();

const D2D1_POINT_2F center = D2D1::Point2F(size.width / 2.0f, size.height / 2.0f);
const D2D1_POINT_2F offset = D2D1::Point2F(size.width * 0.25f, size.height * 0.25f);
const float radiusX = size.width / 2.0f;
const float radiusY = size.height / 2.0f;

const D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES properties = D2D1::RadialGradientBrushProperties(center,

offset,

radiusX,

radiusY);

HR(m_target->CreateRadialGradientBrush(properties,
gradientStopsCollection,
&m_brush));

RadialGradientBrushProperties é outra função de auxiliar fornecida pelo Direct2D ao inicializar uma estrutura D2D1_RADIAL_GRADIENT_BRUSH_PROPERTIES. O método CreateRadialGradientBrush aceita isso junto com a coleção de parada gradiente mostrada anteriormente e retorna um ponteiro de interface ID2D1RadialGradientBrush que representa o pincel de novo.

Você também pode alterar o pincel elipse e origem a qualquer momento, da seguinte maneira:

m_brush->SetCenter(center);
m_brush->SetGradientOriginOffset(offset);
m_brush->SetRadiusX(radiusX);
m_brush->SetRadiusY(radiusY);

Figura 3 mostra o pincel de gradiente radial em ação.

Direct2D também fornece um pincel de bitmap, mas deixarei uma discussão sobre Direct2D bitmaps para um artigo futuro.

Formas

Até o momento mostrei a você somente como preencher um retângulo para que eu poderia focar pincéis, mas Direct2D pode fazer muito mais que simples retângulos. Para os iniciantes, primitivos são fornecidos para retângulos, retângulos arredondados e reticências. Por primitivos, eu apenas significa que há uma estrutura de dados antigo simples para cada um deles.

D2D1_RECT_F representa um retângulo de ponto flutuante e é um typedef para D2D_RECT_F:

struct D2D_RECT_F
{
FLOAT left;
FLOAT top;
FLOAT right;
FLOAT bottom;
};

D2D1_ROUNDED_RECT representa um retângulo arredondado e é definido da seguinte maneira, com radiuses definindo as elipses trimestre que serão usadas para desenhar os cantos:

struct D2D1_ROUNDED_RECT
{
D2D1_RECT_F rect;
FLOAT radiusX;
FLOAT radiusY;
};

D2D1_ELLIPSE representa uma elipse e é definido com um ponto central, bem como radiuses:

struct D2D1_ELLIPSE
{
D2D1_POINT_2F point;
FLOAT radiusX;
FLOAT radiusY;
};

Embora essas estruturas podem não parecer muito interessantes, há dois motivos que menciono-los com antecedência. Primeiro, eles são usados para criar objetos de geometria mais sofisticados. Em segundo lugar, se tudo o que você precisa é preencher ou desenhar um esses primitivos, você deve continuar com eles como você geralmente obterá melhor desempenho se você evitar os objetos de geometria.

Destinos de processamento fornecem um conjunto de preenchimento e desenhar-métodos para preencher e estrutura de tópicos esses primitivos. Até mostrei a você como usar o método FillRectangle. Diâmetro, não interno você com exemplos dos métodos FillRoundedRectangle e FillEllipse, enquanto eles trabalham exatamente da mesma maneira. Em contraste com métodos de preenchimento, os métodos de desenho podem ser usados para uma forma específica de estrutura de tópicos e são muito versátil. Como métodos de preenchimento, os Draw-métodos abrangendo os três primitivos todos os funcionam exatamente da mesma forma, para que abordarei apenas o método DrawRectangle.

Em sua forma mais simples, você pode desenhar o contorno de um retângulo da seguinte maneira:

m_target->DrawRectangle(rect,
m_brush,
20.0f);

O terceiro parâmetro especifica a largura do traço é desenhado para o retângulo de estrutura de tópicos. O traço propriamente dito é centralizado no retângulo, para que se você tinha preenchido o retângulo do mesmo você deve ver o preenchimento e o traço se sobrepõem por 10.0 DIPs. Um quarto parâmetro opcional pode ser fornecido para controlar o estilo de traço que é desenhado. Isso é útil caso você precise desenhar uma linha tracejada ou apenas deseja controlar o tipo de associação no vértices da forma.

Informações de estilo do traço são representadas pela interface ID2D1StrokeStyle. Objetos de estilo traço são recursos independente de dispositivo, o que significa que eles não precisam ser recriados sempre que o destino de processamento é invalidado. No evento, um novo objeto de estilo do traço é criado usando o Direct2D fábrica método do objeto CreateStrokeStyle.

Se você quer apenas adicionar o traço para usar um determinado tipo de associação, você pode criar um estilo de traço da seguinte maneira:

D2D1_STROKE_STYLE_PROPERTIES properties = D2D1::StrokeStyleProperties();
properties.lineJoin = D2D1_LINE_JOIN_BEVEL;

HR(m_factory->CreateStrokeStyle(properties,
0, // dashes
0, // dash count
&m_strokeStyle));

A estrutura D2D1_STROKE_STYLE_PROPERTIES fornece uma variedade de membros para controlar vários aspectos do estilo de traço, em particular a forma ou tampa, em cada extremidade de uma estrutura de tópicos ou de traço, assim como o estilo tracejado próprio. O segundo e terceiro parâmetro para o método CreateStrokeStyle são opcionais e você precisa fornecer a eles somente se você estiver definindo um estilo de traço tracejada personalizado. Para definir um estilo de traço tracejada, verifique se que especificou os traços em pares. O primeiro elemento em cada par é o comprimento de um traço e o segundo é o comprimento do espaço antes do traço próximo. Os próprios valores são multiplicados pela largura do traço. Você pode especificar quantos pares necessário produzir o padrão desejado. Veja um exemplo:

D2D1_STROKE_STYLE_PROPERTIES properties = D2D1::StrokeStyleProperties();
properties.dashStyle = D2D1_DASH_STYLE_CUSTOM;

float dashes[] =
{
2.0f, 1.0f,
3.0f, 1.0f,
};

HR(m_factory->CreateStrokeStyle(properties,
dashes,
_countof(dashes),
&m_strokeStyle));

Você pode escolher de qualquer um número de traço diferentes estilos da enumeração D2D1_DASH_STYLE em vez de definir seus próprios. Figura 4 mostra alguns estilos diferentes de traço no trabalho. Se você examinar de perto, verá que Direct2D automaticamente alfa Geométrico com suavização primitivo para os resultados de melhor aparência.

Há muito mais que Direct2D pode fazer por você, geometrias complexas e transformações, para desenhar texto e bitmaps e muito mais. Espero que abordar esses e mais em colunas futuras.

KENNY KERR* é um profissional de fabricação software especializado no desenvolvimento de software para Windows. Ele adora escrever e ensinar programação e software design aos desenvolvedores. Entrar em weblogs.asp.net/kennykerr.*