Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/sid-bhatia-0/simpledraw.jl

Simple drawing package written in Julia
https://github.com/sid-bhatia-0/simpledraw.jl

2d 2d-graphics draw drawing graphics julia simple

Last synced: 10 days ago
JSON representation

Simple drawing package written in Julia

Awesome Lists containing this project

README

        

# SimpleDraw

This is a lightweight self-contained package that attempts to provide efficient drawing methods for some simple shapes. So far, in this package, all the shapes and drawing algorithms are integer-based, and all the drawing algorithms are single-threaded.

## Table of contents:

* [Getting Started](#getting-started)
* [Notes](#notes)
- [API](#api)
- [Basic drawing](#basic-drawing)
- [Drawing optimizations](#drawing-optimizations)
- [Visualization](#visualization)
- [Benchmarks](#benchmarks)
* [References and License Information](#references-and-license-information)

[List of shapes](#list-of-shapes):

1. [`Background`](#background)
1. [`Point`](#point)
1. [`Line`](#line)
1. [`ThickLine`](#thickline)
1. [`Triangle`](#triangle)
1. [`FilledTriangle`](#filledtriangle)
1. [`Rectangle`](#rectangle)
1. [`FilledRectangle`](#filledrectangle)
1. [`ThickRectangle`](#thickrectangle)
1. [`Circle`](#circle)
1. [`FilledCircle`](#filledcircle)
1. [`ThickCircle`](#thickcircle)
1. [`Bitmap`](#bitmap)
1. [`Image`](#image)
1. [`Character`](#character)
1. [`TextLine`](#textline)

## Getting Started

```julia
import SimpleDraw as SD

# create a canvas (could be any AbstractMatrix)
image = falses(32, 32) # (height, width)

# create the shape
shape = SD.Line(SD.Point(9, 5), SD.Point(14, 19))

# we will draw on the boolean image with the "color" true
color = true

# draw the shape on image
SD.draw!(image, shape, color)

# print the boolean image using Unicode block characters
SD.visualize(image)
```

## Notes

### API

The following types are part of the API.
1. All types in [List of shapes](#list-of-shapes)
1. The abstract type `AbstractShape`
1. The font constants `TERMINUS_16_8`, `TERMINUS_BOLD_16_8`, `TERMINUS_24_12`, `TERMINUS_BOLD_24_12`, `TERMINUS_32_16`, `TERMINUS_BOLD_32_16`.

The following functions are part of the API:
1. `draw!`
1. `is_valid`
1. `get_i_min`
1. `get_i_max`
1. `get_j_min`
1. `get_j_max`
1. `get_i_extrema`
1. `get_j_extrema`
1. `get_height`
1. `get_width`
1. `get_position`
1. `move_i`
1. `move_j`
1. `move`
1. `visualize`

Everything else should be considered internal for now.

### `draw!`

Being able to draw broadly requires three things:

1. `image`: A canvas to draw on. It could be any `AbstractMatrix`.
1. `shape`: The geometric shape to be drawn. Note that primitive `shape`s can easily be composed to create more complex `shape`s. For example,
```julia
struct MyComplexShape{I <: Integer} <: SD.AbstractShape
line::SD.Line{I}
circle::SD.Circle{I}
end
```
1. `color`: The color to draw the shape with. This is what fills up the entries of the `image` matrix at appropriate positions, thereby drawing the geometric shape.

With this in mind, this package provides the `draw!` function, which is commonly invoked as follows:

```julia
SD.draw!(image, shape, color)
```

Under the hood, it calls internal `_draw!` methods that automatically take care of some basic optimizations (see [Drawing optimizations](#drawing-optimizations)). Then there are `_draw!` methods of the form `SD._draw!(f, image, shape, color)` that are heavily used internally. Here, `f` can roughly be thought of as a drawing function applied to every pixel of the shape. This offers a lot of flexibility with the "brush-stroke" and significantly decreases code duplication. At the same time, it does not adversely affect performance. Most users will not need to use these methods directly, but in case you do, please look up the source code as their usage is not very well documented as of now.

By default, the `draw!` function is safe, that is, it draws only those pixels of the shape that lie within the bounds of the image. So you don't have to worry about your program breaking even if it tries to draw something outside the bounds of the `image`. That being said, certain basic optimizations are already enabled for drawing most shapes. See [Drawing optimizations](#drawing-optimizations).

### Drawing optimizations

`DrawingOptimizationStyle` is trait whose subtypes are used to define generic `draw!` methods with different levels of optimization for drawing shapes:
1. `PUT_PIXEL`: Iterate through all the positions needed to draw the shape. For each position, if it lies within the bounds of the image, put a pixel at that position else don't do anything.
1. `CHECK_BOUNDS`: If the shape lies completely outside the bounds of the image, simply return `nothing`. If it lies completely inside the bounds of the image, then draw each pixel of the shape without any further bounds checking. If it is neither of the previous cases, fall back to the slow but safe method of drawing each pixel of the shape only if it lies within the bounds of the image.
1. `CLIP`: Some shapes like `VerticalLine`, `HorizontalLine`, `FilledRectangle` can be direcly clipped into shapes that completely lie within the bounds of the image. In such cases, perform the clipping and draw the clipped shape without any further bounds checking.
1. `PUT_PIXEL_INBOUNDS`: Iterate through all the positions needed to draw the shape. For each position, put a pixel at that position assuming without any bounds checking image.

Use `get_drawing_optimization_style(shape)` to get the default drawing optimization style for a shape. Shapes which do not fall within the above will implement custom `draw!` methods with relevant optimizations.

### Visualization

The `visualize` function helps in visualizing a boolean image directly inside the terminal. This is a quick and effective tool to verify whether a shape is being drawn as expected. This is extremely handy when you want to know about the exact coordinates of the pixels that are being drawn for a shape.

It uses Unicode block characters to represent a pixel. This works well for low resolution images. To visualize slightly higher resolution images, you can maximize your terminal window and reduce its font size.

### Benchmarks

Below are the benchmarks for `v0.5.0` of this package:

```julia-repl
julia> versioninfo()
Julia Version 1.8.5
Commit 17cfb8e65ea (2023-01-08 06:45 UTC)
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 12 × AMD Ryzen 5 6600H with Radeon Graphics
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-13.0.1 (ORCJIT, znver3)
Threads: 1 on 12 virtual cores

julia>
```

Timestamp: 2023_01_11_03_20_28 (yyyy_mm_dd_HH_MM_SS)

Shapes are drawn on an image of type `Matrix{UInt32}` with a color of type `UInt32`

|shape type|image height|image width|median time|memory|shape|
:---|:---|:---|:---|:---|:---|
|Background|64|64|121.069 ns|0 bytes|SimpleDraw.Background()|
|Background|256|256|2.140 μs|0 bytes|SimpleDraw.Background()|
|Background|1024|1024|59.781 μs|0 bytes|SimpleDraw.Background()|
|Point|64|64|2.585 ns|0 bytes|SimpleDraw.Point{Int64}(33, 33)|
|Point|256|256|2.334 ns|0 bytes|SimpleDraw.Point{Int64}(129, 129)|
|Point|1024|1024|2.585 ns|0 bytes|SimpleDraw.Point{Int64}(513, 513)|
|Line|64|64|70.601 ns|0 bytes|SimpleDraw.Line{Int64}(SimpleDraw.Point{Int64}(9, 2), SimpleDraw.Point{Int64}(56, 63))|
|Line|256|256|342.824 ns|0 bytes|SimpleDraw.Line{Int64}(SimpleDraw.Point{Int64}(33, 2), SimpleDraw.Point{Int64}(224, 255))|
|Line|1024|1024|1.470 μs|0 bytes|SimpleDraw.Line{Int64}(SimpleDraw.Point{Int64}(129, 2), SimpleDraw.Point{Int64}(896, 1023))|
|ThickLine|64|64|834.514 ns|0 bytes|SimpleDraw.ThickLine{Int64}(SimpleDraw.Point{Int64}(9, 9), SimpleDraw.Point{Int64}(56, 56), 7)|
|ThickLine|256|256|49.472 μs|0 bytes|SimpleDraw.ThickLine{Int64}(SimpleDraw.Point{Int64}(33, 33), SimpleDraw.Point{Int64}(224, 224), 31)|
|ThickLine|1024|1024|1.273 ms|0 bytes|SimpleDraw.ThickLine{Int64}(SimpleDraw.Point{Int64}(129, 129), SimpleDraw.Point{Int64}(896, 896), 127)|
|Triangle|64|64|185.163 ns|0 bytes|SimpleDraw.Triangle{Int64}(SimpleDraw.Point{Int64}(2, 2), SimpleDraw.Point{Int64}(63, 32), SimpleDraw.Point{Int64}(32, 63))|
|Triangle|256|256|749.029 ns|0 bytes|SimpleDraw.Triangle{Int64}(SimpleDraw.Point{Int64}(2, 2), SimpleDraw.Point{Int64}(255, 128), SimpleDraw.Point{Int64}(128, 255))|
|Triangle|1024|1024|4.349 μs|0 bytes|SimpleDraw.Triangle{Int64}(SimpleDraw.Point{Int64}(2, 2), SimpleDraw.Point{Int64}(1023, 512), SimpleDraw.Point{Int64}(512, 1023))|
|FilledTriangle|64|64|433.065 ns|0 bytes|SimpleDraw.FilledTriangle{Int64}(SimpleDraw.Point{Int64}(2, 2), SimpleDraw.Point{Int64}(63, 32), SimpleDraw.Point{Int64}(32, 63))|
|FilledTriangle|256|256|2.377 μs|0 bytes|SimpleDraw.FilledTriangle{Int64}(SimpleDraw.Point{Int64}(2, 2), SimpleDraw.Point{Int64}(255, 128), SimpleDraw.Point{Int64}(128, 255))|
|FilledTriangle|1024|1024|28.884 μs|0 bytes|SimpleDraw.FilledTriangle{Int64}(SimpleDraw.Point{Int64}(2, 2), SimpleDraw.Point{Int64}(1023, 512), SimpleDraw.Point{Int64}(512, 1023))|
|Rectangle|64|64|56.104 ns|0 bytes|SimpleDraw.Rectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 63, 63)|
|Rectangle|256|256|908.600 ns|0 bytes|SimpleDraw.Rectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 255, 255)|
|Rectangle|1024|1024|9.859 μs|0 bytes|SimpleDraw.Rectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 1023, 1023)|
|FilledRectangle|64|64|518.203 ns|0 bytes|SimpleDraw.FilledRectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 63, 63)|
|FilledRectangle|256|256|3.588 μs|0 bytes|SimpleDraw.FilledRectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 255, 255)|
|FilledRectangle|1024|1024|49.322 μs|0 bytes|SimpleDraw.FilledRectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 1023, 1023)|
|ThickRectangle|64|64|841.000 ns|0 bytes|SimpleDraw.ThickRectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 63, 63, 16)|
|ThickRectangle|256|256|3.241 μs|0 bytes|SimpleDraw.ThickRectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 255, 255, 64)|
|ThickRectangle|1024|1024|53.329 μs|0 bytes|SimpleDraw.ThickRectangle{Int64}(SimpleDraw.Point{Int64}(2, 2), 1023, 1023, 256)|
|Circle|64|64|95.229 ns|0 bytes|SimpleDraw.Circle{Int64}(SimpleDraw.Point{Int64}(2, 2), 62)|
|Circle|256|256|366.939 ns|0 bytes|SimpleDraw.Circle{Int64}(SimpleDraw.Point{Int64}(2, 2), 254)|
|Circle|1024|1024|3.228 μs|0 bytes|SimpleDraw.Circle{Int64}(SimpleDraw.Point{Int64}(2, 2), 1022)|
|FilledCircle|64|64|608.218 ns|0 bytes|SimpleDraw.FilledCircle{Int64}(SimpleDraw.Point{Int64}(2, 2), 62)|
|FilledCircle|256|256|3.966 μs|0 bytes|SimpleDraw.FilledCircle{Int64}(SimpleDraw.Point{Int64}(2, 2), 254)|
|FilledCircle|1024|1024|52.016 μs|0 bytes|SimpleDraw.FilledCircle{Int64}(SimpleDraw.Point{Int64}(2, 2), 1022)|
|ThickCircle|64|64|923.326 ns|0 bytes|SimpleDraw.ThickCircle{Int64}(SimpleDraw.Point{Int64}(2, 2), 62, 16)|
|ThickCircle|256|256|24.506 μs|0 bytes|SimpleDraw.ThickCircle{Int64}(SimpleDraw.Point{Int64}(2, 2), 254, 64)|
|ThickCircle|1024|1024|1.730 ms|0 bytes|SimpleDraw.ThickCircle{Int64}(SimpleDraw.Point{Int64}(2, 2), 1022, 256)|
|Bitmap|64|64|4.490 μs|0 bytes|SimpleDraw.Bitmap{Int64, BitMatrix}(SimpleDraw.Point{Int64}(2, 2), Bool[1 0 … 1 0; 0 1 … 0 1; … ; 1 0 … 1 0; 0 1 … 0 1])|
|Bitmap|256|256|71.583 μs|0 bytes|SimpleDraw.Bitmap{Int64, BitMatrix}(SimpleDraw.Point{Int64}(2, 2), Bool[1 0 … 1 0; 0 1 … 0 1; … ; 1 0 … 1 0; 0 1 … 0 1])|
|Bitmap|1024|1024|1.181 ms|0 bytes|SimpleDraw.Bitmap{Int64, BitMatrix}(SimpleDraw.Point{Int64}(2, 2), Bool[1 0 … 1 0; 0 1 … 0 1; … ; 1 0 … 1 0; 0 1 … 0 1])|
|Image|64|64|2.285 μs|0 bytes|SimpleDraw.Image{Int64, Matrix{UInt32}}(SimpleDraw.Point{Int64}(2, 2), UInt32[0x00000001 0x00000000 … 0x00000001 0x00000000; 0x00000000 0x00000001 … 0x00000000 0x00000001; … ; 0x00000001 0x00000000 … 0x00000001 0x00000000; 0x00000000 0x00000001 … 0x00000000 0x00000001])|
|Image|256|256|38.742 μs|0 bytes|SimpleDraw.Image{Int64, Matrix{UInt32}}(SimpleDraw.Point{Int64}(2, 2), UInt32[0x00000001 0x00000000 … 0x00000001 0x00000000; 0x00000000 0x00000001 … 0x00000000 0x00000001; … ; 0x00000001 0x00000000 … 0x00000001 0x00000000; 0x00000000 0x00000001 … 0x00000000 0x00000001])|
|Image|1024|1024|814.004 μs|0 bytes|SimpleDraw.Image{Int64, Matrix{UInt32}}(SimpleDraw.Point{Int64}(2, 2), UInt32[0x00000001 0x00000000 … 0x00000001 0x00000000; 0x00000000 0x00000001 … 0x00000000 0x00000001; … ; 0x00000001 0x00000000 … 0x00000001 0x00000000; 0x00000000 0x00000001 … 0x00000000 0x00000001])|
|Character|64|64|873.814 ns|0 bytes|SimpleDraw.Character{Int64, Char, SimpleDraw.MonospaceBitmapASCIIFont}(SimpleDraw.Point{Int64}(2, 2), 'A', SimpleDraw.MonospaceBitmapASCIIFont([0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; … ;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]))|
|Character|256|256|874.355 ns|0 bytes|SimpleDraw.Character{Int64, Char, SimpleDraw.MonospaceBitmapASCIIFont}(SimpleDraw.Point{Int64}(2, 2), 'A', SimpleDraw.MonospaceBitmapASCIIFont([0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; … ;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]))|
|Character|1024|1024|881.478 ns|0 bytes|SimpleDraw.Character{Int64, Char, SimpleDraw.MonospaceBitmapASCIIFont}(SimpleDraw.Point{Int64}(2, 2), 'A', SimpleDraw.MonospaceBitmapASCIIFont([0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; … ;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]))|
|TextLine|64|64|3.548 μs|0 bytes|SimpleDraw.TextLine{Int64, String, SimpleDraw.MonospaceBitmapASCIIFont}(SimpleDraw.Point{Int64}(1, 1), "BUDB", SimpleDraw.MonospaceBitmapASCIIFont([0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; … ;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]))|
|TextLine|256|256|14.377 μs|0 bytes|SimpleDraw.TextLine{Int64, String, SimpleDraw.MonospaceBitmapASCIIFont}(SimpleDraw.Point{Int64}(1, 1), "ZZPEKEEOUGIIHKGY", SimpleDraw.MonospaceBitmapASCIIFont([0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; … ;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]))|
|TextLine|1024|1024|58.398 μs|0 bytes|SimpleDraw.TextLine{Int64, String, SimpleDraw.MonospaceBitmapASCIIFont}(SimpleDraw.Point{Int64}(1, 1), "ZBXMZBNLSOTGROCLZJTOONNIEIFUOLCSLIUTYUGDMIEWKIWMYJLOPHGYPLTWBIDF", SimpleDraw.MonospaceBitmapASCIIFont([0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; … ;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0;;; 0 0 … 0 0; 0 0 … 0 0; … ; 0 0 … 0 0; 0 0 … 0 0]))|

Follow these steps to generate benchmarks. Take care to double-check the version of the package you are benchmarking:

1. Clone the project

```
$ git clone https://github.com/Sid-Bhatia-0/SimpleDraw.jl.git
```

1. Start the julia REPL inside the `/benchmark` directory

```
benchmark $ julia
```

1. Activate and instantiate the project

```
julia> import Pkg; Pkg.activate("."); Pkg.instantiate()
```

1. Exit the REP and run `generate_benchmarks.jl` with the `Project.toml` and `Manifest.toml` files in this directory

```
benchmark $ julia --project=. generate_benchmarks.jl
```

This will print a bunch of outputs and produce a markdown file named with a timestamp containing the final benchmarks. Using a timestamp in the name helps ensure that running `generate_benchmarks.jl` multiple times does not overwrite the same file.

## List of shapes

1. ### `Background`

```julia
struct Background <: AbstractShape end
```

1. ### `Point`

```julia
struct Point{I <: Integer} <: AbstractShape
i::I
j::I
end
```

1. ### `Line`

```julia
struct Line{I <: Integer} <: AbstractLine
point1::Point{I}
point2::Point{I}
end
```

`Line(point1, point2)` will draw the same thing as `Line(point2, point1)` as they are always sorted internally. But note that the visual representation of the line may not symmetric with respect to the end points in general.

1. ### `ThickLine`

```julia
struct ThickLine{I <: Integer} <: AbstractLine
point1::Point{I}
point2::Point{I}
thickness::I
end
```

An instance of `ThickLine` is considered valid only if the following conditions hold true:
* `thickness > 0`

1. ### `Triangle`

```julia
struct Triangle{I <: Integer} <: AbstractTriangle
point1::Point{I}
point2::Point{I}
point3::Point{I}
end
```

1. ### `FilledTriangle`

```julia
struct FilledTriangle{I <: Integer} <: AbstractTriangle
point1::Point{I}
point2::Point{I}
point3::Point{I}
end
```

1. ### `Rectangle`

```julia
struct Rectangle{I <: Integer} <: AbstractRectangle
position::Point{I}
height::I
width::I
end
```

An instance of `Rectangle` is considered valid only if the following conditions hold true:
* `height > 0`
* `width > 0`

1. ### `FilledRectangle`

```julia
struct FilledRectangle{I <: Integer} <: AbstractRectangle
position::Point{I}
height::I
width::I
end
```

An instance of `FilledRectangle` is considered valid only if the following conditions hold true:
* `height > 0`
* `width > 0`

1. ### `ThickRectangle`

```julia
struct ThickRectangle{I <: Integer} <: AbstractRectangle
position::Point{I}
height::I
width::I
thickness::I
end
```

An instance of `ThickRectangle` is considered valid only if the following conditions hold true:
* `height > 0`
* `width > 0`
* `thickness > 0`
* `thickness <= min(height, width)`

1. ### `Circle`

```julia
struct Circle{I <: Integer} <: AbstractCircle
position::Point{I}
diameter::I
end
```

An instance of `Circle` is considered valid only if the following conditions hold true:
* `diameter > 0`

1. ### `FilledCircle`

```julia
struct FilledCircle{I <: Integer} <: AbstractCircle
position::Point{I}
diameter::I
end
```

An instance of `FilledCircle` is considered valid only if the following conditions hold true:
* `diameter > 0`

1. ### `ThickCircle`

```julia
struct ThickCircle{I <: Integer} <: AbstractCircle
position::Point{I}
diameter::I
thickness::I
end
```

An instance of `ThickCircle` is considered valid only if the following conditions hold true:
* `diameter > 0`
* `thickness > 0`
* if `diameter` is odd, then `2 * thickness <= diameter + 1`
* if `diameter` is even, then `2 * thickness <= diameter`

1. ### `Bitmap`

```julia
struct Bitmap{I <: Integer, B <: AbstractMatrix{Bool}} <: AbstractShape
position::Point{I}
bitmap::B
end
```

Can be used to draw a 1-bit image using some color on an image. For example, it is used for drawing character glyphs in `Character` or `TextLine`.

1. ### `Image`

```julia
struct Image{I <: Integer, A} <: AbstractShape
position::Point{I}
image::A
end
```

Can be used to draw an existing image on top of an image at some position. No need to pass color explicitly, the sub-image should already be colored. Whereas in `Bitmap`, one needs to pass color explicitly and it can only be of a single color.

1. ### `Character`

```julia
struct Character{I <: Integer, C <: AbstractChar, F <: AbstractFont} <: AbstractShape
position::Point{I}
character::C
font::F
end
```

There are 6 monospace ASCII fonts are available at this point:
1. `TERMINUS_16_8` (height 16 pixels, width 8 pixels)
2. `TERMINUS_BOLD_16_8` (height 16 pixels, width 8 pixels)
3. `TERMINUS_24_12` (height 24 pixels, width 12 pixels)
4. `TERMINUS_BOLD_24_12` (height 24 pixels, width 12 pixels)
5. `TERMINUS_32_16` (height 32 pixels, width 16 pixels)
6. `TERMINUS_BOLD_32_16` (height 32 pixels, width 16 pixels)

Only glyphs for ASCII characters are available as of now.

1. ### `TextLine`

```julia
struct TextLine{I <: Integer, S, F <: AbstractFont} <: AbstractShape
position::Point{I}
text::S
font::F
end
```

There are 6 monospace ASCII fonts are available at this point:
1. `TERMINUS_16_8` (height 16 pixels, width 8 pixels)
2. `TERMINUS_BOLD_16_8` (height 16 pixels, width 8 pixels)
3. `TERMINUS_24_12` (height 24 pixels, width 12 pixels)
4. `TERMINUS_BOLD_24_12` (height 24 pixels, width 12 pixels)
5. `TERMINUS_32_16` (height 32 pixels, width 16 pixels)
6. `TERMINUS_BOLD_32_16` (height 32 pixels, width 16 pixels)

Only glyphs for ASCII characters are available as of now.

## References and License Information

1. Octant drawing: [https://en.wikipedia.org/w/index.php?title=Midpoint_circle_algorithm&oldid=1073593456](https://en.wikipedia.org/w/index.php?title=Midpoint_circle_algorithm&oldid=1073593456)
1. Line drawing: [https://en.wikipedia.org/w/index.php?title=Bresenham%27s_line_algorithm&oldid=1073834153](https://en.wikipedia.org/w/index.php?title=Bresenham%27s_line_algorithm&oldid=1073834153)
1. Fonts: This package supports bitmap fonts for [ASCII](https://en.wikipedia.org/wiki/ASCII) characters at this point. We use a subset of [Terminus Font](http://terminus-font.sourceforge.net/) for drawing the glyphs. Terminus Font is licensed under the SIL Open Font License, Version 1.1. The license is included as OFL.TXT in the `/src/fonts` directory in this repository, and is also available with a FAQ at [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL).
1. Everything else is under [LICENSE](https://github.com/Sid-Bhatia-0/SimpleDraw.jl/blob/master/LICENSE).