Avançar para o conteúdo
Imagem com logotipo, contendo link para a página inicial
  • United Stated of America flag, representing the option for the English language.
  • Bandeira do Brasil, simbolizando a opção pelo idioma Português do Brasil.

Ideias, Regras, Simulação: Desenhando com Primitivas Gráficas (Círculos, Elipses e Polígonos)

Ilustrações criadas usando primitivas gráficas apresentas ao longo dos tópicos de Ideias, Regras, Simulação: formas preenchidas; uma mulher; uma casa; um pintinho; um gato; um cachorro; um porco; e uma vaca. As imagens são saídas apresentadas na janela, resultantes de programas criados para JavaScript com HTML Canvas, GDScript com Godot Engine, Python com PyGame, e Lua com LÖVE. A imagem também apresenta um link para este website: <www.francogarcia.com>, assim como a conta francogarciacom, usada para o Twitter e Instagram do autor.

Créditos para a imagem: Imagem criada pelo autor usando o programa Inkscape; ícones de Font Awesome.

Pré-Requisitos

O material de Ideias, Regras, Simulação promove aprendizado baseado em projetos (simulações interativas e jogos digitais) com recursos multimídia. Assim, ele é complementar ao material de Aprenda Programação, que introduz fundamentos e técnicas básicas de programação, com exemplos para JavaScript, Python, Lua e GDScript (para Godot Engine).

Para Ideias, Regras, Simulação, você precisará de um ambiente de desenvolvimento configurado para uma das linguagens anteriores:

JavaScript (para navegadores) e GDScript fornecem suporte para conteúdo multimídia.

Também estou considerando melhorar os editores online para programação dentro do website, em um estilo de curso interativo. Atualmente, a página de ferramentas fornece opções para:

Os editores possuem suporte para imagens em navegador. Contudo, Python e Lua utilizam a sintaxe para JavaScript com canvas. Caso eu criasse uma abstração e adicionasse suporte para áudio, eles tornar-se-iam opções válidas (ao menos para primeiras atividades).

Contudo, convém configurar um Ambiente Integrado de Desenvolvimento (em inglês, Integrated Development Environment ou IDE), ou a combinação de editor de texto com interpretador assim que possível para programar em sua máquina com maior eficiência. Ambientes online são práticos, mas ambientes locais são mais potencialmente mais completos, eficientes, rápidos e personalizáveis. Caso queira tornar-se profissional, você precisará de um ambiente de desenvolvimento configurado. Quanto antes o fizer, melhor.

Versão em Vídeo (Em Progresso)

Versões em vídeo serão lançadas em breve no canal do autor no YouTube.

Documentação

Pratique consultar pela documentação:

Praticamente todos os links possuem um campo para buscas. Em Lua, a documentação está em uma única página. Você pode procurar por entradas usando o atalho Ctrl F (busca ou search).

Contornos e Preenchimentos

A representação para a simulação de lançamentos de moedas e dados usou linhas, arcos e retângulos para representar resultados. Embora simples, os gráficos cumpriram o papel de representar os lados da moeda ou as faces de um dado.

Contudo, os resultados seriam mais esteticamente agradáveis caso os desenhos tivessem um preenchimento colorido ao invés de serem meros contornos. Por exemplo, em editores gráficos como GIMP e Microsoft Paint fornecem uma ferramenta com ícone de balde (bucket fill) para preencher regiões delimitadas por contornos.

Você já pensou em como elas são criadas? Para implementar um bucket fill, seria necessário adicionar recursos para entrada de dados na janela. Isso é um pouco diferente do feito em Aprenda Programação: Entrada em Console (Terminal); logo, para um tópico mais simples, um recurso bucket fill é implementado em Aprofundamentos.

Por outro lado, existem outros algoritmos para preenchimento de formas. Assim, a partir deste tópico, polígonos com contornos e com preenchimentos serão parte dos recursos de desenho de Ideias, Regras, Simulação. De fato, uma das seções criará desenhos simples utilizando as primitivas gráficas.

A seção será o destino final deste tópico. Para chegar até ele, será necessário aprender a preencher círculos e polígonos. Para complementar as primitivas de desenho estudadas até aqui, pode-se também considerar o desenho de elipses.

Canvas em HTML para JavaScript

Como na Introdução de Ideias, Regras, Simulação, JavaScript requer um arquivo HTML auxiliar para a declaração do canvas. O nome do arquivo HTML é de sua escolha (por exemplo, index.html). O exemplo a seguir assume que o arquivo JavaScript terá nome script.js e que o canvas terá identificador (id) canvas. Caso você altere os valores, lembre-se de modificá-los nos arquivos conforme necessário.

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <title>www.FrancoGarcia.com</title>
    <meta name="author" content="Franco Eusébio Garcia">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div style="text-align: center;">
      <canvas id="canvas"
              width="1"
              height="1"
              style="border: 1px solid #000000;">
      >
          Acessibilidade: texto alternativo para conteúdo gráfico.
      </canvas>
    </div>

    <!-- NOTA Atualizar com nome do arquivo JavaScript. -->
    <script src="./script.js"></script>
  </body>
</html>

O arquivo também mantém o lembrete sobre acessibilidade. Neste tópico, o conteúdo tornar-se-á ainda mais inacessível para pessoas com certos tipos de deficiência visual, devido a adição de conteúdo gráfico sem opções alternativas para comunicação do conteúdo.

No futuro, a intenção é abordar formas de tornar simulações mais acessíveis. Neste momento, este lembrete é apenas informativo, para conscientizar você da importância de acessibilidade.

Círculos

Círculos foram comentados rapidamente em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos). Na ocasião, criou-se círculos usando arcos. Ou seja, obteve-se apenas os contornos.

As próximas subseções apresentam como preencher círculos.

Preenchendo Círculos com Midpoint Circle Algorithm

Uma forma simples de preencher um círculo consiste em modificar o Midpoint Circle Algorithm implementado em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos). Ao invés de desenhar os pontos (pixels) da extremidade, basta desenhar uma linha (segmento de reta) para conectar os pontos com mesma ordenada (valor de y).

Ou seja, em franco_draw_circle(), basta trocar a implementação original, que desenha um pixel por vez usando franco_draw_pixel() com:

# 0 - 44
franco_draw_pixel(center_x + x, center_y - y, color)
# 45 - 89
franco_draw_pixel(center_x + y, center_y - x, color)
# 90 - 134
franco_draw_pixel(center_x - y, center_y - x, color)
# 135 - 179
franco_draw_pixel(center_x - x, center_y - y, color)
# 180 - 224
franco_draw_pixel(center_x - x, center_y + y, color)
# 225 - 269
franco_draw_pixel(center_x - y, center_y + x, color)
# 270 - 314
franco_draw_pixel(center_x + y, center_y + x, color)
# 315 - 359
franco_draw_pixel(center_x + x, center_y + y, color)

Para o desenho de uma linha conectando os pares com mesmo valor de y usando franco_draw_line():

franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)

Os próximos blocos realizam as alterações.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white


func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func franco_draw_circle(center_x, center_y, radius, color):
    var x = radius
    var y = 0
    var e = 3 - 2 * radius
    while (x >= y):
        franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
        franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
        franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
        franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)

        if (e > 0):
            # e = e + 2 * (5 - 2x + 2y)
            e += 10 + 4 * (-x + y)
            x -= 1
        else:
            # e = e + 2 * (3 + 2 * y)
            e += 6 + 4 * y

        y += 1


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_line(x0, y0, x1, y1, color) {
    context.strokeStyle = color
    // context.fillStyle = color
    context.beginPath()
    context.moveTo(x0, y0)
    context.lineTo(x1, y1)
    context.closePath()
    context.stroke()
}


function franco_draw_circle(center_x, center_y, radius, color) {
    let x = radius
    let y = 0
    let e = 3 - 2 * radius
    while (x >= y) {
        franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
        franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
        franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
        franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)

        if (e > 0) {
            // e = e + 2 * (5 - 2x + 2y)
            e += 10 + 4 * (-x + y)
            --x
        } else {
            // e = e + 2 * (3 + 2 * y)
            e += 6 + 4 * y
        }

        ++y
    }
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_line(x0, y0, x1, y1, color):
    pygame.draw.line(window, color, (x0, y0), (x1, y1))


def franco_draw_circle(center_x, center_y, radius, color):
    x = radius
    y = 0
    e = 3 - 2 * radius
    while (x >= y):
        franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
        franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
        franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
        franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)

        if (e > 0):
            # e = e + 2 * (5 - 2x + 2y)
            e += 10 + 4 * (-x + y)
            x -= 1
        else:
            # e = e + 2 * (3 + 2 * y)
            e += 6 + 4 * y

        y += 1


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


function franco_draw_line(x0, y0, x1, y1, color)
    love.graphics.setColor(color)
    love.graphics.line(x0, y0, x1, y1)
end


function franco_draw_circle(center_x, center_y, radius, color)
    local x = radius
    local y = 0
    local e = 3 - 2 * radius
    while (x >= y) do
        franco_draw_line(center_x + x, center_y - y, center_x - x, center_y - y, color)
        franco_draw_line(center_x - x, center_y + y, center_x + x, center_y + y, color)
        franco_draw_line(center_x + y, center_y - x, center_x - y, center_y - x, color)
        franco_draw_line(center_x - y, center_y + x, center_x + y, center_y + x, color)

        if (e > 0) then
            -- e = e + 2 * (5 - 2x + 2y)
            e = e + 10 + 4 * (-x + y)
            x = x - 1
        else
            -- e = e + 2 * (3 + 2 * y)
            e = e + 6 + 4 * y
        end

        y = y + 1
    end
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
end

O canvas a seguir apresenta o resultado.

Desenho de um círculo com a cor branca sobre um fundo preto usando a modificação descrita do Midpoint Circle Algorithm.

Nas versões em JavaScript com canvas e Lua com LÖVE (Love2D), é possível notar que algumas das linhas são mais escuras que outras. Isso ocorre porque elas foram desenhadas mais de uma vez.

Colorindo Círculos com Primitivas Gráficas

Interfaces de Programação de Aplicações (Application Programming Interfaces ou APIs) gráficas comumente fornecem uma primitiva para o desenho de círculos. De fato, GDScript fornece draw_circle(), JavaScript com canvas permite colorir um arco, Python com PyGame possui pygame.draw.circle(), e Lua com LÖVE provê love.graphics.circle(). Assim, elas serão usadas nos próximos exemplos ao invés de uma implementação própria.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white


func franco_draw_circle(center_x, center_y, radius, color):
    draw_circle(Vector2(center_x, center_y), radius, color)


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_circle(center_x, center_y, radius, color) {
    context.fillStyle = color

    context.beginPath()
    context.arc(center_x, center_y,
                radius,
                0, 2 * Math.PI)
    context.fill()
    context.closePath()
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_circle(center_x, center_y, radius, color):
    pygame.draw.circle(window, color, (center_x, center_y), radius)


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

        window.fill(BLACK)

        franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)

        pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}


function franco_draw_circle(center_x, center_y, radius, color)
    love.graphics.setColor(color)
    love.graphics.circle("fill", center_x, center_y, radius)
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    franco_draw_circle(0.5 * WIDTH, 0.5 * HEIGHT, 0.45 * HEIGHT, WHITE)
end

O canvas a seguir apresenta o resultado.

Desenho de um círculo branco sobre fundo preto usando primitivas gráficas.

Convém notar que a primitiva fornecida por PyGame para Python desenha o círculo completo. Ou seja, ela não possui a limitação que o desenho de arcos usando pygame.gfxdraw.arc() possuía.

Elipses

Para uma generalização de círculos, pode-se desenhar elipses. Uma elipse pode ser visualizada como um círculo deformado, que resulta em uma forma oval. Contudo, na realidade, um círculo é um caso particular de elipse.

A entrada da Wikipédia fornece a equações para o Plano Cartesiano e coordenas polares. Contudo, é hora de introduzir uma abordagem mais científica.

Desenhando Contornos de Elipses

Acadêmicos produzem conhecimento científico, que é comumente publicado como artigos em meios como revistas, periódicos, ou websites pessoais. Procurar por trabalhos de professoras ou professor, pesquisadores ou pesquisadores é uma boa opção para encontrar solução para problemas.

Para ilustrar o potencial da abordagem, a implementação do desenho do contorno de elipses seguirá o algoritmo de John Kennedy. O documento ilustra um exemplo de texto em formato de artigo; quando um artigo possui um algoritmo, é fácil segui-lo mesmo quando não se é uma ou um cientista.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const BLUE = Color.blue


func franco_draw_pixel(x, y, color):
    draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


func plot_4_ellipse_points(center_x, center_y, x, y, color):
    franco_draw_pixel(center_x + x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y - y, color)
    franco_draw_pixel(center_x + x, center_y - y, color)


func franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
    var two_a_square = 2 * x_radius * x_radius
    var two_b_square = 2 * y_radius * y_radius

    var x = x_radius
    var y = 0
    var x_change = y_radius * y_radius * (1 - 2 * x_radius)
    var y_change = x_radius * x_radius
    var ellipse_error = 0
    var stopping_x = two_b_square * x_radius
    var stopping_y = 0
    while (stopping_x >= stopping_y):
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        y += 1
        stopping_y += two_a_square
        ellipse_error += y_change
        y_change += two_a_square

        if ((2 * ellipse_error + x_change) > 0):
            x -= 1
            stopping_x -= two_b_square
            ellipse_error += x_change
            x_change += two_b_square

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y):
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        x += 1
        stopping_x += two_b_square
        ellipse_error += x_change
        x_change += two_b_square

        if ((2 * ellipse_error + y_change) > 0):
            y -= 1
            stopping_y -= two_a_square
            ellipse_error += y_change
            y_change += two_a_square


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    franco_draw_ellipse(160, 120, 120, 80, BLUE)
    franco_draw_ellipse(160, 120, 60, 40, RED)
    franco_draw_ellipse(160, 120, 30, 30, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const BLUE = "blue"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function plot_4_ellipse_points(center_x, center_y, x, y, color) {
    franco_draw_pixel(center_x + x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y - y, color)
    franco_draw_pixel(center_x + x, center_y - y, color)
}


function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color) {
    let two_a_square = 2 * x_radius * x_radius
    let two_b_square = 2 * y_radius * y_radius

    let x = x_radius
    let y = 0
    let x_change = y_radius * y_radius * (1 - 2 * x_radius)
    let y_change = x_radius * x_radius
    let ellipse_error = 0
    let stopping_x = two_b_square * x_radius
    let stopping_y = 0
    while (stopping_x >= stopping_y) {
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        ++y
        stopping_y += two_a_square
        ellipse_error += y_change
        y_change += two_a_square

        if ((2 * ellipse_error + x_change) > 0) {
            --x
            stopping_x -= two_b_square
            ellipse_error += x_change
            x_change += two_b_square
        }
    }

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y) {
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        ++x
        stopping_x += two_b_square
        ellipse_error += x_change
        x_change += two_b_square

        if ((2 * ellipse_error + y_change) > 0) {
            --y
            stopping_y -= two_a_square
            ellipse_error += y_change
            y_change += two_a_square
        }
    }
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    franco_draw_ellipse(160, 120, 120, 80, BLUE)
    franco_draw_ellipse(160, 120, 60, 40, RED)
    franco_draw_ellipse(160, 120, 30, 30, WHITE)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
BLUE: Final = pygame.Color("blue")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_pixel(x, y, color):
    window.set_at((x, y), color)


def plot_4_ellipse_points(center_x, center_y, x, y, color):
    franco_draw_pixel(center_x + x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y - y, color)
    franco_draw_pixel(center_x + x, center_y - y, color)


def franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
    two_a_square = 2 * x_radius * x_radius
    two_b_square = 2 * y_radius * y_radius

    x = x_radius
    y = 0
    x_change = y_radius * y_radius * (1 - 2 * x_radius)
    y_change = x_radius * x_radius
    ellipse_error = 0
    stopping_x = two_b_square * x_radius
    stopping_y = 0
    while (stopping_x >= stopping_y):
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        y += 1
        stopping_y += two_a_square
        ellipse_error += y_change
        y_change += two_a_square

        if ((2 * ellipse_error + x_change) > 0):
            x -= 1
            stopping_x -= two_b_square
            ellipse_error += x_change
            x_change += two_b_square

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y):
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        x += 1
        stopping_x += two_b_square
        ellipse_error += x_change
        x_change += two_b_square

        if ((2 * ellipse_error + y_change) > 0):
            y -= 1
            stopping_y -= two_a_square
            ellipse_error += y_change
            y_change += two_a_square


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            franco_draw_ellipse(160, 120, 120, 80, BLUE)
            franco_draw_ellipse(160, 120, 60, 40, RED)
            franco_draw_ellipse(160, 120, 30, 30, WHITE)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}


function franco_draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


function plot_4_ellipse_points(center_x, center_y, x, y, color)
    franco_draw_pixel(center_x + x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y - y, color)
    franco_draw_pixel(center_x + x, center_y - y, color)
end


function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color)
    local two_a_square = 2 * x_radius * x_radius
    local two_b_square = 2 * y_radius * y_radius

    local x = x_radius
    local y = 0
    local x_change = y_radius * y_radius * (1 - 2 * x_radius)
    local y_change = x_radius * x_radius
    local ellipse_error = 0
    local stopping_x = two_b_square * x_radius
    local stopping_y = 0
    while (stopping_x >= stopping_y) do
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        y = y + 1
        stopping_y = stopping_y + two_a_square
        ellipse_error = ellipse_error + y_change
        y_change = y_change + two_a_square

        if ((2 * ellipse_error + x_change) > 0) then
            x = x - 1
            stopping_x = stopping_x - two_b_square
            ellipse_error = ellipse_error + x_change
            x_change = x_change + two_b_square
        end
    end

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y) do
        plot_4_ellipse_points(center_x, center_y, x, y, color)

        x = x + 1
        stopping_x = stopping_x + two_b_square
        ellipse_error = ellipse_error + x_change
        x_change = x_change + two_b_square

        if ((2 * ellipse_error + y_change) > 0) then
            y = y - 1
            stopping_y = stopping_y - two_a_square
            ellipse_error = ellipse_error + y_change
            y_change = y_change + two_a_square
        end
    end
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    franco_draw_ellipse(160, 120, 120, 80, BLUE)
    franco_draw_ellipse(160, 120, 60, 40, RED)
    franco_draw_ellipse(160, 120, 30, 30, WHITE)
end

O canvas a seguir apresenta o resultado.

Desenho de três elipses concêntricas, usando o algoritmo de John Kennedy.

O algoritmo é similar ao de Bresenham para o desenho de círculos.

É interessante notar que uma elipse cujos dois raios (x_radius e y_radius) possuam a mesma medida forma um círculo. De fato, um círculo é um caso particular de elipse.

Preenchendo Elipses

Como o algoritmo de Kennedy é similar ao de Bresenham, a implementação de preenchimento de elipses pode explorar a mesma estratégia usada para círculos: basta desenhar retas para pontos com mesmas ordenadas (valor de y).

Ou seja, basta modificar plot_4_ellipse_lines() que usava franco_draw_pixel():

func franco_draw_pixel(x, y, color):
    draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


func plot_4_ellipse_points(center_x, center_y, x, y, color):
    franco_draw_pixel(center_x + x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y + y, color)
    franco_draw_pixel(center_x - x, center_y - y, color)
    franco_draw_pixel(center_x + x, center_y - y, color)

Para um novo plot_2_ellipse_lines() que usará franco_draw_line() para concluir a implementação.

func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func plot_2_ellipse_lines(center_x, center_y, x, y, color):
    franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
    franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)

Em seguida, bastará atualizar as duas chamadas em franco_draw_ellipse().

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const BLUE = Color.blue


func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func plot_2_ellipse_lines(center_x, center_y, x, y, color):
    franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
    franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)


func franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
    var two_a_square = 2 * x_radius * x_radius
    var two_b_square = 2 * y_radius * y_radius

    var x = x_radius
    var y = 0
    var x_change = y_radius * y_radius * (1 - 2 * x_radius)
    var y_change = x_radius * x_radius
    var ellipse_error = 0
    var stopping_x = two_b_square * x_radius
    var stopping_y = 0
    while (stopping_x >= stopping_y):
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        y += 1
        stopping_y += two_a_square
        ellipse_error += y_change
        y_change += two_a_square

        if ((2 * ellipse_error + x_change) > 0):
            x -= 1
            stopping_x -= two_b_square
            ellipse_error += x_change
            x_change += two_b_square

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y):
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        x += 1
        stopping_x += two_b_square
        ellipse_error += x_change
        x_change += two_b_square

        if ((2 * ellipse_error + y_change) > 0):
            y -= 1
            stopping_y -= two_a_square
            ellipse_error += y_change
            y_change += two_a_square


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    franco_draw_ellipse(160, 120, 120, 80, BLUE)
    franco_draw_ellipse(160, 120, 60, 40, RED)
    franco_draw_ellipse(160, 120, 30, 30, WHITE)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const BLUE = "blue"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_line(x0, y0, x1, y1, color) {
    context.strokeStyle = color
    // context.fillStyle = color
    context.beginPath()
    context.moveTo(x0, y0)
    context.lineTo(x1, y1)
    context.closePath()
    context.stroke()
}


function plot_2_ellipse_lines(center_x, center_y, x, y, color) {
    franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
    franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
}


function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color) {
    let two_a_square = 2 * x_radius * x_radius
    let two_b_square = 2 * y_radius * y_radius

    let x = x_radius
    let y = 0
    let x_change = y_radius * y_radius * (1 - 2 * x_radius)
    let y_change = x_radius * x_radius
    let ellipse_error = 0
    let stopping_x = two_b_square * x_radius
    let stopping_y = 0
    while (stopping_x >= stopping_y) {
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        ++y
        stopping_y += two_a_square
        ellipse_error += y_change
        y_change += two_a_square

        if ((2 * ellipse_error + x_change) > 0) {
            --x
            stopping_x -= two_b_square
            ellipse_error += x_change
            x_change += two_b_square
        }
    }

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y) {
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        ++x
        stopping_x += two_b_square
        ellipse_error += x_change
        x_change += two_b_square

        if ((2 * ellipse_error + y_change) > 0) {
            --y
            stopping_y -= two_a_square
            ellipse_error += y_change
            y_change += two_a_square
        }
    }
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    franco_draw_ellipse(160, 120, 120, 80, BLUE)
    franco_draw_ellipse(160, 120, 60, 40, RED)
    franco_draw_ellipse(160, 120, 30, 30, WHITE)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
BLUE: Final = pygame.Color("blue")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_line(x0, y0, x1, y1, color):
    pygame.draw.line(window, color, (x0, y0), (x1, y1))


def plot_2_ellipse_lines(center_x, center_y, x, y, color):
    franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
    franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)


def franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color):
    two_a_square = 2 * x_radius * x_radius
    two_b_square = 2 * y_radius * y_radius

    x = x_radius
    y = 0
    x_change = y_radius * y_radius * (1 - 2 * x_radius)
    y_change = x_radius * x_radius
    ellipse_error = 0
    stopping_x = two_b_square * x_radius
    stopping_y = 0
    while (stopping_x >= stopping_y):
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        y += 1
        stopping_y += two_a_square
        ellipse_error += y_change
        y_change += two_a_square

        if ((2 * ellipse_error + x_change) > 0):
            x -= 1
            stopping_x -= two_b_square
            ellipse_error += x_change
            x_change += two_b_square

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y):
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        x += 1
        stopping_x += two_b_square
        ellipse_error += x_change
        x_change += two_b_square

        if ((2 * ellipse_error + y_change) > 0):
            y -= 1
            stopping_y -= two_a_square
            ellipse_error += y_change
            y_change += two_a_square


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            franco_draw_ellipse(160, 120, 120, 80, BLUE)
            franco_draw_ellipse(160, 120, 60, 40, RED)
            franco_draw_ellipse(160, 120, 30, 30, WHITE)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}


function franco_draw_line(x0, y0, x1, y1, color)
    love.graphics.setColor(color)
    love.graphics.line(x0, y0, x1, y1)
end


function plot_2_ellipse_lines(center_x, center_y, x, y, color)
    franco_draw_line(center_x + x, center_y + y, center_x - x, center_y + y, color)
    franco_draw_line(center_x - x, center_y - y, center_x + x, center_y - y, color)
end


function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color)
    local two_a_square = 2 * x_radius * x_radius
    local two_b_square = 2 * y_radius * y_radius

    local x = x_radius
    local y = 0
    local x_change = y_radius * y_radius * (1 - 2 * x_radius)
    local y_change = x_radius * x_radius
    local ellipse_error = 0
    local stopping_x = two_b_square * x_radius
    local stopping_y = 0
    while (stopping_x >= stopping_y) do
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        y = y + 1
        stopping_y = stopping_y + two_a_square
        ellipse_error = ellipse_error + y_change
        y_change = y_change + two_a_square

        if ((2 * ellipse_error + x_change) > 0) then
            x = x - 1
            stopping_x = stopping_x - two_b_square
            ellipse_error = ellipse_error + x_change
            x_change = x_change + two_b_square
        end
    end

    x = 0
    y = y_radius
    x_change = y_radius * y_radius
    y_change = x_radius * x_radius * (1 - 2 * y_radius)
    ellipse_error = 0
    stopping_x = 0
    stopping_y = two_a_square * y_radius
    while (stopping_x <= stopping_y) do
        plot_2_ellipse_lines(center_x, center_y, x, y, color)

        x = x + 1
        stopping_x = stopping_x + two_b_square
        ellipse_error = ellipse_error + x_change
        x_change = x_change + two_b_square

        if ((2 * ellipse_error + y_change) > 0) then
            y = y - 1
            stopping_y = stopping_y - two_a_square
            ellipse_error = ellipse_error + y_change
            y_change = y_change + two_a_square
        end
    end
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    franco_draw_ellipse(160, 120, 120, 80, BLUE)
    franco_draw_ellipse(160, 120, 60, 40, RED)
    franco_draw_ellipse(160, 120, 30, 30, WHITE)
end

O canvas a seguir apresenta o resultado.

Desenho de três elipses concêntricas preenchidas, usando uma modificação do algoritmo de John Kennedy criada pelo autor.

A abordagem ilustra como é possível reaproveitar o conhecimento obtido de soluções anteriores para a resolução de novos problemas. Quanto maior seu repertório de problemas resolvidos, maior será o conhecimento que você terá para resolver novos problemas. Em outras palavras, resolver problemas cada vez mais complexos é uma excelente forma de tornar-se melhor em resolver problemas, e, conseqüentemente, em programar. Em suma, resolva problemas para resolver problemas.

Primitivas para Desenho de Elipses

JavaScript com canvas fornece ellipse() para o desenho de elipses. Python com PyGame possui pygame.draw.ellipse() para uma elipse em definida por retângulo, ou pygame.gfxdraw.ellipse() e pygame.gfxdraw.filled_ellipse() para uma elipse definida por raios. Por simplicidade, a implementação escolherá a versão com raios. Lua com LÖVE provê love.graphics.ellipse().

GDScript não fornece uma primitiva para desenho de elipses. Uma alternativa usando apenas recursos existentes consiste em aplicar uma matriz de transformação para modificar o círculo, como sugerido nesta resposta. O exemplo apresenta uma generalização da resposta, adaptando a equação de um círculo para uma equação de elipse.

Com alguns ajustes na equação do círculo, é possível observar que um círculo é uma elipse com .

Assim, a ideia é desenhar um círculo de raio unitário com centro na origem. A matriz de transformação aplicará uma operação de escala (para ajustar os tamanhos dos raios) e de translação (para a posição de centro desejada para a elipse), criando a elipse desejada. Ou seja, a elipse será criada como uma deformação do círculo original.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const BLUE = Color.blue


const POINT_COUNT = 30
func franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = false):
    draw_set_transform(Vector2(center_x, center_y), 0, Vector2(x_radius, y_radius))
    if (fill):
        draw_circle(Vector2(0.0, 0.0), 1.0, color)
    else:
        draw_arc(Vector2(0.0, 0.0), 1.0, 0.0, 2.0 * PI, POINT_COUNT, color)

    draw_set_transform(Vector2(0.0, 0.0), 0, Vector2(1.0, 1.0))


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    franco_draw_ellipse(160, 120, 120, 80, BLUE, true)
    franco_draw_ellipse(160, 120, 60, 40, RED, true)
    franco_draw_ellipse(160, 120, 30, 30, WHITE, true)

    franco_draw_ellipse(160, 120, 30, 20, BLUE)
    franco_draw_ellipse(160, 120, 15, 10, RED)
    franco_draw_ellipse(160, 120, 7, 7, BLACK)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const BLUE = "blue"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = false) {
    if (fill) {
        context.fillStyle = color
    } else {
        context.strokeStyle = color
    }

    context.beginPath()
    context.ellipse(center_x, center_y, x_radius, y_radius, 0.0, 0.0, 2.0 * Math.PI)
    context.closePath()

    if (fill) {
        context.fill()
    } else {
        context.stroke()
    }
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    franco_draw_ellipse(160, 120, 120, 80, BLUE, true)
    franco_draw_ellipse(160, 120, 60, 40, RED, true)
    franco_draw_ellipse(160, 120, 30, 30, WHITE, true)

    franco_draw_ellipse(160, 120, 30, 20, BLUE)
    franco_draw_ellipse(160, 120, 15, 10, RED)
    franco_draw_ellipse(160, 120, 7, 7, BLACK)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import pygame.gfxdraw
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
BLUE: Final = pygame.Color("blue")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = False):
    if (fill):
        pygame.gfxdraw.filled_ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)
    else:
        pygame.gfxdraw.ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            franco_draw_ellipse(160, 120, 120, 80, BLUE, True)
            franco_draw_ellipse(160, 120, 60, 40, RED, True)
            franco_draw_ellipse(160, 120, 30, 30, WHITE, True)

            franco_draw_ellipse(160, 120, 30, 20, BLUE)
            franco_draw_ellipse(160, 120, 15, 10, RED)
            franco_draw_ellipse(160, 120, 7, 7, BLACK)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}


local ELLIPSE_SEGMENTS = nil -- 30
function franco_draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill)
    local fill_string = "line"
    if (fill) then
        fill_string = "fill"
    end

    love.graphics.setColor(color)

    love.graphics.ellipse(fill_string, center_x, center_y, x_radius, y_radius, ELLIPSE_SEGMENTS)
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    franco_draw_ellipse(160, 120, 120, 80, BLUE, true)
    franco_draw_ellipse(160, 120, 60, 40, RED, true)
    franco_draw_ellipse(160, 120, 30, 30, WHITE, true)

    franco_draw_ellipse(160, 120, 30, 20, BLUE)
    franco_draw_ellipse(160, 120, 15, 10, RED)
    franco_draw_ellipse(160, 120, 7, 7, BLACK)
end

O canvas a seguir apresenta o resultado.

Desenho de três elipses concêntricas preenchidas, com primitivas gráficas.

A implementação define o parâmetro fill como um valor lógico (booleano) para fornecer a opção de desenhar apenas o contorno (caso falso) ou preencher a elipse (caso verdadeiro). Isso evita duplicar implementações.

Caso se optasse por dois procedimentos, um deles poderia chamar-se franco_draw_ellipse(), franco_stroke_ellipse() ou franco_outline_ellipse() para o contorno; o outro poderia chamar-se franco_fill_ellipse() para preenchimentos.

Polígonos

Pode-se desenhar polígonos como seqüencias de linhas, com feito para o desenho da coroa na simulação do lançamentos de moedas e dados.

Desenhando Contornos de Polígonos

Para generalizar o processo, é possível usar Listas (Lists) ou Vetores (Arrays) para armazenar vários valores de pontos, que serão conectadas com retas para formar o polígono. O uso de vetores e outras coleções em Ideias, Regras, Simulação será detalhado em tópicos futuros; para este momento, basta saber que uma variável que instancie um vetor pode armazenar vários valores, e cada um desses valores pode ser acessado usando um índice.

Em GDScript, Python e JavaScript, um vetor com tamanho elementos possui o primeiro elemento na posição 0 e o último na posição tamanho - 1. Em Lua, o primeiro elemento está na posição 1 e o último está na posição tamanho. Assim, as implementações manipulam índices de forma um pouco diferente.

Em ambos os casos, o acesso ao elemento na posição indice do vetor é feita usando-se nome_vetor[indice]. Assim, por exemplo, nome_vetor[2] acessaria o terceiro valor armazenado em GDScript, Python e JavaScript. Em Lua, nome_vetor[2] acessaria o segundo valor armazenado no vetor.

Por fim, GDScript, Python e JavaScript usam colchetes para a declaração de vetores (ou listas, dependendo da linguagem). Lua utiliza chaves (mais especificamente, Lua define uma tabela ou table otimizada para o vetor).

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta


func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func franco_draw_polygon(points, color):
    assert(points.size() >= 2)

    var length = points.size()
    for index in range(0, length - 1):
        var current_point = points[index]
        var next_point = points[index + 1]

        franco_draw_line(current_point[0], current_point[1],
                         next_point[0], next_point[1],
                         color)

    franco_draw_line(points[0][0], points[0][1],
                     points[length - 1][0], points[length - 1][1],
                     color)


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    # Triângulo
    franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)

    # Retângulo
    franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)

    # Losango
    franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)

    # Polígono com pontos sortidos
    franco_draw_polygon([
        [160, 10], [150, 70], [170, 90], [140, 230],
        [180, 200], [210, 210], [250, 190], [300, 120],
        [260, 80], [260, 10]
     ], BLUE)

    # F
    franco_draw_polygon([
        [10, 105], [10, 220], [30, 220],
        [30, 170], [60, 170], [60, 150],
        [30, 150], [30, 130], [70, 130],
        [70, 105],
    ], YELLOW)

    # Estrela
    franco_draw_polygon([
        [80, 180], [50, 230], [120, 200],
        [40, 200], [110, 230],
    ], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_line(x0, y0, x1, y1, color) {
    context.strokeStyle = color
    // context.fillStyle = color
    context.beginPath()
    context.moveTo(x0, y0)
    context.lineTo(x1, y1)
    context.closePath()
    context.stroke()
}


function franco_draw_polygon(points, color) {
    console.assert(points.length >= 2)

    let length = points.length
    for (let index = 0; index < length - 1; ++index) {
        let current_point = points[index]
        let next_point = points[index + 1]

        franco_draw_line(current_point[0], current_point[1],
                         next_point[0], next_point[1],
                         color)
    }

    franco_draw_line(points[0][0], points[0][1],
                     points[length - 1][0], points[length - 1][1],
                     color)
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    // Triângulo
    franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)

    // Retângulo
    franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)

    // Losango
    franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)

    // Polígono com pontos sortidos
    franco_draw_polygon([
        [160, 10], [150, 70], [170, 90], [140, 230],
        [180, 200], [210, 210], [250, 190], [300, 120],
        [260, 80], [260, 10]
    ], BLUE)

    // F
    franco_draw_polygon([
        [10, 105], [10, 220], [30, 220],
        [30, 170], [60, 170], [60, 150],
        [30, 150], [30, 130], [70, 130],
        [70, 105],
    ], YELLOW)

    // Estrela
    franco_draw_polygon([
        [80, 180], [50, 230], [120, 200],
        [40, 200], [110, 230],
    ], MAGENTA)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_line(x0, y0, x1, y1, color):
    pygame.draw.line(window, color, (x0, y0), (x1, y1))


def franco_draw_polygon(points, color):
    assert(len(points) >= 2)

    length = len(points)
    for index in range(0, length - 1):
        current_point = points[index]
        next_point = points[index + 1]

        franco_draw_line(current_point[0], current_point[1],
                         next_point[0], next_point[1],
                         color)

    franco_draw_line(points[0][0], points[0][1],
                     points[length - 1][0], points[length - 1][1],
                     color)


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            # Triângulo
            franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)

            # Retângulo
            franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)

            # Losango
            franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)

            # Polígono com pontos sortidos
            franco_draw_polygon([
                [160, 10], [150, 70], [170, 90], [140, 230],
                [180, 200], [210, 210], [250, 190], [300, 120],
                [260, 80], [260, 10]
            ], BLUE)

            # F
            franco_draw_polygon([
                [10, 105], [10, 220], [30, 220],
                [30, 170], [60, 170], [60, 150],
                [30, 150], [30, 130], [70, 130],
                [70, 105],
            ], YELLOW)

            # Estrela
            franco_draw_polygon([
                [80, 180], [50, 230], [120, 200],
                [40, 200], [110, 230],
            ], MAGENTA)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}


