Browse Source

Implement showcase feature

#Closes 26
dev
Andy Meneely 11 years ago
parent
commit
f3e3082f0e
  1. 29
      lib/squib/api/save.rb
  2. 57
      lib/squib/card.rb
  3. 7
      lib/squib/constants.rb
  4. 83
      lib/squib/graphics/showcase.rb
  5. 9
      lib/squib/input_helpers.rb
  6. 25
      samples/showcase.rb
  7. 5914
      spec/data/samples/showcase.rb.txt
  8. 17
      spec/input_helpers_spec.rb
  9. 1
      spec/samples/samples_regression_spec.rb
  10. 2
      spec/spec_helper.rb

29
lib/squib/api/save.rb

@ -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

57
lib/squib/card.rb

@ -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

7
lib/squib/constants.rb

@ -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,

83
lib/squib/graphics/showcase.rb

@ -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

9
lib/squib/input_helpers.rb

@ -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

25
samples/showcase.rb

@ -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

5914
spec/data/samples/showcase.rb.txt

File diff suppressed because it is too large Load Diff

17
spec/input_helpers_spec.rb

@ -199,4 +199,21 @@ describe Squib::InputHelpers do
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

1
spec/samples/samples_regression_spec.rb

@ -58,6 +58,7 @@ describe "Squib samples" do
portrait-landscape.rb
ranges.rb
saves.rb
showcase.rb
text_options.rb
tgc_proofs.rb
units.rb

2
spec/spec_helper.rb

@ -69,7 +69,7 @@ def mock_cairo(strio)
%w(save set_source_color paint restore translate rotate move_to
update_pango_layout width height show_pango_layout rounded_rectangle
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") }
end

Loading…
Cancel
Save