fortplot_raster.f90 Source File


Source Code

module fortplot_raster
    !! Main raster plotting context - type definition and constructor only.
    !! All bound method implementations are in submodules:
    !!   fortplot_raster_impl        - drawing methods (line, arrow, marker, quad)
    !!   fortplot_raster_text_impl   - text rendering methods (text, bbox, rotated)
    !!
    !! This module provides the raster_context type and create_raster_canvas
    !! constructor. See submodules for all method implementations.

    use iso_c_binding
    use fortplot_context, only: plot_context, setup_canvas
    use fortplot_constants, only: EPSILON_COMPARE, REFERENCE_DPI
    use fortplot_bitmap, only: composite_bitmap_to_raster_0, get_text_bitmap_metrics, &
                               render_text_to_bitmap_with_size, &
                               rotate_bitmap_about_anchor
    use fortplot_text, only: calculate_text_width, calculate_text_width_with_size, &
                             calculate_text_height
    use fortplot_text_rendering, only: render_text_to_image, render_text_with_size
    use fortplot_text_helpers, only: prepare_text_for_raster
    use fortplot_logging, only: log_error
    use fortplot_errors, only: fortplot_error_t, ERROR_INTERNAL
    use fortplot_margins, only: plot_margins_t, plot_area_t, calculate_plot_area
    use fortplot_markers, only: get_marker_size, MARKER_CIRCLE, MARKER_SQUARE, &
                                MARKER_DIAMOND, MARKER_CROSS
    use fortplot_raster_drawing, only: draw_line_distance_aa, blend_pixel, &
                                       distance_point_to_line_segment, &
                                       ipart, fpart, rfpart, color_to_byte, &
                                       draw_circle_antialiased, &
                                       draw_circle_outline_antialiased, &
                                       draw_circle_with_edge_face, &
                                       draw_square_with_edge_face, &
                                       draw_diamond_with_edge_face, draw_x_marker
    use fortplot_raster_line_styles, only: draw_styled_line, reset_pattern_distance, &
                                           set_raster_line_style
    use fortplot_raster_core, only: raster_image_t, create_raster_image, &
                                    destroy_raster_image, pt2px, scale_px
    use fortplot_raster_axes, only: raster_draw_axes_and_labels, raster_render_ylabel, &
                                    raster_draw_axes_lines_and_ticks, &
                                    raster_draw_axis_labels_only
    use fortplot_raster_labels, only: raster_draw_axis_labels
    use fortplot_raster_rendering, only: raster_fill_heatmap, raster_fill_quad, &
                                          fill_triangle, &
                                          raster_render_legend_specialized, &
                                          raster_calculate_legend_dimensions, &
                                          raster_set_legend_border_width, &
                                          raster_calculate_legend_position, &
                                          raster_extract_rgb_data, raster_get_png_data
    use fortplot_legend, only: legend_t
    use fortplot_plot_data, only: plot_data_t
    use, intrinsic :: iso_fortran_env, only: wp => real64
    implicit none

    private
    public :: raster_image_t, create_raster_image, destroy_raster_image
    public :: raster_context, create_raster_canvas
    public :: raster_draw_axes_and_labels, raster_render_ylabel
    public :: raster_draw_axes_lines_and_ticks, raster_draw_axis_labels_only

    integer, parameter :: DEFAULT_RASTER_LINE_WIDTH_SCALING = 10

    ! Raster plotting context - backend-agnostic bitmap operations
    type, extends(plot_context) :: raster_context
        type(raster_image_t) :: raster
        type(plot_margins_t) :: margins  ! Common margin functionality
        type(plot_area_t) :: plot_area
        character(len=16) :: last_xscale = 'linear'
        character(len=16) :: last_yscale = 'linear'
        real(wp) :: last_symlog_threshold = 1.0_wp
    contains
        procedure :: line => raster_draw_line
        procedure :: color => raster_set_color_context
        procedure :: text => raster_draw_text
        procedure :: draw_text_styled => raster_draw_text_styled
        procedure :: set_line_width => raster_set_line_width
        procedure :: set_line_style => raster_set_line_style_context
        procedure :: save => raster_save_dummy
        procedure :: draw_marker => raster_draw_marker
        procedure :: set_marker_colors => raster_set_marker_colors
        procedure :: set_marker_colors_with_alpha => raster_set_marker_colors_with_alpha
        procedure :: fill_quad => raster_fill_quad_context
        procedure :: draw_arrow => raster_draw_arrow
        procedure :: get_ascii_output => raster_get_ascii_output
        ! Polymorphic methods to eliminate SELECT TYPE
        procedure :: get_width_scale => raster_get_width_scale
        procedure :: get_height_scale => raster_get_height_scale
        procedure :: fill_heatmap => raster_fill_heatmap_context
        procedure :: render_legend_specialized => &
            raster_render_legend_specialized_context
        procedure :: calculate_legend_dimensions => &
            raster_calculate_legend_dimensions_context
        procedure :: set_legend_border_width => raster_set_legend_border_width_context
        procedure :: calculate_legend_position_backend => &
            raster_calculate_legend_position_context
        procedure :: extract_rgb_data => raster_extract_rgb_data_context
        procedure :: get_png_data_backend => raster_get_png_data_context
        procedure :: prepare_3d_data => raster_prepare_3d_data_context
        procedure :: render_ylabel => raster_render_ylabel_context
        procedure :: draw_axes_and_labels_backend => raster_draw_axes_and_labels_context
        procedure :: draw_axes_lines_and_ticks => &
            raster_draw_axes_lines_and_ticks_context
        procedure :: draw_axis_labels_only => raster_draw_axis_labels_only_context
        procedure :: save_coordinates => raster_save_coordinates
        procedure :: set_coordinates => raster_set_coordinates
        procedure :: render_axes => raster_render_axes
    end type raster_context

    ! Abstract interfaces for submodule-implemented procedures
    interface
        module subroutine raster_draw_line(this, x1, y1, x2, y2)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x1, y1, x2, y2
        end subroutine raster_draw_line

        module subroutine raster_set_color_context(this, r, g, b)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: r, g, b
        end subroutine raster_set_color_context

        module subroutine raster_set_line_width(this, width)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: width
        end subroutine raster_set_line_width

        module subroutine raster_set_line_style_context(this, style)
            class(raster_context), intent(inout) :: this
            character(len=*), intent(in) :: style
        end subroutine raster_set_line_style_context

        module subroutine raster_draw_text(this, x, y, text)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x, y
            character(len=*), intent(in) :: text
        end subroutine raster_draw_text

        module subroutine raster_draw_text_styled(this, x_px, y_px, text, pixel_height, &
                                                  rotation, ha, va, bbox, color)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x_px, y_px
            character(len=*), intent(in) :: text
            real(wp), intent(in) :: pixel_height
            real(wp), intent(in) :: rotation
            character(len=*), intent(in) :: ha, va
            logical, intent(in) :: bbox
            real(wp), intent(in) :: color(3)
        end subroutine raster_draw_text_styled

        module subroutine raster_save_dummy(this, filename)
            class(raster_context), intent(inout) :: this
            character(len=*), intent(in) :: filename
        end subroutine raster_save_dummy

        module subroutine raster_draw_marker(this, x, y, style)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x, y
            character(len=*), intent(in) :: style
        end subroutine raster_draw_marker

        module subroutine draw_raster_marker_by_style(this, px, py, style)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: px, py
            character(len=*), intent(in) :: style
        end subroutine draw_raster_marker_by_style

        module subroutine raster_set_marker_colors(this, edge_r, edge_g, edge_b, face_r, &
                                                   face_g, face_b)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: edge_r, edge_g, edge_b
            real(wp), intent(in) :: face_r, face_g, face_b
        end subroutine raster_set_marker_colors

        module subroutine raster_set_marker_colors_with_alpha(this, edge_r, edge_g, &
                                                              edge_b, edge_alpha, &
                                                              face_r, face_g, face_b, &
                                                              face_alpha)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: edge_r, edge_g, edge_b, edge_alpha
            real(wp), intent(in) :: face_r, face_g, face_b, face_alpha
        end subroutine raster_set_marker_colors_with_alpha

        module subroutine raster_fill_quad_context(this, x_quad, y_quad)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x_quad(4), y_quad(4)
        end subroutine raster_fill_quad_context

        module subroutine raster_draw_arrow(this, x, y, dx, dy, size, style)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x, y, dx, dy, size
            character(len=*), intent(in) :: style
        end subroutine raster_draw_arrow

        module function raster_get_ascii_output(this) result(output)
            class(raster_context), intent(in) :: this
            character(len=:), allocatable :: output
        end function raster_get_ascii_output

        module function raster_get_width_scale(this) result(scale)
            class(raster_context), intent(in) :: this
            real(wp) :: scale
        end function raster_get_width_scale

        module function raster_get_height_scale(this) result(scale)
            class(raster_context), intent(in) :: this
            real(wp) :: scale
        end function raster_get_height_scale

        module subroutine raster_fill_heatmap_context(this, x_grid, y_grid, z_grid, &
                                                      z_min, z_max, colormap_name)
            class(raster_context), intent(inout) :: this
            real(wp), contiguous, intent(in) :: x_grid(:), y_grid(:), z_grid(:, :)
            real(wp), intent(in) :: z_min, z_max
            character(len=*), intent(in), optional :: colormap_name
        end subroutine raster_fill_heatmap_context

        module subroutine raster_render_legend_specialized_context(this, legend, &
                                                                   legend_x, legend_y)
            class(raster_context), intent(inout) :: this
            type(legend_t), intent(in) :: legend
            real(wp), intent(in) :: legend_x, legend_y
        end subroutine raster_render_legend_specialized_context

        module subroutine raster_calculate_legend_dimensions_context(this, legend, &
                                                                     legend_width, &
                                                                     legend_height)
            class(raster_context), intent(in) :: this
            type(legend_t), intent(in) :: legend
            real(wp), intent(out) :: legend_width, legend_height
        end subroutine raster_calculate_legend_dimensions_context

        module subroutine raster_set_legend_border_width_context(this)
            class(raster_context), intent(inout) :: this
        end subroutine raster_set_legend_border_width_context

        module subroutine raster_calculate_legend_position_context(this, legend, x, y)
            class(raster_context), intent(in) :: this
            type(legend_t), intent(in) :: legend
            real(wp), intent(out) :: x, y
        end subroutine raster_calculate_legend_position_context

        module subroutine raster_extract_rgb_data_context(this, width, height, rgb_data)
            class(raster_context), intent(in) :: this
            integer, intent(in) :: width, height
            real(wp), intent(out) :: rgb_data(width, height, 3)
        end subroutine raster_extract_rgb_data_context

        module subroutine raster_get_png_data_context(this, width, height, png_data, &
                                                      status)
            class(raster_context), intent(in) :: this
            integer, intent(in) :: width, height
            integer(1), allocatable, intent(out) :: png_data(:)
            integer, intent(out) :: status
        end subroutine raster_get_png_data_context

        module subroutine raster_prepare_3d_data_context(this, plots)
            class(raster_context), intent(inout) :: this
            type(plot_data_t), intent(in) :: plots(:)
        end subroutine raster_prepare_3d_data_context

        module subroutine raster_render_ylabel_context(this, ylabel)
            class(raster_context), intent(inout) :: this
            character(len=*), intent(in) :: ylabel
        end subroutine raster_render_ylabel_context

        module subroutine raster_draw_axes_and_labels_context(this, xscale, yscale, &
                                                              symlog_threshold, &
                                                              x_min, x_max, y_min, y_max, &
                                                              title, xlabel, ylabel, &
                                                              x_date_format, y_date_format, &
                                                              z_min, z_max, has_3d_plots)
            class(raster_context), intent(inout) :: this
            character(len=*), intent(in) :: xscale, yscale
            real(wp), intent(in) :: symlog_threshold
            real(wp), intent(in) :: x_min, x_max, y_min, y_max
            character(len=:), allocatable, intent(in), optional :: title, xlabel, ylabel
            character(len=*), intent(in), optional :: x_date_format, y_date_format
            real(wp), intent(in), optional :: z_min, z_max
            logical, intent(in) :: has_3d_plots
        end subroutine raster_draw_axes_and_labels_context

        module subroutine raster_draw_axes_lines_and_ticks_context(this, xscale, yscale, &
                                                                   symlog_threshold, &
                                                                   x_min, x_max, y_min, y_max)
            class(raster_context), intent(inout) :: this
            character(len=*), intent(in) :: xscale, yscale
            real(wp), intent(in) :: symlog_threshold
            real(wp), intent(in) :: x_min, x_max, y_min, y_max
        end subroutine raster_draw_axes_lines_and_ticks_context

        module subroutine raster_draw_axis_labels_only_context(this, xscale, yscale, &
                                                               symlog_threshold, &
                                                               x_min, x_max, y_min, y_max, &
                                                               title, xlabel, ylabel, &
                                                               custom_xticks, &
                                                               custom_xtick_labels, &
                                                               custom_yticks, &
                                                               custom_ytick_labels, &
                                                               x_date_format, y_date_format)
            class(raster_context), intent(inout) :: this
            character(len=*), intent(in) :: xscale, yscale
            real(wp), intent(in) :: symlog_threshold
            real(wp), intent(in) :: x_min, x_max, y_min, y_max
            character(len=:), allocatable, intent(in), optional :: title, xlabel, ylabel
            real(wp), intent(in), optional :: custom_xticks(:), custom_yticks(:)
            character(len=*), intent(in), optional :: custom_xtick_labels(:)
            character(len=*), intent(in), optional :: custom_ytick_labels(:)
            character(len=*), intent(in), optional :: x_date_format, y_date_format
        end subroutine raster_draw_axis_labels_only_context

        module subroutine raster_save_coordinates(this, x_min, x_max, y_min, y_max)
            class(raster_context), intent(in) :: this
            real(wp), intent(out) :: x_min, x_max, y_min, y_max
        end subroutine raster_save_coordinates

        module subroutine raster_set_coordinates(this, x_min, x_max, y_min, y_max)
            class(raster_context), intent(inout) :: this
            real(wp), intent(in) :: x_min, x_max, y_min, y_max
        end subroutine raster_set_coordinates

        module subroutine raster_render_axes(this, title_text, xlabel_text, ylabel_text)
             class(raster_context), intent(inout) :: this
             character(len=*), intent(in), optional :: title_text, xlabel_text, ylabel_text
         end subroutine raster_render_axes

         ! Helper procedures for text rendering (not bound methods)
         module subroutine raster_draw_text_with_bbox(image_data, width, height, x_px, y_px, &
                                               text, r, g, b, pixel_height, ha, va, &
                                               bbox, text_w, text_h, ascent_px, &
                                               descent_px, pad)
             integer(1), intent(inout) :: image_data(:)
             integer, intent(in) :: width, height
             real(wp), intent(in) :: x_px, y_px
             character(len=*), intent(in) :: text
             integer(1), intent(in) :: r, g, b
             real(wp), intent(in) :: pixel_height
             character(len=*), intent(in) :: ha, va
             logical, intent(in) :: bbox
             integer, intent(in) :: text_w, text_h
             real(wp), intent(in) :: ascent_px, descent_px
             real(wp), intent(in) :: pad
         end subroutine raster_draw_text_with_bbox

         module subroutine raster_draw_rotated_text_with_bbox(image_data, width, height, x_px, &
                                               y_px, text, r, g, b, pixel_height, &
                                               rotation, ha, va, bbox, ax_src, &
                                               ay_src, pad)
             integer(1), intent(inout) :: image_data(:)
             integer, intent(in) :: width, height
             real(wp), intent(in) :: x_px, y_px
             character(len=*), intent(in) :: text
             integer(1), intent(in) :: r, g, b
             real(wp), intent(in) :: pixel_height
             real(wp), intent(in) :: rotation
             character(len=*), intent(in) :: ha, va
             logical, intent(in) :: bbox
             real(wp), intent(in) :: ax_src, ay_src, pad
         end subroutine raster_draw_rotated_text_with_bbox
     end interface

contains

    function create_raster_canvas(width, height, dpi) result(ctx)
        integer, intent(in) :: width, height
        real(wp), intent(in), optional :: dpi
        type(raster_context) :: ctx

        call setup_canvas(ctx, width, height)

        ctx%raster = create_raster_image(width, height, dpi)
        ctx%margins = plot_margins_t()  ! matplotlib-style margins
        call calculate_plot_area(width, height, ctx%margins, ctx%plot_area)
    end function create_raster_canvas

end module fortplot_raster