Implement showcase feature
#Closes 26
This commit is contained in:
@@ -38,5 +38,34 @@ module Squib
|
||||
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
|
||||
|
||||
+29
-28
@@ -4,42 +4,43 @@ require 'squib/input_helpers'
|
||||
module Squib
|
||||
# Back end graphics. Private.
|
||||
class Card
|
||||
include Squib::InputHelpers
|
||||
include Squib::InputHelpers
|
||||
|
||||
# :nodoc:
|
||||
# @api private
|
||||
attr_reader :width, :height
|
||||
# :nodoc:
|
||||
# @api private
|
||||
attr_reader :width, :height
|
||||
|
||||
# :nodoc:
|
||||
# @api private
|
||||
attr_accessor :cairo_surface, :cairo_context
|
||||
# :nodoc:
|
||||
# @api private
|
||||
attr_accessor :cairo_surface, :cairo_context
|
||||
|
||||
# :nodoc:
|
||||
# @api private
|
||||
def initialize(deck, width, height)
|
||||
@deck=deck; @width=width; @height=height
|
||||
@cairo_surface = Cairo::ImageSurface.new(width,height)
|
||||
@cairo_context = Cairo::Context.new(@cairo_surface)
|
||||
end
|
||||
# :nodoc:
|
||||
# @api private
|
||||
def initialize(deck, width, height)
|
||||
@deck=deck; @width=width; @height=height
|
||||
@cairo_surface = Cairo::ImageSurface.new(width,height)
|
||||
@cairo_context = Cairo::Context.new(@cairo_surface)
|
||||
end
|
||||
|
||||
# A save/restore wrapper for using Cairo
|
||||
# :nodoc:
|
||||
# @api private
|
||||
def use_cairo(&block)
|
||||
@cairo_context.save
|
||||
block.yield(@cairo_context)
|
||||
@cairo_context.restore
|
||||
end
|
||||
def use_cairo(&block)
|
||||
@cairo_context.save
|
||||
block.yield(@cairo_context)
|
||||
@cairo_context.restore
|
||||
end
|
||||
|
||||
########################
|
||||
### BACKEND GRAPHICS ###
|
||||
########################
|
||||
require 'squib/graphics/background'
|
||||
require 'squib/graphics/image'
|
||||
require 'squib/graphics/save_doc'
|
||||
require 'squib/graphics/save_images'
|
||||
require 'squib/graphics/shapes'
|
||||
require 'squib/graphics/text'
|
||||
########################
|
||||
### BACKEND GRAPHICS ###
|
||||
########################
|
||||
require 'squib/graphics/background'
|
||||
require 'squib/graphics/image'
|
||||
require 'squib/graphics/save_doc'
|
||||
require 'squib/graphics/save_images'
|
||||
require 'squib/graphics/shapes'
|
||||
require 'squib/graphics/showcase'
|
||||
require 'squib/graphics/text'
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,7 @@ module Squib
|
||||
:default_font => 'Arial 36',
|
||||
:dir => '_output',
|
||||
:ellipsize => :end,
|
||||
:face => :left,
|
||||
:fill_color => '#0000',
|
||||
:force_id => false,
|
||||
:font => :use_set,
|
||||
@@ -24,17 +25,23 @@ module Squib
|
||||
:justify => false,
|
||||
:margin => 75,
|
||||
:markup => false,
|
||||
:offset => 1.1,
|
||||
:prefix => 'card_',
|
||||
:progress_bar => false,
|
||||
:reflect_offset => 15,
|
||||
:reflect_percent => 0.25,
|
||||
:reflect_strength => 0.2,
|
||||
:range => :all,
|
||||
:rotate => false,
|
||||
:rows => :infinite,
|
||||
:scale => 0.85,
|
||||
:sheet => 0,
|
||||
:spacing => 0,
|
||||
:str => '',
|
||||
:stroke_color => :black,
|
||||
:stroke_width => 2.0,
|
||||
:trim => 0,
|
||||
:trim_radius => 38,
|
||||
:valign => :top,
|
||||
:width => :native,
|
||||
: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 = rotateify(opts) if params.include? :rotate
|
||||
opts = rowify(opts) if params.include? :rows
|
||||
opts = faceify(opts) if params.include? :face
|
||||
opts = convert_units(opts, params)
|
||||
opts
|
||||
end
|
||||
@@ -225,5 +226,13 @@ module Squib
|
||||
opts
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user