function franco_draw_line(x0, y0, x1, y1, color)
    love.graphics.setColor(color)
    love.graphics.line(x0, y0, x1, y1)
end


function franco_draw_polygon(points, color)
    assert(#points >= 2)

    local length = #points
    for index = 1, length - 1 do
        local current_point = points[index]
        local next_point = points[index + 1]

        franco_draw_line(current_point[1], current_point[2],
                         next_point[1], next_point[2],
                         color)
    end

    franco_draw_line(points[1][1], points[1][2],
                     points[length][1], points[length][2],
                     color)
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    -- Triângulo
    franco_draw_polygon({{10, 40}, {40, 40}, {40, 10}}, WHITE)

    -- Retângulo
    franco_draw_polygon({{50, 10}, {50, 80}, {140, 80}, {140, 10}}, RED)

    -- Losango
    franco_draw_polygon({{130, 100}, {150, 160}, {100, 160}, {80, 100}}, GREEN)

    -- Polígono com pontos sortidos
    franco_draw_polygon({
        {160, 10}, {150, 70}, {170, 90}, {140, 230},
        {180, 200}, {210, 210}, {250, 190}, {300, 120},
        {260, 80}, {260, 10}
    }, BLUE)

    -- F
    franco_draw_polygon({
        {10, 105}, {10, 220}, {30, 220},
        {30, 170}, {60, 170}, {60, 150},
        {30, 150}, {30, 130}, {70, 130},
        {70, 105},
    }, YELLOW)

    -- Estrela
    franco_draw_polygon({
        {80, 180}, {50, 230}, {120, 200},
        {40, 200}, {110, 230},
    }, MAGENTA)
end

O canvas a seguir apresenta o resultado.

Desenhos de polígonos usando linhas conectadas por um ponto comum. A figura contém um triângulo branco, um retângulo vermelho, um losango verde, uma letra F amarela, um polígono com pontos sortidos em cor azul, e uma estrela magenta. O fundo da imagem é preto.

Simplificadamente, a estrutura de repetição em franco_draw_polygon() desenha linhas conectando pares de pontos em posições contíguas (uma ao lado da outra). O primeiro ponto é conectado ao segundo, o segundo ao terceiro, e assim por diante. O laço termina no penúltimo ponto, que é conectado ao último.

A chamada de franco_draw_line() fora do laço conecta o primeiro ponto ao último, para conectar o desenho.

Assim, para o uso correto do procedimento, deve-se fornecer ao menos dois pontos para a chamada (de preferência distintos, para o desenho de uma linha). O uso de assert() verifica o tamanho; caso o vetor tenha menos de dois valores, a chamada falha e apresenta um erro na versão de desenvolvimento. Isso é chamado de asserção; asserções foram previamente comentadas em Aprenda Programação: Subrotinas (Funções e Procedimentos) e Aprenda Programação: Registros. Uma asserção não serve para tratamento de erros em programas fornecidos para usuários finais; ela serve para informar o programador ou a programadora que ele ou ela cometeu um erro de implementação (que deve ser corrigido).

Convém notar que, dependendo dos pontos escolhidos, uma reta pode atravessar o desenho. A rigor, isso não formaria um polígono convexo. Em um polígono convexo, todos os ângulos internos são menores que 180°. Além de convexos, polígonos podem ser côncavos ou complexos. Um polígono côncavo pode ter ângulos internos maiores que 180°. A categoria de polígonos complexos inclui as outras duas. Arestas que cruzam o próprio polígono formariam um polígono complexo (mais especificamente, um self-intersecting polygon).

No exemplo criado, o triângulo, o retângulo e o losango são polígonos convexos. A letra F amarela e o polígono azul são côncavos. A estrela magenta é um polígono complexo.

Para usar franco_draw_polygon(), a chamada define um vetor de vetores. Cada ponto (par ordenado) é passado como um vetor.

Refatoração para Abstração de Dados: Criando um Tipo de Dados para Pontos

Para uma solução mais limpa, também seria possível definir um Registros (Structs ou Records) para definir um tipo ponto (Point). Embora GDScript, JavaScript e Python permitam criar classes do paradigma da Programação Orientada a Objetos (ou POO, do inglês, Object-Oriented Programming ou OOP), este tópico assumirá o uso de registros, como definidos em Aprenda Programação: Registros: tipos compostos para armazenamento de dados heterogêneos. OOP será abordado no futuro, quando os tipos de dados requerem processamento mais complexo.

Um registro é um tipo criado pela programador ou pelo programador da aplicação. Ou seja, além de tipos fornecidos pela linguagem de programação, agora você poderá criar seu próprio.

O intuito é criar um novo tipo de dados que armazene dois valores: um valor inteiro ou real para x, e um valor inteiro ou real para y para formar um ponto bidimensional (2D). Em alto nível, um registro Ponto poderia ser descrito como a seguir na forma de pseudocódigo:

registro Ponto
    x: real
    y: real
fim

var ponto_a: Ponto
ponto_a.x = 10
ponto_a.y = 20

var ponto_b: Ponto
ponto_b.x = 20
ponto_b.y = 10

var delta_x: real
delta_x = ponto_a.x - ponto_b.x

Ou seja, a declaração de uma variável do tipo Ponto criaria uma instância de um registro com duas variáveis (x e y) para as coordenadas. Os próximos exemplos refatoram o código da seção anterior para incorporar um registro. Assim, ao invés de vetores com pares de valores para cada coordenada do polígono, um polígono poderá ser composto por um vetor de variáveis do tipo Ponto. Por exemplo, ao invés de [10, 40], poder-se-ia criar um Ponto com x igual a 10 e y igual a 40, e usá-lo como alternativa equivalente.

Para manter a nomenclatura em Inglês, Ponto será traduzido para Point.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta


class Point:
    var x
    var y


    func _init(_x, _y):
        self.x = _x
        self.y = _y


func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func franco_draw_polygon(points, color):
    assert(points.size() >= 2)

    var length = points.size()
    for index in range(0, length - 1):
        var current_point = points[index]
        var next_point = points[index + 1]

        franco_draw_line(current_point.x, current_point.y,
                         next_point.x, next_point.y,
                         color)

    franco_draw_line(points[0].x, points[0].y,
                     points[length - 1].x, points[length - 1].y,
                     color)


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    # Triângulo
    franco_draw_polygon([
        Point.new(10, 40),
        Point.new(40, 40),
        Point.new(40, 10)
    ], WHITE)

    # Retângulo
    franco_draw_polygon([
        Point.new(50, 10),
        Point.new(50, 80),
        Point.new(140, 80),
        Point.new(140, 10),
        Point.new(50, 10)
    ], RED)

    # Losango
    franco_draw_polygon([
        Point.new(130, 100),
        Point.new(150, 160),
        Point.new(100, 160),
        Point.new(80, 100)
    ], GREEN)

    # Polígono com pontos sortidos
    franco_draw_polygon([
        Point.new(160, 10), Point.new(150, 70), Point.new(170, 90), Point.new(140, 230),
        Point.new(180, 200), Point.new(210, 210), Point.new(250, 190), Point.new(300, 120),
        Point.new(260, 80), Point.new(260, 10), Point.new(160, 10)
    ], BLUE)

    # F
    franco_draw_polygon([
        Point.new(10, 105), Point.new(10, 220), Point.new(30, 220),
        Point.new(30, 170), Point.new(60, 170), Point.new(60, 150),
        Point.new(30, 150), Point.new(30, 130), Point.new(70, 130),
        Point.new(70, 105), Point.new(10, 105)
    ], YELLOW)

    # Estrela
    franco_draw_polygon([
        Point.new(80, 180), Point.new(50, 230), Point.new(120, 200),
        Point.new(40, 200), Point.new(110, 230), Point.new(80, 180)
    ], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"


class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
}


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_line(x0, y0, x1, y1, color) {
    context.strokeStyle = color
    // context.fillStyle = color
    context.beginPath()
    context.moveTo(x0, y0)
    context.lineTo(x1, y1)
    context.closePath()
    context.stroke()
}


function franco_draw_polygon(points, color) {
    console.assert(points.length >= 2)

    let length = points.length
    for (let index = 0; index < length - 1; ++index) {
        let current_point = points[index]
        let next_point = points[index + 1]

        franco_draw_line(current_point.x, current_point.y,
                         next_point.x, next_point.y,
                         color)
    }

    franco_draw_line(points[0].x, points[0].y,
                     points[length - 1].x, points[length - 1].y,
                     color)
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    // Triângulo
    franco_draw_polygon([
        new Point(10, 40),
        new Point(40, 40),
        new Point(40, 10)
    ], WHITE)

    // Retângulo
    franco_draw_polygon([
        new Point(50, 10),
        new Point(50, 80),
        new Point(140, 80),
        new Point(140, 10)
    ], RED)

    // Losango
    franco_draw_polygon([
        new Point(130, 100),
        new Point(150, 160),
        new Point(100, 160),
        new Point(80, 100)
    ], GREEN)

    // Polígono com pontos sortidos
    franco_draw_polygon([
        new Point(160, 10), new Point(150, 70), new Point(170, 90), new Point(140, 230),
        new Point(180, 200), new Point(210, 210), new Point(250, 190), new Point(300, 120),
        new Point(260, 80), new Point(260, 10)
    ], BLUE)

    // F
    franco_draw_polygon([
        new Point(10, 105), new Point(10, 220), new Point(30, 220),
        new Point(30, 170), new Point(60, 170), new Point(60, 150),
        new Point(30, 150), new Point(30, 130), new Point(70, 130),
        new Point(70, 105),
    ], YELLOW)

    // Estrela
    franco_draw_polygon([
        new Point(80, 180), new Point(50, 230), new Point(120, 200),
        new Point(40, 200), new Point(110, 230),
    ], MAGENTA)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_line(x0, y0, x1, y1, color):
    pygame.draw.line(window, color, (x0, y0), (x1, y1))


def franco_draw_polygon(points, color):
    assert(len(points) >= 2)

    length = len(points)
    for index in range(0, length - 1):
        current_point = points[index]
        next_point = points[index + 1]

        franco_draw_line(current_point.x, current_point.y,
                         next_point.x, next_point.y,
                         color)

    franco_draw_line(points[0].x, points[0].y,
                     points[length - 1].x, points[length - 1].y,
                     color)


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            # Triângulo
            franco_draw_polygon([
                Point(10, 40),
                Point(40, 40),
                Point(40, 10)
            ], WHITE)

            # Retângulo
            franco_draw_polygon([
                Point(50, 10),
                Point(50, 80),
                Point(140, 80),
                Point(140, 10),
                Point(50, 10)
            ], RED)

            # Losango
            franco_draw_polygon([
                Point(130, 100),
                Point(150, 160),
                Point(100, 160),
                Point(80, 100)
            ], GREEN)

            # Polígono com pontos sortidos
            franco_draw_polygon([
                Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
                Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
                Point(260, 80), Point(260, 10), Point(160, 10)
            ], BLUE)

            # F
            franco_draw_polygon([
                Point(10, 105), Point(10, 220), Point(30, 220),
                Point(30, 170), Point(60, 170), Point(60, 150),
                Point(30, 150), Point(30, 130), Point(70, 130),
                Point(70, 105), Point(10, 105)
            ], YELLOW)

            # Estrela
            franco_draw_polygon([
                Point(80, 180), Point(50, 230), Point(120, 200),
                Point(40, 200), Point(110, 230), Point(80, 180)
            ], MAGENTA)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}


function Point(x, y)
    return {
        x = x,
        y = y
    }
end


function franco_draw_line(x0, y0, x1, y1, color)
    love.graphics.setColor(color)
    love.graphics.line(x0, y0, x1, y1)
end


function franco_draw_polygon(points, color)
    assert(#points >= 2)

    local length = #points
    for index = 1, length - 1 do
        local current_point = points[index]
        local next_point = points[index + 1]

        franco_draw_line(current_point.x, current_point.y,
                         next_point.x, next_point.y,
                         color)
    end

    franco_draw_line(points[1].x, points[1].y,
                     points[length].x, points[length].y,
                     color)
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    -- Triângulo
    franco_draw_polygon({
        Point(10, 40),
        Point(40, 40),
        Point(40, 10)
    }, WHITE)

    -- Retângulo
    franco_draw_polygon({
        Point(50, 10),
        Point(50, 80),
        Point(140, 80),
        Point(140, 10),
        Point(50, 10)
    }, RED)

    -- Losango
    franco_draw_polygon({
        Point(130, 100),
        Point(150, 160),
        Point(100, 160),
        Point(80, 100)
    }, GREEN)

    -- Polígono com pontos sortidos
    franco_draw_polygon({
        Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
        Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
        Point(260, 80), Point(260, 10), Point(160, 10)
    }, BLUE)

    -- F
    franco_draw_polygon({
        Point(10, 105), Point(10, 220), Point(30, 220),
        Point(30, 170), Point(60, 170), Point(60, 150),
        Point(30, 150), Point(30, 130), Point(70, 130),
        Point(70, 105), Point(10, 105)
    }, YELLOW)

    -- Estrela
    franco_draw_polygon({
        Point(80, 180), Point(50, 230), Point(120, 200),
        Point(40, 200), Point(110, 230), Point(80, 180)
    }, MAGENTA)
end

O canvas a seguir apresenta o resultado.

Desenhos de polígonos usando linhas conectadas por um ponto comum. A figura contém um triângulo branco, um retângulo vermelho, um losango verde, uma letra F amarela, um polígono com pontos sortidos em cor azul, e uma estrela magenta. O fundo da imagem é preto.

A versão em GDScript usa uma classe interna (inner class). A versão em Lua utiliza uma table para evitar introduzir um modelo de OOP arbitrário, já que a linguagem não fornece construções específicas para registros ou classes. O mesmo poderia ser feito em JavaScript, usando-se JavaScript Object.

Caso se desejasse, também poder-se-ia refatorar franco_draw_line() para receber um Point ao invés de dois números como coordenadas.

Fornecer alternativas para uso pode enriquecer uma API, tornando-a mais expressiva. A contrapartida é manter todas as versões atualizadas e funcionais. Uma forma elegante de minimizar o problema consiste em definir uma subrotina de nível mais baixo, e chamá-la por subrotinas similares (para abstração de nível mais alto).

Nessa abordagem, poder-se-ia definir um segundo procedimento que fosse uma variação usando pontos. O próximo exemplo ilustra a abordagem em GDScript.

func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func franco_draw_line_from_points(point0, point1, color):
    franco_draw_line(point0.x, point0.y, point1.x, point1.y, color)


func franco_draw_polygon(points, color):
    assert(points.size() >= 2)

    var length = points.size()
    for index in range(0, length - 1):
        var current_point = points[index]
        var next_point = points[index + 1]

        franco_draw_line_from_points(current_point, next_point, color)

    franco_draw_line_from_points(points[0], points[length - 1], color)

franco_draw_line_from_points() recebe dois pontos como parâmetros, e chama a função original franco_draw_line() para o desenho da linha. A versão modificada de franco_draw_polygon() chama franco_draw_line_from_points() para o desenho de cada linha do polígono.

Desde que os parâmetros fossem mantidos, a versão com pontos seria atualizada automaticamente sempre que a versão original fosse modificada, pois tudo que ela faz é chamar a original.

Primitivas para Desenho de Contornos de Polígonos

APIs gráficas comumente fornecem uma subrotina para a criação de polígonos usando uma seqüência de pontos. GDScript fornece draw_polyline(), Python com PyGame possui pygame.draw.polygon(), e Lua com LÖVE provê love.graphics.polygon(). JavaScript não provê uma subrotina específica; no caso, deve-se desenhar as linhas individualmente.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta


func franco_draw_polygon(points, color):
    assert(points.size() >= 2)

    draw_polyline(PoolVector2Array(points), color)


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    # Triângulo
    franco_draw_polygon([Vector2(10, 40), Vector2(40, 40), Vector2(40, 10), Vector2(10, 40)], WHITE)

    # Retângulo
    franco_draw_polygon([Vector2(50, 10), Vector2(50, 80), Vector2(140, 80), Vector2(140, 10), Vector2(50, 10)], RED)

    # Losango
    franco_draw_polygon([Vector2(130, 100), Vector2(150, 160), Vector2(100, 160), Vector2(80, 100), Vector2(130, 100)], GREEN)

    # Polígono com pontos sortidos
    franco_draw_polygon([
        Vector2(160, 10), Vector2(150, 70), Vector2(170, 90), Vector2(140, 230),
        Vector2(180, 200), Vector2(210, 210), Vector2(250, 190), Vector2(300, 120),
        Vector2(260, 80), Vector2(260, 10), Vector2(160, 10)
    ], BLUE)

    # F
    franco_draw_polygon([
        Vector2(10, 105), Vector2(10, 220), Vector2(30, 220),
        Vector2(30, 170), Vector2(60, 170), Vector2(60, 150),
        Vector2(30, 150), Vector2(30, 130), Vector2(70, 130),
        Vector2(70, 105), Vector2(10, 105)
    ], YELLOW)

    # Estrela
    franco_draw_polygon([
        Vector2(80, 180), Vector2(50, 230), Vector2(120, 200),
        Vector2(40, 200), Vector2(110, 230), Vector2(80, 180)
    ], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_polygon(points, color) {
    console.assert(points.length >= 2)

    context.strokeStyle = color
    // context.fillStyle = color

    context.beginPath()
    context.moveTo(points[0][0], points[0][1])

    let length = points.length
    for (let index = 1; index < length; ++index) {
        let next_point = points[index]
        context.lineTo(next_point[0], next_point[1])
    }

    context.closePath()
    context.stroke()
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    // Triângulo
    franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)

    // Retângulo
    franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)

    // Losango
    franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)

    // Polígono com pontos sortidos
    franco_draw_polygon([
        [160, 10], [150, 70], [170, 90], [140, 230],
        [180, 200], [210, 210], [250, 190], [300, 120],
        [260, 80], [260, 10]
    ], BLUE)

    // F
    franco_draw_polygon([
        [10, 105], [10, 220], [30, 220],
        [30, 170], [60, 170], [60, 150],
        [30, 150], [30, 130], [70, 130],
        [70, 105],
    ], YELLOW)

    // Estrela
    franco_draw_polygon([
        [80, 180], [50, 230], [120, 200],
        [40, 200], [110, 230], [80, 180]
    ], MAGENTA)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_polygon(points, color):
    assert(len(points) >= 2)

    # 0: preencher polígono; > 0: espessura da linha.
    pygame.draw.polygon(window, color, points, 1)


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            # Triângulo
            franco_draw_polygon([(10, 40), (40, 40), (40, 10)], WHITE)

            # Retângulo
            franco_draw_polygon([(50, 10), (50, 80), (140, 80), (140, 10)], RED)

            # Losango
            franco_draw_polygon([(130, 100), (150, 160), (100, 160), (80, 100)], GREEN)

            # Polígono com pontos sortidos
            franco_draw_polygon([
                (160, 10), (150, 70), (170, 90), (140, 230),
                (180, 200), (210, 210), (250, 190), (300, 120),
                (260, 80), (260, 10)
            ], BLUE)

            # F
            franco_draw_polygon([
                (10, 105), (10, 220), (30, 220),
                (30, 170), (60, 170), (60, 150),
                (30, 150), (30, 130), (70, 130),
                (70, 105),
            ], YELLOW)

            # Estrela
            franco_draw_polygon([
                (80, 180), (50, 230), (120, 200),
                (40, 200), (110, 230), (80, 180)
            ], MAGENTA)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}


function franco_draw_polygon(points, color)
    assert(#points >= 2)

    love.graphics.setColor(color)
    love.graphics.polygon("line", points)
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    -- Triângulo
    franco_draw_polygon({10, 40, 40, 40, 40, 10}, WHITE)

    -- Retângulo
    franco_draw_polygon({50, 10, 50, 80, 140, 80, 140, 10}, RED)

    -- Losango
    franco_draw_polygon({130, 100, 150, 160, 100, 160, 80, 100}, GREEN)

    -- Polígono com pontos sortidos
    franco_draw_polygon({
        160, 10, 150, 70, 170, 90, 140, 230,
        180, 200, 210, 210, 250, 190, 300, 120,
        260, 80, 260, 10
    }, BLUE)

    -- F
    franco_draw_polygon({
        10, 105, 10, 220, 30, 220,
        30, 170, 60, 170, 60, 150,
        30, 150, 30, 130, 70, 130,
        70, 105,
    }, YELLOW)

    -- Estrela
    -- NOTA LÖVE não desenha.
    franco_draw_polygon({
        80, 180, 50, 230, 120, 200,
        40, 200, 110, 230, 80, 180
    }, MAGENTA)
end

O canvas a seguir apresenta o resultado.

Desenhos de polígonos usando primitivas gráficas. A figura contém um triângulo branco, um retângulo vermelho, um losango verde, uma letra F amarela, um polígono com pontos sortidos em cor azul, e uma estrela magenta. O fundo da imagem é preto.

Como esperado, o resultado é o mesmo. Afinal, trata-se uma refatoração (comumente chamada de extração de classe).

Em GDScript, deve-se repetir o primeiro ponto ao final do vetor usado como parâmetro para que draw_polyline() complete o contorno polígono.

Em JavaScript, usa-se praticamente a mesma implementação anterior. O código de franco_draw_line() foi mesclado em franco_draw_polygon().

Em Lua com LÖVE, pode-se passar os pontos com as coordenadas alternadas diretamente na chamada, ou em um vetor (como table). Também deve-se notar que love.graphics.polygon() não desenha polígonos complexos.

Preenchendo Polígonos

Definidos os contornos, é hora de preenchê-los para se criar polígonos coloridos. O algoritmo de preenchimento possivelmente será o mais complexo até este ponto de Ideias, Regras, Simulação. Para implementá-lo, será necessário usar operações de Listas (Lists) ou Vetores (Arrays), como inserção de novos valores e ordenação.

Assim, caso você esteja iniciando suas atividades de programação, pode ser interessante explorar as primitivas pré-definidas. Mais tarde, quando você tiver mais prática, você pode retornar ao algoritmo de preenchimento de polígonos.

Preenchendo Polígonos com Scanline Rendering (Scan-Line Algorithm)

Um dos algoritmos mais simples para preenchimento de polígonos chama-se scanline rendering, também conhecido como scan-line algorithm. Uma boa descrição pode ser encontrada nesta página (em Inglês).

A implementação deste tópico será um pouco mais simples e ineficiente, mantendo-se todos os pontos armazenados em um vetor. Ela segue os seguintes passos:

  1. Determinação de maior e menor valores da ordenada y do polígono. Os valores serão usados para preencher o polígono linha a linha;
  2. Identificação do valor da abscissa x para o valor mínimo de y. Ele será usado para percorrer as linhas durante o preenchimento;
  3. Cálculo de coeficiente angular de retas. O coeficiente será usado para determinar pontos inclinados dentro do polígono;
  4. Determinação de segmentos de reta para desenho em cada y. Pontos serão pareados dois a dois. Desenhar-se-á linhas entre intervalos para x de pares alternados. Isso permite colorir partes relevantes, assim como ignorar lacunas.

O próximo canvas apresenta uma animação do preenchimento feito pelo algoritmo. Para iniciá-la, pode-se usar o botão Iniciar Animação. A velocidade de reprodução pode ser ajustada.




Animação de preenchimento de desenhos de polígonos usando Scanline Algorithm. A figura contém um triângulo branco, um retângulo vermelho, um losango verde, uma letra F amarela, um polígono com pontos sortidos em cor azul, e uma estrela magenta. O fundo da imagem é preto.

Simplificadamente, a implementação do algoritmo mapeia os segmentos de reta que formam o polígono (selecionando pares de pontos consecutivos). Em seguida, inicializa-se uma instância de uma aresta Edge para armazenar os valores mínimo e máximo de y, o inverso (recíproca) do coeficiente angular, e o valor de x para o qual y é mínimo (para iniciar o desenho do segmento de reta). Cada valor é armazenado de um vetor chamado edges.

Após processar todos os pares, inicia-se o desenho do preenchimento. O código inicia-se na estrutura de repetição enquanto (while).

Mapeia-se cada edge que deve ser desenhada para a linha (y) atual, isto é, aquelas cujo valor mínimo da ordenada (minimum_y) seja maior ou igual a y e que (ao mesmo tempo) tenham valor máximo da ordenada (maximum_y) menor que y. Cada valor inicial de x é armazenado em um vetor (starting_x).

Para o próximo passo, o valor inicial de x para cada aresta é calculado usando-se o inverso do coeficiente angular. Isso é similar ao que foi feito previamente para o desenho de linhas oblíquas em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos).

A ordenação de valores é feita para tratar o caso de linhas cruzarem-se ao longo do desenho (ao que pode ocorrer em polígonos complexos). Por fim, basta desenhar segmentos de reta alternados. O primeiro desenhado; o segundo é ignorado; o terceiro é desenhado; o quarto é ignorado; e assim por diante. Isso permite ignorar lacunas entre linhas do polígono.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta


class Point:
    var x
    var y


    func _init(_x, _y):
        self.x = _x
        self.y = _y


class Edge:
    var maximum_y
    var minimum_y
    var x
    # m (coeficient angular)
    var inverted_slope


func franco_draw_line(x0, y0, x1, y1, color):
    draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


func franco_draw_polygon(points, color):
    assert(points.size() >= 2)

    var length = points.size()

    var edges = []
    var minimum_y = points[0].y
    var maximum_y = points[0].y
    for index in range(0, length):
        var current_point = points[index]
        var next_point
        if (index < (length - 1)):
            next_point = points[index + 1]
        else:
            next_point = points[0]

        var x0 = current_point.x
        var y0 = current_point.y
        var x1 = next_point.x
        var y1 = next_point.y
        if (current_point.y <= next_point.y):
            x0 = next_point.x
            y0 = next_point.y
            x1 = current_point.x
            y1 = current_point.y

        var edge = Edge.new()
        if (y1 < y0):
            edge.maximum_y = y0
            edge.minimum_y = y1
            edge.x = x1
        else:
            edge.maximum_y = y1
            edge.minimum_y = y0
            edge.x = x0

        if (y0 != y1):
            edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
        else:
            edge.inverted_slope = 0.0

        edges.append(edge)

        if (edge.minimum_y < minimum_y):
            minimum_y = edge.minimum_y

        if (edge.maximum_y > maximum_y):
            maximum_y = edge.maximum_y

    var PAINT_LAST_PIXEL = 1
    var y = minimum_y
    while (y < maximum_y):
        var starting_x = []
        for edge in edges:
            if ((y >= edge.minimum_y) and (y < edge.maximum_y)):
                starting_x.append(edge.x)

                edge.x += edge.inverted_slope

        starting_x.sort()
        for index in range(0, starting_x.size() - 1, 2):
            var x0 = starting_x[index] - PAINT_LAST_PIXEL
            var x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
            franco_draw_line(x0, y, x1, y, color)

        y += 1


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    # Triângulo
    franco_draw_polygon([
        Point.new(10, 40),
        Point.new(40, 40),
        Point.new(40, 10)
    ], WHITE)

    # Retângulo
    franco_draw_polygon([
        Point.new(50, 10),
        Point.new(50, 80),
        Point.new(140, 80),
        Point.new(140, 10),
        Point.new(50, 10)
    ], RED)

    # Losango
    franco_draw_polygon([
        Point.new(130, 100),
        Point.new(150, 160),
        Point.new(100, 160),
        Point.new(80, 100)
    ], GREEN)

    # Polígono com pontos sortidos
    franco_draw_polygon([
        Point.new(160, 10), Point.new(150, 70), Point.new(170, 90), Point.new(140, 230),
        Point.new(180, 200), Point.new(210, 210), Point.new(250, 190), Point.new(300, 120),
        Point.new(260, 80), Point.new(260, 10), Point.new(160, 10)
    ], BLUE)

    # F
    franco_draw_polygon([
        Point.new(10, 105), Point.new(10, 220), Point.new(30, 220),
        Point.new(30, 170), Point.new(60, 170), Point.new(60, 150),
        Point.new(30, 150), Point.new(30, 130), Point.new(70, 130),
        Point.new(70, 105), Point.new(10, 105)
    ], YELLOW)

    # Estrela
    franco_draw_polygon([
        Point.new(80, 180), Point.new(50, 230), Point.new(120, 200),
        Point.new(40, 200), Point.new(110, 230), Point.new(80, 180)
    ], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"


class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
}


class Edge {
    constructor() {
        this.maximum_y = null
        this.minimum_y = null
        this.x = null
        // m (coeficient angular)
        this.inverted_slope = null
    }
}


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_line(x0, y0, x1, y1, color) {
    context.strokeStyle = color
    // context.fillStyle = color
    context.beginPath()
    context.moveTo(x0, y0)
    context.lineTo(x1, y1)
    context.closePath()
    context.stroke()
}


function franco_draw_polygon(points, color) {
    console.assert(points.length >= 2)

    var length = points.length

    let edges = []
    let minimum_y = points[0].y
    let maximum_y = points[0].y
    for (let index = 0; index < length; ++index) {
        let current_point = points[index]
        let next_point
        if (index < (length - 1)) {
            next_point = points[index + 1]
        } else {
            next_point = points[0]
        }

        let x0 = current_point.x
        let y0 = current_point.y
        let x1 = next_point.x
        let y1 = next_point.y
        if (current_point.y <= next_point.y) {
            x0 = next_point.x
            y0 = next_point.y
            x1 = current_point.x
            y1 = current_point.y
        }

        let edge = new Edge()
        if (y1 < y0) {
            edge.maximum_y = y0
            edge.minimum_y = y1
            edge.x = x1
        } else {
            edge.maximum_y = y1
            edge.minimum_y = y0
            edge.x = x0
        }

        if (y0 !== y1) {
            edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
        } else {
            edge.inverted_slope = 0.0
        }

        edges.push(edge)

        if (edge.minimum_y < minimum_y) {
            minimum_y = edge.minimum_y
        }

        if (edge.maximum_y > maximum_y) {
            maximum_y = edge.maximum_y
        }
    }

    let PAINT_LAST_PIXEL = 1
    let y = minimum_y
    while (y < maximum_y) {
        let starting_x = []
        for (let edge of edges) {
            if ((y >= edge.minimum_y) && (y < edge.maximum_y)) {
                starting_x.push(edge.x)

                edge.x += edge.inverted_slope
            }
        }

        starting_x.sort(function(x, y) {
            return x - y
        })
        for (let index = 0, end = starting_x.length - 1;
             index <= end;
             index += 2) {
            let x0 = starting_x[index] - PAINT_LAST_PIXEL
            let x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
            franco_draw_line(x0, y, x1, y, color)
        }

        y += 1
    }
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    // Triângulo
    franco_draw_polygon([
        new Point(10, 40),
        new Point(40, 40),
        new Point(40, 10)
    ], WHITE)

    // Retângulo
    franco_draw_polygon([
        new Point(50, 10),
        new Point(50, 80),
        new Point(140, 80),
        new Point(140, 10)
    ], RED)

    // Losango
    franco_draw_polygon([
        new Point(130, 100),
        new Point(150, 160),
        new Point(100, 160),
        new Point(80, 100)
    ], GREEN)

    // Polígono com pontos sortidos
    franco_draw_polygon([
        new Point(160, 10), new Point(150, 70), new Point(170, 90), new Point(140, 230),
        new Point(180, 200), new Point(210, 210), new Point(250, 190), new Point(300, 120),
        new Point(260, 80), new Point(260, 10)
    ], BLUE)

    // F
    franco_draw_polygon([
        new Point(10, 105), new Point(10, 220), new Point(30, 220),
        new Point(30, 170), new Point(60, 170), new Point(60, 150),
        new Point(30, 150), new Point(30, 130), new Point(70, 130),
        new Point(70, 105),
    ], YELLOW)

    // Estrela
    franco_draw_polygon([
        new Point(80, 180), new Point(50, 230), new Point(120, 200),
        new Point(40, 200), new Point(110, 230),
    ], MAGENTA)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y



class Edge:
    def __init__(self):
        self.maximum_y = None
        self.minimum_y = None
        self.x = None
        # m (coeficient angular)
        self.inverted_slope = None



pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_line(x0, y0, x1, y1, color):
    pygame.draw.line(window, color, (x0, y0), (x1, y1))


def franco_draw_polygon(points, color):
    assert(len(points) >= 2)

    length = len(points)
    edges = []
    minimum_y = points[0].y
    maximum_y = points[0].y
    for index in range(0, length):
        current_point = points[index]
        next_point = None
        if (index < (length - 1)):
            next_point = points[index + 1]
        else:
            next_point = points[0]

        x0 = current_point.x
        y0 = current_point.y
        x1 = next_point.x
        y1 = next_point.y
        if (current_point.y <= next_point.y):
            x0 = next_point.x
            y0 = next_point.y
            x1 = current_point.x
            y1 = current_point.y

        edge = Edge()
        if (y1 < y0):
            edge.maximum_y = y0
            edge.minimum_y = y1
            edge.x = x1
        else:
            edge.maximum_y = y1
            edge.minimum_y = y0
            edge.x = x0

        if (y0 != y1):
            edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
        else:
            edge.inverted_slope = 0.0

        edges.append(edge)

        if (edge.minimum_y < minimum_y):
            minimum_y = edge.minimum_y

        if (edge.maximum_y > maximum_y):
            maximum_y = edge.maximum_y

    PAINT_LAST_PIXEL = 1
    y = minimum_y
    while (y < maximum_y):
        starting_x = []
        for edge in edges:
            if ((y >= edge.minimum_y) and (y < edge.maximum_y)):
                starting_x.append(edge.x)

                edge.x += edge.inverted_slope

        starting_x.sort()
        for index in range(0, len(starting_x) - 1, 2):
            x0 = starting_x[index] - PAINT_LAST_PIXEL
            x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
            franco_draw_line(x0, y, x1, y, color)

        y += 1


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            # Triângulo
            franco_draw_polygon([
                Point(10, 40),
                Point(40, 40),
                Point(40, 10)
            ], WHITE)

            # Retângulo
            franco_draw_polygon([
                Point(50, 10),
                Point(50, 80),
                Point(140, 80),
                Point(140, 10),
                Point(50, 10)
            ], RED)

            # Losango
            franco_draw_polygon([
                Point(130, 100),
                Point(150, 160),
                Point(100, 160),
                Point(80, 100)
            ], GREEN)

            # Polígono com pontos sortidos
            franco_draw_polygon([
                Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
                Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
                Point(260, 80), Point(260, 10), Point(160, 10)
            ], BLUE)

            # F
            franco_draw_polygon([
                Point(10, 105), Point(10, 220), Point(30, 220),
                Point(30, 170), Point(60, 170), Point(60, 150),
                Point(30, 150), Point(30, 130), Point(70, 130),
                Point(70, 105), Point(10, 105)
            ], YELLOW)

            # Estrela
            franco_draw_polygon([
                Point(80, 180), Point(50, 230), Point(120, 200),
                Point(40, 200), Point(110, 230), Point(80, 180)
            ], MAGENTA)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}


function Point(x, y)
    return {
        x = x,
        y = y
    }
end


function Edge()
    return {
        maximum_y = nil,
        minimum_y = nil,
        x = nil,
        -- m (coeficient angular)
        inverted_slope = nil
    }
end


function franco_draw_line(x0, y0, x1, y1, color)
    love.graphics.setColor(color)
    love.graphics.line(x0, y0, x1, y1)
end


