Canvas
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © lejmer

//@version=5

//@description A library implementing a kind of "canvas" using a table where each pixel is represented by a table cell and the pixel color by the background color of each cell.
// To use the library, you need to create a color matrix (represented as an array) and a canvas table.
// The canvas table is the container of the canvas, and the color matrix determines what color each pixel in the canvas should have.
library("Canvas")

var int BG_CLR_INDEX = 0
var int FG_CLR_INDEX = 1

var int META_DATA_ROWS = 2

var int MAX_ROWS = 100

//@function Function that returns the maximum size of the canvas (100). The canvas is always square, so the size is equal to rows (as opposed to not rows multiplied by columns).
//@returns The maximum size of the canvas (100).
export max_canvas_size() =>
//{
    MAX_ROWS
//}

//@function Get the current background color of the color matrix. This is the default color used when erasing pixels or clearing a canvas.
//@param color_matrix The color matrix.
//@returns The current background color.
export get_bg_color(array<color> color_matrix) =>
//{
    array.get(color_matrix, BG_CLR_INDEX)
//}

//@function Get the current foreground color of the color matrix. This is the default color used when drawing pixels.
//@param color_matrix The color matrix.
//@returns The current foreground color.
export get_fg_color(array<color> color_matrix) =>
//{
    array.get(color_matrix, FG_CLR_INDEX)
//}

//@function Set the background color of the color matrix. This is the default color used when erasing pixels or clearing a canvas.
//@param color_matrix The color matrix.
//@param bg_color The new background color.
export set_bg_color(array<color> color_matrix, color bg_color) =>
//{
    array.set(color_matrix, BG_CLR_INDEX, bg_color)
//}

//@function Set the foreground color of the color matrix. This is the default color used when drawing pixels.
//@param color_matrix The color matrix.
//@param fg_color The new foreground color.
export set_fg_color(array<color> color_matrix, color fg_color) =>
//{
    array.set(color_matrix, FG_CLR_INDEX, fg_color)
//}

//@function Function that returns how many rows a color matrix consists of.
//@param color_matrix The color matrix.
//@param rows (Optional) The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
//@returns The number of rows a color matrix consists of.
export color_matrix_rows(array<color> color_matrix, int rows = na) =>
//{
    math.min(MAX_ROWS, na(rows) ? int(math.sqrt(array.size(color_matrix) - META_DATA_ROWS)) : rows)
//}

//@function Get the color of the pixel at the specified coordinates.
//@param color_matrix The color matrix.
//@param x The X coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param y The Y coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param rows (Optional) The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
//@returns The color of the pixel at the specified coordinates.
export pixel_color(array<color> color_matrix, int x, int y, int rows = na) =>
//{
    int matrix_rows = color_matrix_rows(color_matrix, rows)
    int index = x * matrix_rows + y + META_DATA_ROWS
    
    if (index >= 0 and index < array.size(color_matrix))
        array.get(color_matrix, index)
//}

//@function Draw a pixel at the specified X and Y coordinates. Uses the specified color.
//@param color_matrix The color matrix.
//@param x The X coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param y The Y coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param pixel_color The color of the pixel.
//@param rows (Optional) The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
export draw_pixel(array<color> color_matrix, int x, int y, color pixel_color, int rows = na) =>
//{
    int matrix_rows = color_matrix_rows(color_matrix, rows)
    int index = x * matrix_rows + y + META_DATA_ROWS
    
    if (index >= 0 and index < array.size(color_matrix))
        array.set(color_matrix, index, pixel_color)
//}

//@function Draw a pixel at the specified X and Y coordinates. Uses the current foreground color.
//@param color_matrix The color matrix.
//@param x The X coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param y The Y coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param rows (Optional) The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
export draw_pixel(array<color> color_matrix, int x, int y, int rows = na) =>
//{
    draw_pixel(color_matrix, x, y, get_fg_color(color_matrix), rows)
//}

//@function Erase a pixel at the specified X and Y coordinates, replacing it with the background color.
//@param color_matrix The color matrix.
//@param x The X coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param y The Y coordinate for the pixel. Must be between 0 and "color_matrix_rows() - 1".
//@param rows (Optional) The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
export erase_pixel(array<color> color_matrix, int x, int y, int rows = na) =>
//{
    int matrix_rows = color_matrix_rows(color_matrix, rows)
    int index = x * matrix_rows + y + META_DATA_ROWS
    
    if (index >= 0 and index < array.size(color_matrix))
        array.set(color_matrix, x * matrix_rows + y + META_DATA_ROWS, get_bg_color(color_matrix))
//}

