parent
e108750ca3
commit
f3e3082f0e
|
|
@ -38,5 +38,34 @@ module Squib
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Renders a range of files in a showcase as if they are sitting on a reflective surface
|
||||||
|
# See {file:samples/showcase.rb} for full example
|
||||||
|
#
|
||||||
|
# @example
|
||||||
|
# showcase file: 'showcase_output.png', trim: 78, trim_radius: 32
|
||||||
|
#
|
||||||
|
# @option opts [Enumerable, :all] range (:all) the range of cards over which this will be rendered. See {file:README.md#Specifying_Ranges Specifying Ranges}
|
||||||
|
# @option opts [Fixnum] trim (0) the margin around the card to trim before putting into the showcase
|
||||||
|
# @option opts [Fixnum] trim_radius (38) the rounded rectangle radius around the card to trim before putting into the showcase
|
||||||
|
# @option opts [Fixnum] margin (75) the margin around the entire showcase
|
||||||
|
# @option opts [Fixnum] scale (0.8) percentage of original width of each (trimmed) card to scale to. Must be between 0.0 and 1.0, but starts looking bad around 0.6.
|
||||||
|
# @option opts [Fixnum] offset (1.1) percentage of the scaled width of each card to shift each offset. e.g. 1.1 is a 10% shift, and 0.95 is overlapping by 5%
|
||||||
|
# @option opts [String, Color] fill_color (:white) backdrop color. Usually black or white.
|
||||||
|
# @option opts [Fixnum] reflect_offset (15) the number of pixels between the bottom of the card and the reflection
|
||||||
|
# @option opts [Fixnum] reflect_strength (0.2) the starting alpha transparency of the reflection (at the top of the card). Percentage between 0 and 1. Looks more realistic at low values since even shiny surfaces lose a lot of light.
|
||||||
|
# @option opts [Fixnum] reflect_percent (0.25) the length of the reflection in percentage of the card. Larger values tend to make the reflection draw just as much attention as the card, which is not good.
|
||||||
|
# @option opts [:left, :right] face (:left) which direction the cards face. Anything but `:right` will face left
|
||||||
|
# @option opts [String] dir (_output) the directory for the output to be sent to. Will be created if it doesn't exist.
|
||||||
|
# @option opts [String] file ('showcase.png') the file to save in dir. Will be overwritten.
|
||||||
|
def showcase(opts = {})
|
||||||
|
opts = {file: 'showcase.png', fill_color: :white}.merge(opts)
|
||||||
|
opts = needs(opts,[:range, :trim, :trim_radius, :creatable_dir, :file_to_save, :face])
|
||||||
|
render_showcase(opts[:range], opts[:trim], opts[:trim_radius],
|
||||||
|
opts[:scale], opts[:offset], opts[:fill_color],
|
||||||
|
opts[:reflect_offset], opts[:reflect_percent], opts[:reflect_strength],
|
||||||
|
opts[:margin], opts[:face],
|
||||||
|
opts[:dir], opts[:file])
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,42 +4,43 @@ require 'squib/input_helpers'
|
||||||
module Squib
|
module Squib
|
||||||
# Back end graphics. Private.
|
# Back end graphics. Private.
|
||||||
class Card
|
class Card
|
||||||
include Squib::InputHelpers
|
include Squib::InputHelpers
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
# @api private
|
# @api private
|
||||||
attr_reader :width, :height
|
attr_reader :width, :height
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
# @api private
|
# @api private
|
||||||
attr_accessor :cairo_surface, :cairo_context
|
attr_accessor :cairo_surface, :cairo_context
|
||||||
|
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
# @api private
|
# @api private
|
||||||
def initialize(deck, width, height)
|
def initialize(deck, width, height)
|
||||||
@deck=deck; @width=width; @height=height
|
@deck=deck; @width=width; @height=height
|
||||||
@cairo_surface = Cairo::ImageSurface.new(width,height)
|
@cairo_surface = Cairo::ImageSurface.new(width,height)
|
||||||
@cairo_context = Cairo::Context.new(@cairo_surface)
|
@cairo_context = Cairo::Context.new(@cairo_surface)
|
||||||
end
|
end
|
||||||
|
|
||||||
# A save/restore wrapper for using Cairo
|
# A save/restore wrapper for using Cairo
|
||||||
# :nodoc:
|
# :nodoc:
|
||||||
# @api private
|
# @api private
|
||||||
def use_cairo(&block)
|
def use_cairo(&block)
|
||||||
@cairo_context.save
|
@cairo_context.save
|
||||||
block.yield(@cairo_context)
|
block.yield(@cairo_context)
|
||||||
@cairo_context.restore
|
@cairo_context.restore
|
||||||
end
|
end
|
||||||
|
|
||||||
########################
|
########################
|
||||||
### BACKEND GRAPHICS ###
|
### BACKEND GRAPHICS ###
|
||||||
########################
|
########################
|
||||||
require 'squib/graphics/background'
|
require 'squib/graphics/background'
|
||||||
require 'squib/graphics/image'
|
require 'squib/graphics/image'
|
||||||
require 'squib/graphics/save_doc'
|
require 'squib/graphics/save_doc'
|
||||||
require 'squib/graphics/save_images'
|
require 'squib/graphics/save_images'
|
||||||
require 'squib/graphics/shapes'
|
require 'squib/graphics/shapes'
|
||||||
require 'squib/graphics/text'
|
require 'squib/graphics/showcase'
|
||||||
|
require 'squib/graphics/text'
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ module Squib
|
||||||
:default_font => 'Arial 36',
|
:default_font => 'Arial 36',
|
||||||
:dir => '_output',
|
:dir => '_output',
|
||||||
:ellipsize => :end,
|
:ellipsize => :end,
|
||||||
|
:face => :left,
|
||||||
:fill_color => '#0000',
|
:fill_color => '#0000',
|
||||||
:force_id => false,
|
:force_id => false,
|
||||||
:font => :use_set,
|
:font => :use_set,
|
||||||
|
|
@ -24,17 +25,23 @@ module Squib
|
||||||
:justify => false,
|
:justify => false,
|
||||||
:margin => 75,
|
:margin => 75,
|
||||||
:markup => false,
|
:markup => false,
|
||||||
|
:offset => 1.1,
|
||||||
:prefix => 'card_',
|
:prefix => 'card_',
|
||||||
:progress_bar => false,
|
:progress_bar => false,
|
||||||
|
:reflect_offset => 15,
|
||||||
|
:reflect_percent => 0.25,
|
||||||
|
:reflect_strength => 0.2,
|
||||||
:range => :all,
|
:range => :all,
|
||||||
:rotate => false,
|
:rotate => false,
|
||||||
:rows => :infinite,
|
:rows => :infinite,
|
||||||
|
:scale => 0.85,
|
||||||
:sheet => 0,
|
:sheet => 0,
|
||||||
:spacing => 0,
|
:spacing => 0,
|
||||||
:str => '',
|
:str => '',
|
||||||
:stroke_color => :black,
|
:stroke_color => :black,
|
||||||
:stroke_width => 2.0,
|
:stroke_width => 2.0,
|
||||||
:trim => 0,
|
:trim => 0,
|
||||||
|
:trim_radius => 38,
|
||||||
:valign => :top,
|
:valign => :top,
|
||||||
:width => :native,
|
:width => :native,
|
||||||
:wrap => true,
|
:wrap => true,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
module Squib
|
||||||
|
class Deck
|
||||||
|
|
||||||
|
# So the Cairo people have said over and over again that they won't support the 3x3 matrices that would handle perspective transforms.
|
||||||
|
# Since our perspective transform needs are a bit simpler, we can use a "striping method" that does the job for us.
|
||||||
|
# It's a little bit involved, but it works well enough for limited ranges of our parameters.
|
||||||
|
# These were also helpful:
|
||||||
|
# http://kapo-cpp.blogspot.com/2008/01/perspective-effect-using-cairo.html
|
||||||
|
# http://zetcode.com/gui/pygtk/drawingII/
|
||||||
|
# :nodoc:
|
||||||
|
# @api private
|
||||||
|
def render_showcase(range,
|
||||||
|
trim, trim_radius, scale, offset, fill_color,
|
||||||
|
reflect_offset, reflect_percent, reflect_strength, margin, face_right,
|
||||||
|
dir, file_to_save)
|
||||||
|
out_width = range.size * ((@width - 2*trim) * scale * offset) + 2*margin
|
||||||
|
out_height = reflect_offset + (1.0 + reflect_percent) * (@height - 2*trim) + 2*margin
|
||||||
|
out_cc = Cairo::Context.new(Cairo::ImageSurface.new(out_width, out_height))
|
||||||
|
out_cc.set_source_color(fill_color)
|
||||||
|
out_cc.paint
|
||||||
|
|
||||||
|
cards = range.collect { |i| @cards[i] }
|
||||||
|
cards.each_with_index do |card, i|
|
||||||
|
trimmed = trim_rounded(card.cairo_surface, trim, trim_radius)
|
||||||
|
reflected = reflect(trimmed, reflect_offset, reflect_percent, reflect_strength)
|
||||||
|
perspectived = perspective(reflected, scale, face_right)
|
||||||
|
out_cc.set_source(perspectived, margin + i * perspectived.width * offset, margin)
|
||||||
|
out_cc.paint
|
||||||
|
end
|
||||||
|
out_cc.target.write_to_png("#{dir}/#{file_to_save}")
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
# @api private
|
||||||
|
def trim_rounded(src, trim, radius)
|
||||||
|
trim_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width-2.0*trim, src.height-2.0*trim))
|
||||||
|
trim_cc.rounded_rectangle(0, 0, trim_cc.target.width, trim_cc.target.height, radius, radius)
|
||||||
|
trim_cc.set_source(src, -1 * trim, -1 * trim)
|
||||||
|
trim_cc.clip
|
||||||
|
trim_cc.paint
|
||||||
|
return trim_cc.target
|
||||||
|
end
|
||||||
|
|
||||||
|
# :nodoc:
|
||||||
|
# @api private
|
||||||
|
def reflect(src, roffset, rpercent, rstrength)
|
||||||
|
tmp_cc = Cairo::Context.new(Cairo::ImageSurface.new(src.width, src.height * (1.0 + rpercent) + roffset))
|
||||||
|
tmp_cc.set_source(src, 0, 0)
|
||||||
|
tmp_cc.paint
|
||||||
|
# Flip affine magic from: http://cairographics.org/matrix_transform/
|
||||||
|
matrix = Cairo::Matrix.new(1, 0, 0, -1, 0, 2 * src.height + roffset)
|
||||||
|
tmp_cc.transform(matrix) # flips the coordinate system
|
||||||
|
top_y = src.height # top of the reflection
|
||||||
|
bottom_y = src.height * (1.0 - rpercent) + roffset # bottom of the reflection
|
||||||
|
gradient = Cairo::LinearPattern.new(0,top_y, 0,bottom_y)
|
||||||
|
gradient.add_color_stop_rgba(0.0, 0,0,0, rstrength) # start a little reflected
|
||||||
|
gradient.add_color_stop_rgba(1.0, 0,0,0, 0.0) # fade to nothing
|
||||||
|
tmp_cc.set_source(src, 0, 0)
|
||||||
|
tmp_cc.mask(gradient)
|
||||||
|
return tmp_cc.target
|
||||||
|
end
|
||||||
|
|
||||||
|
def perspective(src, scale, face_right)
|
||||||
|
dest_cxt = Cairo::Context.new(Cairo::ImageSurface.new(src.width * scale, src.height))
|
||||||
|
in_thickness = 1 # Take strip 1 pixel-width at a time
|
||||||
|
out_thickness = 3 # Scale it to 3 pixels wider to cover any gaps
|
||||||
|
(0..src.width).step(in_thickness) do |i|
|
||||||
|
percentage = i / src.width.to_f
|
||||||
|
i = src.width - i if face_right
|
||||||
|
factor = scale + (percentage * (1.0 - scale)) #linear interpolation
|
||||||
|
dest_cxt.save
|
||||||
|
dest_cxt.translate 0, src.height / 2.0 * (1.0 - factor)
|
||||||
|
dest_cxt.scale factor * scale, factor
|
||||||
|
dest_cxt.set_source src, 0, 0
|
||||||
|
dest_cxt.rounded_rectangle i, 0, out_thickness, src.height, 0,0
|
||||||
|
dest_cxt.fill
|
||||||
|
dest_cxt.restore
|
||||||
|
end
|
||||||
|
return dest_cxt.target
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -28,6 +28,7 @@ module Squib
|
||||||
opts = formatify(opts) if params.include? :formats
|
opts = formatify(opts) if params.include? :formats
|
||||||
opts = rotateify(opts) if params.include? :rotate
|
opts = rotateify(opts) if params.include? :rotate
|
||||||
opts = rowify(opts) if params.include? :rows
|
opts = rowify(opts) if params.include? :rows
|
||||||
|
opts = faceify(opts) if params.include? :face
|
||||||
opts = convert_units(opts, params)
|
opts = convert_units(opts, params)
|
||||||
opts
|
opts
|
||||||
end
|
end
|
||||||
|
|
@ -225,5 +226,13 @@ module Squib
|
||||||
opts
|
opts
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used for showcase - face right if it's :right
|
||||||
|
# :nodoc:
|
||||||
|
# @api private
|
||||||
|
def faceify(opts)
|
||||||
|
opts[:face] = (opts[:face].to_s.downcase == 'right')
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
require 'squib'
|
||||||
|
|
||||||
|
# Showcases are a neat way to show off your cards in a modern way, using a
|
||||||
|
# reflection and a persepctive effect to make them look 3D
|
||||||
|
Squib::Deck.new(cards: 4) do
|
||||||
|
background color: '#CE534D'
|
||||||
|
rect fill_color: '#DED4B9', x: 78, y: 78,
|
||||||
|
width: '2.25in', height: '3.25in', radius: 32
|
||||||
|
text str: %w(Grifter Thief Thug Kingpin),
|
||||||
|
font: 'Helvetica,Sans weight=800 120',
|
||||||
|
x: 78, y: 78, width: '2.25in', align: :center
|
||||||
|
svg file: 'spanner.svg', x: (825-500)/2, y: 500, width: 500, height: 500
|
||||||
|
|
||||||
|
# Defaults are pretty sensible.
|
||||||
|
showcase file: 'showcase.png'
|
||||||
|
|
||||||
|
# Here's a more complete example.
|
||||||
|
# Tons of ways to tweak it if you like - check the docs.
|
||||||
|
showcase trim: 32, trim_radius: 32, margin: 100, face: :right,
|
||||||
|
scale: 0.85, offset: 0.95, fill_color: :black,
|
||||||
|
reflect_offset: 25, reflect_strength: 0.1, reflect_percent: 0.4,
|
||||||
|
file: 'showcase2.png'
|
||||||
|
|
||||||
|
save_png prefix: 'showcase_individual_' # to show that they're not trimmed
|
||||||
|
end
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -199,4 +199,21 @@ describe Squib::InputHelpers do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context '#faceify' do
|
||||||
|
it 'is false on left' do
|
||||||
|
opts = @deck.send(:faceify, {face: :left})
|
||||||
|
expect(opts).to eq({ face: false })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is true on right' do
|
||||||
|
opts = @deck.send(:faceify, {face: 'Right'})
|
||||||
|
expect(opts).to eq({ face: true })
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is false on anything else' do
|
||||||
|
opts = @deck.send(:faceify, {face: 'flugelhorn'})
|
||||||
|
expect(opts).to eq({ face: false })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ describe "Squib samples" do
|
||||||
portrait-landscape.rb
|
portrait-landscape.rb
|
||||||
ranges.rb
|
ranges.rb
|
||||||
saves.rb
|
saves.rb
|
||||||
|
showcase.rb
|
||||||
text_options.rb
|
text_options.rb
|
||||||
tgc_proofs.rb
|
tgc_proofs.rb
|
||||||
units.rb
|
units.rb
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ def mock_cairo(strio)
|
||||||
%w(save set_source_color paint restore translate rotate move_to
|
%w(save set_source_color paint restore translate rotate move_to
|
||||||
update_pango_layout width height show_pango_layout rounded_rectangle
|
update_pango_layout width height show_pango_layout rounded_rectangle
|
||||||
set_line_width stroke fill set_source scale render_rsvg_handle circle
|
set_line_width stroke fill set_source scale render_rsvg_handle circle
|
||||||
triangle line_to operator= show_page).each do |m|
|
triangle line_to operator= show_page clip transform mask).each do |m|
|
||||||
allow(cxt).to receive(m) { |*args| strio << scrub_hex("cairo: #{m}(#{args})\n") }
|
allow(cxt).to receive(m) { |*args| strio << scrub_hex("cairo: #{m}(#{args})\n") }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue