Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add screenshot 1342 #2039

Draft
wants to merge 46 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
e2c8b46
fixed the header levels in the sprite doc
sabadam32 Mar 2, 2024
c2dcc0f
Revert "fixed the header levels in the sprite doc"
sabadam32 Mar 2, 2024
b0d6718
Merge branch 'pythonarcade:development' into development
sabadam32 Mar 4, 2024
5377d26
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Mar 7, 2024
b8ff2f4
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Mar 10, 2024
6d5b217
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Mar 14, 2024
37a5224
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Mar 29, 2024
7523e89
added screenshot functions
sabadam32 Mar 29, 2024
cc598c8
fix tests
sabadam32 Mar 29, 2024
3d47991
fix locations
sabadam32 Mar 29, 2024
e061b0c
removed stash file and updated screenshot oath
sabadam32 Mar 29, 2024
ce14121
fix tests
sabadam32 Mar 29, 2024
3472eab
Use cleaner pytest screenshot fixtures
pushfoo Mar 29, 2024
d38d28e
Try to make Window.save_screenshot friendlier + better documented
pushfoo Mar 31, 2024
b104c00
Use shorter intersphinx mapping instead of full URL
pushfoo Mar 31, 2024
931075e
Phrasing tweak for Window.save_screenshot docstring
pushfoo Mar 31, 2024
6811519
Sync implementation + docstring for window_command.save_screenshot
pushfoo Mar 31, 2024
b377f88
Use intersphinx link in screenshot kwargs field
pushfoo Mar 31, 2024
0dcdcf1
Add Window.save_timestamped_screenshot method + doc
pushfoo Mar 31, 2024
161566e
Add creenshot / helper doc, helpers, and examples
pushfoo Mar 31, 2024
29ccec7
Revert get_timestamped_screenshot
pushfoo Mar 31, 2024
2b74056
Spacing in Window.__init__
pushfoo Mar 31, 2024
f70685a
Revert whitespace noise in application.py
pushfoo Mar 31, 2024
6741ea9
Commit overlooked example py file + screenshot for doc
pushfoo Mar 31, 2024
eb900a6
Fix code-block directive spacing
pushfoo Mar 31, 2024
b6b5341
Better date and time section tweaks
pushfoo Mar 31, 2024
b53b413
Intro block revision
pushfoo Mar 31, 2024
c42872a
Second pair of intro tweaks
pushfoo Mar 31, 2024
ae00a90
Add module-level spacing above get_timestamp
pushfoo Apr 2, 2024
2dfd5b5
Put format string first in get_timestamp
pushfoo Apr 2, 2024
09a363e
Add tzinfo to get_timestamp
pushfoo Apr 2, 2024
50d38ee
Account for DX + Sabadam32's suggestions
pushfoo Apr 2, 2024
e6a708a
Add timezone to format string + clean up tests
pushfoo Apr 3, 2024
d7eeecb
Phrasing tweak to intro
pushfoo Apr 3, 2024
8e92ac5
Merge pull request #1 from pushfoo/screenshot_pr_cleaning
sabadam32 Apr 4, 2024
f585d83
add in get_image
sabadam32 Apr 4, 2024
37b0962
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Apr 5, 2024
caa493e
Merge branch 'development' into add_screenshot_1342
sabadam32 Apr 5, 2024
5aafe39
fix typing errors
sabadam32 Apr 5, 2024
90164e8
fix type error
sabadam32 Apr 5, 2024
58f2d70
removed datetime type
sabadam32 Apr 6, 2024
ff1abb1
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Apr 6, 2024
a2278d3
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Apr 20, 2024
d9c0178
Merge branch 'development' into add_screenshot_1342
sabadam32 Apr 20, 2024
856689a
Merge branch 'development' of https://github.com/pythonarcade/arcade …
sabadam32 Apr 30, 2024
43e6ac5
Merge branch 'development' into add_screenshot_1342
sabadam32 Apr 30, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions arcade/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
# Error out if we import Arcade with an incompatible version of Python.
import sys
import os
from datetime import datetime, tzinfo
from typing import Optional

from pathlib import Path
Expand All @@ -34,6 +35,50 @@ def configure_logging(level: Optional[int] = None):
LOG.addHandler(ch)


def get_timestamp(
how: str = "%Y_%m_%d_%H%M_%S_%f%Z",
when: Optional[types.HasStrftime | datetime] = None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Did HasStrftime not match datetime? Was it only pyright which complained?
  2. To my understanding, the | syntax is a 3.9+ feature, but we support 3.8+

Copy link
Contributor Author

@sabadam32 sabadam32 Apr 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it didn't. pyright complained for sure, but I don't remember if the build failed due to this check

Copy link
Member

@pushfoo pushfoo Apr 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure? I see references to line 41 in the GitHub CI builds on the previous commits. That's the other line it seems.

tzinfo: Optional[tzinfo] = None
) -> str:
"""Return a timestamp as a formatted string.

.. tip:: To print text to the console, see :ref:`logging`!

This function :ref:`helps people who can't <debug-timestamps-who>`
use a :ref:`better alternative <debug-better-datetime>`.

Calling this function without any arguments returns a string
with the current system time down to microseconds:

.. code-block:: python

# This code assumes the function is called at exactly 3PM
# on April 3rd, 2024 in the computer's local time zone.
>>> arcade.get_timestamp()
`2024_04_03_1500_00_000000'


See the following to learn more:

* For general information, see :ref:`debug-timestamps`
* For custom formatting & times, see :ref:`debug-timestamps-example-when-how`
* To use time zones such as UTC, see :ref:`debug-timestamps-example-timezone`
* The general :py:mod:`datetime` documentation
* Python's guide to
:ref:`datetime-like behavior <strftime-strptime-behavior>`


:param how: A :ref:`valid datetime format string <strtime-strptime-behavior>`
:param tzinfo: A :py:class:`datetime.tzinfo` instance.
:param when: ``None`` or a :ref:`a datetime-like object <strftime-strptime-behavior>`
:return: A formatted string for either a passed ``when`` or
:py:meth:`datetime.now <datetime.datetime.now>`

"""
when = when or datetime.now(tzinfo)
return when.strftime(how)


# The following is used to load ffmpeg libraries.
# Currently Arcade is only shipping binaries for Mac OS
# as ffmpeg is not needed for support on Windows and Linux.
Expand Down Expand Up @@ -83,6 +128,7 @@ def configure_logging(level: Optional[int] = None):
from .window_commands import start_render
from .window_commands import unschedule
from .window_commands import schedule_once
from .window_commands import save_screenshot

from .sections import Section, SectionManager

Expand Down Expand Up @@ -330,6 +376,7 @@ def configure_logging(level: Optional[int] = None):
'get_screens',
'get_sprites_at_exact_point',
'get_sprites_at_point',
'get_timestamp',
'SpatialHash',
'get_timings',
'create_text_sprite',
Expand All @@ -353,6 +400,7 @@ def configure_logging(level: Optional[int] = None):
'read_tmx',
'load_tilemap',
'run',
'save_screenshot',
'schedule',
'set_background_color',
'set_window',
Expand Down
59 changes: 57 additions & 2 deletions arcade/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
import logging
import os
import time
from typing import List, Tuple, Optional

from typing import List, Tuple, Optional, Union
from pathlib import Path
import pyglet

import pyglet.gl as gl
Expand All @@ -23,6 +23,9 @@
from arcade.types import Color, RGBOrA255, RGBANormalized
from arcade import SectionManager
from arcade.utils import is_raspberry_pi
from arcade.types import Rect
from PIL import Image

from arcade.camera import Projector
from arcade.camera.default import DefaultProjector

Expand Down Expand Up @@ -915,6 +918,58 @@ def on_mouse_leave(self, x: int, y: int):
"""
pass

def save_screenshot(
self,
path: Union[Path, str],
format: Optional[str] = None,
**kwargs
) -> None:
"""Save a screenshot to a specified file name.

.. warning:: This may overwrite existing files!

.. code-block:: python

# By default, the image format is detected from the
# file extension on the path you pass.
window_instance.save_screenshot("screenshot.png")

You can also use the same arguments as :py:meth:`PIL.Image.save`:

* You can pass a ``format`` to stop Pillow from guessing the
format from the file name
* The pillow documentation provides a list of supported
:external+PIL:ref:`image-file-formats`

:param path: The full path and the png image filename to save.
:param format: A :py:mod:`PIL` format name.
:param kwargs: Varies with :external+PIL:ref:`selected format <image-file-formats>`
"""
img = self.ctx.get_framebuffer_image(self.ctx.screen)
img.save(path, format=format, **kwargs)

def get_image(
self,
viewport: Rect | None
) -> Image.Image:
"""Get an image from the window.

.. code-block:: python

# Get an image from a portion of the window by specfying the viewport.
viewport = Rect(10, 16, 20, 20)
image = window_instance.get_image(viewport)

# Get an image of the whole Window
image = window_instance.get_image()

:param viewport: The area of the screen to get defined by the x, y, width, height values
"""
return self.ctx.get_framebuffer_image(self.ctx.screen, viewport=viewport)

def get_pixel(self):
pass


def open_window(
width: int,
Expand Down
15 changes: 13 additions & 2 deletions arcade/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
from arcade.gl.framebuffer import Framebuffer
from pyglet.math import Mat4
from arcade.texture_atlas import TextureAtlas
from arcade.types import Rect


__all__ = ["ArcadeContext"]

Expand Down Expand Up @@ -497,19 +499,28 @@ def get_framebuffer_image(
fbo: Framebuffer,
components: int = 4,
flip: bool = True,
viewport: Optional[Rect] = None
) -> Image.Image:
"""
Shortcut method for reading data from a framebuffer and converting it to a PIL image.

:param fbo: Framebuffer to get image from
:param components: Number of components to read
:param flip: Flip the image upside down
:param viewport: x, y, width, height to read
"""
mode = "RGBA"[:components]
if viewport:
width = viewport[2] - viewport[0]
height = viewport[3] - viewport[1]
else:
width = fbo.width
height = fbo.height

image = Image.frombuffer(
mode,
(fbo.width, fbo.height),
fbo.read(components=components),
(width, height),
fbo.read(viewport=viewport, components=components),
)
if flip:
image = image.transpose(Image.Transpose.FLIP_TOP_BOTTOM)
Expand Down
65 changes: 65 additions & 0 deletions arcade/examples/debug_screenshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Take screenshots for debugging.

This example shows you how to take debug screenshots by:

1. Setting the window's background to a non-transparent color
2. Randomly arranging sprites to display a pattern over it
3. Using arcade.save_screenshot

After installing arcade version 3.0.0 or higher, this example can be run
from the command line with:
python -m arcade.examples.debug_screenshot
"""
import random
import arcade
from arcade.types import Color


SCREENSHOT_FILE_NAME = "debug_screenshot_image.png"

# How many sprites to draw and how big they'll be
NUM_SPRITES = 100
MIN_RADIUS_PX = 5
MAX_RADIUS_PX = 50

# Window size
WIDTH_PX = 800
HEIGHT_PX = 600


class ScreenshotWindow(arcade.Window):

def __init__(self):
super().__init__(WIDTH_PX, HEIGHT_PX, "Press space to save a screenshot")

# Important: we have to set a non-transparent background color,
# or else the screenshot will have a transparent background.
self.background_color = arcade.color.AMAZON

# Randomize circle sprite positions, sizes, and colors
self.sprites = arcade.SpriteList()
for i in range(NUM_SPRITES):
sprite = arcade.SpriteCircle(
random.randint(MIN_RADIUS_PX, MAX_RADIUS_PX),
Color.random(a=255)
)
sprite.position = (
random.uniform(0, self.width),
random.uniform(0, self.height)
)
self.sprites.append(sprite)

def on_draw(self):
self.clear()
self.sprites.draw()

def on_key_press(self, key, modifiers):
if key == arcade.key.SPACE:
arcade.save_screenshot(SCREENSHOT_FILE_NAME)
# You can also use the format below instead.
# self.save_screenshot(SCREENSHOT_FILE_NAME)


if __name__ == "__main__":
window = ScreenshotWindow()
arcade.run()
24 changes: 23 additions & 1 deletion arcade/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
Tuple,
Union,
TYPE_CHECKING,
TypeVar
TypeVar,
Protocol
)
from typing_extensions import Self

Expand Down Expand Up @@ -496,6 +497,27 @@ class TiledObject(NamedTuple):
type: Optional[str] = None


class HasStrftime(Protocol):
"""Marks :ref:`datetime-like <strftime-strptime-behavior>` behavior.

Ideally, this will be one of the
:ref:`improved replacements <debug-better-datettime>` for :py:mod:`datetime`.
"""

def strftime(self, format: str) -> str:
"""Uses a C89 format string to format datetime-like data.

To learn more, see:

* :ref:`debug-better-datetime`
* The Python documentation's guide to :external:ref:`strftime-strptime-behavior`

:param format: A valid format string.
:return: The object's data as a formatter string.
"""
...


if sys.version_info >= (3, 12):
from collections.abc import Buffer as BufferProtocol
else:
Expand Down
34 changes: 31 additions & 3 deletions arcade/window_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
import os

import pyglet

from pathlib import Path
from typing import (
Callable,
Optional,
Tuple,
TYPE_CHECKING
TYPE_CHECKING,
Union
)
from arcade.types import RGBA255, Color

Expand All @@ -36,7 +37,8 @@
"set_background_color",
"schedule",
"unschedule",
"schedule_once"
"schedule_once",
"save_screenshot"
]


Expand Down Expand Up @@ -302,3 +304,29 @@ def some_action(delta_time):
:param delay: Delay in seconds
"""
pyglet.clock.schedule_once(function_pointer, delay)


def save_screenshot(
path: Union[ Path, str],
format: Optional[str] = None,
**kwargs
) -> None:
"""Save a screenshot to a specified file name.

.. warning:: This may overwrite existing files!

.. code-block:: python

# By default, the image format is detected from the
# file extension on the path you pass.
window_instance.save_screenshot("screenshot.png")

This works identically to
:py:meth:`Window.save_screenshot <arcade.Window.save_screenshot>`

:param path: The full path and the png image filename to save.
:param format: A :py:mod:`PIL` format name.
:param kwargs: Varies with :external+PIL:ref:`selected format <image-file-formats>`
"""
window = get_window()
window.save_screenshot(path, format=format, **kwargs)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ The Python Arcade Library
programming_guide/texture_atlas
programming_guide/edge_artifacts/index
programming_guide/logging
programming_guide/screenshots_timestamps
programming_guide/opengl_notes
programming_guide/performance_tips
programming_guide/headless
Expand Down
3 changes: 3 additions & 0 deletions doc/programming_guide/logging.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Arcade has a few options to log additional information around timings and how
things are working internally. The two major ways to do this by turning on
logging, and by querying the OpenGL context.

To export data as part of debugging rather than logging, you may want to see
the :ref:`debug-helpers`.

Turn on logging
---------------

Expand Down
Loading
Loading