def draw_text(text: str, start_x: float, start_y: float, color: Color, font_size: float = 12, width: int = 0, align: str = "left", font_name=('calibri', 'arial'), bold: bool = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", rotation: float = 0): """ Args: text: Text to draw start_x: start_y: color: font_size: width: align: font_name: bold: italic: anchor_x: anchor_y: rotation: Returns: """ # Scale the font up, so it matches with the sizes of the old code back # when Pyglet drew the text. font_size *= 1.25 # Text isn't anti-aliased, so we'll draw big, and then shrink scale_up = 5 scale_down = 5 font_size *= scale_up # If the cache gets too large, dump it and start over. if len(draw_text.cache) > 5000: draw_text.cache = {} key = f"{text}{color}{font_size}{width}{align}{font_name}{bold}{italic}" if key in draw_text.cache: label = draw_text.cache[key] text_sprite = label.text_sprite_list[0] if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'" ) if anchor_y == "top": text_sprite.center_y = start_y - text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_y}'" ) text_sprite.angle = rotation label.text_sprite_list.update_positions() else: label = Text() # Figure out the font to use font = None # Font was specified with a string if isinstance(font_name, str): try: font = PIL.ImageFont.truetype(font_name, int(font_size)) except OSError: # print(f"1 Can't find font: {font_name}") pass if font is None: try: temp_font_name = f"{font_name}.ttf" font = PIL.ImageFont.truetype(temp_font_name, int(font_size)) except OSError: # print(f"2 Can't find font: {temp_font_name}") pass # We were instead given a list of font names, in order of preference else: for font_string_name in font_name: try: font = PIL.ImageFont.truetype(font_string_name, int(font_size)) # print(f"3 Found font: {font_string_name}") except OSError: # print(f"3 Can't find font: {font_string_name}") pass if font is None: try: temp_font_name = f"{font_name}.ttf" font = PIL.ImageFont.truetype(temp_font_name, int(font_size)) except OSError: # print(f"4 Can't find font: {temp_font_name}") pass if font is not None: break # Default font if no font if font is None: font_names = ("arial.ttf", "/usr/share/fonts/truetype/freefont/FreeMono.ttf", '/System/Library/Fonts/SFNSDisplay.ttf') for font_string_name in font_names: try: font = PIL.ImageFont.truetype(font_string_name, int(font_size)) break except OSError: # print(f"5 Can't find font: {font_string_name}") pass # This is stupid. We have to have an image to figure out what size # the text will be when we draw it. Of course, we don't know how big # to make the image. Catch-22. So we just make a small image we'll trash text_image_size = (10, 10) image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Get size the text will be text_image_size = draw.multiline_textsize(text, font=font) # Create image of proper size text_height = text_image_size[1] text_width = text_image_size[0] image_start_x = 0 if width == 0: width = text_image_size[0] else: # Wait! We were given a field width. if align == "center": # Center text on given field width field_width = width * scale_up text_image_size = field_width, text_height image_start_x = (field_width - text_width) // 2 width = field_width else: image_start_x = 0 # If we draw a y at 0, then the text is drawn with a baseline of 0, # cutting off letters that drop below the baseline. This shoves it # up a bit. image_start_y = -font_size * scale_up * 0.02 image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Convert to tuple if needed, because the multiline_text does not take a # list for a color if isinstance(color, list): color = tuple(color) draw.multiline_text((image_start_x, image_start_y), text, color, align=align, font=font) image = image.resize((width // scale_down, text_height // scale_down), resample=PIL.Image.LANCZOS) text_sprite = Sprite() text_sprite._texture = Texture(key) text_sprite._texture.image = image text_sprite.image = image text_sprite.texture_name = key text_sprite.width = image.width text_sprite.height = image.height if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'" ) if anchor_y == "top": text_sprite.center_y = start_y + text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError( f"anchor_x should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'" ) text_sprite.angle = rotation from arcade.sprite_list import SpriteList label.text_sprite_list = SpriteList() label.text_sprite_list.append(text_sprite) draw_text.cache[key] = label label.text_sprite_list.draw()
def __init__(self, width, height, color): super().__init__() image = PIL.Image.new('RGBA', (width, height), color) self.texture = Texture("Solid", image) self._points = self.texture.unscaled_hitbox_points
def load_texture(file_name: str, x: float = 0, y: float = 0, width: float = 0, height: float = 0, mirrored: bool = False, flipped: bool = False, scale: float = 1) -> arcade.Texture: """ Load image from disk and create a texture. Args: :filename (str): Name of the file to that holds the texture. :x (float): X position of the crop area of the texture. :y (float): Y position of the crop area of the texture. :width (float): Width of the crop area of the texture. :height (float): Height of the crop area of the texture. :scale (float): Scale factor to apply on the new texture. Returns: The new texture. Raises: None >>> import arcade >>> arcade.open_window(800,600,"Drawing Example") >>> name = "arcade/examples/images/meteorGrey_big1.png" >>> texture1 = load_texture(name, 1, 1, 50, 50) >>> texture2 = load_texture(name, 1, 1, 50, 50) >>> texture = load_texture(name, 200, 1, 50, 50) Traceback (most recent call last): ... ValueError: Can't load texture starting at an x of 200 when the image is only 101 across. >>> texture = load_texture(name, 1, 50, 50, 50) Traceback (most recent call last): ... ValueError: Can't load texture ending at an y of 100 when the image is only 84 high. >>> texture = load_texture(name, 1, 150, 50, 50) Traceback (most recent call last): ... ValueError: Can't load texture starting at an y of 150 when the image is only 84 high. >>> texture = load_texture(name, 0, 0, 400, 50) Traceback (most recent call last): ... ValueError: Can't load texture ending at an x of 400 when the image is only 101 wide. >>> arcade.close_window() """ # See if we already loaded this file, and we can just use a cached version. cache_name = "{}{}{}{}{}{}{}{}".format(file_name, x, y, width, height, scale, flipped, mirrored) if cache_name in load_texture.texture_cache: return load_texture.texture_cache[cache_name] source_image = PIL.Image.open(file_name) source_image_width, source_image_height = source_image.size if x != 0 or y != 0 or width != 0 or height != 0: if x > source_image_width: raise ValueError("Can't load texture starting at an x of {} " "when the image is only {} across.".format( x, source_image_width)) if y > source_image_height: raise ValueError("Can't load texture starting at an y of {} " "when the image is only {} high.".format( y, source_image_height)) if x + width > source_image_width: raise ValueError("Can't load texture ending at an x of {} " "when the image is only {} wide.".format( x + width, source_image_width)) if y + height > source_image_height: raise ValueError("Can't load texture ending at an y of {} " "when the image is only {} high.".format( y + height, source_image_height)) image = source_image.crop((x, y, x + width, y + height)) else: image = source_image # image = _trim_image(image) if mirrored: image = PIL.ImageOps.mirror(image) if flipped: image = PIL.ImageOps.flip(image) image_width, image_height = image.size image_bytes = image.convert("RGBA").tobytes("raw", "RGBA", 0, -1) texture = gl.GLuint(0) gl.glGenTextures(1, ctypes.byref(texture)) gl.glBindTexture(gl.GL_TEXTURE_2D, texture) gl.glPixelStorei(gl.GL_UNPACK_ALIGNMENT, 1) gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_REPEAT) gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_REPEAT) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST) gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST) glu.gluBuild2DMipmaps(gl.GL_TEXTURE_2D, gl.GL_RGBA, image_width, image_height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, image_bytes) image_width *= scale image_height *= scale result = Texture(texture, image_width, image_height) load_texture.texture_cache[cache_name] = result return result
def draw_text(text: str, start_x: float, start_y: float, color: Color, font_size: float = 12, width: int = 0, align: str = "left", font_name: Union[str, Tuple[str, ...]] = ('calibri', 'arial'), bold: bool = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", rotation: float = 0) -> Sprite: # This is a custom version of arcade's draw_text function, because the normal one doesn't work global draw_text_cache # Scale the font up, so it matches with the sizes of the old code back # when Pyglet drew the text. font_size *= 1.25 # Text isn't anti-aliased, so we'll draw big, and then shrink scale_up = 2 scale_down = 2 font_size *= scale_up # If the cache gets too large, dump it and start over. if len(draw_text_cache) > 5000: draw_text_cache = {} r, g, b, alpha = get_four_byte_color(color) cache_color = f"{r}{g}{b}" key = f"{text}{cache_color}{font_size}{width}{align}{font_name}{bold}{italic}" try: label = draw_text_cache[key] except KeyError: # doesn't exist, create it label = Text() # Figure out the font to use font = None # Font was specified with a string if isinstance(font_name, str): font_name = font_name, font_names = chain( *[[font_string_name, f"{font_string_name}.ttf"] for font_string_name in font_name], DEFAULT_FONT_NAMES) font_found = False for font_string_name in font_names: try: font = ImageFont.truetype(font_string_name, int(font_size), layout_engine=ImageFont.LAYOUT_BASIC) except OSError: continue else: font_found = True break if not font_found: raise RuntimeError( "Unable to find a default font on this system. Please specify an available font." ) # This is stupid. We have to have an image to figure out what size # the text will be when we draw it. Of course, we don't know how big # to make the image. Catch-22. So we just make a small image we'll trash text_image_size = (10, 10) image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Get size the text will be text_image_size = draw.multiline_textsize(text, font=font) # Add some extra pixels at the bottom to account for letters that drop below the baseline. text_image_size = text_image_size[0], text_image_size[1] + int( font_size * 0.25) # Create image of proper size text_height = text_image_size[1] text_width = text_image_size[0] image_start_x = 0 if width == 0: width = text_image_size[0] else: # Wait! We were given a field width. if align == "center": # Center text on given field width field_width = width * scale_up text_image_size = field_width, text_height image_start_x = (field_width - text_width) // 2 width = field_width else: image_start_x = 0 # Find y of top-left corner image_start_y = 0 # Create image image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Convert to tuple if needed, because the multiline_text does not take a # list for a color if isinstance(color, list): color = cast(RGBA, tuple(color)) draw.multiline_text((image_start_x, image_start_y), text, color, align=align, font=font) image = image.resize( (max(1, width // scale_down), text_height // scale_down), resample=PIL.Image.LANCZOS) text_sprite = Sprite() text_sprite._texture = Texture(key) text_sprite.texture.image = image text_sprite.width = image.width text_sprite.height = image.height from arcade.sprite_list import SpriteList label.text_sprite_list = SpriteList() label.text_sprite_list.append(text_sprite) draw_text_cache[key] = label text_sprite = label.text_sprite_list[0] if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'" ) if anchor_y == "top": text_sprite.center_y = start_y - text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError( f"anchor_y should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'" ) text_sprite.angle = rotation text_sprite.alpha = alpha label.text_sprite_list.draw() return text_sprite
def draw_text(text: str, start_x: float, start_y: float, color: Color, font_size: float = 12, width: int = 0, align: str = "left", font_name: Union[str, Tuple[str, ...]] = ('calibri', 'arial'), bold: bool = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", rotation: float = 0): """ :param str text: Text to draw :param float start_x: :param float start_y: :param Color color: Color of the text :param float font_size: Size of the text :param float width: :param str align: :param Union[str, Tuple[str, ...]] font_name: :param bool bold: :param bool italic: :param str anchor_x: :param str anchor_y: :param float rotation: """ # Scale the font up, so it matches with the sizes of the old code back # when Pyglet drew the text. font_size *= 1.25 # Text isn't anti-aliased, so we'll draw big, and then shrink scale_up = 5 scale_down = 5 font_size *= scale_up # If the cache gets too large, dump it and start over. if len(draw_text.cache ) > 5000: # type: ignore # dynamic attribute on function obj draw_text.cache = { } # type: ignore # dynamic attribute on function obj key = f"{text}{color}{font_size}{width}{align}{font_name}{bold}{italic}" if key in draw_text.cache: # type: ignore # dynamic attribute on function obj label = draw_text.cache[ key] # type: ignore # dynamic attribute on function obj text_sprite = label.text_sprite_list[0] if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'" ) if anchor_y == "top": text_sprite.center_y = start_y - text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError( f"anchor_y should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'" ) text_sprite.angle = rotation else: label = Text() # Figure out the font to use font = None # Font was specified with a string if isinstance(font_name, str): font_name = [font_name] font_names = chain( *[[font_string_name, f"{font_string_name}.ttf"] for font_string_name in font_name], DEFAULT_FONT_NAMES) font_found = False for font_string_name in font_names: try: font = PIL.ImageFont.truetype(font_string_name, int(font_size)) except OSError: continue else: font_found = True break if not font_found: raise RuntimeError( "Unable to find a default font on this system. Please specify an available font." ) # This is stupid. We have to have an image to figure out what size # the text will be when we draw it. Of course, we don't know how big # to make the image. Catch-22. So we just make a small image we'll trash text_image_size = (10, 10) image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Get size the text will be text_image_size = draw.multiline_textsize(text, font=font) # Create image of proper size text_height = text_image_size[1] text_width = text_image_size[0] image_start_x = 0 if width == 0: width = text_image_size[0] else: # Wait! We were given a field width. if align == "center": # Center text on given field width field_width = width * scale_up text_image_size = field_width, text_height image_start_x = (field_width - text_width) // 2 width = field_width else: image_start_x = 0 # If we draw a y at 0, then the text is drawn with a baseline of 0, # cutting off letters that drop below the baseline. This shoves it # up a bit. image_start_y = -font_size * scale_up * 0.02 image = PIL.Image.new("RGBA", text_image_size) draw = PIL.ImageDraw.Draw(image) # Convert to tuple if needed, because the multiline_text does not take a # list for a color if isinstance(color, list): color = cast(RGBA, tuple(color)) draw.multiline_text((image_start_x, image_start_y), text, color, align=align, font=font) image = image.resize((width // scale_down, text_height // scale_down), resample=PIL.Image.LANCZOS) text_sprite = Sprite() text_sprite._texture = Texture(key) text_sprite.texture.image = image text_sprite.image = image text_sprite.texture_name = key text_sprite.width = image.width text_sprite.height = image.height if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'" ) if anchor_y == "top": text_sprite.center_y = start_y + text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.bottom = start_y else: raise ValueError( f"anchor_y should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'" ) text_sprite.angle = rotation from arcade.sprite_list import SpriteList label.text_sprite_list = SpriteList() label.text_sprite_list.append(text_sprite) draw_text.cache[ key] = label # type: ignore # dynamic attribute on function obj label.text_sprite_list.draw()
def draw_text(text: str, start_x: float, start_y: float, color: Color, font_size: float = 12, width: int = 0, align: str = "left", font_name: Union[str, Tuple[str, ...]] = ('calibri', 'arial'), bold: bool = False, italic: bool = False, anchor_x: str = "left", anchor_y: str = "baseline", rotation: float = 0) -> Sprite: """ Draws text to the screen. Internally this works by creating an image, and using the Pillow library to draw the text to it. Then use that image to create a sprite. We cache the sprite (so we don't have to recreate over and over, which is slow) and use it to draw text to the screen. This implementation does not support bold/italic like the older Pyglet-based implementation of draw_text. However if you specify the 'italic' or 'bold' version of the font via the font name, you will get that font. Just the booleans do not work. :param str text: Text to draw :param float start_x: x coordinate of the lower-left point to start drawing text :param float start_y: y coordinate of the lower-left point to start drawing text :param Color color: Color of the text :param float font_size: Size of the text :param float width: Width of the text-box for the text to go into. Used with alignment. :param str align: Align left, right, center :param Union[str, Tuple[str, ...]] font_name: Font name, or list of font names in order of preference :param bool bold: Bold the font (currently unsupported) :param bool italic: Italicize the font (currently unsupported) :param str anchor_x: Anchor the font location, defaults to 'left' :param str anchor_y: Anchor the font location, defaults to 'baseline' :param float rotation: Rotate the text """ global draw_text_cache # If the cache gets too large, dump it and start over. if len(draw_text_cache) > 5000: draw_text_cache = {} r, g, b, alpha = get_four_byte_color(color) cache_color = f"{r}{g}{b}" key = f"{text}{cache_color}{font_size}{width}{align}{font_name}{bold}{italic}" try: label = draw_text_cache[key] except KeyError: # doesn't exist, create it image = get_text_image(text=text, text_color=color, font_size=font_size, width=width, align=align, font_name=font_name) text_sprite = Sprite() text_sprite._texture = Texture(key) text_sprite.texture.image = image text_sprite.width = image.width text_sprite.height = image.height from arcade.sprite_list import SpriteList label = Text() label.text_sprite_list = SpriteList() label.text_sprite_list.append(text_sprite) draw_text_cache[key] = label text_sprite = label.text_sprite_list[0] if anchor_x == "left": text_sprite.center_x = start_x + text_sprite.width / 2 elif anchor_x == "center": text_sprite.center_x = start_x elif anchor_x == "right": text_sprite.right = start_x else: raise ValueError( f"anchor_x should be 'left', 'center', or 'right'. Not '{anchor_x}'" ) if anchor_y == "top": text_sprite.center_y = start_y - text_sprite.height / 2 elif anchor_y == "center": text_sprite.center_y = start_y elif anchor_y == "bottom" or anchor_y == "baseline": text_sprite.center_y = start_y + text_sprite.height / 2 else: raise ValueError( f"anchor_y should be 'top', 'center', 'bottom', or 'baseline'. Not '{anchor_y}'" ) text_sprite.angle = rotation text_sprite.alpha = alpha label.text_sprite_list.draw() return text_sprite