Skip to content

capturegraph.adapters.apple_dng_convert #

Apple DNG Conversion Utilities (Internal)#

.. warning:: Do not use these functions directly.

Use the cross-platform wrappers from ``dng_convert`` instead::

    from capturegraph.adapters import dng_to_jpeg, dng_to_heif, dng_to_png, dng_to_pil

These wrappers automatically use Apple Core Image when available on macOS,
and fall back to rawpy on other platforms.

macOS-specific utilities for converting DNG (Digital Negative) raw files to JPEG, HEIF, and PNG formats using Apple's Core Image (Quartz) framework.

This preserves Apple's native tone mapping and color science, producing results consistent with Preview.app and Photos.app. EXIF metadata from the source DNG is preserved in the output files.

Thread Safety

These functions are thread-safe and can be called concurrently from multiple threads. Each thread uses its own CIContext and operations are wrapped in autorelease pools to prevent memory issues with Objective-C objects.

Requires
  • macOS
  • PyObjC (pyobjc-framework-Quartz, pyobjc-framework-Cocoa)

apple_dng_to_jpeg(dng_path, output_path, quality=1.0, max_axis=None) #

Convert a DNG file to JPEG using Apple's Core Image (Quartz) framework.

This preserves Apple's native tone mapping and color science, producing results consistent with Preview.app. EXIF metadata from the source DNG is preserved in the output JPEG.

Parameters:

Name Type Description Default
dng_path str | Path

Path to the input DNG file.

required
output_path str | Path

Path for the output JPEG file.

required
quality float

JPEG compression quality (0.0-1.0). Default is 1.0 for maximum quality.

1.0
max_axis int

If provided, resize so the largest dimension equals this value.

None

Returns:

Type Description
Path

Path to the created JPEG file.

Raises:

Type Description
ImportError

If not running on macOS or PyObjC is not installed.

FileNotFoundError

If the DNG file does not exist.

RuntimeError

If the conversion fails.

Example
from capturegraph.adapters import apple_dng_to_jpeg

# Maximum quality conversion
apple_dng_to_jpeg("DSC_0001.dng", "output.jpg")

# Slightly compressed for smaller file size
apple_dng_to_jpeg("DSC_0001.dng", "output.jpg", quality=0.95)

# Save a smaller version for analysis
apple_dng_to_jpeg("DSC_0001.dng", "thumb.jpg", max_axis=1024)
Source code in capturegraph-lib/capturegraph/adapters/apple_dng_convert.py
def apple_dng_to_jpeg(
    dng_path: str | Path,
    output_path: str | Path,
    quality: float = 1.0,
    max_axis: int = None,
) -> Path:
    """
    Convert a DNG file to JPEG using Apple's Core Image (Quartz) framework.

    This preserves Apple's native tone mapping and color science, producing
    results consistent with Preview.app. EXIF metadata from the source DNG
    is preserved in the output JPEG.

    Args:
        dng_path: Path to the input DNG file.
        output_path: Path for the output JPEG file.
        quality: JPEG compression quality (0.0-1.0). Default is 1.0 for
            maximum quality.
        max_axis: If provided, resize so the largest dimension equals this value.

    Returns:
        Path to the created JPEG file.

    Raises:
        ImportError: If not running on macOS or PyObjC is not installed.
        FileNotFoundError: If the DNG file does not exist.
        RuntimeError: If the conversion fails.

    Example:
        ```python
        from capturegraph.adapters import apple_dng_to_jpeg

        # Maximum quality conversion
        apple_dng_to_jpeg("DSC_0001.dng", "output.jpg")

        # Slightly compressed for smaller file size
        apple_dng_to_jpeg("DSC_0001.dng", "output.jpg", quality=0.95)

        # Save a smaller version for analysis
        apple_dng_to_jpeg("DSC_0001.dng", "thumb.jpg", max_axis=1024)
        ```
    """
    dng_path = Path(dng_path).resolve()
    output_path = Path(output_path).resolve()

    # Wrap in autorelease pool for thread safety
    with _autorelease_pool():
        ci_image, properties = _load_dng_image(dng_path)
        context, _, srgb_color_space = _prepare_output(output_path)

        _write_image_with_metadata(
            context=context,
            ci_image=ci_image,
            output_path=output_path,
            image_type="public.jpeg",
            color_space=srgb_color_space,
            properties=properties,
            quality=quality,
            max_axis=max_axis,
        )

    return output_path

apple_dng_to_heif(dng_path, output_path, quality=1.0, max_axis=None) #

Convert a DNG file to HEIF using Apple's Core Image (Quartz) framework.

This preserves Apple's native tone mapping and color science. HEIF provides better compression than JPEG at equivalent quality levels (typically 40-50% smaller files). EXIF metadata from the source DNG is preserved.

