# `filmcolor` - Film Stock Color Emulation `filmcolor` is a Python script that transforms digital color positive images (like those from a digital camera) into simulated film negatives. It bases its transformations on datasheet parameters for specific film stocks, focusing on color response, H&D curves, spatial effects like halation and diffusion, and saturation. ## Features * Applies film stock characteristics based on a JSON datasheet. * Converts linear RGB input to log exposure. * Applies H&D (Hurter–Driffield) curves for tonal mapping. * Simulates dye coupler diffusion and interlayer effects. * Applies halation effects. * Adjusts saturation based on coupler properties. * Supports RAW (Sony ARW, DNG), TIFF, PNG, and JPG inputs. * Outputs TIFF (16-bit uncompressed), PNG (8-bit), or JPG (8-bit). ## Usage The script is designed to be run with `uv`, which handles dependency management. ### Direct Execution with `uv` (Recommended) You can run `filmcolor` directly from its Git repository URL: ```bash uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmcolor -- ``` **Example:** ```bash # Ensure input.jpg and sim_data/portra_400.json are in your current directory or provide full paths # Output will be output_negative.tiff uv run https://git.dws.rip/dubey/filmsim/raw/branch/main/filmcolor -- input.jpg sim_data/portra_400.json output_negative.tiff ``` ### Local Execution If you have cloned the repository or downloaded the script: 1. Navigate to the script's directory or ensure it's in your PATH. 2. Run the script: ```bash uv run ./filmcolor -- ``` Or, if `filmcolor` is in the current directory and executable: ```bash ./filmcolor ``` ## Arguments ### Positional Arguments: * `input_image`: Path to the input RGB image. * Supported formats: TIFF, PNG, JPG, Sony ARW, DNG. * Non-RAW images are assumed to be sRGB and will be linearized. RAW files are processed to sRGB. * `datasheet_json`: Path to the film datasheet JSON file (e.g., `sim_data/portra_400.json`). * `output_image`: Path to save the output emulated film negative. * Output format is determined by the file extension: * `.tiff` / `.tif`: 16-bit uncompressed TIFF. * `.png`: 8-bit PNG. * `.jpg` / `.jpeg`: 8-bit JPG (quality 95). ### Options: The script currently does not have additional command-line options beyond the positional arguments. Configuration is primarily driven by the content of the `datasheet_json` file. ## Input Image Preparation * **Linear RGB:** The script internally works with linear RGB data. * If an 8-bit or 16-bit integer image (PNG, JPG, TIFF) is provided, it's assumed to be sRGB encoded and will be automatically linearized. * If a float32/float64 image is provided, it's assumed to be already in linear RGB. * RAW files (ARW, DNG) are demosaiced, white-balanced (using camera WB), and converted to linear sRGB. * **Alpha Channel:** If an alpha channel is present, it will be discarded. ## Datasheet Format The `datasheet_json` file contains detailed parameters for the film stock being simulated. This includes: * Information (name, format, version) * Processing parameters (gamma, balance) * Physical properties (halation strength and size, coupler diffusion, interlayer diffusion) * H&D curves * Calibration data (ISO, middle gray log exposure) An example datasheet (`sim_data/portra_400.json`) is provided in the repository. ## Workflow 1. **Load Image:** Reads the input image. RAW files are processed using `rawpy`. sRGB images are linearized. 2. **Load Datasheet:** Parses the specified JSON film datasheet. 3. **Convert to Log Exposure:** Transforms linear RGB pixel values to log exposure (LogE) space, anchored by the `middle_gray_logE` from the datasheet. 4. **Apply H&D Curves:** Maps LogE values to densities for each color channel (R, G, B) using the H&D curves from the datasheet. This step also incorporates processing gamma and balance shifts. 5. **Convert Density to Transmittance:** Converts the resulting densities back to linear transmittance values (0-1 range). 6. **Apply Spatial Effects:** * **Diffusion Blur:** Simulates dye coupler and interlayer diffusion by applying a Gaussian blur. The blur radius is derived from `diffusion_um` values in the datasheet and the image dimensions/film format. * **Halation:** Simulates light scattering within the film base by applying channel-specific Gaussian blurs, controlled by `halation.strength` and `halation.size_um`. 7. **Apply Saturation:** Adjusts image saturation based on the `couplers.amount` parameter. 8. **Save Output:** The processed image (now a simulated film negative) is saved to the specified output file, converting to 16-bit for TIFF or 8-bit for PNG/JPG.