Browse Source

Implementing mask feature for images

Fixes #32.
dev
Andy Meneely 11 years ago
parent
commit
7a53f68393
  1. 4
      CHANGELOG.md
  2. 10
      lib/squib/api/image.rb
  3. 2
      lib/squib/constants.rb
  4. 20
      lib/squib/graphics/image.rb
  5. 52
      samples/glass-heart.svg
  6. 14
      samples/load_images.rb
  7. 4
      spec/api/api_image_spec.rb
  8. 26
      spec/data/samples/load_images.rb.txt
  9. 11
      spec/graphics/graphics_images_spec.rb

4
CHANGELOG.md

@ -1,7 +1,9 @@
# Squib CHANGELOG
## v0.3.0a
* Gradients! Can now specify linear or radial gradients anywhere you specify colors. See README for more details.
* Masks! The `png` and `svg` commands can be used as if they are a mask, so you can color the icon with any color you like. Can be handy for switching to black-and-white, or for reusing the same image but different colors across cards.
* Gradients! Can now specify linear or radial gradients anywhere you specify colors. See README and `samples/gradients.rb` for more details.
* Special thanks to Shalom Craimer for the idea and proof-of-concept on the above two features!
* Added new sample table for color constants in `samples/colors.rb`
## v0.2.0

10
lib/squib/api/image.rb

@ -17,16 +17,17 @@ module Squib
# @option opts alpha [Decimal] (1.0) the alpha-transparency percentage used to blend this image. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
# @option opts blend [:none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity] (:none) the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
# @option opts angle [FixNum] (0) Rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
# @option opts mask [String] (nil) If specified, the image will be used as a mask for the given color/gradient. Transparent pixels are ignored, opaque pixels are the given color.
# @return [nil] Returns nil
# @api public
def png(opts = {})
opts = needs(opts, [:range, :files, :x, :y, :width, :height, :alpha, :layout, :blend, :angle])
opts = needs(opts, [:range, :files, :x, :y, :width, :height, :alpha, :layout, :blend, :angle, :mask])
Dir.chdir(@img_dir) do
@progress_bar.start('Loading PNG(s)', opts[:range].size) do |bar|
opts[:range].each do |i|
@cards[i].png(opts[:file][i],
opts[:x][i], opts[:y][i], opts[:width][i], opts[:height][i],
opts[:alpha][i], opts[:blend][i], opts[:angle][i])
opts[:alpha][i], opts[:blend][i], opts[:angle][i], opts[:mask][i])
bar.increment
end
end
@ -51,16 +52,17 @@ module Squib
# @option opts alpha [Decimal] (1.0) the alpha-transparency percentage used to blend this image. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
# @option opts blend [:none, :multiply, :screen, :overlay, :darken, :lighten, :color_dodge, :color_burn, :hard_light, :soft_light, :difference, :exclusion, :hsl_hue, :hsl_saturation, :hsl_color, :hsl_luminosity] (:none) the composite blend operator used when applying this image. See Blend Modes at http://cairographics.org/operators. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
# @option opts angle [FixNum] (0) Rotation of the in radians. Note that this rotates around the upper-left corner, making the placement of x-y coordinates slightly tricky. Supports Arrays, see {file:README.md#Arrays_and_Singleton_Expansion Arrays and Singleon Expansion}
# @option opts mask [String] (nil) If specified, the image will be used as a mask for the given color/gradient. Transparent pixels are ignored, opaque pixels are the given color.
# @return [nil] Returns nil
# @api public
def svg(opts = {})
p = needs(opts,[:range, :files, :svgid, :force_svgid, :x, :y, :width, :height, :layout, :alpha, :blend, :angle])
p = needs(opts,[:range, :files, :svgid, :force_svgid, :x, :y, :width, :height, :layout, :alpha, :blend, :angle, :mask])
Dir.chdir(@img_dir) do
@progress_bar.start('Loading SVG(s)', p[:range].size) do |bar|
p[:range].each do |i|
unless p[:force_id][i] && p[:id][i].to_s.empty?
@cards[i].svg(p[:file][i], p[:id][i], p[:x][i], p[:y][i],
p[:width][i], p[:height][i], p[:alpha][i], p[:blend][i], p[:angle][i])
p[:width][i], p[:height][i], p[:alpha][i], p[:blend][i], p[:angle][i],p[:mask][i])
end
bar.increment
end

2
lib/squib/constants.rb

@ -25,6 +25,7 @@ module Squib
:justify => false,
:margin => 75,
:markup => false,
:mask => nil,
:offset => 1.1,
:prefix => 'card_',
:progress_bar => false,
@ -95,6 +96,7 @@ module Squib
:justify => :justify,
:layout => :layout,
:markup => :markup,
:mask => :mask,
:rect_radius => :radius,
:spacing => :spacing,
:str => :str,

20
lib/squib/graphics/image.rb

