screenshot_utils.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """
  2. Copyright (c) Contributors to the Open 3D Engine Project.
  3. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. """
  6. import azlmbr.atom
  7. import azlmbr.utils
  8. import azlmbr.legacy.general as general
  9. from editor_python_test_tools.editor_test_helper import EditorTestHelper
  10. DEFAULT_FRAME_WIDTH = 1920
  11. DEFAULT_FRAME_HEIGHT = 1080
  12. FOLDER_PATH = '@user@/PythonTests/Automated/Screenshots'
  13. helper = EditorTestHelper(log_prefix="Atom_ScreenshotHelper")
  14. class ScreenshotHelper(object):
  15. """
  16. A helper to capture screenshots and wait for them.
  17. """
  18. def __init__(self, idle_wait_frames_callback, frame_width=DEFAULT_FRAME_WIDTH, frame_height=DEFAULT_FRAME_HEIGHT):
  19. super().__init__()
  20. self.done = False
  21. self.capturedScreenshot = False
  22. self.max_frames_to_wait = 60
  23. self.prepare_viewport_for_screenshot(frame_width, frame_height)
  24. self.idle_wait_frames_callback = idle_wait_frames_callback
  25. def capture_screenshot_blocking_in_game_mode(self, filename):
  26. helper.enter_game_mode(["", ""])
  27. general.idle_wait_frames(120)
  28. self.capture_screenshot_blocking(filename)
  29. helper.exit_game_mode(["", ""])
  30. def prepare_viewport_for_screenshot(self, frame_width, frame_height):
  31. cur_viewport_size = general.get_viewport_size()
  32. if int(cur_viewport_size.x) != frame_width or int(cur_viewport_size.y) != frame_height:
  33. general.set_viewport_expansion_policy('FixedSize')
  34. general.idle_wait_frames(1)
  35. general.set_viewport_size(frame_width, frame_height)
  36. general.set_cvar_integer('r_DisplayInfo', 0)
  37. general.update_viewport()
  38. general.idle_wait_frames(120)
  39. new_viewport_size = general.get_viewport_size()
  40. if int(new_viewport_size.x) != frame_width or int(new_viewport_size.y) != frame_height:
  41. general.log("Resolution is incorrect!")
  42. general.log(f"width: {int(new_viewport_size.x)}")
  43. general.log(f"height: {int(new_viewport_size.y)}")
  44. def capture_screenshot_blocking(self, filename, folder_path=FOLDER_PATH):
  45. """
  46. Capture a screenshot and block the execution until the screenshot has been written to the disk.
  47. """
  48. self.done = False
  49. self.capturedScreenshot = False
  50. outcome = azlmbr.atom.FrameCaptureRequestBus(
  51. azlmbr.bus.Broadcast, "CaptureScreenshot", f"{folder_path}/{filename}")
  52. if outcome.IsSuccess():
  53. self.handler = azlmbr.atom.FrameCaptureNotificationBusHandler()
  54. self.handler.connect(outcome.GetValue())
  55. self.handler.add_callback('OnFrameCaptureFinished', self.on_screenshot_captured)
  56. self.wait_until_screenshot()
  57. general.log("Screenshot taken.")
  58. else:
  59. general.log(f"Screenshot failed. {outcome.GetError().error_message}")
  60. return self.capturedScreenshot
  61. def on_screenshot_captured(self, parameters):
  62. # the parameters come in as a tuple
  63. if parameters[0] == azlmbr.atom.FrameCaptureResult_Success:
  64. general.log(f"screenshot saved: {parameters[1]}")
  65. self.capturedScreenshot = True
  66. else:
  67. general.log(f"screenshot failed: {parameters[1]}")
  68. self.done = True
  69. self.handler.disconnect()
  70. def wait_until_screenshot(self):
  71. frames_waited = 0
  72. while self.done == False:
  73. self.idle_wait_frames_callback(1)
  74. if frames_waited > self.max_frames_to_wait:
  75. general.log("timeout while waiting for the screenshot to be written")
  76. self.handler.disconnect()
  77. break
  78. else:
  79. frames_waited = frames_waited + 1
  80. general.log(f"(waited {frames_waited} frames)")
  81. def take_screenshot_game_mode(screenshot_name, entity_name=None):
  82. """
  83. Enters game mode & takes a screenshot, then exits game mode after.
  84. :param screenshot_name: name to give the captured screenshot .png file.
  85. :param entity_name: name of the entity being tested (for generating unique log lines).
  86. :return: None
  87. """
  88. general.enter_game_mode()
  89. helper.wait_for_condition(lambda: general.is_in_game_mode(), 2.0)
  90. general.log(f"{entity_name}_test: Entered game mode: {general.is_in_game_mode()}")
  91. ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{screenshot_name}.png")
  92. general.idle_wait(1.0)
  93. general.exit_game_mode()
  94. helper.wait_for_condition(lambda: not general.is_in_game_mode(), 2.0)
  95. general.log(f"{entity_name}_test: Exit game mode: {not general.is_in_game_mode()}")
  96. def compare_screenshots(imageA, imageB, min_diff_filter=0.01):
  97. """
  98. Compare 2 images through an Ebus call. Image order doesn't matter.
  99. RMS (root mean square) is used for the difference indicator.
  100. 2 final scores are provided: diff_score, filtered_diff_score (after applying min_diff_filter).
  101. The higher value the more different.
  102. :param imageA: one of the image.
  103. :param imageB: the other image.
  104. :param min_diff_filter: diff values less than this will be filtered out when calculating filtered_diff_score.
  105. :return: Outcome, if success, outcome contains a class ImageDiffResult, containing
  106. result_code (Success, FormatMismatch, SizeMismatch, UnsupportedFormat),
  107. diff_score, filtered_diff_score. If failure, out come contains result_code only.
  108. """
  109. outcome = azlmbr.atom.FrameCaptureTestRequestBus(
  110. azlmbr.bus.Broadcast, "CompareScreenshots", imageA, imageB, min_diff_filter)
  111. return outcome