Repository: ManimCommunity/manim

Issue:
Follow-up to #562 -- bug with Transform / Mobject.become
`git bisect` says that merging #562 introduced a problem with `Mobject.become`: The example

```
class FadeInAndOut(Scene):
    def construct(self):
        square = Square(color=BLUE).shift(2 * UP)
        annotation = Text("Fade In", height=0.8)
        self.add(annotation)
        self.play(FadeIn(square))

        annotation.become(Text("Fade Out", height=0.8))
        self.add(annotation)
        self.play(FadeOut(square))
```
from our documentation renders as

![FadeInAndOut](https://user-images.githubusercontent.com/11851593/97088951-9e026680-1634-11eb-8bb5-45bb8ac59338.gif)

Same thing if the call to `become` is replaced by playing a `Transform` animation; the old text is simply kept on screen.

_Originally posted by @behackl in https://github.com/ManimCommunity/manim/pull/562#issuecomment-716033185_


Discussion:

Resolving patch:
diff --git a/manim/renderer/cairo_renderer.py b/manim/renderer/cairo_renderer.py
--- a/manim/renderer/cairo_renderer.py
+++ b/manim/renderer/cairo_renderer.py
@@ -76,7 +76,6 @@ def __init__(self, camera_class=None, **kwargs):
         self.animations_hashes = []
         self.num_plays = 0
         self.time = 0
-        self.static_mobject_data = None
 
     def init(self, scene):
         self.file_writer = SceneFileWriter(
@@ -102,6 +101,7 @@ def update_frame(  # TODO Description in Docstring
         self,
         scene,
         mobjects=None,
+        background=None,
         include_submobjects=True,
         ignore_skipping=True,
         **kwargs,
@@ -130,8 +130,8 @@ def update_frame(  # TODO Description in Docstring
                 scene.mobjects,
                 scene.foreground_mobjects,
             )
-        if self.static_mobject_data is not None:
-            self.camera.set_frame_to_background(self.static_mobject_data)
+        if background is not None:
+            self.camera.set_frame_to_background(background)
         else:
             self.camera.reset()
 
@@ -150,10 +150,6 @@ def get_frame(self):
         """
         return np.array(self.camera.pixel_array)
 
-    def save_static_mobject_data(self, scene, static_mobjects):
-        self.update_frame(scene, static_mobjects)
-        self.static_mobject_data = self.get_frame()
-
     def add_frame(self, frame, num_frames=1):
         """
         Adds a frame to the video_file_stream
diff --git a/manim/scene/scene.py b/manim/scene/scene.py
--- a/manim/scene/scene.py
+++ b/manim/scene/scene.py
@@ -21,7 +21,7 @@
 from ..container import Container
 from ..mobject.mobject import Mobject
 from ..scene.scene_file_writer import SceneFileWriter
-from ..utils.iterables import list_update
+from ..utils.iterables import list_update, list_difference_update
 from ..utils.hashing import get_hash_from_play_call, get_hash_from_wait_call
 from ..utils.family import extract_mobject_family_members
 from ..renderer.cairo_renderer import CairoRenderer
@@ -530,7 +530,8 @@ def get_moving_mobjects(self, *animations):
                 return mobjects[i:]
         return []
 
-    def get_moving_and_static_mobjects(self, animations):
+    def get_moving_and_stationary_mobjects(self, animations):
+        moving_mobjects = self.get_moving_mobjects(*animations)
         all_mobjects = list_update(self.mobjects, self.foreground_mobjects)
         all_mobject_families = extract_mobject_family_members(
             all_mobjects,
@@ -542,10 +543,10 @@ def get_moving_and_static_mobjects(self, animations):
             moving_mobjects,
             use_z_index=self.renderer.camera.use_z_index,
         )
-        static_mobjects = filter(
-            lambda m: m not in all_moving_mobject_families, all_mobject_families
+        stationary_mobjects = list_difference_update(
+            all_mobject_families, all_moving_mobject_families
         )
-        return all_moving_mobject_families, static_mobjects
+        return all_moving_mobject_families, stationary_mobjects
 
     def get_time_progression(
         self, run_time, n_iterations=None, override_skip_animations=False
@@ -720,12 +721,12 @@ def compile_method(state):
 
         return animations
 
-    def play(self, *args, **kwargs):
-        self.renderer.play(self, *args, **kwargs)
-
     def wait(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
         self.renderer.wait(self, duration=duration, stop_condition=stop_condition)
 
+    def play(self, *args, **kwargs):
+        self.renderer.play(self, *args, **kwargs)
+
     def play_internal(self, *args, **kwargs):
         """
         This method is used to prep the animations for rendering,
@@ -749,10 +750,11 @@ def play_internal(self, *args, **kwargs):
 
         # Paint all non-moving objects onto the screen, so they don't
         # have to be rendered every frame
-        moving_mobjects, static_mobjects = self.get_moving_and_static_mobjects(
+        moving_mobjects, stationary_mobjects = self.get_moving_and_stationary_mobjects(
             animations
         )
-        self.renderer.save_static_mobject_data(self, static_mobjects)
+        self.renderer.update_frame(self, mobjects=stationary_mobjects)
+        self.static_image = self.renderer.get_frame()
 
         last_t = 0
         for t in self.get_animation_time_progression(animations):
@@ -763,7 +765,7 @@ def play_internal(self, *args, **kwargs):
                 alpha = t / animation.run_time
                 animation.interpolate(alpha)
             self.update_mobjects(dt)
-            self.renderer.update_frame(self, moving_mobjects)
+            self.renderer.update_frame(self, moving_mobjects, self.static_image)
             self.renderer.add_frame(self.renderer.get_frame())
 
         for animation in animations:


Test patch:
diff --git a/tests/control_data/graphical_units_data/transform/FadeInAndOutTest.npz b/tests/control_data/graphical_units_data/transform/FadeInAndOutTest.npz
new file mode 100644
Binary files /dev/null and b/tests/control_data/graphical_units_data/transform/FadeInAndOutTest.npz differ
diff --git a/tests/test_graphical_units/test_transform.py b/tests/test_graphical_units/test_transform.py
--- a/tests/test_graphical_units/test_transform.py
+++ b/tests/test_graphical_units/test_transform.py
@@ -119,6 +119,18 @@ def construct(self):
         self.play(CyclicReplace(square, circle))
 
 
+class FadeInAndOutTest(Scene):
+    def construct(self):
+        square = Square(color=BLUE).shift(2 * UP)
+        annotation = Square(color=BLUE)
+        self.add(annotation)
+        self.play(FadeIn(square))
+
+        annotation.become(Square(color=RED).rotate(PI / 4))
+        self.add(annotation)
+        self.play(FadeOut(square))
+
+
 MODULE_NAME = "transform"