manim icon indicating copy to clipboard operation
manim copied to clipboard

[Bug] Adding two or more images with `ImageMobject` showing the recent image in the Scene.

Open Varniex opened this issue 1 year ago • 3 comments

Describe the bug

The ImageMobject works perfectly fine as long as there's only one image in the Scene but as soon as another ImageMobject (with different image) has been added in the Scene, both of the images in the screen referenced to the recently added image in the Scene.

Additional context

Here's the video that may clear the things out.

https://user-images.githubusercontent.com/41651498/229421639-118509e1-e8b4-4eb6-b27f-6d6a3de0a052.mp4

I guess the bug is in ShaderWrapper's init_textures function. The name_to_ids has the key "name" which is same ("Texture") for all the ImageMobject which are added in the Scene. So, the texture id of the recently added ImageMobject in the scene gets updated to all the previous ones.

Varniex avatar Apr 03 '23 05:04 Varniex

may I try this bug

samithkavishke avatar Apr 04 '23 03:04 samithkavishke

For this issue, I have an ugly solution: modify image_mobject.py,

class ImageMobject(Mobject):
    shader_folder: str = "image"
    shader_dtype: Sequence[Tuple[str, type, Tuple[int]]] = [
        ('point', np.float32, (3,)),
        ('im_coords', np.float32, (2,)),
        ('opacity', np.float32, (1,)),
    ]

    def __init__(        
        self,
        filename: str,
        height: float = 4.0,
        **kwargs
    ):
        self.height = height
        self.image_path = get_full_raster_image_path(filename)
        self.image = Image.open(self.image_path)
        super().__init__(texture_paths={"Texture"+str(id(self)): self.image_path}, **kwargs)

modify shader_wrapper.py

    def init_program_code(self) -> None:
        def get_code(name: str) -> str | None:
            texture_name=""
            if self.texture_paths is not None and self.shader_folder=="image":
                for k in self.texture_paths:
                    texture_name = k
                
            return get_shader_code_from_file(
                os.path.join(self.shader_folder, f"{name}.glsl"),texture_name=texture_name
            )

        self.program_code: dict[str, str | None] = {
            "vertex_shader": get_code("vert"),
            "geometry_shader": get_code("geom"),
            "fragment_shader": get_code("frag"),
        }

modify shaders.py

@lru_cache()
def get_shader_code_from_file(filename: str, texture_name="") -> str | None:
    if not filename:
        return None

    try:
        filepath = find_file(
            filename,
            directories=[get_shader_dir(), "/"],
            extensions=[],
        )
    except IOError:
        return None

    with open(filepath, "r") as f:
        result = f.read()
        result = re.sub(r"\s+ASPECT_RATIO\s+=\s+[\s0-9/.]+", f" ASPECT_RATIO = {ASPECT_RATIO}", result)

    # To share functionality between shaders, some functions are read in
    # from other files an inserted into the relevant strings before
    # passing to ctx.program for compiling
    # Replace "#INSERT " lines with relevant code
    insertions = re.findall(r"^#INSERT .*\.glsl$", result, flags=re.MULTILINE)
    for line in insertions:
        inserted_code = get_shader_code_from_file(
            os.path.join("inserts", line.replace("#INSERT ", ""))
        )
        result = result.replace(line, inserted_code)
    
    if texture_name!="":
        result = result.replace("Texture",texture_name)
    
    return result

zhuanvi avatar Aug 25 '23 13:08 zhuanvi

@germanzhu : please mention to add the field "texture_paths" to class ShaderWrapper in shader_wrapper.py.

salunkhedurgesh avatar Oct 18 '23 08:10 salunkhedurgesh