lib/psd/renderer/compose.rb in psd-2.1.2 vs lib/psd/renderer/compose.rb in psd-3.1.2

- old
+ new

@@ -4,24 +4,19 @@ # # Mostly based on similar code from libpsd. module Compose extend self - DEFAULT_OPTS = { - opacity: 255, - fill_opacity: 255 - } - # # Normal blend modes # - def normal(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def normal(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), r(fg), mix_alpha) new_g = blend_channel(g(bg), g(fg), mix_alpha) new_b = blend_channel(b(bg), b(fg), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) @@ -30,39 +25,39 @@ # # Subtractive blend modes # - def darken(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def darken(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = r(fg) <= r(bg) ? blend_channel(r(bg), r(fg), mix_alpha) : r(bg) new_g = g(fg) <= g(bg) ? blend_channel(g(bg), g(fg), mix_alpha) : g(bg) new_b = b(fg) <= b(bg) ? blend_channel(b(bg), b(fg), mix_alpha) : b(bg) rgba(new_r, new_g, new_b, dst_alpha) end - def multiply(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def multiply(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), r(fg) * r(bg) >> 8, mix_alpha) new_g = blend_channel(g(bg), g(fg) * g(bg) >> 8, mix_alpha) new_b = blend_channel(b(bg), b(fg) * b(bg) >> 8, mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def color_burn(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def color_burn(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| if f > 0 f = ((255 - b) << 8) / f f > 255 ? 0 : (255 - f) @@ -76,15 +71,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def linear_burn(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def linear_burn(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), (r(fg) < (255 - r(bg))) ? 0 : r(fg) - (255 - r(bg)), mix_alpha) new_g = blend_channel(g(bg), (g(fg) < (255 - g(bg))) ? 0 : g(fg) - (255 - g(bg)), mix_alpha) new_b = blend_channel(b(bg), (b(fg) < (255 - b(bg))) ? 0 : b(fg) - (255 - b(bg)), mix_alpha) @@ -93,41 +88,41 @@ # # Additive blend modes # - def lighten(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def lighten(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = r(fg) >= r(bg) ? blend_channel(r(bg), r(fg), mix_alpha) : r(bg) new_g = g(fg) >= g(bg) ? blend_channel(g(bg), g(fg), mix_alpha) : g(bg) new_b = b(fg) >= b(bg) ? blend_channel(b(bg), b(fg), mix_alpha) : b(bg) rgba(new_r, new_g, new_b, dst_alpha) end - def screen(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def screen(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), 255 - ((255 - r(bg)) * (255 - r(fg)) >> 8), mix_alpha) new_g = blend_channel(g(bg), 255 - ((255 - g(bg)) * (255 - g(fg)) >> 8), mix_alpha) new_b = blend_channel(b(bg), 255 - ((255 - b(bg)) * (255 - b(fg)) >> 8), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def color_dodge(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def color_dodge(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| f < 255 ? [(b << 8) / (255 - f), 255].min : b end @@ -136,15 +131,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def linear_dodge(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def linear_dodge(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), (r(bg) + r(fg)) > 255 ? 255 : r(bg) + r(fg), mix_alpha) new_g = blend_channel(g(bg), (g(bg) + g(fg)) > 255 ? 255 : g(bg) + g(fg), mix_alpha) new_b = blend_channel(b(bg), (b(bg) + b(fg)) > 255 ? 255 : b(bg) + b(fg), mix_alpha) @@ -154,15 +149,15 @@ # # Contrasting blend modes # - def overlay(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def overlay(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| if b < 128 b * f >> 7 else @@ -175,15 +170,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def soft_light(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def soft_light(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| c1 = b * f >> 8 c2 = 255 - ((255 - b) * (255 - f) >> 8) ((255 - b) * c1 >> 8) + (b * c2 >> 8) @@ -194,15 +189,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def hard_light(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def hard_light(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| if f < 128 b * f >> 7 else @@ -215,15 +210,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def vivid_light(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def vivid_light(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| if f < 255 [(b * b / (255 - f) + f * f / (255 - b)) >> 1, 255].min else @@ -236,15 +231,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def linear_light(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def linear_light(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| if b < 255 [f * f / (255 - b), 255].min else @@ -257,15 +252,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def pin_light(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def pin_light(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) calculate_foreground = Proc.new do |b, f| if f >= 128 [b, (f - 128) * 2].max else @@ -278,15 +273,15 @@ new_b = blend_channel(b(bg), calculate_foreground.call(b(bg), b(fg)), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def hard_mix(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def hard_mix(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), (r(bg) + r(fg) <= 255) ? 0 : 255, mix_alpha) new_g = blend_channel(g(bg), (g(bg) + g(fg) <= 255) ? 0 : 255, mix_alpha) new_b = blend_channel(b(bg), (b(bg) + b(fg) <= 255) ? 0 : 255, mix_alpha) @@ -295,60 +290,58 @@ # # Inversion blend modes # - def difference(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def difference(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), (r(bg) - r(fg)).abs, mix_alpha) new_g = blend_channel(g(bg), (g(bg) - g(fg)).abs, mix_alpha) new_b = blend_channel(b(bg), (b(bg) - b(fg)).abs, mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end - def exclusion(fg, bg, opts={}) - return apply_opacity(fg, opts) if fully_transparent?(bg) + def exclusion(fg, bg, opacity) + return apply_opacity(fg, opacity) if fully_transparent?(bg) return bg if fully_transparent?(fg) - mix_alpha, dst_alpha = calculate_alphas(fg, bg, DEFAULT_OPTS.merge(opts)) + mix_alpha, dst_alpha = calculate_alphas(fg, bg, opacity) new_r = blend_channel(r(bg), r(bg) + r(fg) - (r(bg) * r(fg) >> 7), mix_alpha) new_g = blend_channel(g(bg), g(bg) + g(fg) - (g(bg) * g(fg) >> 7), mix_alpha) new_b = blend_channel(b(bg), b(bg) + b(fg) - (b(bg) * b(fg) >> 7), mix_alpha) rgba(new_r, new_g, new_b, dst_alpha) end + [:r, :g, :b, :a, :rgba, :fully_transparent?].each do |meth| + define_method(meth) { |*args| ChunkyPNG::Color.send(meth, *args) } + end + # If the blend mode is missing, we fall back to normal composition. def method_missing(method, *args, &block) - return ChunkyPNG::Color.send(method, *args) if ChunkyPNG::Color.respond_to?(method) normal(*args) end private - def calculate_alphas(fg, bg, opts) - opacity = calculate_opacity(opts) + def calculate_alphas(fg, bg, opacity) src_alpha = a(fg) * opacity >> 8 dst_alpha = a(bg) mix_alpha = (src_alpha << 8) / (src_alpha + ((256 - src_alpha) * dst_alpha >> 8)) dst_alpha = dst_alpha + ((256 - dst_alpha) * src_alpha >> 8) return mix_alpha, dst_alpha end - def calculate_opacity(opts) - opts[:opacity] * opts[:fill_opacity] / 255 - end - - def apply_opacity(color, opts) - (color & 0xffffff00) | ((color & 0x000000ff) * calculate_opacity(opts) / 255) + def apply_opacity(color, opacity) + (color & 0xffffff00) | ((color & 0x000000ff) * opacity / 255) end def blend_channel(bg, fg, alpha) ((bg << 8) + (fg - bg) * alpha) >> 8 end \ No newline at end of file