Parameters:

Name Type Description Default
dng_path str | Path

Path to the input DNG file.

required
output_path str | Path

Path for the output HEIF file (typically .heic extension).

required
quality float

HEIF compression quality (0.0-1.0). Default is 1.0 for maximum quality.

1.0
max_axis int

If provided, resize so the largest dimension equals this value.

None

Returns:

Type Description
Path

Path to the created HEIF file.

Raises:

Type Description
ImportError

If not running on macOS or PyObjC is not installed.

FileNotFoundError

If the DNG file does not exist.

RuntimeError

If the conversion fails.

Example
from capturegraph.adapters import apple_dng_to_heif

# Maximum quality conversion
apple_dng_to_heif("DSC_0001.dng", "output.heic")

# Good quality with better compression
apple_dng_to_heif("DSC_0001.dng", "output.heic", quality=0.9)

# Save a smaller version for analysis
apple_dng_to_heif("DSC_0001.dng", "thumb.heic", max_axis=1024)
Source code in capturegraph-lib/capturegraph/adapters/apple_dng_convert.py
def apple_dng_to_heif(
    dng_path: str | Path,
    output_path: str | Path,
    quality: float = 1.0,
    max_axis: int = None,
) -> Path:
    """
    Convert a DNG file to HEIF using Apple's Core Image (Quartz) framework.

    This preserves Apple's native tone mapping and color science. HEIF provides
    better compression than JPEG at equivalent quality levels (typically 40-50%
    smaller files). EXIF metadata from the source DNG is preserved.

    Args:
        dng_path: Path to the input DNG file.
        output_path: Path for the output HEIF file (typically .heic extension).
        quality: HEIF compression quality (0.0-1.0). Default is 1.0 for
            maximum quality.
        max_axis: If provided, resize so the largest dimension equals this value.

    Returns:
        Path to the created HEIF file.

    Raises:
        ImportError: If not running on macOS or PyObjC is not installed.
        FileNotFoundError: If the DNG file does not exist.
        RuntimeError: If the conversion fails.

    Example:
        ```python
        from capturegraph.adapters import apple_dng_to_heif

        # Maximum quality conversion
        apple_dng_to_heif("DSC_0001.dng", "output.heic")

        # Good quality with better compression
        apple_dng_to_heif("DSC_0001.dng", "output.heic", quality=0.9)

        # Save a smaller version for analysis
        apple_dng_to_heif("DSC_0001.dng", "thumb.heic", max_axis=1024)
        ```
    """
    dng_path = Path(dng_path).resolve()
    output_path = Path(output_path).resolve()

    # Wrap in autorelease pool for thread safety
    with _autorelease_pool():
        ci_image, properties = _load_dng_image(dng_path)
        context, _, srgb_color_space = _prepare_output(output_path)

        _write_image_with_metadata(
            context=context,
            ci_image=ci_image,
            output_path=output_path,
            image_type="public.heic",
            color_space=srgb_color_space,
            properties=properties,
            quality=quality,
            max_axis=max_axis,
        )

    return output_path

apple_dng_to_png(dng_path, output_path, max_axis=None) #

Convert a DNG file to PNG using Apple's Core Image (Quartz) framework.

This preserves Apple's native tone mapping and color science. PNG is lossless, so no quality parameter is needed. Note that PNG has limited EXIF support, but basic metadata will be preserved where possible.

Parameters:

Name Type Description Default
dng_path str | Path

Path to the input DNG file.

required
output_path str | Path

Path for the output PNG file.

required
max_axis int

If provided, resize so the largest dimension equals this value.

None

Returns:

Type Description
Path

Path to the created PNG file.

Raises:

Type Description
ImportError

If not running on macOS or PyObjC is not installed.

FileNotFoundError

If the DNG file does not exist.

RuntimeError

If the conversion fails.

Example
from capturegraph.adapters import apple_dng_to_png

# Lossless conversion
apple_dng_to_png("DSC_0001.dng", "output.png")

