_c4d_basebitmap

Demonstrates the usage of the central bitmap type.

_c4d_basebitmap.py

"""Demonstrates the usage of the central bitmap type.

Run this example in the Cinema 4D Script Manager.

Topics:
    - Copying containers
    - Iterating over containers
    - Nested containers
    - Container IDs
    - Using containers in message and callback contexts
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "2027"

import c4d
import os
import maxon

doc: c4d.documents.BaseDocument # The active document.

def FindSvgFile() -> str:
    """Returns the first SVG file path found in the Cinema 4D installation directory.
    """
    path: str = str(maxon.Application.GetUrl(maxon.APPLICATION_URLTYPE.STARTUP_DIR))
    if path.startswith("file:///"):
        path = path[8:]  # Remove a "file:///" prefix, as Python cannot handle it in os.walk.

    for root, _, files in os.walk(path):
        for name in files:
            if name.lower().endswith(".svg"):
                return os.path.join(root, name)
    return ""

SVG_PATH: str = FindSvgFile()
if not SVG_PATH:
    raise FileNotFoundError("No SVG file found in the Cinema 4D installation directory.")


def main() -> None:
    """Finds an SVG file in the Cinema 4D installation and displays rasterized versions of it.
    """
    print("\n--- Instantiation -----------------------------------------------------------------\n")

    # BaseBitmap is the central bitmap type in the Cinema API.

    # Initialize an empty bitmap from scratch. Bitmaps are one of the cases where we do not really
    # have to shield us against allocation failures with mxutils.CheckType on __init__() because
    # the actual allocation happens in Init() rather than in __init__().
    bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.BaseBitmap()
    if bmp.Init(x=512, y=512, depth=32, flags=c4d.INITBITMAPFLAGS_NONE) != c4d.IMAGERESULT_OK:
        raise RuntimeError("Failed to initialize bitmap")

    # Bitmaps can be displayed with c4d.bitmaps.ShowBitmap().
    c4d.bitmaps.ShowBitmap(bmp, "Empty Bitmap")
    print(f"{bmp = }")

    # We can load existing bitmap files from disk using InitWith(). The URL could be a local file
    # url such as "c:/path/to/your/bitmap/file.png" or an asset URL as we use here. This will in fact
    # be able any maxon.Url scheme, i.e., also things such as ram disk, network drive, etc. URLs.
    bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.BaseBitmap()
    bmp.InitWith("asset:///file_5b6a5fe03176444c")
    c4d.bitmaps.ShowBitmap(bmp, "UV Test Grid Asset")

    # Last but not least, you can also load vector files with InitWithVectorFile(). Svg files can
    # be initialized to a 'preferred height' for rasterization. Remember that you have to enable 
    # transparencies via View > Enable Transparency in the Picture Viewer to see transparencies.
    for prefHeight in (32, 128, 512):
        bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.BaseBitmap()
        if bmp.InitWithVectorFile(SVG_PATH, prefHeight) != c4d.IMAGERESULT_OK:
            print(f"Failed to rasterize SVG file: {SVG_PATH}")
            continue

        name: str = os.path.basename(SVG_PATH)
        c4d.bitmaps.ShowBitmap(bmp, f"{name}-{prefHeight}")

    print("\n--- Reading Basic Data ------------------------------------------------------------\n")

    # Load the UV Test Grid asset again.
    bmp: c4d.bitmaps.BaseBitmap = c4d.bitmaps.BaseBitmap()
    bmp.InitWith("asset:///file_5b6a5fe03176444c")

    # Get the width, height, bit depth, and buffer size of a line of the bitmap.
    width: int = bmp.GetBw()
    height: int = bmp.GetBh()
    depth: int = bmp.GetBt()
    lineBufferSize: int = bmp.GetBpz()
    colorMode: int = bmp.GetColorMode()

    # The number of channels.
    channels: int = bmp.GetChannelCount()
    print(f"{width = }, {height = }, {depth = }, {lineBufferSize = }, {colorMode = }, {channels = }")

    print("\n--- Getting Pixels ----------------------------------------------------------------\n")

    # Singular pixel access is rather slow and should be avoided when possible.
    firstLine: list[c4d.Vector] = [bmp.GetPixelDirect(x, 0) for x in range(width)]
    print(f"firstLine (slow access) = {firstLine}")

    # It it s much more performant to access pixels in bulk with GetPixelCnt(). This method can
    # at most address a full row of pixels at a time.
    # The increment is the number of bytes between pixels in the destination buffer. Keep the
    # bitmap's native mode to avoid an unnecessary color-depth conversion.
    increment: int = bmp.GetBt() // 8
    byteBuffer: bytearray = bytearray(width * increment)
    buffer: memoryview = memoryview(byteBuffer)
    bmp.GetPixelCnt(0, 0, width, buffer, increment, colorMode, c4d.PIXELCNT_NONE)

    firstLine: list[c4d.Vector] = [c4d.Vector(buffer[i], buffer[i + 1], buffer[i + 2]) 
                                   for i in range(0, len(buffer), 3)]

    print(f"firstLine (fast access) = {firstLine}")

    print("\n--- Setting Pixels ----------------------------------------------------------------\n")

    # Exactly the same applies to setting pixels. BaseBitmap is also not a draw interface, use
    # GeClipMap for such tasks.

    # Set the (2, 2) pixel to pure red.
    bmp.SetPixel(2, 2, 255, 0, 0)

    # With buffer access, wwe write the 4th row into black to red gradient.
    colors: list[c4d.Vector] = [c4d.utils.MixVec(c4d.Vector(0), 
                                                 c4d.Vector(1, 0, 0), 
                                                 i / width) 
                                for i in range(width)]

    byteBuffer: bytearray = bytearray(width * increment)
    buffer: memoryview = memoryview(byteBuffer)
    for x in range(width):
        offset: int = x * 3
        buffer[offset + 0] = int(colors[x].x * 255) 
        buffer[offset + 1] = int(colors[x].y * 255)
        buffer[offset + 2] = int(colors[x].z * 255)

    if not bmp.SetPixelCnt(0, 3, width, byteBuffer, increment, colorMode, c4d.PIXELCNT_NONE):
        raise RuntimeError("Failed to set pixel data")

    c4d.bitmaps.ShowBitmap(bmp, "Manipulated Bitmap")
    print("Drew red gradient on the 4th row")

    print("\n--- Copying and Cloning -----------------------------------------------------------\n")


if __name__ == "__main__":
    c4d.ClearPythonConsole()
    main()