@ -14,8 +14,8 @@ module Squib
# :nodoc:
# @api private
def png(file, x, y, width, height, alpha, blend, angle)
Squib.logger.debug {"Rendering: #{file} @#{x},#{y} #{width}x#{height}, alpha: #{alpha}, blend: #{blend}, angle: #{angle}"}
def png(file, x, y, width, height, alpha, blend, angle, mask)
Squib.logger.debug {"Rendering: #{file} @#{x},#{y} #{width}x#{height}, alpha: #{alpha}, blend: #{blend}, angle: #{angle}, mask: #{mask}"}
return if file.nil? or file.eql? ''
png = Squib.cache_load_image(file)
use_cairo do |cc|
@ -30,14 +30,19 @@ module Squib
cc.translate(-1 * x, -1 * y)
cc.set_source(png, x, y)
cc.operator = blend unless blend == :none
if mask.nil?
cc.paint(alpha)
else
cc.set_source_squibcolor(mask)
cc.mask(png, x, y)
end
end
end
# :nodoc:
# @api private
def svg(file, id, x, y, width, height, alpha, blend, angle)
Squib.logger.debug {"Rendering: #{file}, id: #{id} @#{x},#{y} #{width}x#{height}, alpha: #{alpha}, blend: #{blend}, angle: #{angle}"}
def svg(file, id, x, y, width, height, alpha, blend, angle, mask)
Squib.logger.debug {"Rendering: #{file}, id: #{id} @#{x},#{y} #{width}x#{height}, alpha: #{alpha}, blend: #{blend}, angle: #{angle}, mask: #{mask}"}
return if file.nil? or file.eql? ''
svg = RSVG::Handle.new_from_file(file)
width = svg.width if width == :native
@ -50,9 +55,14 @@ module Squib
cc.translate(x, y)
cc.rotate(angle)
cc.translate(-1 * x, -1 * y)
cc.set_source(tmp, x, y)
cc.operator = blend unless blend == :none
if mask.nil?
cc.set_source(tmp, x, y)
cc.paint(alpha)
else
cc.set_source_squibcolor(mask)
cc.mask(tmp, x, y)
end
end
end

52
samples/glass-heart.svg

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 512 512"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="glass-heart.svg">
<metadata
id="metadata22">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs20" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1018"
id="namedview18"
showgrid="false"
inkscape:zoom="1.3037281"
inkscape:cx="399.78879"
inkscape:cy="267.43808"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<path
style="fill:#000000;stroke:#000000;stroke-width:18.68857956;stroke-opacity:1;fill-opacity:1"
d="M 371.24414 28.179688 C 329.86691 28.096623 287.41274 51.03218 261.00391 102.875 C 196.11155 -14.027671 24.628137 13.619783 29.029297 157.66016 C 33.157605 292.77111 183.5304 347.1093 261.00391 481.30078 C 338.40266 347.24199 496.61198 301.41271 492.29492 157.66016 C 489.89034 77.591144 431.71855 28.301089 371.24414 28.179688 z M 358.9082 49.480469 C 372.3905 49.61861 388.76091 53.818062 405.73047 62.265625 C 444.51802 81.57434 471.74936 115.76108 466.55273 138.62305 C 461.3561 161.48501 425.69966 164.36536 386.91211 145.05664 C 348.12455 125.74793 320.89517 91.56314 326.0918 68.701172 C 329.0149 55.841315 341.57382 49.302858 358.9082 49.480469 z M 148.91016 49.492188 C 159.88856 49.603754 173.21708 52.996781 187.03516 59.820312 C 218.61934 75.416956 240.79389 103.02923 236.5625 121.49609 C 232.3311 139.96294 203.29706 142.29 171.71289 126.69336 C 140.12871 111.09671 117.95416 83.482482 122.18555 65.015625 C 124.56571 54.628018 134.79506 49.348745 148.91016 49.492188 z M 263.11719 179.0332 C 373.07132 179.0332 462.20703 196.90249 462.20703 218.94727 C 462.20703 223.81036 457.86399 228.46946 449.92188 232.7793 C 411.40329 313.40731 320.36729 360.01624 263.00195 457.04297 C 206.07092 360.7538 119.55662 313.16333 78.189453 235.61133 C 84.123672 237.57886 90.694402 239.43942 97.783203 241.1875 C 76.471815 234.83175 64.027344 227.18135 64.027344 218.94727 C 64.027344 196.90249 153.16306 179.0332 263.11719 179.0332 z M 97.783203 241.1875 C 133.52789 251.84773 194.23275 258.86328 263.11719 258.86328 C 348.81534 258.86328 421.8618 248.00627 449.92188 232.7793 C 450.10835 232.38896 450.30232 232.00327 450.48633 231.61133 C 365.1295 267.11537 188.10527 263.46067 97.783203 241.1875 z "
id="path10" />
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

14
samples/load_images.rb

@ -32,5 +32,19 @@ Squib::Deck.new(width: 825, height: 1125, cards: 1) do
png file: 'shiny-purse.png', x: 300, y: 800, angle: Math::PI / 4
svg file: 'spanner.svg', x: 300, y: 900, angle: Math::PI / 2 - 0.1
# Images can also be used as masks instead of being directly painted.
# This is particularly useful for switching directly over to black-and-white for printing
# Or, if you want the same image to be used but with different colors/gradients
svg mask: '#00ff00',
file: 'glass-heart.svg',
x: 500, y: 600, width: 200, height: 200
svg mask: '(500,1000)(500,800) #333@0.0 #ccc@1.0 ',
file: 'glass-heart.svg',
x: 500, y: 800, width: 200, height: 200
# Masks are based on transparency, so this is just a square
png mask: :magenta, file: 'shiny-purse.png',
x: 650, y: 950
save prefix: 'load_images_', format: :png
end

4
spec/api/api_image_spec.rb

@ -7,7 +7,7 @@ describe Squib::Deck, 'images' do
it 'calls Card#png, Dir, and progress bar' do
card = instance_double(Squib::Card)
progress = double(Squib::Progress)
expect(card).to receive(:png).with('foo', 0, 1, :native, :native, 0.5, :overlay, 0.75).once
expect(card).to receive(:png).with('foo', 0, 1, :native, :native, 0.5, :overlay, 0.75, nil).once
expect(Dir).to receive(:chdir).with('.').and_yield.once
expect(progress).to receive(:start).and_yield(progress).once
expect(progress).to receive(:increment).once
@ -23,7 +23,7 @@ describe Squib::Deck, 'images' do
it 'calls Card#svg, Dir, and progress bar' do
card = instance_double(Squib::Card)
progress = double(Squib::Progress)
expect(card).to receive(:svg).with('foo', '#bar', 0, 1, 20, 30, 0.5, :overlay, 0.75).once
expect(card).to receive(:svg).with('foo', '#bar', 0, 1, 20, 30, 0.5, :overlay, 0.75, nil).once
expect(Dir).to receive(:chdir).with('.').and_yield.once
expect(progress).to receive(:start).and_yield(progress).once
expect(progress).to receive(:increment).once

26
spec/data/samples/load_images.rb.txt

@ -109,4 +109,30 @@ cairo: translate([-300, -900])
cairo: set_source([MockDouble, 300, 900])
cairo: paint([1.0])
cairo: restore([])
cairo: scale([0.390625, 0.390625])
cairo: render_rsvg_handle([RSVG::Handle, nil])
cairo: save([])
cairo: translate([500, 600])
cairo: rotate([0])
cairo: translate([-500, -600])
cairo: set_source_color(["#00ff00"])
cairo: mask([MockDouble, 500, 600])
cairo: restore([])
cairo: scale([0.390625, 0.390625])
cairo: render_rsvg_handle([RSVG::Handle, nil])
cairo: save([])
cairo: translate([500, 800])
cairo: rotate([0])
cairo: translate([-500, -800])
cairo: set_source([LinearPattern])
cairo: mask([MockDouble, 500, 800])
cairo: restore([])
cairo: save([])
cairo: translate([650, 950])
cairo: rotate([0])
cairo: translate([-650, -950])
cairo: set_source([ImageSurface, 650, 950])
cairo: set_source_color([:magenta])
cairo: mask([ImageSurface, 650, 950])
cairo: restore([])
surface: write_to_png(["_output/load_images_0.png"])

11
spec/graphics/graphics_images_spec.rb

@ -26,7 +26,7 @@ describe Squib::Card do
card = Squib::Card.new(@deck, 100, 150)
# png(file, x, y, alpha, blend, angle)
card.png('foo.png', 37, 38, :native, :native, 0.9, :none, 0.0)
card.png('foo.png', 37, 38, :native, :native, 0.9, :none, 0.0, nil)
end
it 'sets blend when needed' do
@ -34,9 +34,8 @@ describe Squib::Card do
expect(@context).to receive(:operator=).with(:overlay).once
card = Squib::Card.new(@deck, 100, 150)
card.png('foo.png', 37, 38, :native, :native, 0.9, :overlay, 0.0)
card.png('foo.png', 37, 38, :native, :native, 0.9, :overlay, 0.0, nil)
end
end
context '#svg' do
@ -55,7 +54,7 @@ describe Squib::Card do
card = Squib::Card.new(@deck, 100, 150)
# svg(file, id, x, y, width, height, alpha, blend, angle)
card.svg('foo.png', 'id', 37, 38, :native, :native, 0.9, :none, 0.0)
card.svg('foo.png', 'id', 37, 38, :native, :native, 0.9, :none, 0.0, nil)
end
it 'sets blend when needed' do
@ -64,7 +63,7 @@ describe Squib::Card do
expect(@context).to receive(:operator=).with(:overlay).once
card = Squib::Card.new(@deck, 100, 150)
card.svg('foo.png', nil, 37, 38, :native, :native, 0.9, :overlay, 0.0)
card.svg('foo.png', nil, 37, 38, :native, :native, 0.9, :overlay, 0.0, nil)
end
it 'sets width & height when needed' do
@ -74,7 +73,7 @@ describe Squib::Card do
expect(@context).to receive(:scale).with(2.0, 3.0).once
card = Squib::Card.new(@deck, 100, 150)
card.svg('foo.png', nil, 37, 38, 200, 300, 0.9, :none, 0.0)
card.svg('foo.png', nil, 37, 38, 200, 300, 0.9, :none, 0.0, nil)
end
end

Loading…
Cancel
Save