drop shadows! they're a thing now.

Fixes #306, #264
This commit is contained in:
Andy Meneely
2021-07-23 11:01:41 -04:00
parent d92aab4bf3
commit b25f0d535b
15 changed files with 258 additions and 25 deletions
+39
View File
@@ -0,0 +1,39 @@
require_relative 'color_validator'
module Squib::Args
module_function def extract_drop_shadow(opts, deck)
DropShadow.new(deck.custom_colors).extract! opts, deck
end
class DropShadow
include ArgLoader
include ColorValidator
def initialize(custom_colors)
@custom_colors = custom_colors
end
def self.parameters
{
shadow_color: :black,
shadow_offset_x: 3,
shadow_offset_y: 3,
shadow_radius: nil,
shadow_trim: 0,
}
end
def self.expanding_parameters
self.parameters.keys # all of them
end
def self.params_with_units
[:shadow_offset_x, :shadow_offset_y, :shadow_radius, :shadow_trim]
end
def validate_shadow_color(arg, _i)
colorify(arg, @custom_colors)
end
end
end
+6 -2
View File
@@ -1,5 +1,7 @@
require_relative '../errors_warnings/warn_unexpected_params'
require_relative '../args/card_range'
require_relative '../args/save_batch'
require_relative '../args/drop_shadow'
module Squib
class Deck
@@ -24,6 +26,7 @@ module Squib
range
dir prefix suffix count_format
rotate trim trim_radius
shadow_offset_x shadow_offset_y shadow_radius shadow_color shadow_trim
)
end
@@ -31,9 +34,10 @@ module Squib
warn_if_unexpected opts
range = Args.extract_range opts, deck
batch = Args.extract_save_batch opts, deck
shadow = Args.extract_drop_shadow opts, deck
@bar.start("Saving PNGs to #{batch.summary}", deck.size) do |bar|
range.map do |i|
deck.cards[i].save_png(batch[i])
deck.cards[i].save_png(batch[i], shadow[i])
bar.increment
end
end
+8 -6
View File
@@ -20,12 +20,14 @@ module Squib
def_delegators :cairo_cxt, :save, :set_source_color, :paint, :restore,
:translate, :rotate, :move_to, :update_pango_layout, :width, :height,
:show_pango_layout, :rectangle, :rounded_rectangle, :set_line_width, :stroke, :fill,
:set_source, :scale, :render_rsvg_handle, :circle, :triangle, :line_to,
:operator=, :show_page, :clip, :transform, :mask, :create_pango_layout,
:antialias=, :curve_to, :matrix, :matrix=, :identity_matrix, :pango_layout_path,
:stroke_preserve, :target, :new_path, :new_sub_path, :reset_clip, :fill_preserve, :close_path,
:set_line_join, :set_line_cap, :set_dash, :arc, :arc_negative
:show_pango_layout, :rectangle, :rounded_rectangle, :set_line_width,
:stroke, :fill, :set_source, :scale, :render_rsvg_handle, :circle,
:triangle, :line_to, :operator=, :show_page, :clip, :transform, :mask,
:create_pango_layout, :antialias=, :curve_to, :matrix, :matrix=,
:identity_matrix, :pango_layout_path, :stroke_preserve, :target,
:new_path, :new_sub_path, :reset_clip, :fill_preserve, :close_path,
:set_line_join, :set_line_cap, :set_dash, :arc, :arc_negative,
:pseudo_blur
# :nodoc:
# @api private
+52 -13
View File
@@ -1,12 +1,14 @@
require_relative 'cairo_context_wrapper'
module Squib
class Card
# :nodoc:
# @api private
def save_png(batch)
surface = if preprocess_save?(batch)
def save_png(batch, shadow)
surface = if preprocess_save?(batch, shadow)
w, h = compute_dimensions(batch.rotate, batch.trim)
preprocessed_save(w, h, batch)
preprocessed_save(w, h, batch, shadow)
else
@cairo_surface
end
@@ -15,8 +17,8 @@ module Squib
# :nodoc:
# @api private
def preprocess_save?(batch)
batch.rotate != false || batch.trim > 0
def preprocess_save?(batch, shadow)
batch.rotate != false || batch.trim > 0 || !(shadow.shadow_radius.nil?)
end
def compute_dimensions(rotate, trim)
@@ -27,25 +29,62 @@ module Squib
end
end
def preprocessed_save(width, height, batch)
new_cc = Cairo::Context.new(Cairo::ImageSurface.new(width, height))
def preprocessed_save(w, h, batch, shadow)
new_cc = Cairo::Context.new(Cairo::ImageSurface.new(w, h))
trim_radius = batch.trim_radius
if batch.rotate != false
new_cc.translate(width * 0.5, height * 0.5)
new_cc.rotate(batch.angle)
new_cc.translate(height * -0.5, width * -0.5)
new_cc.rounded_rectangle(0, 0, height, width, trim_radius, trim_radius)
new_cc.translate w * 0.5, h * 0.5
new_cc.rotate batch.angle
new_cc.translate h * -0.5, w * -0.5
new_cc.rounded_rectangle(0, 0, h, w, trim_radius, trim_radius)
else
new_cc.rounded_rectangle(0, 0, width, height, trim_radius, trim_radius)
new_cc.rounded_rectangle(0, 0, w, h, trim_radius, trim_radius)
end
new_cc.clip
new_cc.set_source(@cairo_surface, -batch.trim, -batch.trim)
new_cc.paint
new_cc.reset_clip
new_cc = drop_shadow(new_cc, shadow, batch) unless shadow.shadow_radius.nil?
return new_cc.target
end
# pseudo-blur behave weirdly with a radius of 0 - wrapping
def blur(cc, r, &block)
if r == 0
yield(block)
else
cc.pseudo_blur(r, &block)
end
end
def drop_shadow(cc, s, batch)
off_x = s.shadow_offset_x
off_y = s.shadow_offset_y
s_trim = s.shadow_trim
s_rad = s.shadow_radius
new_w = cc.target.width + off_x + 3 * s_rad - (2 * s_trim)
new_h = cc.target.height + off_y + 3 * s_rad - (2 * s_trim)
new_cc = Squib::Graphics::CairoContextWrapper.new(
Cairo::Context.new(Cairo::ImageSurface.new(new_w, new_h)))
blur(new_cc, s_rad) do
# fill in with shadow color
new_cc.set_source_squibcolor s.shadow_color
new_cc.rectangle 0, 0, new_cc.target.width, new_cc.target.height
new_cc.fill
# then, paint but blend with :dest_in to get a shadow-shaped drawing
new_cc.set_source cc.target, s_rad + off_x, s_rad + off_y
new_cc.operator = :dest_in # see https://www.cairographics.org/operators/
new_cc.paint
end
new_cc.set_source cc.target, s_rad, s_rad
new_cc.operator = :over
new_cc.paint
return new_cc
end
def write_png(surface, i, b)
surface.write_to_png("#{b.dir}/#{b.prefix}#{b.count_format % i}#{b.suffix}.png")
filename = "#{b.dir}/#{b.prefix}#{b.count_format % i}#{b.suffix}.png"
surface.write_to_png filename
end
end