function franco_draw_polygon(points, color)
    assert(#points >= 2)

    local length = #points

    local edges = {}
    local minimum_y = points[1].y
    local maximum_y = points[1].y
    for index = 1, length do
        local current_point = points[index]
        local next_point
        if (index < length) then
            next_point = points[index + 1]
        else
            next_point = points[1]
        end

        local x0 = current_point.x
        local y0 = current_point.y
        local x1 = next_point.x
        local y1 = next_point.y
        if (current_point.y <= next_point.y) then
            x0 = next_point.x
            y0 = next_point.y
            x1 = current_point.x
            y1 = current_point.y
        end

        local edge = Edge()
        if (y1 < y0) then
            edge.maximum_y = y0
            edge.minimum_y = y1
            edge.x = x1
        else
            edge.maximum_y = y1
            edge.minimum_y = y0
            edge.x = x0
        end

        if (y0 ~= y1) then
            edge.inverted_slope = 1.0 * (x0 - x1) / (y0 - y1)
        else
            edge.inverted_slope = 0.0
        end

        table.insert(edges, edge)

        if (edge.minimum_y < minimum_y) then
            minimum_y = edge.minimum_y
        end

        if (edge.maximum_y > maximum_y) then
            maximum_y = edge.maximum_y
        end
    end

    local PAINT_LAST_PIXEL = 1
    local y = minimum_y
    while (y < maximum_y) do
        local starting_x = {}
        for _, edge in ipairs(edges) do
            if ((y >= edge.minimum_y) and (y < edge.maximum_y)) then
                table.insert(starting_x, edge.x)

                edge.x = edge.x + edge.inverted_slope
            end
        end

        table.sort(starting_x)
        for index = 1, #starting_x - 1, 2 do
            local x0 = starting_x[index] - PAINT_LAST_PIXEL
            local x1 = starting_x[index + 1] + PAINT_LAST_PIXEL
            franco_draw_line(x0, y, x1, y, color)
        end

        y = y + 1
    end
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    -- Triângulo
    franco_draw_polygon({
        Point(10, 40),
        Point(40, 40),
        Point(40, 10)
    }, WHITE)

    -- Retângulo
    franco_draw_polygon({
        Point(50, 10),
        Point(50, 80),
        Point(140, 80),
        Point(140, 10),
        Point(50, 10)
    }, RED)

    -- Losango
    franco_draw_polygon({
        Point(130, 100),
        Point(150, 160),
        Point(100, 160),
        Point(80, 100)
    }, GREEN)

    -- Polígono com pontos sortidos
    franco_draw_polygon({
        Point(160, 10), Point(150, 70), Point(170, 90), Point(140, 230),
        Point(180, 200), Point(210, 210), Point(250, 190), Point(300, 120),
        Point(260, 80), Point(260, 10), Point(160, 10)
    }, BLUE)

    -- F
    franco_draw_polygon({
        Point(10, 105), Point(10, 220), Point(30, 220),
        Point(30, 170), Point(60, 170), Point(60, 150),
        Point(30, 150), Point(30, 130), Point(70, 130),
        Point(70, 105), Point(10, 105)
    }, YELLOW)

    -- Estrela
    franco_draw_polygon({
        Point(80, 180), Point(50, 230), Point(120, 200),
        Point(40, 200), Point(110, 230), Point(80, 180)
    }, MAGENTA)
end

O canvas a seguir apresenta o resultado.

Desenhos de polígonos preenchidos usando Scanline Algorithm. A figura contém um triângulo branco, um retângulo vermelho, um losango verde, uma letra F amarela, um polígono com pontos sortidos em cor azul, e uma estrela magenta. O fundo da imagem é preto.

Para uma implementação mais eficiente, poder-se-ia remover valores em edges cujos valores máximos de maximum_y superassem o valor atual y (pois eles não seriam mais desenhados). Da mesma forma, poder-se-ia começar a buscar por novos valores apenas para valores compatíveis de minimum_y (pois valores menores não seria desenhados).

Preenchendo Polígonos com Primitivas Gráficas

Embora primitivas gráficas permitam preencher polígonos, muitas implementações de APIs restringem-se a polígonos convexos (por serem mais rápidos e simples de desenhar). De fato, o preenchimento da estrela magenta falhará em alguns dos exemplos (GDScript, JavaScript com canvas e Lua com LÖVE). Ou seja, apenas a implementação em Python com PyGame preencherá a estrela corretamente (com a lacuna na parte central).

Posto o adendo, o código relevante é apresentado em franco_draw_polygon(). GDScript fornece draw_colored_polygon() para o desenho de polígonos coloridos. JavaScript com canvas usa uma combinação de beginPath(), moveTo(), lineTo(), closePath(), e fill(). Python com PyGame fornece pygame.draw.polygon(). Lua com LÖVE provê love.graphics.polygon().

Em alguns casos, basta trocar o modo de desenho.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const WIDTH = 320
const HEIGHT = 240
const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const YELLOW = Color.yellow
const MAGENTA = Color.magenta


func franco_draw_polygon(points, color):
    assert(points.size() >= 2)

    draw_colored_polygon(PoolVector2Array(points), color)


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title("Olá, meu nome é Franco!")


func _draw():
    VisualServer.set_default_clear_color(BLACK)

    # Triângulo
    franco_draw_polygon([Vector2(10, 40), Vector2(40, 40), Vector2(40, 10), Vector2(10, 40)], WHITE)

    # Retângulo
    franco_draw_polygon([Vector2(50, 10), Vector2(50, 80), Vector2(140, 80), Vector2(140, 10), Vector2(50, 10)], RED)

    # Losango
    franco_draw_polygon([Vector2(130, 100), Vector2(150, 160), Vector2(100, 160), Vector2(80, 100), Vector2(130, 100)], GREEN)

    # Polígono com pontos sortidos
    franco_draw_polygon([
        Vector2(160, 10), Vector2(150, 70), Vector2(170, 90), Vector2(140, 230),
        Vector2(180, 200), Vector2(210, 210), Vector2(250, 190), Vector2(300, 120),
        Vector2(260, 80), Vector2(260, 10), Vector2(160, 10)
    ], BLUE)

    # F
    franco_draw_polygon([
        Vector2(10, 105), Vector2(10, 220), Vector2(30, 220),
        Vector2(30, 170), Vector2(60, 170), Vector2(60, 150),
        Vector2(30, 150), Vector2(30, 130), Vector2(70, 130),
        Vector2(70, 105), Vector2(10, 105)
    ], YELLOW)

    # Estrela
    # NOTA Godot não desenha.
    franco_draw_polygon([
        Vector2(80, 180), Vector2(50, 230), Vector2(120, 200),
        Vector2(40, 200), Vector2(110, 230), Vector2(80, 180)
    ], MAGENTA)
const WIDTH = 320
const HEIGHT = 240
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const YELLOW = "yellow"
const MAGENTA = "magenta"


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")
document.title = "Olá, meu nome é Franco!"


function franco_draw_polygon(points, color) {
    console.assert(points.length >= 2)

    // context.strokeStyle = color
    context.fillStyle = color

    context.beginPath()
    context.moveTo(points[0][0], points[0][1])

    let length = points.length
    for (let index = 1; index < length; ++index) {
        let next_point = points[index]
        context.lineTo(next_point[0], next_point[1])
    }

    context.closePath()
    context.fill()
}


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    // Triângulo
    franco_draw_polygon([[10, 40], [40, 40], [40, 10]], WHITE)

    // Retângulo
    franco_draw_polygon([[50, 10], [50, 80], [140, 80], [140, 10]], RED)

    // Losango
    franco_draw_polygon([[130, 100], [150, 160], [100, 160], [80, 100]], GREEN)

    // Polígono com pontos sortidos
    franco_draw_polygon([
        [160, 10], [150, 70], [170, 90], [140, 230],
        [180, 200], [210, 210], [250, 190], [300, 120],
        [260, 80], [260, 10]
    ], BLUE)

    // F
    franco_draw_polygon([
        [10, 105], [10, 220], [30, 220],
        [30, 170], [60, 170], [60, 150],
        [30, 150], [30, 130], [70, 130],
        [70, 105],
    ], YELLOW)

    // Estrela
    // NOTA Canvas não preenche corretamente.
    franco_draw_polygon([
        [80, 180], [50, 230], [120, 200],
        [40, 200], [110, 230], [80, 180]
    ], MAGENTA)
}


function main() {
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys

from typing import Final


WIDTH: Final = 320
HEIGHT: Final = 240
BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")


pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))


def franco_draw_polygon(points, color):
    assert(len(points) >= 2)

    # 0: preencher polígono; > 0: espessura da linha.
    pygame.draw.polygon(window, color, points, 0)


def main():
    pygame.display.set_caption("Olá, meu nome é Franco!")

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            window.fill(BLACK)

            # Triângulo
            franco_draw_polygon([(10, 40), (40, 40), (40, 10)], WHITE)

            # Retângulo
            franco_draw_polygon([(50, 10), (50, 80), (140, 80), (140, 10)], RED)

            # Losango
            franco_draw_polygon([(130, 100), (150, 160), (100, 160), (80, 100)], GREEN)

            # Polígono com pontos sortidos
            franco_draw_polygon([
                (160, 10), (150, 70), (170, 90), (140, 230),
                (180, 200), (210, 210), (250, 190), (300, 120),
                (260, 80), (260, 10)
            ], BLUE)

            # F
            franco_draw_polygon([
                (10, 105), (10, 220), (30, 220),
                (30, 170), (60, 170), (60, 150),
                (30, 150), (30, 130), (70, 130),
                (70, 105),
            ], YELLOW)

            # Estrela
            franco_draw_polygon([
                (80, 180), (50, 230), (120, 200),
                (40, 200), (110, 230), (80, 180)
            ], MAGENTA)

            pygame.display.flip()


if (__name__ == "__main__"):
    main()
io.stdout:setvbuf('no')


local WIDTH = 320
local HEIGHT = 240
local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}


function franco_draw_polygon(points, color)
    assert(#points >= 2)

    love.graphics.setColor(color)
    love.graphics.polygon("fill", points)
end


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle("Olá, meu nome é Franco!")
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    -- Triângulo
    franco_draw_polygon({10, 40, 40, 40, 40, 10}, WHITE)

    -- Retângulo
    franco_draw_polygon({50, 10, 50, 80, 140, 80, 140, 10}, RED)

    -- Losango
    franco_draw_polygon({130, 100, 150, 160, 100, 160, 80, 100}, GREEN)

    -- Polígono com pontos sortidos
    franco_draw_polygon({
        160, 10, 150, 70, 170, 90, 140, 230,
        180, 200, 210, 210, 250, 190, 300, 120,
        260, 80, 260, 10
    }, BLUE)

    -- F
    franco_draw_polygon({
        10, 105, 10, 220, 30, 220,
        30, 170, 60, 170, 60, 150,
        30, 150, 30, 130, 70, 130,
        70, 105,
    }, YELLOW)

    -- Estrela
    -- NOTA LÖVE preenche incorretamente.
    franco_draw_polygon({
        80, 180, 50, 230, 120, 200,
        40, 200, 110, 230, 80, 180
    }, MAGENTA)
end

O canvas a seguir apresenta o resultado.

Desenhos de polígonos preenchidos usando primitivas gráficas. A figura contém um triângulo branco, um retângulo vermelho, um losango verde, uma letra F amarela, um polígono com pontos sortidos em cor azul, e uma estrela magenta. O fundo da imagem é preto.

Embora a restrição de preenchimento de polígonos complexos seja uma restrição, é possível dividir formas complexas em triângulos, o que permitiria o preenchimento correto. A técnica será abordada em LÖVE durante a criação de uma biblioteca de primitivas gráficas.

Desenhando com Primitivas Gráficas

Neste ponto, as primitivas gráficas para desenho incluem recursos para a criação de contornos e preenchimentos para pontos, linhas, polígonos (retângulos ou definidos por pontos) e elipses (elipses arbitrárias, círculos e arcos). As primitivas foram definidas neste tópico e em Pixels e Primitivas Gráficas (Pontos, Linhas, e Arcos). Além disso, também é possível escrever texto, como feito em Introdução (Janela e Olá, mundo!).

Assim, agora é possível desenhar ilustrações semelhantes as que poderiam criadas com de editores de imagem raster simples (como Microsoft Paint). Ou seja, com observações atentas, pensando um pouco e com criatividade, já seria possível entender como um editor de imagem simples funciona, assim como pensar em como criar seu próprio.

De fato, pode-se combinar primitivas gráficas existentes para implementar recursos adicionais fornecidos pelo Microsoft Paint. Por exemplo:

  1. Alterando-se linhas contínuas e espaços, seria possível definir linhas tracejadas (ou pontilhadas);
  2. Desenhando pequenos círculos preenchidos ou pontos, seria possível criar a ferramenta de spray;
  3. Sobrepondo-se o desenho de figuras (ou desenhando um contorno por fora), seria possível criar bordas;
  4. Colorindo-se partes do desenho com a cor de fundo (por exemplo, branco), seria possível implementar uma borracha;
  5. Para a escrita de texto, pode-se usar as subrotinas apresentadas em tópicos anteriores;
  6. Desenhando pixels ao longo de um movimento do mouse, seria possível criar uma ferramenta de lápis ou pincel...

Dos itens mencionados, apenas o último requer funcionalidades ainda não abordadas em Ideias, Regras, Simulação. Tal desenho requereria o uso de entrada em tempo real via mouse, além de gerenciamento de cliques.

O momento de explorar a entrada aproxima-se; contudo, para evitar introduzir muitos outros conceitos em único tópico, o restante desta seção apresenta algumas ilustrações criadas usando as primitivas gráficas descritas até o momento. Antes disso, convém definir uma biblioteca própria para agrupar subrotinas para os desenhos.

Criação de Biblioteca para Desenhos com Primitivas Gráficas

Uma biblioteca em programação pode reunir definições para tipos de dados, valores, variáveis, e subrotinas para desempenhar processamentos arbitrários pré-definidos. Tanto para a continuidade deste tópico, quanto para tópicos futuros, seria conveniente agrupar todas as subrotinas criadas para desenho em uma biblioteca. Isso permitiria importar o código definido sempre que for necessário usá-lo, ao invés de duplicá-lo entre projetos. Isso promove boas práticas de programação como reuso de código; você pode aprender mais em Aprenda Programação: Bibliotecas.

Existem duas possibilidades para a criação da biblioteca para este tópico:

  1. Usar as primitivas fornecidas pelas APIs de cada biblioteca, framework ou motor usados;
  2. Usar as definições criadas ao longo dos tópicos pelo autor.

Para um código mais simples, conciso e eficiente, a biblioteca seguirá a primeira possibilidade. Contudo, caso se adotasse uma API comum para a biblioteca (no sentido de padronizar nomes e assinaturas para subrotinas, além de comportamentos, efeitos colaterais e resultados esperados), seria possível alterná-las. De fato, esse técnica foi apresentada em Bibliotecas como Abstração por Interfaces.

Como um projeto com bibliotecas pode utilizar múltiplos arquivos, os nomes para cada arquivo do exemplo a seguir será definido como franco_graphics.{EXTENSÃO}. Assim:

  • GDScript: franco_graphics.gd;
  • JavaScript: franco_graphics.js ou franco_graphics.mjs. A escolha do nome dependerá da abordagem adotada para a biblioteca (global ou módulo). Alguns interpretadores exigem o uso de .mjs como extensão para módulos, mas outros não;
  • Python: franco_graphics.py;
  • Lua: franco_graphics.lua.

Como a versão em GDScript poderá ser feita usando um Node convencional, não será mais necessário prefixar subrotinas com franco_. Assim, por exemplo, franco_draw_pixel() passará a chamar-se draw_pixel().

Para evitar duplicações de código, cada subrotina de desenho define um parâmetro fill para preenchimento. O valor padrão será true (verdadeiro) para criar um desenho preenchido. Para desenhar apenas o contorno, basta passar o valor false (falso).

extends Node
class_name FrancoGraphics


const POINT_COUNT = 100

const BLACK = Color.black
const WHITE = Color.white
const RED = Color.red
const GREEN = Color.green
const BLUE = Color.blue
const CYAN = Color.cyan
const YELLOW = Color.yellow
const MAGENTA = Color.magenta


static func draw_pixel(drawable, x, y, color):
    drawable.draw_primitive(PoolVector2Array([Vector2(x, y)]),
                                    PoolColorArray([color]),
                                    PoolVector2Array())


static func draw_line(drawable, x0, y0, x1, y1, color):
    drawable.draw_line(Vector2(x0, y0), Vector2(x1, y1), color)


static func draw_rectangle(drawable, x, y, width, height, color, fill = true):
    drawable.draw_rect(Rect2(x, y, width, height), color, fill)


static func draw_square(drawable, x, y, side, color, fill = true):
    draw_rectangle(drawable, x, y, side, side, color, fill)


static func draw_polygon(drawable, points, color, fill = true):
    assert(points.size() >= 2)

    if (fill):
        drawable.draw_colored_polygon(PoolVector2Array(points), color)
    else:
        drawable.draw_polyline(PoolVector2Array(points), color)


static func draw_ellipse(drawable, center_x, center_y, x_radius, y_radius, color, fill = true):
    drawable.draw_set_transform(Vector2(center_x, center_y), 0, Vector2(x_radius, y_radius))
    if (fill):
        drawable.draw_circle(Vector2(0.0, 0.0), 1.0, color)
    else:
        drawable.draw_arc(Vector2(0.0, 0.0), 1.0, 0.0, 2.0 * PI, POINT_COUNT, color)

    drawable.draw_set_transform(Vector2(0.0, 0.0), 0, Vector2(1.0, 1.0))


static func draw_arc(drawable, center_x, center_y, radius, start_angle, end_angle, color, fill = true):
    if (fill):
        var center = Vector2(center_x, center_y)
        # <https://docs.godotengine.org/en/latest/tutorials/2d/custom_drawing_in_2d.html>
        var points_arc = PoolVector2Array()
        points_arc.push_back(center)
        for i in range(POINT_COUNT + 1):
            var angle_point = start_angle + i * (end_angle - start_angle) / POINT_COUNT
            points_arc.push_back(center + radius * Vector2(cos(angle_point), sin(angle_point)))

        drawable.draw_colored_polygon(points_arc, color)
    else:
        drawable.draw_arc(Vector2(center_x, center_y),
                radius,
                start_angle, end_angle,
                POINT_COUNT,
                color)


static func draw_circle(drawable, center_x, center_y, radius, color, fill = true):
    if (fill):
        drawable.draw_circle(Vector2(center_x, center_y), radius, color)
    else:
        draw_arc(drawable, center_x, center_y, radius, 0.0, 2.0 * PI, color, false)
const BLACK = "black"
const WHITE = "white"
const RED = "red"
const GREEN = "green"
const BLUE = "blue"
const CYAN = "cyan"
const YELLOW = "yellow"
const MAGENTA = "magenta"


class Point {
    constructor(x, y) {
        this.x = x
        this.y = y
    }
}


let canvas = document.getElementById("canvas")
let context = canvas.getContext("2d")


function fill_or_line_set_color(fill, color) {
    if (fill) {
        context.fillStyle = color
    } else {
        context.strokeStyle = color
    }
}


function fill_or_line_draw(fill) {
    if (fill) {
        context.fill()
    } else {
        context.stroke()
    }
}


function draw_pixel(x, y, color) {
    context.fillStyle = color
    context.fillRect(x, y, 1, 1)
}


function draw_line(x0, y0, x1, y1, color) {
    context.strokeStyle = color
    // context.fillStyle = color
    context.beginPath()
    context.moveTo(x0, y0)
    context.lineTo(x1, y1)
    context.closePath()
    context.stroke()
}


function draw_rectangle(x, y, width, height, color, fill = true) {
    if (fill) {
        context.fillStyle = color
        context.fillRect(x, y, width, height)
    } else {
        context.strokeStyle = color
        context.strokeRect(x, y, width, height)
    }
}


function draw_square(x, y, side, color, fill = true) {
    draw_rectangle(x, y, side, side, color, fill)
}


function draw_polygon(points, color, fill = true) {
    console.assert(points.length >= 2)

    fill_or_line_set_color(fill, color)

    context.beginPath()
    context.moveTo(points[0].x, points[0].y)

    let length = points.length
    for (let index = 1; index < length; ++index) {
        let next_point = points[index]
        context.lineTo(next_point.x, next_point.y)
    }

    context.closePath()

    fill_or_line_draw(fill)
}


function draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = true) {
    fill_or_line_set_color(fill, color)

    context.beginPath()
    context.ellipse(center_x, center_y, x_radius, y_radius, 0.0, 0.0, 2.0 * Math.PI)
    context.closePath()

    fill_or_line_draw(fill)
}


