Repository: ManimCommunity/manim
Pull request: Make it easier to change background color
## List of Changes
- Change `background_color` and `background_opacity` to become properties of `Camera`
- Add, for convenience, a `camera` property to `Scene`

## Motivation
This is a suggestion for simplifying the interface for setting the background color in a scene by calling `self.camera.background_color = RED` etc, which was briefly discussed in #604. 

Resolves #604. 

## Acknowledgement
- [x] I have read the [Contributing Guidelines](https://github.com/ManimCommunity/manim/wiki/Documentation-guidelines-(WIP))


Problem statement:
Make it easier to change background colour.
**Describe the bug**
Right now I can only change the background colour in my source file by writing
`config["background_color"] = WHITE` for example.

The problem is that this works only for one scene. If I write
```
config["background_color"] = WHITE
...Scene1...

config["background_color"] = BLUE
...Scene2...
```
then Scene1 will render with blue background.


**Expected behavior**
Something as prominent and important as the background colour of a scene should be easier to change, and more importantly should be changable from within a scene.
I am asking for
```
self.play(Animation1)
self.background_color=Blue
self.play(Animation2)
self.background_color=Green
self.play(Animation3)
```

Hints from issue discussion:
I agree this should be supported. However, the current architecture is not going to allow for it. Because of two reasons.

### Reason 1: the file is read before scenes are rendered
`manim/__main__.py` executes the whole module before rendering the scene. To side-step this, you could use your own script instead of calling manim from the CLI:
```
class Scene1():
    ...

class Scene2():
    ...

if __name__ == '__main__':
    config["background_color"] = WHITE
    s1 = Scene1()
    s1.render()
    config["background_color"] = BLUE
    s2 = Scene2()
    s2.render()
```

This is definitely not ideal, but can serve as temporary workaround.


### Reason 2: The config dict is global
The *actual* problem though is that the config is global. I am not opposed to making it not global and have each Scene contain its own config. But this is a major change that will require a bit of refactoring and testing. At the very least, every mobject will have to contain a reference to its parent scene so they can access the Scene's specific config. Which is not bad, but it will increase the overhead in general.

We should come back to this after #620 is merged.

Maybe this approach is a bit naive, and maybe there is a better way to do things currently -- but if we would add a property `background_color` to `Scene` like

```py
class Scene(Container):
    [...]

    @property
    def background_color(self):
        return self.renderer.camera.background_color

    @background_color.setter
    def background_color(self, color):
        self.renderer.camera.background_color = color
        self.renderer.camera.init_background()

```
then the background color can be set just as suggested above, via `self.background_color = GREEN` etc. -- and for simple test cases, this produced correct output for me.

But then again, I'm still not that deep into the camera and renderer; this might not be a very practical approach.
This is the right way of doing it. I thought about this at some point and forgot to update this issue. Thanks for trying it out.

For now, we can just do self.renderer.camera.backgorund_color = GREEN. But I agree we could perhaps add a new property as @behackl suggests.
Uhm also another alternative is to just do

```python
class Scene():
    @property
    def camera(self):
        return self.renderer.camera
```

and call it a day?
I really don't mind too much whether it is `self.camera.background_color = ...` or `self.background_color = ...` (the former might actually be cleaner, true) -- but without the setter calling `self.renderer.camera.init_background()`, it would not work for me. 
After thinking for another minute, I think the cleanest solution would be to add `background_color` as a parameter to `Camera` whose setter knows to run `init_background`, and then possibly have the `camera` property you suggest for `Scene`. :-)
Well. The *true* solution is to get rid of `Camera.CONFIG`, which would immediately put `background_color` in the constructor of `Camera`. But we know how that goes.

Commit message: black

Patch to review:
diff --git a/manim/camera/camera.py b/manim/camera/camera.py
index 9835f4a411..63f61d89f0 100644
--- a/manim/camera/camera.py
+++ b/manim/camera/camera.py
@@ -89,11 +89,12 @@ def __init__(self, video_quality_config, background=None, **kwargs):
             "frame_height",
             "frame_width",
             "frame_rate",
-            "background_color",
-            "background_opacity",
         ]:
             setattr(self, attr, kwargs.get(attr, config[attr]))
 
+        for attr in ["background_color", "background_opacity"]:
+            setattr(self, f"_{attr}", kwargs.get(attr, config[attr]))
+
         # This one is in the same boat as the above, but it doesn't have the
         # same name as the corresponding key so it has to be handled on its own
         self.max_allowable_norm = config["frame_width"]
@@ -117,6 +118,24 @@ def __deepcopy__(self, memo):
         self.canvas = None
         return copy.copy(self)
 
+    @property
+    def background_color(self):
+        return self._background_color
+
+    @background_color.setter
+    def background_color(self, color):
+        self._background_color = color
+        self.init_background()
+
+    @property
+    def background_opacity(self):
+        return self._background_opacity
+
+    @background_opacity.setter
+    def background_opacity(self, alpha):
+        self._background_opacity = alpha
+        self.init_background()
+
     def type_or_raise(self, mobject):
         """Return the type of mobject, if it is a type that can be rendered.
 
diff --git a/manim/scene/scene.py b/manim/scene/scene.py
index ee6983feb1..145a310222 100644
--- a/manim/scene/scene.py
+++ b/manim/scene/scene.py
@@ -80,6 +80,10 @@ def __init__(self, renderer=None, **kwargs):
 
         self.setup()
 
+    @property
+    def camera(self):
+        return self.renderer.camera
+
     def render(self):
         """
         Render this Scene.
diff --git a/tests/test_color.py b/tests/test_color.py
index 259d83fcfb..3235e80e2e 100644
--- a/tests/test_color.py
+++ b/tests/test_color.py
@@ -1,8 +1,28 @@
 import pytest
-from manim import Camera, tempconfig, config
+import numpy as np
+
+from manim import Camera, Scene, tempconfig, config
 
 
 def test_import_color():
     import manim.utils.color as C
 
     C.WHITE
+
+
+def test_background_color():
+    import manim.utils.color as C
+
+    S = Scene()
+    S.camera.background_color = C.RED
+    S.renderer.update_frame(S)
+    assert np.all(S.renderer.get_frame()[0, 0] == np.array([252, 98, 85, 255]))
+
+    S.camera.background_color = C.BLUE
+    S.renderer.update_frame(S)
+    assert np.all(S.renderer.get_frame()[0, 0] == np.array([88, 196, 221, 255]))
+
+    S.camera.background_color = C.GREEN
+    S.camera.background_opacity = 0.5
+    S.renderer.update_frame(S)
+    assert np.all(S.renderer.get_frame()[0, 0] == np.array([131, 193, 103, 127]))


Code review comments:
diff_hunk: @@ -1,8 +1,28 @@
 import pytest
-from manim import Camera, tempconfig, config
+import numpy as np
+
+from manim import Camera, Scene, tempconfig, config
 
 
 def test_import_color():
     import manim.utils.color as C
 
     C.WHITE
+
+
+def test_background_color():
+    import manim.utils.color as C
+
+    S = Scene()
+    S.camera.background_color = C.RED
path: tests/test_color.py
text: @user1:
**Suggestion/Issue :** Nice Test, but I think it's wrong as it depends on the constant RED, which make this test dependent to another thing not tested here.
Could you, instead of using it, use an hardcoded np.array representing the RED color? 
(same goes for all of the other tests)


@author:
Good call! I've changed the test to use explicitly specified colors in 7ef9e644 and 619a9473.