From 4627d0eb35c1b8a3f7d19030d5c525ab383c30b8 Mon Sep 17 00:00:00 2001 From: John Marzulli Date: Thu, 4 Mar 2021 19:27:33 -0800 Subject: [PATCH 1/5] Add a few basic object definitions so drawing of some things can be batched. --- rendering/drawing.py | 81 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/rendering/drawing.py b/rendering/drawing.py index f18a0cd..1d26985 100644 --- a/rendering/drawing.py +++ b/rendering/drawing.py @@ -249,3 +249,84 @@ def segment( framebuffer, segments_to_draw, color) + + +class HollowCircle(object): + def __init__( + self, + center: list, + radius: int, + width: int, + color: list, + is_antialiased: bool = True + ) -> None: + super().__init__() + + self.__center__ = center + self.__radius__ = radius + self.__width__ = width + self.__color__ = color + self.__is_antialiased__ = is_antialiased + + def render( + self, + framebuffer + ) -> None: + circle( + framebuffer, + self.__color__, + self.__center__, + self.__radius__, + self.__width__, + self.__is_antialiased__) + + +class FilledCircle(object): + def __init__( + self, + center: list, + radius: int, + color: list, + is_antialiased: bool = True + ) -> None: + super().__init__() + + self.__center__ = center + self.__radius__ = radius + self.__color__ = color + self.__is_antialiased__ = is_antialiased + + def render( + self, + framebuffer + ) -> None: + filled_circle( + framebuffer, + self.__color__, + self.__center__, + self.__radius__, + self.__is_antialiased__) + + +class FilledPolygon(object): + def __init__( + self, + points: list, + color: list, + is_antialiased: bool = True + ) -> None: + super().__init__() + + self.__points__ = points + self.__color__ = color + self.__is_anti_aliased__ = is_antialiased + + def render( + self, + framebuffer + ) -> None: + polygon( + framebuffer, + self.__color__, + self.__points__, + self.__is_anti_aliased__) From acbcef21de34d297e093dad20a4d88af29ebd22f Mon Sep 17 00:00:00 2001 From: John Marzulli Date: Thu, 4 Mar 2021 19:32:37 -0800 Subject: [PATCH 2/5] Add a hollow polygon object. --- rendering/drawing.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/rendering/drawing.py b/rendering/drawing.py index 1d26985..1282768 100644 --- a/rendering/drawing.py +++ b/rendering/drawing.py @@ -308,6 +308,34 @@ def render( self.__is_antialiased__) +class HollowPolygon(object): + def __init__( + self, + points: list, + color: list, + width: int, + is_antialiased: bool = True + ) -> None: + super().__init__() + + self.__points__ = points + self.__color__ = color + self.__width__ = width + self.__is_anti_aliased__ = is_antialiased + + def render( + self, + framebuffer + ) -> None: + segments( + framebuffer, + self.__color__, + True, + self.__points__, + self.__width__, + self.__is_anti_aliased__) + + class FilledPolygon(object): def __init__( self, From 7b8adca8ab2317dfc757c3253479b9707be74a46 Mon Sep 17 00:00:00 2001 From: John Marzulli Date: Fri, 5 Mar 2021 00:55:01 -0800 Subject: [PATCH 3/5] Give new drawing elements pydocs --- rendering/drawing.py | 191 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 186 insertions(+), 5 deletions(-) diff --git a/rendering/drawing.py b/rendering/drawing.py index 1282768..e9e56ca 100644 --- a/rendering/drawing.py +++ b/rendering/drawing.py @@ -251,15 +251,30 @@ def segment( color) -class HollowCircle(object): +class HollowCircle: + """ + Holds the information to draw an hollow (unfilled) circle + """ + def __init__( self, center: list, radius: int, - width: int, color: list, + width: int = 1, is_antialiased: bool = True ) -> None: + """ + Create the circle to be drawn + + Args: + center (list): The center of the circle (screen space) + radius (int): The radius of the circle. + color (list): The color of the circle. + width (int, optional): The width of the circle. Defaults to 1 + is_antialiased (bool, optional): Should the circle be antialiased? Defaults to True. + """ + super().__init__() self.__center__ = center @@ -272,6 +287,12 @@ def render( self, framebuffer ) -> None: + """ + Draws the circle to the framebuffer + + Args: + framebuffer: The framebuffer to draw to. + """ circle( framebuffer, self.__color__, @@ -281,7 +302,11 @@ def render( self.__is_antialiased__) -class FilledCircle(object): +class FilledCircle: + """ + Holds the information to draw an solid (filled) circle + """ + def __init__( self, center: list, @@ -289,6 +314,16 @@ def __init__( color: list, is_antialiased: bool = True ) -> None: + """ + Create the circle to be drawn + + Args: + center (list): The center of the circle (screen space) + radius (int): The radius of the circle. + color (list): The color of the circle. + is_antialiased (bool, optional): Should the circle be antialiased? Defaults to True. + """ + super().__init__() self.__center__ = center @@ -300,6 +335,13 @@ def render( self, framebuffer ) -> None: + """ + Draws the circle to the framebuffer + + Args: + framebuffer: The framebuffer to draw to. + """ + filled_circle( framebuffer, self.__color__, @@ -308,7 +350,111 @@ def render( self.__is_antialiased__) -class HollowPolygon(object): +class Segment: + """ + Holds the information for a line segment. + """ + + def __init__( + self, + start: list, + end: list, + color: list, + width: int = 1, + is_antialiased: bool = True + ) -> None: + """ + Create the segment to be drawn. + + Args: + start (list): The starting position of the segment (screen space) + end (list): The ending position of the segment (screen space) + color (list): The color of the segment + width (int, optional): The width of the segment. Defaults to 1. + is_antialiased (bool, optional): Is this antialiased? Defaults to True. + """ + super().__init__() + + self.__start__ = start + self.__end__ = end + self.__color__ = color + self.__width__ = width + self.__is_anti_aliased__ = is_antialiased + + def render( + self, + framebuffer + ) -> None: + """ + Renders the segment. + + Args: + framebuffer: The framebuffer to draw to. + """ + segment( + framebuffer, + self.__color__, + self.__start__, + self.__end__, + self.__width__, + self.__is_anti_aliased__) + + +class Segments: + """ + Holds the information for a set of connected line segment. + These segments do not connect the final point back to the start. + """ + + def __init__( + self, + points: list, + color: list, + width: int = 1, + is_antialiased: bool = True + ) -> None: + """ + Create the segments to be drawn. + + Args: + points (list): The starting to ending points of the segment sets (screen space) + color (list): The color of the segment + width (int, optional): The width of the segment. Defaults to 1. + is_antialiased (bool, optional): Is this antialiased? Defaults to True. + """ + + super().__init__() + + self.__points__ = points + self.__color__ = color + self.__width__ = width + self.__is_anti_aliased__ = is_antialiased + + def render( + self, + framebuffer + ) -> None: + """ + Render the segments to the framebuffer + + Args: + framebuffer: The framebuffer to draw to. + """ + + segments( + framebuffer, + self.__color__, + False, + self.__points__, + self.__width__, + self.__is_anti_aliased__) + + +class HollowPolygon: + """ + Stores the info for a hollow (unfilled) polylon. + """ + def __init__( self, points: list, @@ -316,6 +462,16 @@ def __init__( width: int, is_antialiased: bool = True ) -> None: + """ + Create the polygon to be drawn. This is unfilled + + Args: + points (list): The points of the segment that define the polygon (screen space) + color (list): The color of the polygon's outline + width (int, optional): The width of the polygon's outline. Defaults to 1. + is_antialiased (bool, optional): Is this antialiased? Defaults to True. + """ + super().__init__() self.__points__ = points @@ -327,6 +483,12 @@ def render( self, framebuffer ) -> None: + """ + Renders the polygon. + + Args: + framebuffer: The framebuffer to draw to. + """ segments( framebuffer, self.__color__, @@ -336,13 +498,26 @@ def render( self.__is_anti_aliased__) -class FilledPolygon(object): +class FilledPolygon: + """ + Stores the info for a solid (filled) polylon. + """ + def __init__( self, points: list, color: list, is_antialiased: bool = True ) -> None: + """ + Create the polygon to be drawn. This is solid/filled + + Args: + points (list): The points of the segment that define the polygon (screen space) + color (list): The color of the polygon + is_antialiased (bool, optional): Is this antialiased? Defaults to True. + """ + super().__init__() self.__points__ = points @@ -353,6 +528,12 @@ def render( self, framebuffer ) -> None: + """ + Renders the polygon. + + Args: + framebuffer: The framebuffer to draw to. + """ polygon( framebuffer, self.__color__, From 811519043bbac6dc36889c15fc86ceb1f6c02e04 Mon Sep 17 00:00:00 2001 From: John Marzulli Date: Fri, 5 Mar 2021 00:55:32 -0800 Subject: [PATCH 4/5] Convert the most expensive views to use the new drawing elements. --- views/adsb_on_screen_reticles.py | 43 ++++------- views/compass_and_heading_bottom_element.py | 2 +- views/compass_and_heading_top_element.py | 53 +++++++------ views/level_reference.py | 31 +++++--- views/roll_indicator.py | 86 +++++++++------------ 5 files changed, 101 insertions(+), 114 deletions(-) diff --git a/views/adsb_on_screen_reticles.py b/views/adsb_on_screen_reticles.py index b6464b2..4b3dfcb 100644 --- a/views/adsb_on_screen_reticles.py +++ b/views/adsb_on_screen_reticles.py @@ -81,13 +81,12 @@ def __get_onscreen_reticle__( return on_screen_reticle - def __render_on_screen_reticle__( + def __get_reticle_render_element__( self, - framebuffer, orientation: AhrsData, rotation_center: list, traffic: Traffic - ): + ) -> drawing.HollowPolygon: """ Draws a single reticle on the screen. @@ -103,7 +102,7 @@ def __render_on_screen_reticle__( traffic) if reticle_x is None or reticle_y is None: - return + return None # Render using the Above us bug on_screen_reticle_scale = hud_elements.get_reticle_size(traffic.distance) @@ -114,10 +113,10 @@ def __render_on_screen_reticle__( [reticle_x, reticle_y]) if reticle_x < self.__min_x__ or reticle_x > self.__max_x__: - return + return None if reticle_y < self.__min_y__ or reticle_y > self.__max_y__: - return + return None # Used for debugging reticle rotation # drawing.segment( @@ -132,9 +131,11 @@ def __render_on_screen_reticle__( # [reticle_x, reticle_y], # rotation_center) - self.__render_target_reticle__( - framebuffer, - reticle) + return drawing.HollowPolygon( + reticle, + colors.RED, + self.__line_width__, + True) def __get_rotation_point__( self, @@ -197,30 +198,14 @@ def render( # find the position of the center of the 0 pitch indicator rotation_center = self.__get_rotation_point__(orientation) - with TaskProfiler('views.on_screen_reticles.AdsbOnScreenReticles.rendering'): - # pylint:disable=expression-not-assigned - [self.__render_on_screen_reticle__( - framebuffer, + reticles = [self.__get_reticle_render_element__( orientation, rotation_center, traffic) for traffic in traffic_reports] - def __render_target_reticle__( - self, - framebuffer, - reticle_lines - ): - """ - Renders a targetting reticle on the screen. - Assumes the X/Y projection has already been performed. - """ - - drawing.segments( - framebuffer, - colors.RED, - True, - reticle_lines, - self.__line_width__) + with TaskProfiler('views.on_screen_reticles.AdsbOnScreenReticles.rendering'): + # pylint:disable=expression-not-assigned + [reticle.render(framebuffer) for reticle in reticles if reticle is not None] if __name__ == '__main__': diff --git a/views/compass_and_heading_bottom_element.py b/views/compass_and_heading_bottom_element.py index 3a58286..926515f 100644 --- a/views/compass_and_heading_bottom_element.py +++ b/views/compass_and_heading_bottom_element.py @@ -19,7 +19,7 @@ def __get_mark_line_start__( def __get_mark_line_end__( self ) -> int: - return self.__bottom_border__ - self.__font_height__ + return self.__bottom_border__ - self.__font_half_height__ def __get_compass_y_position__( self diff --git a/views/compass_and_heading_top_element.py b/views/compass_and_heading_top_element.py index d71a6f9..a44a2ac 100644 --- a/views/compass_and_heading_top_element.py +++ b/views/compass_and_heading_top_element.py @@ -21,7 +21,7 @@ class CompassAndHeadingTopElement(AhrsElement): def __get_mark_line_start__( self ) -> int: - return 0 + return self.__top_border__ + self.__font_height__ def __get_mark_line_end__( self @@ -51,9 +51,6 @@ def __init__( self.pixels_per_degree_x = framebuffer_size[0] / 360.0 - self.__heading_text_box_lines__ = self.__get_heading_box_points__( - self.__compass_box_y_position__) - self.__heading_strip_offset__ = {} for heading in range(0, 181): @@ -65,6 +62,8 @@ def __init__( for heading in range(0, 361): self.__heading_strip__[heading] = self.__generate_heading_strip__(heading) + self.__heading_box_elements__ = self.__get_hollow_heading_box_elements__() + def __get_heading_box_points__( self, text_vertical_position: int @@ -147,40 +146,31 @@ def render( heading = orientation.get_onscreen_projection_heading() with TaskProfiler("views.compass_and_heading_top_element.CompassAndHeadingTopElement.heading_marks"): + # pylint:disable=expression-not-assigned [self.__render_heading_mark__( framebuffer, heading_mark_to_render[0], heading_mark_to_render[1]) for heading_mark_to_render in self.__heading_strip__[heading]] + heading_text = "{0} | {1}".format( + str(apply_declination( + orientation.get_onscreen_compass_heading())).rjust(3), + str(apply_declination( + orientation.get_onscreen_gps_heading())).rjust(3)) + with TaskProfiler("views.compass_and_heading_top_element.CompassAndHeadingTopElement.heading_box"): # Render the text that is showing our AHRS and GPS headings self.__render_hollow_heading_box__( - orientation, + heading_text, framebuffer) def __render_hollow_heading_box__( self, - orientation: AhrsData, + heading_text: str, framebuffer ): - heading_text = "{0} | {1}".format( - str(apply_declination( - orientation.get_onscreen_compass_heading())).rjust(3), - str(apply_declination( - orientation.get_onscreen_gps_heading())).rjust(3)) - - drawing.polygon( - framebuffer, - colors.BLACK, - self.__heading_text_box_lines__, - False) - - drawing.segments( - framebuffer, - colors.GREEN, - True, - self.__heading_text_box_lines__, - self.__thin_line_width__) + # pylint:disable=expression-not-assigned + [box_element.render(framebuffer) for box_element in self.__heading_box_elements__] self.__render_horizontal_centered_text__( framebuffer, @@ -191,6 +181,21 @@ def __render_hollow_heading_box__( 1.0, True) + def __get_hollow_heading_box_elements__( + self + ) -> list: + heading_text_box_lines = self.__get_heading_box_points__(self.__compass_box_y_position__) + + return [ + drawing.FilledPolygon( + heading_text_box_lines, + colors.BLACK, + False), + drawing.HollowPolygon( + heading_text_box_lines, + colors.GREEN, + self.__thin_line_width__)] + def __render_heading_text__( self, framebuffer, diff --git a/views/level_reference.py b/views/level_reference.py index 9a5590e..88d78b2 100644 --- a/views/level_reference.py +++ b/views/level_reference.py @@ -39,9 +39,27 @@ def __init__( [width - edge_proportion, self.__center_y__], [width, self.__center_y__]] - self.level_reference_lines.append(artificial_horizon_level) - self.level_reference_lines.append(left_hash) - self.level_reference_lines.append(right_hash) + line_width = int(self.__line_width__ * 1.5) + + self.__reference_segments__ = [ + drawing.Segment( + artificial_horizon_level[0], + artificial_horizon_level[1], + colors.WHITE, + line_width, + False), + drawing.Segment( + left_hash[0], + left_hash[1], + colors.WHITE, + line_width, + False), + drawing.Segment( + right_hash[0], + right_hash[1], + colors.WHITE, + line_width, + False)] def render( self, @@ -53,12 +71,7 @@ def render( """ # pylint:disable=expression-not-assigned - [drawing.segments( - framebuffer, - colors.WHITE, - False, - line, - int(self.__line_width__ * 1.5)) for line in self.level_reference_lines] + [segment.render(framebuffer) for segment in self.__reference_segments__] if __name__ == '__main__': diff --git a/views/roll_indicator.py b/views/roll_indicator.py index b2b9a22..6f34698 100644 --- a/views/roll_indicator.py +++ b/views/roll_indicator.py @@ -36,7 +36,16 @@ def __init__( self.__current_angle_triangle__ = self.__get_current_angle_triangle_shape__() self.__current_angle_box__ = self.__get_current_angle_box_shape__() self.__arc_width__ = int(self.__line_width__ * 1.5) - self.__roll_angle_marks__ = self.__get_major_roll_indicator_marks__() + + roll_angle_marks = self.__get_major_roll_indicator_marks__() + + self.__indicator_elements__ = [drawing.Segments(self.__indicator_arc__, colors.WHITE, self.__arc_width__)] + + # Draw the important angle/roll step marks + self.__indicator_elements__.extend([drawing.Segment(segment_start, segment_end, colors.WHITE, self.__line_width__) for segment_start, segment_end in roll_angle_marks]) + + if not local_debug.IS_PI: + self.__indicator_elements__.extend([drawing.FilledCircle(segment_start, self.__thin_line_width__, colors.WHITE) for segment_start, segment_end in roll_angle_marks]) def __get_point_on_arc__( self, @@ -238,58 +247,33 @@ def render( # Pi given the cost of anti-aliasing is_antialiased = not local_debug.IS_PI - drawing.segments( - framebuffer, - colors.WHITE, - False, - self.__indicator_arc__, - self.__arc_width__, - is_antialiased) - - # Draw the important angle/roll step marks - for segment_start, segment_end in self.__roll_angle_marks__: - drawing.segment( - framebuffer, + # Draws the current roll + indicator_objects = [ + drawing.FilledPolygon( + rotate_points( + self.__zero_angle_triangle__, + self.__indicator_arc_center__, + -orientation.roll), + colors.WHITE, + is_antialiased), + drawing.FilledPolygon( + rotate_points( + self.__current_angle_triangle__, + self.__indicator_arc_center__, + -orientation.roll), colors.WHITE, - segment_start, - segment_end, - self.__line_width__, - True) - - if not local_debug.IS_PI: - drawing.filled_circle( - framebuffer, - colors.WHITE, - segment_start, - self.__thin_line_width__) + is_antialiased), + drawing.FilledPolygon( + rotate_points( + self.__current_angle_box__, + self.__indicator_arc_center__, + -orientation.roll), + colors.WHITE, + is_antialiased)] - # Draws the current roll - drawing.polygon( - framebuffer, - colors.WHITE, - rotate_points( - self.__zero_angle_triangle__, - self.__indicator_arc_center__, - -orientation.roll), - is_antialiased) - - drawing.polygon( - framebuffer, - colors.WHITE, - rotate_points( - self.__current_angle_triangle__, - self.__indicator_arc_center__, - -orientation.roll), - is_antialiased) - - drawing.polygon( - framebuffer, - colors.WHITE, - rotate_points( - self.__current_angle_box__, - self.__indicator_arc_center__, - -orientation.roll), - is_antialiased) + # pylint:disable=expression-not-assigned + [mark.render(framebuffer) for mark in self.__indicator_elements__] + [indicator.render(framebuffer) for indicator in indicator_objects] if __name__ == '__main__': From 529202357a5c7923fc4aa1611a5743e9b8f75703 Mon Sep 17 00:00:00 2001 From: John Marzulli Date: Fri, 5 Mar 2021 02:00:03 -0800 Subject: [PATCH 5/5] Reduce the number of calls to filter --- views/adsb_on_screen_reticles.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/views/adsb_on_screen_reticles.py b/views/adsb_on_screen_reticles.py index 4b3dfcb..85c29ff 100644 --- a/views/adsb_on_screen_reticles.py +++ b/views/adsb_on_screen_reticles.py @@ -178,22 +178,14 @@ def render( """ with TaskProfiler('views.on_screen_reticles.AdsbOnScreenReticles.preperation'): + our_heading = orientation.get_onscreen_projection_heading() # Get the traffic, and bail out of we have none traffic_reports = HudDataCache.get_reliable_traffic() traffic_reports = list( filter( - lambda x: not x.is_on_ground(), - traffic_reports)) - - our_heading = orientation.get_onscreen_projection_heading() - - traffic_reports = list( - filter( - lambda x: math.fabs(our_heading - x.bearing) < 45, - traffic_reports)) - - traffic_reports = traffic_reports[:hud_elements.MAX_TARGET_BUGS] + lambda x: not x.is_on_ground() and (math.fabs(our_heading - x.bearing) < 45), + traffic_reports))[:hud_elements.MAX_TARGET_BUGS] # find the position of the center of the 0 pitch indicator rotation_center = self.__get_rotation_point__(orientation)