Skip to content

Instantly share code, notes, and snippets.

@anon987654321
Last active November 11, 2024 13:56
Show Gist options
  • Select an option

  • Save anon987654321/ac701dec236868a8f024414edc6dc41e to your computer and use it in GitHub Desktop.

Select an option

Save anon987654321/ac701dec236868a8f024414edc6dc41e to your computer and use it in GitHub Desktop.
Postpro

POSTPRO Analog Post-Processing Suite

Overview

POSTPRO is a Ruby-based image processing tool that uses libvips to create cinematic effects. It provides a range of transformations to enhance photos and videos with a professional, film-quality touch.

Features

  • Diverse Cinematic Effects: Includes HDR simulation, infrared emulation, vintage film looks, and more.
  • Interactive CLI: Simple command-line interface for selecting effects and settings.
  • Batch Processing: Efficiently process multiple images at once.
  • Customizable Effects: Adjustable parameters for fine-tuning results.

Installation

Install libvips and required Ruby gems:

# OpenBSD
pkg_add libvips
# Ubuntu
apt-get install libvips
# macOS
brew install vips

# Install Ruby gems
gem install --user-install ruby-vips tty-prompt

Usage

Run POSTPRO interactively:

$ ruby postpro.rb  
Welcome to POSTPRO v0.1
    -- Advanced analog emulation for next-level cinematic color grading.
Input file: input.jpg
Apply a random combination of effects? Yes
How many different images to generate? 5
How many effects to combine? 3
Image saved to output_2117.jpg
Image saved to output_1915.jpg
Image saved to output_1242.jpg
Image saved to output_3064.jpg
Image saved to output_6496.jpg

Before

After

Available Effects

The following effects are available to apply to your images:

  • HDR Simulation: Enhances the dynamic range, providing more detail in shadows and highlights.
  • Day-for-Night Conversion: Converts bright daytime scenes to appear as if shot at night.
  • Infrared Effect: Swaps color channels to achieve surreal infrared effects.
  • Anamorphic Lens Simulation: Adds horizontal lens flares and boosts color depth for a cinematic look.
  • Split Toning: Applies different colors to shadows and highlights to create a dramatic mood.
  • Selective Color Adjustment: Enhances specific colors while leaving others intact.
  • Skin Tone Preservation: Maintains natural skin tones during other effects.
  • Lens Distortion: Simulates barrel or pincushion lens distortion.
  • Film Halation: Adds a soft glow around bright areas to simulate the effect of film halation.
  • Posterization: Reduces the number of colors to create a poster-like effect.
  • Duotone: Converts an image to two colors for striking visual styles.
  • Glitch Effects: Adds digital artifacts and noise to simulate digital errors.
  • 3D LUT Application: Applies Look-Up Tables for complex color grading.
  • Color Matching: Matches the color palette of one image to another for consistency.
  • Underwater Effect: Simulates the color cast and light diffusion found underwater.
  • Night Vision: Mimics the green-tinted appearance of night vision.
  • Heat Vision: Adds thermal imaging effects, displaying variations in temperature.
  • Lens Flare Addition: Adds lens flare artifacts for a dynamic light effect.
  • Soft Focus Glow: Creates a soft glow effect for dreamy imagery.
  • Film Damage Simulation: Adds scratches, dust, and jitters for a worn film look.

Known Issues and Improvements

  • Resource Management: Consider using a task queue for better performance with multiple images.
  • Error Handling: Improve messages for file issues or permission errors.
  • Input Validation: Ensure only valid image formats are processed.

Acknowledgements

Shoutout to Guro and the libvips developers!

