Repository: ManimCommunity/manim

Issue:
Allow Images for color_using_background_image
## Enhancement proposal
`VMobject.color_using_background_image()` can only work with image file paths. This method should also be able to handle actual `Image` objetcs. This would make working with generated Images a lot easier.


Discussion:

Resolving patch:
diff --git a/manim/camera/camera.py b/manim/camera/camera.py
--- a/manim/camera/camera.py
+++ b/manim/camera/camera.py
@@ -6,8 +6,10 @@
 import copy
 import itertools as it
 import operator as op
+import pathlib
 import time
 from functools import reduce
+from typing import Union
 
 import cairo
 import numpy as np
@@ -573,11 +575,9 @@ def display_multiple_vectorized_mobjects(self, vmobjects, pixel_array):
         """
         if len(vmobjects) == 0:
             return
-        batch_file_pairs = it.groupby(
-            vmobjects, lambda vm: vm.get_background_image_file()
-        )
-        for file_name, batch in batch_file_pairs:
-            if file_name:
+        batch_image_pairs = it.groupby(vmobjects, lambda vm: vm.get_background_image())
+        for image, batch in batch_image_pairs:
+            if image:
                 self.display_multiple_background_colored_vmobjects(batch, pixel_array)
             else:
                 self.display_multiple_non_background_colored_vmobjects(
@@ -1208,30 +1208,33 @@ def resize_background_array_to_match(self, background_array, pixel_array):
         mode = "RGBA" if pixel_array.shape[2] == 4 else "RGB"
         return self.resize_background_array(background_array, width, height, mode)
 
-    def get_background_array(self, file_name):
+    def get_background_array(self, image: Union[Image.Image, pathlib.Path, str]):
         """Gets the background array that has the passed file_name.
 
         Parameters
         ----------
-        file_name : str
-            The file_name of the background image.
+        image
+            The background image or its file name.
 
         Returns
         -------
         np.ndarray
-            The pixel array of the file whose file name is `file_name`
+            The pixel array of the image.
         """
-        if file_name in self.file_name_to_pixel_array_map:
-            return self.file_name_to_pixel_array_map[file_name]
-        full_path = get_full_raster_image_path(file_name)
-        image = Image.open(full_path)
+        image_key = str(image)
+
+        if image_key in self.file_name_to_pixel_array_map:
+            return self.file_name_to_pixel_array_map[image_key]
+        if isinstance(image, str):
+            full_path = get_full_raster_image_path(image)
+            image = Image.open(full_path)
         back_array = np.array(image)
 
         pixel_array = self.pixel_array
         if not np.all(pixel_array.shape == back_array.shape):
             back_array = self.resize_background_array_to_match(back_array, pixel_array)
 
-        self.file_name_to_pixel_array_map[file_name] = back_array
+        self.file_name_to_pixel_array_map[image_key] = back_array
         return back_array
 
     def display(self, *cvmobjects):
@@ -1247,12 +1250,10 @@ def display(self, *cvmobjects):
         np.array
             The pixel array with the `cvmobjects` displayed.
         """
-        batch_image_file_pairs = it.groupby(
-            cvmobjects, lambda cv: cv.get_background_image_file()
-        )
+        batch_image_pairs = it.groupby(cvmobjects, lambda cv: cv.get_background_image())
         curr_array = None
-        for batch, image_file in batch_image_file_pairs:
-            background_array = self.get_background_array(image_file)
+        for image, batch in batch_image_pairs:
+            background_array = self.get_background_array(image)
             pixel_array = self.pixel_array
             self.camera.display_multiple_non_background_colored_vmobjects(
                 batch, pixel_array
diff --git a/manim/mobject/types/vectorized_mobject.py b/manim/mobject/types/vectorized_mobject.py
--- a/manim/mobject/types/vectorized_mobject.py
+++ b/manim/mobject/types/vectorized_mobject.py
@@ -13,10 +13,11 @@
 
 import itertools as it
 import sys
-from typing import Iterable, Optional, Sequence
+from typing import Iterable, Optional, Sequence, Union
 
 import colour
 import numpy as np
+from PIL.Image import Image
 
 from ...constants import *
 from ...mobject.mobject import Mobject
@@ -67,7 +68,7 @@ def __init__(
         close_new_points=False,
         pre_function_handle_to_anchor_scale_factor=0.01,
         make_smooth_after_applying_functions=False,
-        background_image_file=None,
+        background_image=None,
         shade_in_3d=False,
         # This is within a pixel
         # TODO, do we care about accounting for
@@ -91,7 +92,7 @@ def __init__(
             pre_function_handle_to_anchor_scale_factor
         )
         self.make_smooth_after_applying_functions = make_smooth_after_applying_functions
-        self.background_image_file = background_image_file
+        self.background_image = background_image
         self.shade_in_3d = shade_in_3d
         self.tolerance_for_point_equality = tolerance_for_point_equality
         self.n_points_per_cubic_curve = n_points_per_cubic_curve
@@ -227,7 +228,7 @@ def set_style(
         background_stroke_opacity=None,
         sheen_factor=None,
         sheen_direction=None,
-        background_image_file=None,
+        background_image=None,
         family=True,
     ):
         self.set_fill(color=fill_color, opacity=fill_opacity, family=family)
@@ -249,8 +250,8 @@ def set_style(
                 direction=sheen_direction,
                 family=family,
             )
-        if background_image_file:
-            self.color_using_background_image(background_image_file)
+        if background_image:
+            self.color_using_background_image(background_image)
         return self
 
     def get_style(self, simple=False):
@@ -272,7 +273,7 @@ def get_style(self, simple=False):
             ret["background_stroke_opacity"] = self.get_stroke_opacity(background=True)
             ret["sheen_factor"] = self.get_sheen_factor()
             ret["sheen_direction"] = self.get_sheen_direction()
-            ret["background_image_file"] = self.get_background_image_file()
+            ret["background_image"] = self.get_background_image()
 
         return ret
 
@@ -429,18 +430,18 @@ def get_gradient_start_and_end_points(self):
             offset = np.dot(bases, direction)
             return (c - offset, c + offset)
 
-    def color_using_background_image(self, background_image_file):
-        self.background_image_file = background_image_file
+    def color_using_background_image(self, background_image: Union[Image.Image, str]):
+        self.background_image = background_image
         self.set_color(WHITE)
         for submob in self.submobjects:
-            submob.color_using_background_image(background_image_file)
+            submob.color_using_background_image(background_image)
         return self
 
-    def get_background_image_file(self):
-        return self.background_image_file
+    def get_background_image(self) -> Union[Image.Image, str]:
+        return self.background_image
 
-    def match_background_image_file(self, vmobject):
-        self.color_using_background_image(vmobject.get_background_image_file())
+    def match_background_image(self, vmobject):
+        self.color_using_background_image(vmobject.get_background_image())
         return self
 
     def set_shade_in_3d(self, value=True, z_index_as_group=False):


Test patch: