class PSD class Renderer class Blender attr_reader :fg, :bg # Takes a foreground Canvas and a background Canvas def initialize(fg, bg) @fg = fg @bg = bg @opacity = @fg.opacity.to_i @fill_opacity = @fg.fill_opacity.to_i PSD.logger.debug "Blender: name = #{fg.node.name}, opacity = #{@opacity}, fill opacity = #{@fill_opacity}" end # Composes the foreground Canvas onto the background Canvas using the # blending mode specified by the foreground. def compose! PSD.logger.debug "#{fg.node.debug_name} -> #{bg.node.debug_name}: #{fg.node.blending_mode} blending" PSD.logger.debug "fg: (#{fg.left}, #{fg.top}) #{fg.width}x#{fg.height}; bg: (#{bg.left}, #{bg.top}) #{bg.width}x#{bg.height}" offset_x = fg.left - bg.left offset_y = fg.top - bg.top fg.height.times do |y| fg.width.times do |x| base_x = x + offset_x base_y = y + offset_y next if base_x < 0 || base_y < 0 || base_x >= bg.width || base_y >= bg.height color = Compose.send( fg.node.blending_mode, fg.get_pixel(x, y), bg.get_pixel(base_x, base_y), calculated_opacity ) bg.set_pixel base_x, base_y, color end end end private def compose_options { opacity: @opacity, fill_opacity: @fill_opacity } end def calculated_opacity @calculated_opacity ||= compose_options[:opacity] * compose_options[:fill_opacity] / 255 end end end end