#!/usr/bin/env ruby
# encoding: utf-8
# POSTPRO Analog Post-Processing Suite
# Uses Ruby and libvips to apply advanced analog emulation and color grading effects.
require 'vips'
require 'json'
require 'fileutils'
require 'logger'
require 'async'
require 'async/semaphore'
require 'ruby-progressbar'
require 'tty-prompt'
# Set up logging for traceability
LOGGER = Logger.new('postpro.log')
LOGGER.level = Logger::INFO
PROMPT = TTY::Prompt.new
# Available profiles with descriptions
PROFILES = {
hdr_simulation: "Enhances the dynamic range, providing more detail in shadows and highlights.",
day_for_night: "Transforms daytime footage to appear as though it was shot at night.",
infrared_effect: "Mimics infrared photography with surreal color renditions and high contrast.",
anamorphic_simulation: "Adds horizontal lens flares and oval bokeh to simulate anamorphic lenses.",
split_toning: "Applies different colors to shadows and highlights for creative color grading.",
selective_color_adjustment: "Allows isolation and adjustment of specific colors.",
skin_tone_preservation: "Maintains natural skin tones while applying other effects.",
lens_distortion_effect: "Simulates various lens distortions like pincushion or barrel.",
film_halation_simulation: "Recreates the glow or halo effect around bright areas of film.",
color_channel_mixing: "Adjusts individual color channels for unique effects and corrections.",
posterization: "Reduces the number of colors for a flat, poster-like effect.",
duotone_effect: "Converts an image to two colors, often used for striking visual styles.",
glitch_effects: "Introduces digital artifacts and noise to simulate digital errors.",
lut_application: "Applies complex color grading presets using 3D Look-Up Tables.",
color_matching: "Matches the color palette of one image to another for consistency.",
underwater_effect: "Simulates underwater scenes with color casts and light diffusion.",
night_vision_effect: "Mimics the appearance of night vision devices with a green tint.",
heat_vision_effect: "Simulates thermal imaging, displaying variations in temperature.",
lens_flare_addition: "Introduces lens flare artifacts to simulate light scattering.",
soft_focus_glow: "Adds a subtle glow for a dreamy or romantic atmosphere.",
film_damage_simulation: "Adds scratches, dust, and frame jitters to emulate damaged film.",
film_grain: "Adds realistic grain to simulate the texture of high-ISO film stocks."
}
# Utility method for linear adjustments
def linear_adjust(image, scales, offsets)
image.linear(scales, offsets).cast(:uchar)
end
# Apply the specified effect to the given image
# Refined for Hollywood-quality grading
def apply_effect(image, effect)
case effect
when :hdr_simulation
apply_hdr_simulation(image)
when :day_for_night
apply_day_for_night(image)
when :infrared_effect
apply_infrared_effect(image)
when :anamorphic_simulation
apply_anamorphic_simulation(image)
when :split_toning
apply_split_toning(image)
when :selective_color_adjustment
apply_selective_color_adjustment(image)
when :skin_tone_preservation
apply_skin_tone_preservation(image)
when :lens_distortion_effect
apply_lens_distortion_effect(image)
when :film_halation_simulation
apply_film_halation_simulation(image)
when :color_channel_mixing
apply_color_channel_mixing(image)
when :posterization
apply_posterization(image)
when :duotone_effect
apply_duotone_effect(image)
when :glitch_effects
apply_glitch_effects(image)
when :lut_application
apply_lut_application(image)
when :color_matching
apply_color_matching(image)
when :underwater_effect
apply_underwater_effect(image)
when :night_vision_effect
apply_night_vision_effect(image)
when :heat_vision_effect
apply_heat_vision_effect(image)
when :lens_flare_addition
apply_lens_flare_addition(image)
when :soft_focus_glow
apply_soft_focus_glow(image)
when :film_damage_simulation
apply_film_damage_simulation(image)
when :film_grain
apply_film_grain(image)
else
LOGGER.error("Effect not recognized: #{effect}")
image
end
end
# Apply a random combination of effects and save the configuration
# Always include film grain for added realism
def apply_random_effect_combo(image, effects, num_effects)
selected_effects = effects.sample(num_effects - 1) + [:film_grain]
LOGGER.info("Applying a random combination of effects: #{selected_effects.join(', ')}")
File.open("random_effects_config_#{rand(1000..9999)}.json", 'w') do |file|
file.write(JSON.pretty_generate({ effects: selected_effects }))
end
selected_effects.each do |effect|
begin
image = apply_effect(image, effect)
rescue NoMethodError => e
LOGGER.error("Error applying effect '#{effect}': #{e.message}")
end
end
image
end
# Effect implementations
def apply_hdr_simulation(image)
image.hist_local(100, 100, max_slope: 5.0).cast(:uchar)
end
def apply_day_for_night(image)
linear_adjust(image, [0.4, 0.4, 0.6], [0, 0, -40]).gamma(1.2)
end
def apply_infrared_effect(image)
r, g, b = image.bandsplit
Vips::Image.bandjoin([g, b, r * 0.8]).cast(:uchar)
end
def apply_anamorphic_simulation(image)
flare = Vips::Image.gaussnoise(image.width, image.height, sigma: 25).linear([2], [80]).gaussblur(15)
(image * 0.85 + flare * 0.15).linear([1.1, 1.05, 1.0], [0]).cast(:uchar)
end
def apply_split_toning(image)
highlights = linear_adjust(image, [1.0, 0.85, 0.75], [30, 10, 0])
shadows = linear_adjust(image, [0.75, 0.9, 1.0], [-20, -10, 0])
image.ifthenelse(highlights, shadows, blend: true).gaussblur(0.3)
end
def apply_selective_color_adjustment(image)
linear_adjust(image, [1.05, 1.0, 1.15], [0, -10, -20])
end
def apply_skin_tone_preservation(image)
mask = image.in_range([0.3, 0.2, 0.15], [0.8, 0.6, 0.5])
enhanced = linear_adjust(image, [1.1, 1.05, 1.05], [10, 0, 0])
image.ifthenelse(enhanced, image, blend: true).gaussblur(0.8)
end
def apply_lens_distortion_effect(image)
width = image.width
height = image.height
x = Vips::Image.xyz(width, height)[0].linear([2.0 / width], [-1])
y = Vips::Image.xyz(width, height)[1].linear([2.0 / height], [-1])
r = (x ** 2 + y ** 2).sqrt
factor = 1.0 + 0.25 * r
distorted = image.mapim([x * factor, y * factor])
distorted.cast(:uchar)
end
def apply_film_halation_simulation(image)
glow = image.gaussblur(25) * 0.4
(image + glow).cast(:uchar)
end
def apply_soft_focus_glow(image)
glow = image.gaussblur(15) * 0.2
(image + glow).linear([1.0, 0.95, 0.9], [0]).cast(:uchar)
end
def apply_color_matching(image)
image
end
def apply_underwater_effect(image)
linear_adjust(image, [0.8, 0.9, 1.2], [-30, -20, 0]).gaussblur(5)
end
def apply_heat_vision_effect(image)
linear_adjust(image, [1.5, 0.5, 0.2], [0, 0, 50])
end
def apply_night_vision_effect(image)
green_tinted = linear_adjust(image, [0.1, 1.0, 0.1], [0, 50, 0])
green_tinted.gaussblur(3).cast(:uchar)
end
def apply_lens_flare_addition(image)
flare = Vips::Image.gaussnoise(image.width, image.height, sigma: 20).linear([1.5], [50]).gaussblur(30)
(image + flare * 0.3).cast(:uchar)
end
def apply_film_damage_simulation(image)
scratches = Vips::Image.gaussnoise(image.width, image.height, sigma: 5).linear([0.5], [50])
(image + scratches).cast(:uchar)
end
def apply_film_grain(image)
grain = Vips::Image.gaussnoise(image.width, image.height, sigma: 15)
(image + grain * 0.2).cast(:uchar)
end
# Main script logic
if __FILE__ == $0
puts "Welcome to POSTPRO v0.1"
puts " -- Advanced analog emulation for next-level cinematic color grading."
options = {}
options[:input] = PROMPT.ask("Input file:")
apply_combo = PROMPT.yes?("Apply a random combination of effects?")
image = Vips::Image.new_from_file(options[:input])
if apply_combo
num_images = PROMPT.ask("How many different images to generate?", convert: :int, default: 1)
num_effects = PROMPT.ask("How many effects to combine?", convert: :int, default: 3)
num_images.times do
output_image = apply_random_effect_combo(image.copy, PROFILES.keys, num_effects)
output_path = "output_#{rand(1000..9999)}.jpg"
output_image.write_to_file(output_path)
LOGGER.info("Image saved successfully to #{output_path}")
puts "Image saved to #{output_path}"
end
else
effect = PROMPT.select("Choose an effect to apply:", PROFILES.keys.map(&:to_s)).to_sym
output_image = apply_effect(image, effect)
output_path = "output_#{rand(1000..9999)}.jpg"
output_image.write_to_file(output_path)
LOGGER.info("Image saved successfully to #{output_path}")
puts "Processing complete! Image saved to #{output_path}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment