Browse Source

Crop PNGs, now with more rounded corners!

Contributes to #11
dev
Andy Meneely 10 years ago
parent
commit
541c8d49ca
  1. 3
      CHANGELOG.md
  2. 11
      lib/squib/api/image.rb
  3. 36
      lib/squib/args/transform.rb
  4. 8
      lib/squib/graphics/image.rb
  5. 12
      samples/load_images.rb
  6. BIN
      samples/sprites.png
  7. 25
      spec/args/transform_spec.rb
  8. 6
      spec/data/samples/basic.rb.txt
  9. 3
      spec/data/samples/custom_config.rb.txt
  10. 67
      spec/data/samples/load_images.rb.txt
  11. 6
      spec/data/samples/tgc_proofs.rb.txt
  12. BIN
      spec/samples/expected/load_images_00.png
  13. 2
      spec/samples/samples_regression_spec.rb

3
CHANGELOG.md

@ -3,6 +3,9 @@ Squib follows [semantic versioning](http://semver.org).
## v0.9.0 / Unreleased ## v0.9.0 / Unreleased
Features:
* Crop your PNGs! This means you can work from spritesheets if you want. New options to `png` are documented in the API docs and in the `load_images.rb` sample. (#11)
Chores: Chores:
* Ripped out a lot of old constants used from the old way we handled arguments. Yay negative churn! * Ripped out a lot of old constants used from the old way we handled arguments. Yay negative churn!
* Emit a warning when a `config.yml` option is not recognized * Emit a warning when a `config.yml` option is not recognized

11
lib/squib/api/image.rb

@ -25,6 +25,13 @@ module Squib
# @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 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 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. Note: the origin for gradient coordinates is at the given x,y, not at 0,0 as it is most other places. # @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. Note: the origin for gradient coordinates is at the given x,y, not at 0,0 as it is most other places.
# @option opts crop_x [Integer] (0) Crop the loaded image at this x coordinate. Supports Unit Conversion, see {file:README.md#Units Units}.
# @option opts crop_y [Integer] (0) Crop the loaded image at this y coordinate. Supports Unit Conversion, see {file:README.md#Units Units}.
# @option opts crop_corner_radius [Integer] (0): Radius for rounded corners, both x and y. When set, overrides crop_corner_x_radius and crop_corner_y_radius. Supports Unit Conversion, see {file:README.md#Units Units}.
# @option opts crop_corner_x_radius [Integer] (0): x radius for rounded corners of cropped image. Supports Unit Conversion, see {file:README.md#Units Units}.
# @option opts crop_corner_y_radius [Integer] (0): y radius for rounded corners of cropped image. Supports Unit Conversion, see {file:README.md#Units Units}.
# @option opts crop_width [Integer] (:native): Width of the cropped image. Supports Unit Conversion, see {file:README.md#Units Units}.
# @option opts crop_height [Integer] (:native): Height of the cropped image. Supports Unit Conversion, see {file:README.md#Units Units}.
# @return [nil] Returns nil # @return [nil] Returns nil
# @api public # @api public
def png(opts = {}) def png(opts = {})
@ -32,7 +39,7 @@ module Squib
range = Args::CardRange.new(opts[:range], deck_size: size) range = Args::CardRange.new(opts[:range], deck_size: size)
paint = Args::Paint.new(custom_colors).load!(opts, expand_by: size, layout: layout) paint = Args::Paint.new(custom_colors).load!(opts, expand_by: size, layout: layout)
box = Args::ScaleBox.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi) box = Args::ScaleBox.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi)
trans = Args::Transform.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) trans = Args::Transform.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi)
ifile = Args::InputFile.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) ifile = Args::InputFile.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
@progress_bar.start('Loading PNG(s)', range.size) do |bar| @progress_bar.start('Loading PNG(s)', range.size) do |bar|
range.each do |i| range.each do |i|
@ -72,7 +79,7 @@ module Squib
range = Args::CardRange.new(opts[:range], deck_size: size) range = Args::CardRange.new(opts[:range], deck_size: size)
paint = Args::Paint.new(custom_colors).load!(opts, expand_by: size, layout: layout) paint = Args::Paint.new(custom_colors).load!(opts, expand_by: size, layout: layout)
box = Args::ScaleBox.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi) box = Args::ScaleBox.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi)
trans = Args::Transform.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) trans = Args::Transform.new(self).load!(opts, expand_by: size, layout: layout, dpi: dpi)
ifile = Args::InputFile.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) ifile = Args::InputFile.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
svg_args = Args::SvgSpecial.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) svg_args = Args::SvgSpecial.new.load!(opts, expand_by: size, layout: layout, dpi: dpi)
@progress_bar.start('Loading SVG(s)', range.size) do |bar| @progress_bar.start('Loading SVG(s)', range.size) do |bar|