function draw_arc(center_x, center_y, radius, start_angle, end_angle, color, fill = true) {
    fill_or_line_set_color(fill, color)

    context.beginPath()
    context.arc(center_x, center_y,
                radius,
                start_angle, end_angle)

    if (!fill) {
        context.stroke()
    }

    context.closePath()

    if (fill) {
        context.fill()
    }
}


function draw_circle(center_x, center_y, radius, color, fill = true) {
    draw_arc(center_x, center_y, radius, 0, 2 * Math.PI, color, fill)
}


export {
    BLACK,
    WHITE,
    RED,
    GREEN,
    BLUE,
    CYAN,
    YELLOW,
    MAGENTA,
    Point,
    canvas,
    context,
    draw_pixel,
    draw_line,
    draw_rectangle,
    draw_square,
    draw_polygon,
    draw_ellipse,
    draw_arc,
    draw_circle,
}
import math
import pygame
import pygame.gfxdraw

from typing import Final


POINT_COUNT: Final = 100

BLACK: Final = pygame.Color("black")
WHITE: Final = pygame.Color("white")
RED: Final = pygame.Color("red")
GREEN: Final = pygame.Color("green")
BLUE: Final = pygame.Color("blue")
CYAN: Final = pygame.Color("cyan")
YELLOW: Final = pygame.Color("yellow")
MAGENTA: Final = pygame.Color("magenta")


window = None


def init(width, height):
    global window

    pygame.init()
    window = pygame.display.set_mode((width, height))

    return window


# 0: fill polygon; > 0: line width.
def fill_or_line(fill):
    if (fill):
        return 0

    return 1


def draw_pixel(x, y, color):
    window.set_at((x, y), color)


def draw_line(x0, y0, x1, y1, color):
    pygame.draw.line(window, color, (x0, y0), (x1, y1))


def draw_rectangle(x, y, width, height, color, fill = True):
    line_width = fill_or_line(fill)
    pygame.draw.rect(window, color, (x, y, width, height), line_width)


def draw_square(x, y, side, color, fill = True):
    draw_rectangle(x, y, side, side, color, fill)


def draw_polygon(points, color, fill = True):
    assert(len(points) >= 2)

    line_width = fill_or_line(fill)
    pygame.draw.polygon(window, color, points, line_width)


def draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill = True):
    if (fill):
        pygame.gfxdraw.filled_ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)
    else:
        pygame.gfxdraw.ellipse(window, int(center_x), int(center_y), int(x_radius), int(y_radius), color)


def draw_arc(center_x, center_y, radius, start_angle, end_angle, color, fill = True):
    if (fill):
        # pygame.gfxdraw.pie
        # pygame.gfxdraw.filled_arc
        center = (center_x, center_y)
        points_arc = []
        points_arc.append(center)
        for i in range(POINT_COUNT + 1):
            angle_point = start_angle + i * (end_angle - start_angle) / POINT_COUNT
            points_arc.append((center_x + radius * math.cos(angle_point),
                               center_y + radius * math.sin(angle_point)))

        draw_polygon(points_arc, color, True)
    else:
        pygame.gfxdraw.arc(window,
                           int(center_x), int(center_y),
                           int(radius),
                           int(math.degrees(start_angle)), int(math.degrees(end_angle)),
                           color)


def draw_circle(center_x, center_y, radius, color, fill = True):
    line_width = fill_or_line(fill)
    pygame.draw.circle(window, color, (center_x, center_y), radius, line_width)
local POINT_COUNT = 100

local BLACK = {0.0, 0.0, 0.0}
local WHITE = {1.0, 1.0, 1.0}
local RED = {1.0, 0.0, 0.0}
local GREEN = {0.0, 1.0, 0.0}
local BLUE = {0.0, 0.0, 1.0}
local CYAN = {0.0, 1.0, 1.0}
local YELLOW = {1.0, 1.0, 0.0}
local MAGENTA = {1.0, 0.0, 1.0}


local function Point(x, y)
    return {
        x = x,
        y = y
    }
end


local function fill_or_line(fill)
    if (not fill) then
        return "line"
    end

    return "fill"
end


local function draw_pixel(x, y, color)
    love.graphics.setColor(color)
    love.graphics.points(x, y)
end


local function draw_line(x0, y0, x1, y1, color)
    love.graphics.setColor(color)
    love.graphics.line(x0, y0, x1, y1)
end


-- NOTA Não é possível usar fill = fill or true, porque fill == false sempre
-- seria inicializado como true.


local function draw_rectangle(x, y, width, height, color, fill)
    if (fill == nil) then
        fill = true
    end

    love.graphics.setColor(color)
    love.graphics.rectangle(fill_or_line(fill), x, y, width, height)
end


local function draw_square(x, y, side, color, fill)
    draw_rectangle(x, y, side, side, color, fill)
end


local function draw_polygon(points, color, fill)
    assert(#points >= 2)

    if (fill == nil) then
        fill = true
    end

    local points_array = {}
    for _, point in ipairs(points) do
        table.insert(points_array, point.x)
        table.insert(points_array, point.y)
    end

    love.graphics.setColor(color)
    if (not fill) then
        love.graphics.polygon(fill_or_line(fill), points_array)
    elseif (love.math.isConvex(points_array)) then
        love.graphics.polygon("fill", points_array)
    else
        -- NOTA Polígono não pode interseccionar ele mesmo.
        triangles = love.math.triangulate(points_array)
        for _, triangle in ipairs(triangles) do
            love.graphics.polygon("fill", triangle)
        end
    end
end


local function draw_ellipse(center_x, center_y, x_radius, y_radius, color, fill)
    if (fill == nil) then
        fill = true
    end

    love.graphics.setColor(color)
    love.graphics.ellipse(fill_or_line(fill), center_x, center_y, x_radius, y_radius, POINT_COUNT)
end


local function draw_arc(center_x, center_y, radius, start_angle, end_angle, color, fill)
    if (fill == nil) then
        fill = true
    end

    love.graphics.setColor(color)
    love.graphics.arc(fill_or_line(fill), "open",
                      center_x, center_y,
                      radius,
                      start_angle, end_angle,
                      POINT_COUNT)
end


local function draw_circle(center_x, center_y, radius, color, fill)
    if (fill == nil) then
        fill = true
    end

    love.graphics.setColor(color)
    love.graphics.circle(fill_or_line(fill), center_x, center_y, radius)
end


return {
    BLACK = BLACK,
    WHITE = WHITE,
    RED = RED,
    GREEN = GREEN,
    BLUE = BLUE,
    CYAN = CYAN,
    YELLOW = YELLOW,
    MAGENTA = MAGENTA,
    Point = Point,
    canvas = canvas,
    context = context,
    draw_pixel = draw_pixel,
    draw_line = draw_line,
    draw_rectangle = draw_rectangle,
    draw_square = draw_square,
    draw_polygon = draw_polygon,
    draw_ellipse = draw_ellipse,
    draw_arc = draw_arc,
    draw_circle = draw_circle
}

Deve-se notar que nenhuma das implementações define um ponto de entrada. A biblioteca apenas fornecerá definições e código para desempenhar determinar tarefas. O código do programa será definido pelo arquivo que importar a biblioteca; assim, novos arquivos definirão a aplicação.

Essa não é a única abordagem possível. Estruturas de nível mais alto, como arcabouços (frameworks) ou motores (engines) podem definir o ponto de entrada na definição do código da biblioteca. Nesse caso, o código da aplicação em desenvolvimento deverá seguir as convenções do framework ou motor para a criação do programa. Isso ocorre em Lua com LÖVE e GDScript com Godot.

No mais, a maior parte do código reaproveita implementações de seções e tópicos anteriores, realizando os ajustes para alternar entre o desenho do contorno ou do preenchimento.

Algumas implementações definem uma função interna para ajuste de contorno ou preenchimento, para evitar duplicação de código. Embora isso seja uma boa prática em geral, no caso dos exemplos a escolha talvez torne o código mais complexo. De qualquer forma, como os exemplos têm propósito educativo, a refatoração do código comum ilustra a (boa) prática.

Além disso, cada linguagem de programação apresenta as próprias convenções para bibliotecas. Em alguns casos, podem existir várias alternativas. Você pode aprender mais sobre elas em Aprenda Programação: Bibliotecas.

Os próximos parágrafos descrevem particularidades de cada implementação. Para facilitar o uso, assume-se uma janela por programa. Assim, a variável usada para manipular a janela pode ser global. Para uma implementação mais genérica, poder-se-ia passar a janela (ou abstração para desenho na tela, como o contexto) como parâmetro -- como feito em GDScript.

Na versão em GDScript, o uso de class_name permite definir um nome para a classe (o arquivo) gerado. Esse nome poderá ser usado para a importação do código. Para evitar a necessidade de instanciar um objeto da classe, declarou-se todas as subrotinas como static. Em POO, um método static é um método de classe, isto é, que não depende de uma instância (um objeto) para uso. Além disso, todas as subrotinas incluem um parâmetro drawable, pois operações de desenho requerem um Node que permita usar _draw() (por exemplo, Control). Esse Node precisará ser declarado no arquivo que chamar o código; para usá-lo, pode-se passar self como parâmetro (isso será feito na próxima seção).

Alternativamente, poder-se-ia criar uma variável drawable e um procedimento init(), como feito em Python. Nesse caso, nenhuma subrotina poderia ser static, e seria necessário operar com uma instância de FrancoGraphics, criada com FrancoGraphics.new().

Em Python, criou-se uma função init() (de initialize ou inicializar) para inicializar a janela. A função utiliza global window para indicar que window é a variável declarada fora da subrotina, com escopo global (para a biblioteca, chamada de módulo). init() será chamada no código da aplicação para a criação da janela de PyGame (que requer o tamanho). Alternativamente, poder-se-ia fazer como em GDScript: cada subrotina poderia ter um parâmetro window, fornecido pelo código que usasse a biblioteca.

Na versão em JavaScript, é importante notar o uso de export ao final do arquivo para a exportação de declarações de variáveis, constantes, tipos de dados e subrotinas que se deseja fornecer na biblioteca. Apenas recursos marcados com export serão fornecidos para o código da aplicação. Assim, definições internas (como fill_or_line_set_color() e fill_or_line_draw()) não precisam ser exportadas; elas são um mero detalhe de implementação interno. Adicionalmente, poder-se-ia fazer como em GDScript: cada subrotina poderia ter um parâmetro window, fornecido pelo código que usasse a biblioteca.

Algo similar ocorre em Lua: todas as definições são marcadas com local. Os recursos que se deseja exportar são retornados (return) como uma table ao final do arquivo. LÖVE não requer um parâmetro para a janela porque assume a existência de uma única janela por aplicação.

Além disso, a implementação em Lua em LÖVE ilustra o uso de triangulação para decompor o desenho da letra F em triângulos. Isso é feito usando love.math.triangulate(). Após a decomposição, cada triângulo é desenhando individualmente usando love.graphics.polygon() usando uma estrutura de repetição.

Assim, do ponto de vista computacional, os recursos básicos estão implementados em todas as linguagens, e disponíveis em uma API pública (Public API). A API pode ser usada para a criação de desenhos. No futuro, ela poderia ser melhorada. Por exemplo, para desenhos mais sofisticados, seria interessante rotacionar as primitivas (por exemplo, para o desenho de elipses inclinadas); neste momento, isso só pode ser aproximado desenhando-se polígonos manualmente.

Do ponto de vista artístico, a qualidade dependerá das habilidades, criatividade, e senso estético da pessoa que criar os desenhos.

Exemplo de Como Usar as Bibliotecas

Após definidas, pode-se importar ou carregar bibliotecas toda vez que for necessário usar o código criado. Ou seja, não será mais necessário redigitar o código (ou copiar e colar implementações); doravante, bastará carregar as implementações dos arquivos definindo as bibliotecas.

Os próximos blocos de código carregam as bibliotecas para usá-las. Assim, exceto para Lua com LÖVE (main.lua), os nomes dos arquivos são de sua escolha. Por exemplo:

  • GDScript: main.gd, script.gd, ou outro nome;
  • JavaScript: main.js, script.js, ou outro nome;
  • Python: main.js, script.js, ou outro nome;
  • Lua: main.js (obrigatório para o ponto de entrada).

Para usar a biblioteca, a implementação de cada linguagem deverá usar o respectivo franco_graphics.{EXTENSÃO}. Assim, por exemplo, a versão em Lua usará franco_graphics.lua.

Em GDScript e JavaScript, o uso de bibliotecas será um pouco mais complexo que em Python e Lua. Assim, se você preferir e achar difícil usar as biblioteca corretamente neste momento, você pode copiar e colar o código dos arquivos (GDScript) franco_graphics.gd em main.gd, e (JavaScript) franco_graphics.js em main.js. Para usá-las corretamente, continue a ler o texto desta seção.

Para ilustrar o uso de cada subrotina, desenhos de contornos são feitos dentro (ou acima) da versão preenchida.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const TITLE = "Olá, meu nome é Franco!"
const WIDTH = 320
const HEIGHT = 240


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title(TITLE)


func _draw():
    VisualServer.set_default_clear_color(FrancoGraphics.BLACK)

    for x in range(0, 20, 2):
        FrancoGraphics.draw_pixel(self, x, 10, FrancoGraphics.WHITE)

    FrancoGraphics.draw_line(self, 30, 10, 60, 10, FrancoGraphics.BLUE)

    FrancoGraphics.draw_rectangle(self, 10, 20, 100, 20, FrancoGraphics.RED)
    FrancoGraphics.draw_rectangle(self, 12, 22, 96, 16, FrancoGraphics.WHITE, false)

    FrancoGraphics.draw_square(self, 20, 50, 22, FrancoGraphics.GREEN)
    FrancoGraphics.draw_square(self, 22, 52, 18, FrancoGraphics.BLACK, false)

    var polygon = [
        Vector2(10, 105), Vector2(10, 220), Vector2(30, 220),
        Vector2(30, 170), Vector2(60, 170), Vector2(60, 150),
        Vector2(30, 150), Vector2(30, 130), Vector2(70, 130),
        Vector2(70, 105), Vector2(10, 105)
    ]
    FrancoGraphics.draw_polygon(self, polygon, FrancoGraphics.BLUE)
    FrancoGraphics.draw_polygon(self, polygon, FrancoGraphics.YELLOW, false)

    FrancoGraphics.draw_ellipse(self, 160, 25, 15, 20, FrancoGraphics.BLUE)
    FrancoGraphics.draw_ellipse(self, 160, 25, 10, 15, FrancoGraphics.GREEN, false)

    FrancoGraphics.draw_arc(self, 160, 120, 20, 0.0, PI, FrancoGraphics.BLUE)
    FrancoGraphics.draw_arc(self, 160, 120, 10, 0.0, PI, FrancoGraphics.CYAN, false)

    FrancoGraphics.draw_circle(self, 160, 80, 20, FrancoGraphics.WHITE)
    FrancoGraphics.draw_circle(self, 160, 80, 16, FrancoGraphics.RED, false)
import {
    BLACK,
    WHITE,
    RED,
    GREEN,
    BLUE,
    CYAN,
    YELLOW,
    MAGENTA,
    Point,
    canvas,
    context,
    draw_pixel,
    draw_line,
    draw_rectangle,
    draw_square,
    draw_polygon,
    draw_ellipse,
    draw_arc,
    draw_circle,
} from "./franco_graphics.js"


const TITLE = "Olá, meu nome é Franco!"
const WIDTH = 320
const HEIGHT = 240


function draw() {
    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = BLACK
    context.fillRect(0, 0, WIDTH, HEIGHT)

    for (let x = 0; x < 20; x += 2) {
        draw_pixel(x, 10, WHITE)
    }

    draw_line(30, 10, 60, 10, BLUE)

    draw_rectangle(10, 20, 100, 20, RED)
    draw_rectangle(12, 22, 96, 16, WHITE, false)

    draw_square(20, 50, 22, GREEN)
    draw_square(22, 52, 18, BLACK, false)

    let polygon = [
        new Point(10, 105), new Point(10, 220), new Point(30, 220),
        new Point(30, 170), new Point(60, 170), new Point(60, 150),
        new Point(30, 150), new Point(30, 130), new Point(70, 130),
        new Point(70, 105), new Point(10, 105)
    ]
    draw_polygon(polygon, BLUE)
    draw_polygon(polygon, YELLOW, false)

    draw_ellipse(160, 25, 15, 20, BLUE)
    draw_ellipse(160, 25, 10, 15, GREEN, false)

    draw_arc(160, 120, 20, 0.0, Math.PI, BLUE)
    draw_arc(160, 120, 10, 0.0, Math.PI, CYAN, false)

    draw_circle(160, 80, 20, WHITE)
    draw_circle(160, 80, 16, RED, false)
}


function main() {
    document.title = TITLE
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}


main()
import math
import pygame
import sys
from typing import Final

import franco_graphics


TITLE: Final = "Olá, meu nome é Franco!"
WIDTH: Final = 320
HEIGHT: Final = 240


def draw():
    franco_graphics.window.fill(franco_graphics.BLACK)

    for x in range(0, 20, 2):
        franco_graphics.draw_pixel(x, 10, franco_graphics.WHITE)

    franco_graphics.draw_line(30, 10, 60, 10, franco_graphics.BLUE)

    franco_graphics.draw_rectangle(10, 20, 100, 20, franco_graphics.RED)
    franco_graphics.draw_rectangle(12, 22, 96, 16, franco_graphics.WHITE, False)

    franco_graphics.draw_square(20, 50, 22, franco_graphics.GREEN)
    franco_graphics.draw_square(22, 52, 18, franco_graphics.BLACK, False)

    polygon = [
        (10, 105), (10, 220), (30, 220),
        (30, 170), (60, 170), (60, 150),
        (30, 150), (30, 130), (70, 130),
        (70, 105),
    ]
    franco_graphics.draw_polygon(polygon, franco_graphics.BLUE)
    franco_graphics.draw_polygon(polygon, franco_graphics.YELLOW, False)

    franco_graphics.draw_ellipse(160, 25, 15, 20, franco_graphics.BLUE)
    franco_graphics.draw_ellipse(160, 25, 10, 15, franco_graphics.GREEN, False)

    franco_graphics.draw_arc(160, 120, 20, 0.0, math.pi, franco_graphics.BLUE)
    franco_graphics.draw_arc(160, 120, 10, 0.0, math.pi, franco_graphics.CYAN, False)

    franco_graphics.draw_circle(160, 80, 20, franco_graphics.WHITE)
    franco_graphics.draw_circle(160, 80, 16, franco_graphics.RED, False)

    pygame.display.flip()


def main():
    franco_graphics.init(WIDTH, HEIGHT)

    pygame.display.set_caption(TITLE)

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            draw()


if (__name__ == "__main__"):
    main()
local franco_graphics = require("franco_graphics")
local BLACK = franco_graphics.BLACK
local WHITE = franco_graphics.WHITE
local RED = franco_graphics.RED
local GREEN = franco_graphics.GREEN
local BLUE = franco_graphics.BLUE
local CYAN = franco_graphics.CYAN
local YELLOW = franco_graphics.YELLOW
local MAGENTA = franco_graphics.MAGENTA
local Point = franco_graphics.Point
local draw_pixel = franco_graphics.draw_pixel
local draw_line = franco_graphics.draw_line
local draw_rectangle = franco_graphics.draw_rectangle
local draw_square = franco_graphics.draw_square
local draw_polygon = franco_graphics.draw_polygon
local draw_ellipse = franco_graphics.draw_ellipse
local draw_arc = franco_graphics.draw_arc
local draw_circle = franco_graphics.draw_circle


io.stdout:setvbuf('no')


local TITLE = "Olá, meu nome é Franco!"
local WIDTH = 320
local HEIGHT = 240


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle(TITLE)
end


function love.draw()
    love.graphics.setBackgroundColor(BLACK)

    for x = 0, 19, 2 do
        draw_pixel(x, 10, WHITE)
    end

    draw_line(30, 10, 60, 10, BLUE)

    draw_rectangle(10, 20, 100, 20, RED)
    draw_rectangle(12, 22, 96, 16, WHITE, false)

    draw_square(20, 50, 22, GREEN)
    draw_square(22, 52, 18, BLACK, false)

    local polygon = {
        Point(10, 105), Point(10, 220), Point(30, 220),
        Point(30, 170), Point(60, 170), Point(60, 150),
        Point(30, 150), Point(30, 130), Point(70, 130),
        Point(70, 105)--, Point(10, 105)
    }
    draw_polygon(polygon, BLUE)
    draw_polygon(polygon, YELLOW, false)

    draw_ellipse(160, 25, 15, 20, BLUE)
    draw_ellipse(160, 25, 10, 15, GREEN, false)

    draw_arc(160, 120, 20, 0.0, math.pi, BLUE)
    draw_arc(160, 120, 10, 0.0, math.pi, CYAN, false)

    draw_circle(160, 80, 20, WHITE)
    draw_circle(160, 80, 16, RED, false)
end

O canvas a seguir apresenta o resultado.

Desenho das primitivas gráficas criadas para testar a biblioteca definida. A imagem contém linhas, retângulos, quadrados, um polígono formando uma letra F, arcos, círculos e elipses. As formas possuem um contorno sobre o preenchimento.

Algumas implementações armazenam referências para recursos das bibliotecas em variáveis. Isso reduz a necessidade de digitar o nome usado para a importação da biblioteca. Assim, por exemplo, em Lua poder-se-ia usar franco_graphics.draw_pixel() diretamente, como feito em Python.

A escolha de alternar formas entre as diferentes implementações serve para ilustrar diferentes recursos; afinal, digitar franco_graphics antes de cada uso da biblioteca exige esforços significativos de escrita. Adotar um nome como fg ou fgg para a biblioteca (ou como alias na importação) poderia ser mais curto, simples e rápido de escrever. O uso de um alias para a importação é explicado em Aprenda Programação: Bibliotecas.

No mais, o desenho de pixels individuais ilustra a criação de uma linha tracejada. No caso, desenha-se apenas o índices pares para alternar entre a cor de fundo e a cor escolhida para o pixel. Para uma linha pontilhada, poder-se-ia explorar a mesma estratégia usando-se círculos.

Configurações Adicionais para JavaScript

A versão em JavaScript exigirá um arquivo HTML que carregue o código da biblioteca. É importante notar os caminhos para os arquivos e o uso de type="module".

<!DOCTYPE html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <title>www.FrancoGarcia.com</title>
    <meta name="author" content="Franco Eusébio Garcia">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div style="text-align: center;">
      <canvas id="canvas"
              width="1"
              height="1"
              style="border: 1px solid #000000;">
      >
          Acessibilidade: texto alternativo para conteúdo gráfico.
      </canvas>
    </div>

    <!-- NOTA Atualizar com nomes dos arquivos JavaScript. -->
    <script src="/franco_graphics.js" type="module"></script>
    <script src="./script.js" type="module"></script>
  </body>
</html>

Contudo, isso não será suficiente. Para usar o programa, será necessário iniciar um servidor Web local (ou hospedar o conteúdo em um servidor remoto). Isso é detalhado em Aprenda Programação: Bibliotecas.

Caso o interpretador Python esteja instalado em sua máquina, uma forma simples de iniciar um servidor Web local consiste em navegar até o diretório dos arquivos, abrir um interpretador de comandos e usar o seguinte comando:

python -m http.server 8080 --bind 127.0.0.1 --directory ./

De forma geral, um endereço IP possui o formato host:port (servidor:porta). localhost corresponde ao endereço IPv4 127.0.0.1. Ele é conhecido como servidor local da máquina -- ao invés de estar na Internet ou em uma rede externa, a rede é a própria máquina. O valor 8080 pode ser alterado; ele é chamado de porta (port).

Agora você poderá acessar o seguinte endereço em seu navegador para acessar a página criada: http://localhost:8080/ (caso sua página chame-se index.html) ou http://localhost:8080/nome_arquivo.html (para qualquer nome).

Criando Ilustrações Simples

Como a biblioteca de desenho está pronta para uso, os próximos tópicos podem importá-la sempre que for necessário desenhar primitivas gráficas. Assim, os próximos exemplos omitem o código da biblioteca e focam no código da aplicação. Isso similar ao o que sempre foi feito com bibliotecas para Matemática, por exemplo.

Além disso, a criação de uma biblioteca própria possui um benefício adicional. Como a biblioteca é sua, você pode continuar a melhorá-la: basta adicionar novos recursos ou aprimorar funcionalidades existentes. De fato, toda nova subrotina incorporada à biblioteca torna-la-á mais completa e versátil. Por exemplo, a criação de uma subrotina como draw_background_color() ou clear_screen() poderia preencher o fundo da tela com uma cor para limpá-la.

De qualquer forma, agora é hora de deixar sua criatividade aflorar.

# Raíz deve ser um Node que permita desenhar usando _draw().
extends Control


const TITLE = "www.FrancoGarcia.com"
const WIDTH = 320
const HEIGHT = 240


func _ready():
    OS.set_window_size(Vector2(WIDTH, HEIGHT))
    OS.set_window_title(TITLE)


func _draw():
    var BROWN = Color.brown
    var WEB_MAROON = Color.webmaroon
    var SKY_BLUE = Color.skyblue
    var SUNRISE = Color("#F7CD5D")

    VisualServer.set_default_clear_color(SKY_BLUE)

    # Sol
    FrancoGraphics.draw_circle(self, 0.95 * WIDTH, 0.1 * HEIGHT, 0.15 * WIDTH, SUNRISE)

    # Cabelo
    FrancoGraphics.draw_rectangle(self, 0.2 * WIDTH, 0.3 * HEIGHT, 0.6 * WIDTH, 0.9 * HEIGHT, FrancoGraphics.BLACK)

    # Pescoço
    FrancoGraphics.draw_rectangle(self, 0.4 * WIDTH, 0.8 * HEIGHT, 0.2 * WIDTH, 0.2 * HEIGHT, BROWN)

    # Cabeça
    FrancoGraphics.draw_ellipse(self, 0.5 * WIDTH, 0.5 * HEIGHT, 0.26 * WIDTH, 0.35 * WIDTH, BROWN)

    # Sobrancelha esquerda
    FrancoGraphics.draw_rectangle(self, 0.3 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, FrancoGraphics.BLACK)
    # Olho esquerdo
    FrancoGraphics.draw_ellipse(self, 0.37 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, FrancoGraphics.WHITE)
    FrancoGraphics.draw_circle(self, 0.38 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, FrancoGraphics.BLACK)

    # Sobrancelha direita
    FrancoGraphics.draw_rectangle(self, 0.55 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, FrancoGraphics.BLACK)
    # Olho direito
    FrancoGraphics.draw_ellipse(self, 0.63 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, FrancoGraphics.WHITE)
    FrancoGraphics.draw_circle(self, 0.62 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, FrancoGraphics.BLACK)

    # Nariz
    FrancoGraphics.draw_polygon(self, [
        Vector2(0.5 * WIDTH, 0.5 * HEIGHT),
        Vector2(0.4 * WIDTH, 0.65 * HEIGHT),
        Vector2(0.6 * WIDTH, 0.65 * HEIGHT),
        Vector2(0.5 * WIDTH, 0.5 * HEIGHT),
    ], WEB_MAROON)

    # Boca
    FrancoGraphics.draw_arc(self, 0.5 * WIDTH, 0.7 * HEIGHT, 0.1 * WIDTH, 0.0, PI, FrancoGraphics.RED)
    FrancoGraphics.draw_arc(self, 0.5 * WIDTH, 0.72 * HEIGHT, 0.07 * WIDTH, 0.0, PI, FrancoGraphics.WHITE)

    # Queixo
    FrancoGraphics.draw_arc(self, 0.5 * WIDTH, 0.82 * HEIGHT, 0.1 * WIDTH, 0.25 * PI, 0.75 * PI, WEB_MAROON, false)

    # Cabelo (sobre a testa)
    FrancoGraphics.draw_arc(self, 0.5 * WIDTH, 0.3 * HEIGHT, 0.3 * WIDTH, PI, 2.0 * PI, FrancoGraphics.BLACK)
import {
    BLACK,
    WHITE,
    RED,
    GREEN,
    BLUE,
    CYAN,
    YELLOW,
    MAGENTA,
    Point,
    canvas,
    context,
    draw_pixel,
    draw_line,
    draw_rectangle,
    draw_square,
    draw_polygon,
    draw_ellipse,
    draw_arc,
    draw_circle,
} from "./franco_graphics.js"


const TITLE = "www.FrancoGarcia.com"
const WIDTH = 320
const HEIGHT = 240


function draw() {
    var BROWN = "brown"
    var WEB_MAROON = "maroon"
    var SKY_BLUE = "skyblue"
    var SUNRISE = "#F7CD5D"

    context.clearRect(0, 0, WIDTH, HEIGHT)
    context.fillStyle = SKY_BLUE
    context.fillRect(0, 0, WIDTH, HEIGHT)

    // Sol
    draw_circle(0.95 * WIDTH, 0.1 * HEIGHT, 0.15 * WIDTH, SUNRISE)

    // Cabelo
    draw_rectangle(0.2 * WIDTH, 0.3 * HEIGHT, 0.6 * WIDTH, 0.9 * HEIGHT, BLACK)

    // Pescoço
    draw_rectangle(0.4 * WIDTH, 0.8 * HEIGHT, 0.2 * WIDTH, 0.2 * HEIGHT, BROWN)

    // Cabeça
    draw_ellipse(0.5 * WIDTH, 0.5 * HEIGHT, 0.26 * WIDTH, 0.35 * WIDTH, BROWN)

    // Sobrancelha esquerda
    draw_rectangle(0.3 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, BLACK)
    // Olho esquerdo
    draw_ellipse(0.37 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, WHITE)
    draw_circle(0.38 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, BLACK)

    // Sobrancelha direita
    draw_rectangle(0.55 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, BLACK)
    // Olho direito
    draw_ellipse(0.63 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, WHITE)
    draw_circle(0.62 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, BLACK)

    // Nariz
    draw_polygon([
        new Point(0.5 * WIDTH, 0.5 * HEIGHT),
        new Point(0.4 * WIDTH, 0.65 * HEIGHT),
        new Point(0.6 * WIDTH, 0.65 * HEIGHT),
        new Point(0.5 * WIDTH, 0.5 * HEIGHT),
    ], WEB_MAROON)

    // Boca
    draw_arc(0.5 * WIDTH, 0.7 * HEIGHT, 0.1 * WIDTH, 0.0, Math.PI, RED)
    draw_arc(0.5 * WIDTH, 0.72 * HEIGHT, 0.07 * WIDTH, 0.0, Math.PI, WHITE)

    // Queixo
    draw_arc(0.5 * WIDTH, 0.82 * HEIGHT, 0.1 * WIDTH, 0.25 * Math.PI, 0.75 * Math.PI, WEB_MAROON, false)

    // Cabelo (sobre a testa)
    draw_arc(0.5 * WIDTH, 0.3 * HEIGHT, 0.3 * WIDTH, Math.PI, 2.0 * Math.PI, BLACK)
}


function main() {
    document.title = TITLE
    canvas.width = WIDTH
    canvas.height = HEIGHT

    draw()
}

main()
import math
import pygame
import sys
from typing import Final

import franco_graphics


TITLE: Final = "www.FrancoGarcia.com"
WIDTH: Final = 320
HEIGHT: Final = 240


def draw():
    BROWN = pygame.Color("brown")
    WEB_MAROON = pygame.Color("maroon")
    SKY_BLUE = pygame.Color("skyblue")
    SUNRISE = pygame.Color("#F7CD5D")

    franco_graphics.window.fill(SKY_BLUE)

    # Sol
    franco_graphics.draw_circle(0.95 * WIDTH, 0.1 * HEIGHT, 0.15 * WIDTH, SUNRISE)

    # Cabelo
    franco_graphics.draw_rectangle(0.2 * WIDTH, 0.3 * HEIGHT, 0.6 * WIDTH, 0.9 * HEIGHT, franco_graphics.BLACK)

    # Pescoço
    franco_graphics.draw_rectangle(0.4 * WIDTH, 0.8 * HEIGHT, 0.2 * WIDTH, 0.2 * HEIGHT, BROWN)

    # Cabeça
    franco_graphics.draw_ellipse(0.5 * WIDTH, 0.5 * HEIGHT, 0.26 * WIDTH, 0.35 * WIDTH, BROWN)

    # Sobrancelha esquerda
    franco_graphics.draw_rectangle(0.3 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, franco_graphics.BLACK)
    # Olho esquerdo
    franco_graphics.draw_ellipse(0.37 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, franco_graphics.WHITE)
    franco_graphics.draw_circle(0.38 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, franco_graphics.BLACK)

    # Sobrancelha direita
    franco_graphics.draw_rectangle(0.55 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, franco_graphics.BLACK)
    # Olho direito
    franco_graphics.draw_ellipse(0.63 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, franco_graphics.WHITE)
    franco_graphics.draw_circle(0.62 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, franco_graphics.BLACK)

    # Nariz
    franco_graphics.draw_polygon([
        (0.5 * WIDTH, 0.5 * HEIGHT),
        (0.4 * WIDTH, 0.65 * HEIGHT),
        (0.6 * WIDTH, 0.65 * HEIGHT),
        (0.5 * WIDTH, 0.5 * HEIGHT),
    ], WEB_MAROON)

    # Boca
    franco_graphics.draw_arc(0.5 * WIDTH, 0.7 * HEIGHT, 0.1 * WIDTH, 0.0, math.pi, franco_graphics.RED)
    franco_graphics.draw_arc(0.5 * WIDTH, 0.72 * HEIGHT, 0.07 * WIDTH, 0.0, math.pi, franco_graphics.WHITE)

    # Queixo
    franco_graphics.draw_arc(0.5 * WIDTH, 0.82 * HEIGHT, 0.1 * WIDTH, 0.25 * math.pi, 0.75 * math.pi, WEB_MAROON, False)

    # Cabelo (sobre a testa)
    franco_graphics.draw_arc(0.5 * WIDTH, 0.3 * HEIGHT, 0.3 * WIDTH, math.pi, 2.0 * math.pi, franco_graphics.BLACK)

    pygame.display.flip()


def main():
    franco_graphics.init(WIDTH, HEIGHT)

    pygame.display.set_caption(TITLE)

    while (True):
        for event in pygame.event.get():
            if (event.type == pygame.QUIT):
                pygame.quit()
                sys.exit(0)

            draw()


if (__name__ == "__main__"):
    main()
local franco_graphics = require("franco_graphics")


io.stdout:setvbuf('no')


local TITLE = "www.FrancoGarcia.com"
local WIDTH = 320
local HEIGHT = 240


function love.load()
    love.window.setMode(WIDTH, HEIGHT)
    love.window.setTitle(TITLE)
end


function love.draw()
    local BROWN = {165 / 255.0, 42 / 255.0, 42 / 255.0}
    local WEB_MAROON = {128 / 255.0, 0.0, 0.0}
    local SKY_BLUE = {135 / 255.0, 206 / 255.0, 235 / 255.0}
    local SUNRISE = {247 / 255.0, 205 / 255.0, 93 / 255.0}

    love.graphics.setBackgroundColor(SKY_BLUE)

    -- Sol
    franco_graphics.draw_circle(0.95 * WIDTH, 0.1 * HEIGHT, 0.15 * WIDTH, SUNRISE)

    -- Cabelo
    franco_graphics.draw_rectangle(0.2 * WIDTH, 0.3 * HEIGHT, 0.6 * WIDTH, 0.9 * HEIGHT, franco_graphics.BLACK)

    -- Pescoço
    franco_graphics.draw_rectangle(0.4 * WIDTH, 0.8 * HEIGHT, 0.2 * WIDTH, 0.2 * HEIGHT, BROWN)

    -- Cabeça
    franco_graphics.draw_ellipse(0.5 * WIDTH, 0.5 * HEIGHT, 0.26 * WIDTH, 0.35 * WIDTH, BROWN)

    -- Sobrancelha esquerda
    franco_graphics.draw_rectangle(0.3 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, franco_graphics.BLACK)
    -- Olho esquerdo
    franco_graphics.draw_ellipse(0.37 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, franco_graphics.WHITE)
    franco_graphics.draw_circle(0.38 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, franco_graphics.BLACK)

    -- Sobrancelha direita
    franco_graphics.draw_rectangle(0.55 * WIDTH, 0.4 * HEIGHT, 0.15 * WIDTH, 0.02 * HEIGHT, franco_graphics.BLACK)
    -- Olho direito
    franco_graphics.draw_ellipse(0.63 * WIDTH, 0.5 * HEIGHT, 0.065 * WIDTH, 0.05 * WIDTH, franco_graphics.WHITE)
    franco_graphics.draw_circle(0.62 * WIDTH, 0.5 * HEIGHT, 0.03 * WIDTH, franco_graphics.BLACK)

    -- Nariz
    franco_graphics.draw_polygon({
        franco_graphics.Point(0.5 * WIDTH, 0.5 * HEIGHT),
        franco_graphics.Point(0.4 * WIDTH, 0.65 * HEIGHT),
        franco_graphics.Point(0.6 * WIDTH, 0.65 * HEIGHT),
        franco_graphics.Point(0.5 * WIDTH, 0.5 * HEIGHT),
    }, WEB_MAROON)

    -- Boca
    franco_graphics.draw_arc(0.5 * WIDTH, 0.7 * HEIGHT, 0.1 * WIDTH, 0.0, math.pi, franco_graphics.RED)
    franco_graphics.draw_arc(0.5 * WIDTH, 0.72 * HEIGHT, 0.07 * WIDTH, 0.0, math.pi, franco_graphics.WHITE)

    -- Queixo
    franco_graphics.draw_arc(0.5 * WIDTH, 0.82 * HEIGHT, 0.1 * WIDTH, 0.25 * math.pi, 0.75 * math.pi, WEB_MAROON, false)

    -- Cabelo (sobre a testa)
    franco_graphics.draw_arc(0.5 * WIDTH, 0.3 * HEIGHT, 0.3 * WIDTH, math.pi, 2.0 * math.pi, franco_graphics.BLACK)
end

O canvas a seguir apresenta o resultado.

Um rosto desenhado com primitivas gráficas. O desenho retrata uma mulher com pele morena, cabelos pretos, olhos pretos. O fundo da imagem possui um céu azul com sol amarelo. A imagem é composta por círculos, elipses, retângulos, arcos e polígonos (triângulos).

O resultado inclui o link para este website desenhado como texto. Uma subrotina para escrita de texto poderia ser incluída como parte da biblioteca de desenhos -- e, de fato, será em breve.

Deve-se notar que imagens que precisem aparecer atrás de outras devem ser desenhadas antes. Para definir ordens arbitrárias para posicionamento relativo de imagens (ou seja, para definir quais devem aparecer na frente ou atrás de outras), seria possível implementar algoritmos para ordenação de desenhos (por exemplo, Z-buffer).

No mais, a imagem criada provavelmente é um dos melhores trabalhos gráficos (manuais, isto é, não procedurais) do autor. Ou seja, não é preciso muito para fazer melhor.

Você certamente pode criar desenhos mais bonitos com primitivas gráficas. Caso trabalhar com porcentagens da largura (WIDTH) e altura (HEIGHT) da imagem seja complicado, você pode usar valores específicos para pixels (por exemplo, (0, 0), (12, 34), e assim por diante). Use sua criatividade, programe sua primeira imagem artística, e compartilhe seus resultados com o autor e/ou com as hashtags #IdeiasRegrasSimulação e #FrancoGarciaCom.

Para códigos de cores, você pode verificar esta lista de cores para a Internet. Ela possui os nomes de cores usadas para JavaScript, assim como combinações RGB. Outra possibilidade é usar ferramentas como color wheels ou color pickers, mencionadas em tópicos anteriores..

Além disso, existem ferramentas para computador que permitem usar o mouse para descobrir os valores RGB para uma cor selecionada da tela. Por exemplo, o KDE fornece uma chamada KColorChooser; o Windows possui uma ferramenta semelhante em PowerToys, mas você deve instalá-la. Em navegadores, também é possível inspecionar um elemento (acessível via clique direito do mouse), e usar a ferramenta de conta gotas para escolher uma cor da página aberta.

Para algumas inspirações, você pode consultar as próximas ilustrações. Tente identificar as primitivas gráficas utilizadas (ou seja, treine suas habilidades de reconhecimento de padrões), e crie imagens semelhantes. Assim, você treinará suas habilidades de reconhecimento de padrões, de pensamento computacional, e de programação.

Um casa desenhada com primitivas gráficas. O desenho retrata casa em tom azul claro, com uma porta marrom, duas janelas com quadros em marrom, telhado vermelho, e uma árvore na frente (tronco marrom, copa verde). O fundo da imagem possui um céu azul com sol amarelo. A imagem é composta por círculos, retângulos, polígonos (triângulos) e segmentos de reta.
Um pintinho desenhado com primitivas gráficas. O desenho retrata uma pintinho amarelo, com bico vermelho e olhos pretos. O fundo da imagem é azul. A imagem é composta por círculos, e polígonos (triângulos).
Um gato desenhado com primitivas gráficas. O desenho retrata um gato em tons de cinza claro, com olhos pretos, focinho rosa, boca rosa, e orelhas em um tom de cinza mais escuro, com detalhes em rosa. O fundo da imagem é azul. A imagem é composta por círculos, elipses, arcos, polígonos (triângulos) e segmentos de retas.
Um cachorro desenhado com primitivas gráficas. O desenho retrata um cão em tons de marrom claro, com olhos pretos, focinho preto, língua rosa, e orelhas em um tom de marrom mais escuro. O fundo da imagem é azul. A imagem é composta por círculos, elipses, retângulos, arcos, e segmentos de retas.
Um porco desenhado com primitivas gráficas. O desenho retrata um porco em tons de rosa, com focinho rosa escuro e orelhas também em rosa escuro. O fundo da imagem é azul. A imagem é composta por círculos e elipses.
Uma vaca desenhada com primitivas gráficas. O desenho retrata uma vaca em tons de bege, com boca rosa e um sorriso vermelho. O fundo da imagem é azul. A imagem é composta por círculos, elipses, e arcos.

Os exemplos demonstram que, embora não seja muito conveniente desenhar com primitivas gráficas diretamente no código para criar ilustrações, certamente é algo possível. Adotando-se estilos minimalistas, é possível criar arte esteticamente agradável -- mesmo por pessoas sem muito talento e habilidades artísticas, como o autor deste material.

Conceitos Abordados de Aprenda Programação

Conceitos de Aprenda Programação abordados neste tópico:

  1. Ponto de entrada;
  2. Saída;
  3. Tipos de dados;
  4. Variáveis e constantes;
  5. Aritmética;
  6. Operações relacionais e comparações;
  7. Operações lógicas;
  8. Estruturas de condição;
  9. Subrotinas (funções e procedimentos);
  10. Estruturas de repetição (laços ou loops);
  11. Coleções: vetores;
  12. Registros (Structs ou Records);
  13. Bibliotecas.

Novos Itens para Seu Inventário

Habilidades de Pensamento Computacional:

Ferramentas:

  • Tabela de cores;
  • Ferramentas para identificar uma cor na tela.

Habilidades:

  • Desenho de elipses;
  • Desenho de polígonos;
  • Preenchimento de círculos, elipses, polígonos;
  • Desenhos simples com primitivas gráficas.

Conceitos:

  • Primitivas gráficas: elipses;
  • Primitivas gráficas: polígonos;
  • Contorno e preenchimento.

Recursos de programação:

  • Desenhos (contorno e preenchimento) de polígonos, círculos e elipses;
  • Criação de desenhos simples usando primitivas gráficas.

Pratique

Para aprender programação, prática deliberada deve acompanhar conceitos. Tente fazer os próximos exercícios para praticar.

  1. Adicione uma subrotina clear_background() à biblioteca de gráficos que preencha o fundo de tela com uma cor sólida. Isso efetivamente limpará o fundo da janela para próximos desenhos;

  2. Adicione uma subrotina draw_text() à biblioteca de gráficos que desenha o texto de uma cadeia de caracteres na janela. O desenho de texto usando APIs foi comentado anteriormente.

    A subrotina deve ter como parâmetros: o texto a ser escrito, o ponto inicial (x e y) e a cor desejada. Caso precise de mais parâmetros, você pode adicioná-los.

    Use a subrotina para assinar suas criações artísticas.

  3. Crie seus próprios desenhos usando a biblioteca de gráficos. Exemplos de desenhos simples (no estilo dos criados por crianças pequenas e pelo autor deste material) incluem:

    • Prédio;
    • Carro;
    • Floresta com algumas árvores (use uma estrutura de repetição para desenhá-las);
    • Boneco ou boneca de neve;
    • Pássaro (como dois arcos);
    • Pingüim;
    • Coelho;
    • Panda;
    • Leão;
    • Coala;
    • Face de uso de pelúcia;
    • Borboleta;
    • Joaninha;
    • Flores.

    Se possível, compartilhe suas criações com as hashtags #IdeiasRegrasSimulação e #FrancoGarciaCom para divulgá-las (assim como este material) e também mostre para o autor. Informações para contato estão ao final desta página.

  4. Retorne à simulação de dados e moedas do tópico anterior. Crie gráficos mais sofisticados;

  5. Desenhe um polígono que altere as cores das linhas usando cores pré-definidas em um vetor. Dica: cada linha do polígono pode ser um par de pontos com um cor. O segundo ponto do par será o primeiro ponto da próxima linha (com outra cor). Use uma estrutura de repetições para desenhar o polígono;

  6. Desenhe polígonos com franco_draw_polygon() ou draw_polygon() para praticar o uso de vetores. Tente desenhar letras e formas geométricas. Também tente implementar draw_rectangle() usando draw_polygon() (para isso, você deverá calcular os demais pontos baseando-se no ponto fornecido, e nos valores para altura e largura);

  7. Refatore o exercício anterior. Crie um registro Polygon que armazene um vetor (ou uma lista) de pontos e um vetor de cores. Utilize os valores para desenhar o polígono fornecido;

  8. Quais as vantagens de usar uma biblioteca? Quais são as desvantagens?

    O tópico Aprenda Programação: Bibliotecas pode ser útil como referência.

Aprofundamentos

Em Ideias, Regras, Simulação, esta seção apresenta conteúdo complementar.

Bucket Fill Usando Flood Fill Algorithm

O algoritmo Flood Fill (ou Seed Fill) fornece outra forma de preencher polígonos. Diferentemente de scan-line, flood fill funciona melhor em programas interativos, com entrada de usuários finais. Isso ocorre porque é necessário fornecer um ponto dentro de uma região que se deseja preencher. Assim, ele pode ser uma possível abordagem para a implementação de ferramentas de balde (bucket fill) em editores gráficos.

O próximo canvas apresenta uma implementação do preenchimento feito pelo algoritmo flood fill. É possível executar o algoritmo de uma vez, ou habilitar uma animação. Atenção: a implementação é lenta e isso é intencional (como será explicado).

Para iniciar o preenchimento, pode-se escolher uma cor. Em seguida, deve-se clicar sobre um pixel do canvas. Quanto maior a região delimitada, maior será o tempo de preenchimento. Ou seja, mesmo que a execução demore, a implementação está funcionando corretamente.

Logo, é interessante começar clicando em uma região pequena (por exemplo, a pupila preta de um olho ou uma narina em rosa claro).

Clique em um pixel e espere. O preenchimento terminará... Eventualmente.


Exemplo de preenchimento usando flood fill algorithm.

A versão implementada é a descrita em span filling. Como a implementação da ferramenta não foi otimizada, pode-se notar que ela é lenta; os períodos de aparente pausa são verificações que não levam a preenchimentos. Na versão sem animação, o preenchimento será realizado após alguns segundos. Na versão com animação, é possível o programa leve alguns minutos para terminar, dependendo da complexidade e do tamanho da região a ser preenchida. O programa parece começar, parar, continuar, parar, continuar... Até termina de preencher a região.

Se você não gosta de esperar, você pode ter certeza de usuários e usuários de seu programa também gostariam de resultados mais imediatos. Convém entender o problema.

Vetores Grandes e Complexidade Algorítmica (Complexidade Computacional)

A falta de otimizações é intencional. Ela foi deixada como um exemplo da importância que se deve tomar com estruturas de dados e vetores grandes (com milhares de posições). Para entender a razão da demora, deve-se aprender sobre complexidade algorítmica.

Em partes pequenas da imagem, como a pupila, o número de pixels verificados é baixo. Assim, mesmo uma versão sem otimizações leva pouco tempo para terminar.

Contudo, o canvas possui dimensões de 320x240 pixels, ou seja, 76800 pixels. Regiões grandes podem ter algumas dezenas de milhares de pixels, aumentado significativamente o tempo de execução do programa.

No caso específico da implementação feita para flood fill pelo autor, como não se verifica se um determinado ponto já foi inserido ou verificado, faz-se múltiplas verificações desnecessariamente. O tempo de execução é agravado na versão com animação, que adiciona pequena pausas após um dos laços.

Próximos Passos

Computação não é mágica, embora possa parecer. Conforme você aprender os fundamentos, você poderá criar sistemas que fazem a magia. Ciência para você; mágica para os outros.

De fato, agora você sabe como gráficos simples são criados desde o primeiro pixel. Começamos de um pixel e agora temos algumas imagens... Simpáticas, por que não? Talvez adoráveis ou fofas?

Talvez impressione uma criança, talvez consiga um sorriso de algumas leitoras e alguns leitores. Certamente não será uma obra de arte que deslumbrará a humanidade. Por enquanto, o único prêmio será uma etiqueta (tag) Fofo para o tópico. Adorável talvez fosse um termo melhor para a tag, mas fofo parece mais casual e apropriado. Definitivamente Fofo não seria uma tag antecipada pelo autor como categoria quando da criação deste website. Enfim, a vida é repleta de surpresas.

De qualquer forma, agora que a tag existe, ela também foi aplicada ao tópico Aprenda Programação: Entrada em Linha de Comando. O programa bunnysay criado nesse tópico possivelmente também possa ser considerado fofo.

Chegará o momento em que o autor revelará seus talentos artísticos. Não desenhando diretamente, mas escrevendo um programa de computador para fazê-lo. De fato, a geração procedural de conteúdo aproxima-se a cada tópico de Ideias, Regras, Simulação.

Enquanto isso, a beleza artística fica sob sua responsabilidade. Talvez você seja capaz de criar uma ilustração impressionante. Talvez suas habilidades artísticas sejam melhores e mais treinadas que as do autor. Assim, crie suas ilustrações para praticar o uso de subrotinas e bibliotecas. Grandes artistas podem fazer mágicas com recursos simples. Existem, inclusive, pessoas que consideram que restrições e minimalismo possam intensificar a criatividade.

Qualquer que seja o caso, computação é parte ciência, parte arte. Convém praticar ambas, especialmente porque a criação de imagens treinará suas habilidades de abstração, representação, modelagem, e reconhecimento de padrões, que são todas importantes para o pensamento computacional.

Aliás, talvez você crie do qual você orgulhe-se; talvez você queira salvar sua criação em um arquivo para compartilhá-lo. Alternativamente, talvez você queira criar imagens em editores gráficos mais avançados (ou obtê-las de amigos ou da Internet), e usá-las em seu programa.

Tudo isso é possível; você poderá fazê-lo em breve: o próximo tópico introduz matrizes e arquivos, usando arquivos de imagens como exemplos.

No mais, se você criou uma ilustração criativa ou interessante, considere compartilhá-la. Alternativamente, se você considerou este material útil, você também pode compartilhá-lo. Se possível, use as hashtags #IdeiasRegrasSimulação e #FrancoGarciaCom.

Agradeço a atenção. Até a próxima!

Ideias, Regras, Simulação

  1. Motivação;
  2. Introdução: Janela e Olá Mundo;
  3. Pixels e primitivas gráficas (pontos, linhas, e arcos);
  4. Aleatoriedade e ruído;
  5. Moedas e dados, retângulos e quadrados;
  6. Desenhando com primitivas gráficas (contornos e preenchimento para círculos, elipses e polígonos);
  7. Salvando e carregando arquivos de imagens;
  8. ...

A escrita do material está em progresso; portanto, se você chegou muito cedo e os itens anteriores não possuem links, por favor, retorne a esta página para conferir as atualizações.

Caso queira entrar em contato ou tenha alguma dúvida, você pode conversar comigo das seguintes maneiras:

Contatos e redes sociais também estão no rodapé das páginas.

Suas opiniões sobre a série serão fundamentais para que eu possa tornar o material mais acessível e simples para mais pessoas.

  • Vídeo
  • Informática
  • Programação
  • Iniciante
  • Pensamento Computacional
  • Ideias, Regras, Simulação
  • Python
  • PyGame
  • Lua
  • LÖVE (Love2D)
  • Javascript
  • HTML Canvas
  • Godot
  • Gdscript
  • Fofo

Este é o tópico mais recente