# Save a smaller version for analysis
apple_dng_to_png("DSC_0001.dng", "thumb.png", max_axis=1024)
Source code in capturegraph-lib/capturegraph/adapters/apple_dng_convert.py
def apple_dng_to_png(
    dng_path: str | Path,
    output_path: str | Path,
    max_axis: int = None,
) -> Path:
    """
    Convert a DNG file to PNG using Apple's Core Image (Quartz) framework.

    This preserves Apple's native tone mapping and color science. PNG is
    lossless, so no quality parameter is needed. Note that PNG has limited
    EXIF support, but basic metadata will be preserved where possible.

    Args:
        dng_path: Path to the input DNG file.
        output_path: Path for the output PNG file.
        max_axis: If provided, resize so the largest dimension equals this value.

    Returns:
        Path to the created PNG file.

    Raises:
        ImportError: If not running on macOS or PyObjC is not installed.
        FileNotFoundError: If the DNG file does not exist.
        RuntimeError: If the conversion fails.

    Example:
        ```python
        from capturegraph.adapters import apple_dng_to_png

        # Lossless conversion
        apple_dng_to_png("DSC_0001.dng", "output.png")

        # Save a smaller version for analysis
        apple_dng_to_png("DSC_0001.dng", "thumb.png", max_axis=1024)
        ```
    """
    dng_path = Path(dng_path).resolve()
    output_path = Path(output_path).resolve()

    # Wrap in autorelease pool for thread safety
    with _autorelease_pool():
        ci_image, properties = _load_dng_image(dng_path)
        context, _, srgb_color_space = _prepare_output(output_path)

        _write_image_with_metadata(
            context=context,
            ci_image=ci_image,
            output_path=output_path,
            image_type="public.png",
            color_space=srgb_color_space,
            properties=properties,
            quality=None,  # PNG is lossless
            max_axis=max_axis,
        )

    return output_path

apple_dng_to_pil(dng_path) #

Open a DNG file and return it as a PIL Image using Apple's Core Image.

Uses Apple's native RAW processing with proper tone mapping and color science, producing results consistent with Preview.app and Photos.app.

Parameters:

Name Type Description Default
dng_path str | Path

Path to the input DNG file.

required

Returns:

Type Description
Image

PIL.Image.Image: The processed image.

Raises:

Type Description
ImportError

If not running on macOS, PyObjC not installed, or Pillow not installed.

FileNotFoundError

If the DNG file does not exist.

RuntimeError

If the conversion fails.

Note

Remember to call .close() on the returned image when done.

Example
from capturegraph.adapters import apple_dng_to_pil

pil = apple_dng_to_pil("photo.dng")
pil.thumbnail((1024, 1024))
pil.save("thumb.jpg")
pil.close()
Source code in capturegraph-lib/capturegraph/adapters/apple_dng_convert.py
def apple_dng_to_pil(dng_path: str | Path) -> Image:
    """
    Open a DNG file and return it as a PIL Image using Apple's Core Image.

    Uses Apple's native RAW processing with proper tone mapping and color
    science, producing results consistent with Preview.app and Photos.app.

    Args:
        dng_path: Path to the input DNG file.

    Returns:
        PIL.Image.Image: The processed image.

    Raises:
        ImportError: If not running on macOS, PyObjC not installed, or Pillow not installed.
        FileNotFoundError: If the DNG file does not exist.
        RuntimeError: If the conversion fails.

    Note:
        Remember to call `.close()` on the returned image when done.

    Example:
        ```python
        from capturegraph.adapters import apple_dng_to_pil

        pil = apple_dng_to_pil("photo.dng")
        pil.thumbnail((1024, 1024))
        pil.save("thumb.jpg")
        pil.close()
        ```
    """
    dng_path = Path(dng_path).resolve()

    # Wrap in autorelease pool for thread safety
    with _autorelease_pool():
        ci_image, _ = _load_dng_image(dng_path)

        # Use thread-local context for thread safety
        context = _get_thread_context()
        extent = ci_image.extent()
        cg_image = context.createCGImage_fromRect_(ci_image, extent)

        if not cg_image:
            raise RuntimeError("Failed to render CIImage to CGImage")

        # Get image dimensions
        width = Quartz.CGImageGetWidth(cg_image)
        height = Quartz.CGImageGetHeight(cg_image)

        # Get bitmap data
        # Create a bitmap context to draw the CGImage into
        bytes_per_row = width * 4
        color_space = Quartz.CGColorSpaceCreateDeviceRGB()

        # Create a buffer for the bitmap data
        buffer_size = bytes_per_row * height
        buffer = ctypes.create_string_buffer(buffer_size)

        # Create bitmap context
        bitmap_context = Quartz.CGBitmapContextCreate(
            buffer,
            width,
            height,
            8,  # bits per component
            bytes_per_row,
            color_space,
            Quartz.kCGImageAlphaPremultipliedLast,  # RGBA
        )

        if not bitmap_context:
            raise RuntimeError("Failed to create bitmap context")

        # Draw the CGImage into the bitmap context
        rect = Quartz.CGRectMake(0, 0, width, height)
        Quartz.CGContextDrawImage(bitmap_context, rect, cg_image)

        # Get the raw bytes
        raw_data = ctypes.string_at(buffer, buffer_size)

        # Create PIL Image from raw RGBA data
        pil_image = Image.frombytes("RGBA", (width, height), raw_data)

    # Convert to RGB (drop alpha channel) - can be done outside pool
    return pil_image.convert("RGB")