36
lib/squib/args/transform.rb

@ -7,8 +7,20 @@ module Squib
class Transform class Transform
include ArgLoader include ArgLoader
def initialize(deck = nil)
@deck = deck
end
def self.parameters def self.parameters
{ angle: 0 } { angle: 0,
crop_x: 0,
crop_y: 0,
crop_width: :native,
crop_height: :native,
crop_corner_radius: nil,
crop_corner_x_radius: 0,
crop_corner_y_radius: 0,
}
end end
def self.expanding_parameters def self.expanding_parameters
@ -19,6 +31,28 @@ module Squib
parameters.keys # all of them parameters.keys # all of them
end end
def validate_crop_width(arg, _i)
return arg if @deck.nil?
return @deck.width if arg == :deck
arg
end
def validate_crop_height(arg, _i)
return arg if @deck.nil?
return @deck.height if arg == :deck
arg
end
def validate_crop_corner_x_radius(arg, i)
return crop_corner_radius[i] unless crop_corner_radius[i].nil?
arg
end
def validate_crop_corner_y_radius(arg, i)
return crop_corner_radius[i] unless crop_corner_radius[i].nil?
arg
end
end end
end end

8
lib/squib/graphics/image.rb

@ -30,6 +30,14 @@ module Squib
end end
cc.rotate(trans.angle) cc.rotate(trans.angle)
cc.translate(-box.x, -box.y) cc.translate(-box.x, -box.y)
# cc.translate(trans.crop_x, trans.crop_y)
trans.crop_width = png.width.to_f if trans.crop_width == :native
trans.crop_height = png.height.to_f if trans.crop_height == :native
cc.rounded_rectangle(box.x, box.y, trans.crop_width, trans.crop_height, trans.crop_corner_x_radius, trans.crop_corner_y_radius)
cc.clip
cc.translate(-trans.crop_x, -trans.crop_y)
cc.set_source(png, box.x, box.y) cc.set_source(png, box.x, box.y)
cc.operator = paint.blend unless paint.blend == :none cc.operator = paint.blend unless paint.blend == :none
if paint.mask.empty? if paint.mask.empty?

12
samples/load_images.rb

@ -18,6 +18,18 @@ Squib::Deck.new(width: 825, height: 1125, cards: 1) do
png file: 'shiny-purse.png', x: 240, y: 350, width: 35, height: :scale png file: 'shiny-purse.png', x: 240, y: 350, width: 35, height: :scale
png file: 'shiny-purse.png', x: 240, y: 390, width: :scale, height: 35 png file: 'shiny-purse.png', x: 240, y: 390, width: :scale, height: 35
# You can also crop the loaded images, so you can work from a sprite sheet
png file: 'sprites.png', x: 300, y: 350 # entire sprite sheet
png file: 'sprites.png', x: 300, y: 425, # just the robot golem image
crop_x: 0, crop_y: 0, crop_corner_radius: 10,
crop_width: 64, crop_height: 64
png file: 'sprites.png', x: 400, y: 425, # just the drakkar ship image
crop_x: 64, crop_y: 0, crop_corner_x_radius: 25, crop_corner_y_radius: 25,
crop_width: 64, crop_height: 64
png file: 'sprites.png', x: 500, y: 415, # just the drakkar ship image, rotated
crop_x: 64, crop_y: 0, crop_corner_x_radius: 25, crop_corner_y_radius: 25,
crop_width: 64, crop_height: 64, angle: Math::PI / 6
# We can also limit our rendering to a single object, if the SVG ID is set # We can also limit our rendering to a single object, if the SVG ID is set
svg file: 'spanner.svg', id: '#backdrop', x: 50, y: 350, width: 75, height: 75 svg file: 'spanner.svg', id: '#backdrop', x: 50, y: 350, width: 75, height: 75
# Squib prepends a #-sign if one is not specified # Squib prepends a #-sign if one is not specified

BIN
samples/sprites.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

25
spec/args/transform_spec.rb

@ -0,0 +1,25 @@
require 'spec_helper'
require 'squib/args/transform'
describe Squib::Args::Box do
subject(:trans) { Squib::Args::Transform.new }
let(:expected_defaults) { {x: [0], y: [0], crop_width: [:native], crop_height: [:native] } }
context 'validation' do
it 'replaces with deck width and height' do
args = {crop_width: :deck, crop_height: :deck}
deck = OpenStruct.new(width: 123, height: 456)
trans = Squib::Args::Transform.new(deck)
trans.load!(args, expand_by: 1)
expect(trans).to have_attributes(crop_width: [123], crop_height: [456])
end
it 'has radius override x_radius and y_radius' do
args = {crop_corner_x_radius: 1, crop_corner_y_radius: 2, crop_corner_radius: 3}
trans.load!(args, expand_by: 2)
expect(trans).to have_attributes(crop_corner_x_radius: [3, 3], crop_corner_y_radius: [3, 3])
end
end
end

6
spec/data/samples/basic.rb.txt

@ -182,6 +182,9 @@ cairo: save([])
cairo: translate([620, 75]) cairo: translate([620, 75])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-620, -75]) cairo: translate([-620, -75])
cairo: rounded_rectangle([620, 75, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 620, 75]) cairo: set_source([ImageSurface, 620, 75])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -189,6 +192,9 @@ cairo: save([])
cairo: translate([620, 75]) cairo: translate([620, 75])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-620, -75]) cairo: translate([-620, -75])
cairo: rounded_rectangle([620, 75, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 620, 75]) cairo: set_source([ImageSurface, 620, 75])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])

3
spec/data/samples/custom_config.rb.txt

@ -28,6 +28,9 @@ cairo: save([])
cairo: translate([620, 75]) cairo: translate([620, 75])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-620, -75]) cairo: translate([-620, -75])
cairo: rounded_rectangle([620, 75, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 620, 75]) cairo: set_source([ImageSurface, 620, 75])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])

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

@ -18,6 +18,9 @@ cairo: save([])
cairo: translate([620, 75]) cairo: translate([620, 75])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-620, -75]) cairo: translate([-620, -75])
cairo: rounded_rectangle([620, 75, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 620, 75]) cairo: set_source([ImageSurface, 620, 75])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -38,6 +41,9 @@ cairo: translate([305, 50])
cairo: scale([1.953125, 1.953125]) cairo: scale([1.953125, 1.953125])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-305, -50]) cairo: translate([-305, -50])
cairo: rounded_rectangle([305, 50, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 305, 50]) cairo: set_source([ImageSurface, 305, 50])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -58,6 +64,9 @@ cairo: translate([240, 350])
cairo: scale([0.2734375, 0.2734375]) cairo: scale([0.2734375, 0.2734375])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-240, -350]) cairo: translate([-240, -350])
cairo: rounded_rectangle([240, 350, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 240, 350]) cairo: set_source([ImageSurface, 240, 350])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -66,10 +75,53 @@ cairo: translate([240, 390])
cairo: scale([0.2734375, 0.2734375]) cairo: scale([0.2734375, 0.2734375])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-240, -390]) cairo: translate([-240, -390])
cairo: rounded_rectangle([240, 390, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 240, 390]) cairo: set_source([ImageSurface, 240, 390])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
cairo: save([]) cairo: save([])
cairo: translate([300, 350])
cairo: rotate([0])
cairo: translate([-300, -350])
cairo: rounded_rectangle([300, 350, 128.0, 64.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 300, 350])
cairo: paint([1.0])
cairo: restore([])
cairo: save([])
cairo: translate([300, 425])
cairo: rotate([0])
cairo: translate([-300, -425])
cairo: rounded_rectangle([300, 425, 64, 64, 10, 10])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 300, 425])
cairo: paint([1.0])
cairo: restore([])
cairo: save([])
cairo: translate([400, 425])
cairo: rotate([0])
cairo: translate([-400, -425])
cairo: rounded_rectangle([400, 425, 64, 64, 25, 25])
cairo: clip([])
cairo: translate([-64, 0])
cairo: set_source([ImageSurface, 400, 425])
cairo: paint([1.0])
cairo: restore([])
cairo: save([])
cairo: translate([500, 415])
cairo: rotate([0.5235987755982988])
cairo: translate([-500, -415])
cairo: rounded_rectangle([500, 415, 64, 64, 25, 25])
cairo: clip([])
cairo: translate([-64, 0])
cairo: set_source([ImageSurface, 500, 415])
cairo: paint([1.0])
cairo: restore([])
cairo: save([])
cairo: translate([50, 350]) cairo: translate([50, 350])
cairo: rotate([0]) cairo: rotate([0])
cairo: scale([0.5859375, 0.5859375]) cairo: scale([0.5859375, 0.5859375])
@ -97,6 +149,9 @@ cairo: save([])
cairo: translate([50, 700]) cairo: translate([50, 700])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-50, -700]) cairo: translate([-50, -700])
cairo: rounded_rectangle([50, 700, 100.0, 100.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 50, 700]) cairo: set_source([ImageSurface, 50, 700])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -104,6 +159,9 @@ cairo: save([])
cairo: translate([70, 750]) cairo: translate([70, 750])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-70, -750]) cairo: translate([-70, -750])
cairo: rounded_rectangle([70, 750, 100.0, 100.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 70, 750]) cairo: set_source([ImageSurface, 70, 750])
cairo: operator=([:color_burn]) cairo: operator=([:color_burn])
cairo: paint([0.75]) cairo: paint([0.75])
@ -112,6 +170,9 @@ cairo: save([])
cairo: translate([300, 700]) cairo: translate([300, 700])
cairo: rotate([0.0]) cairo: rotate([0.0])
cairo: translate([-300, -700]) cairo: translate([-300, -700])
cairo: rounded_rectangle([300, 700, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 300, 700]) cairo: set_source([ImageSurface, 300, 700])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -119,6 +180,9 @@ cairo: save([])
cairo: translate([300, 800]) cairo: translate([300, 800])
cairo: rotate([0.7853981633974483]) cairo: rotate([0.7853981633974483])
cairo: translate([-300, -800]) cairo: translate([-300, -800])
cairo: rounded_rectangle([300, 800, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 300, 800]) cairo: set_source([ImageSurface, 300, 800])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -148,6 +212,9 @@ cairo: save([])
cairo: translate([650, 950]) cairo: translate([650, 950])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-650, -950]) cairo: translate([-650, -950])
cairo: rounded_rectangle([650, 950, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 650, 950]) cairo: set_source([ImageSurface, 650, 950])
cairo: set_source_color(["magenta"]) cairo: set_source_color(["magenta"])
cairo: mask([ImageSurface, 650, 950]) cairo: mask([ImageSurface, 650, 950])

6
spec/data/samples/tgc_proofs.rb.txt

@ -62,6 +62,9 @@ cairo: save([])
cairo: translate([620, 75]) cairo: translate([620, 75])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([-620, -75]) cairo: translate([-620, -75])
cairo: rounded_rectangle([620, 75, 128.0, 128.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 620, 75]) cairo: set_source([ImageSurface, 620, 75])
cairo: paint([1.0]) cairo: paint([1.0])
cairo: restore([]) cairo: restore([])
@ -75,6 +78,9 @@ cairo: save([])
cairo: translate([0, 0]) cairo: translate([0, 0])
cairo: rotate([0]) cairo: rotate([0])
cairo: translate([0, 0]) cairo: translate([0, 0])
cairo: rounded_rectangle([0, 0, 825.0, 1125.0, 0, 0])
cairo: clip([])
cairo: translate([0, 0])
cairo: set_source([ImageSurface, 0, 0]) cairo: set_source([ImageSurface, 0, 0])
cairo: paint([0.5]) cairo: paint([0.5])
cairo: restore([]) cairo: restore([])

BIN
spec/samples/expected/load_images_00.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 135 KiB

2
spec/samples/samples_regression_spec.rb

@ -73,7 +73,7 @@ describe "Squib samples" do
log = StringIO.new log = StringIO.new
mock_cairo(log) mock_cairo(log)
load sample load sample
# overwrite_sample(sample, log) # Use TEMPORARILY once happy with the new sample log overwrite_sample(sample, log) # Use TEMPORARILY once happy with the new sample log
test_file_str = File.open(sample_regression_file(sample), 'r:UTF-8').read test_file_str = File.open(sample_regression_file(sample), 'r:UTF-8').read
expect(log.string).to eq(test_file_str) expect(log.string).to eq(test_file_str)
end end

Loading…
Cancel
Save