//@function Create and initialize a color matrix with the specified number of rows. The number of columns will be equal to the number of rows.
//@param rows The number of rows the color matrix should consist of. This can be omitted, but if used, can speed up execution. It can never be greater than "max_canvas_size()".
//@param bg_color (Optional) The initial background color. The default is black.
//@param fg_color (Optional) The initial foreground color. The default is white.
//@returns The array representing the color matrix.
export init_color_matrix(simple int rows, color bg_color = #101010, color fg_color = #d6d6d6) =>
//{
    array<color> color_matrix = array.new<color>(rows * rows + META_DATA_ROWS, bg_color)
    
    array.set(color_matrix, BG_CLR_INDEX, bg_color)
    array.set(color_matrix, FG_CLR_INDEX, fg_color)
    
    color_matrix
//}

//@function Create and initialize a canvas table.
//@param color_matrix The color matrix.  
//@param pixel_width (Optional) The pixel width (in % of the pane width). The default width is 0.35%.
//@param pixel_height (Optional) The pixel width (in % of the pane height). The default width is 0.60%.
//@param position (Optional) The position for the table representing the canvas. The default is "position.middle_center".
//@returns The canvas table.
export init_canvas(array<color> color_matrix, float pixel_width = 0.35, float pixel_height = 0.6, string position = position.middle_center) =>
//{
    int matrix_rows = color_matrix_rows(color_matrix)
    color bg_color = get_bg_color(color_matrix)
    
    float cell_width = math.min(math.max(pixel_width, 1e-16), 100 / float(matrix_rows))
    float cell_height = math.min(math.max(pixel_height, 1e-16), 100 / float(matrix_rows))
    
    table canvas = table.new(
         position = position,
         columns = matrix_rows,
         rows = matrix_rows,
         bgcolor = color.rgb(0, 0, 0, 100),
         border_color =
         bg_color,
         border_width = 0)
    
    for i = 0 to matrix_rows - 1
    //{
        for j = 0 to matrix_rows - 1
        //{
            table.cell(canvas,
                 column = j,
                 row = i,
                 text = "",
                 width = cell_width,
                 height = cell_height,
                 bgcolor = bg_color,
                 text_color = bg_color)
        //}
    //}
    
    canvas
//}

//@function Clear a color matrix, replacing all pixels with the current background color.
//@param color_matrix The color matrix.
//@param rows The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
export clear(array<color> color_matrix, int rows = na) =>
//{
    if (barstate.islast)
    //{
        int matrix_rows = color_matrix_rows(color_matrix, rows)
        color bg_color = get_bg_color(color_matrix)
        
        for i = 0 to matrix_rows - 1
        //{
            for j = 0 to matrix_rows - 1
                draw_pixel(color_matrix, i, j, bg_color, matrix_rows)
        //}
    //}
//}

//@function This updates the canvas with the colors from the color matrix. No changes to the canvas gets plotted until this function is called.
//@param canvas The canvas table.
//@param color_matrix The color matrix.
//@param rows The number of rows of the color matrix. This can be omitted, but if used, can speed up execution.
export update(table canvas, array<color> color_matrix, int rows = na) =>
//{
    if (barstate.islast)
    //{
        int matrix_rows = color_matrix_rows(color_matrix, rows)
        
        for i = 0 to matrix_rows - 1
        //{
            for j = 0 to matrix_rows - 1
                table.cell_set_bgcolor(canvas, j, i, pixel_color(color_matrix, j, i, matrix_rows))
        //}
    //}
//}



// ----------------------------------------------------------------------------------------------------------------------------
//    Test code for the library.
// ----------------------------------------------------------------------------------------------------------------------------

// Get the maximum canvas size possible.
var int CANVAS_SIZE = max_canvas_size()

// Create and initialize the color matrix.
var array<color> color_matrix = init_color_matrix(CANVAS_SIZE)

// Create and initialize the canvas table.
var table canvas = init_canvas(color_matrix)

// Clear the color matrix.
clear(color_matrix)

// Draw a face.
if (barstate.islast)
//{
    // The left eye.
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.3),     int(CANVAS_SIZE * 0.2))
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.3 + 1), int(CANVAS_SIZE * 0.2))
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.3 + 1), int(CANVAS_SIZE * 0.2 + 1))
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.3),     int(CANVAS_SIZE * 0.2 + 1))
    
    // The right eye.
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.7),     int(CANVAS_SIZE * 0.2))
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.7 - 1), int(CANVAS_SIZE * 0.2))
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.7 - 1), int(CANVAS_SIZE * 0.2 + 1))
    draw_pixel(color_matrix, int(CANVAS_SIZE * 0.7),     int(CANVAS_SIZE * 0.2 + 1))
    
    // The mouth.
    float radius = CANVAS_SIZE * 0.3
    
    for i = 0 to 180
    //{
        float radians = i * math.pi / 180
        int pixel_x = int(CANVAS_SIZE * 0.5) + int(math.round(radius * math.cos(radians)))
        int pixel_y = int(CANVAS_SIZE * 0.5) + int(math.round(radius * math.sin(radians)))
        
        draw_pixel(color_matrix, pixel_x, pixel_y)
    //}
//}

// Plot the canvas.
update(canvas, color_matrix)
