diff --git a/CHANGELOG.md b/CHANGELOG.md index 9551573..2331395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ Squib follows [semantic versioning](http://semver.org). ## 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: * 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 diff --git a/lib/squib/api/image.rb b/lib/squib/api/image.rb index 36cea85..88ff0e9 100644 --- a/lib/squib/api/image.rb +++ b/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 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 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 # @api public def png(opts = {}) @@ -32,7 +39,7 @@ module Squib range = Args::CardRange.new(opts[:range], deck_size: size) 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) - 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) @progress_bar.start('Loading PNG(s)', range.size) do |bar| range.each do |i| @@ -72,7 +79,7 @@ module Squib range = Args::CardRange.new(opts[:range], deck_size: size) 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) - 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) svg_args = Args::SvgSpecial.new.load!(opts, expand_by: size, layout: layout, dpi: dpi) @progress_bar.start('Loading SVG(s)', range.size) do |bar| diff --git a/lib/squib/args/transform.rb b/lib/squib/args/transform.rb index 4c179f4..16262ab 100644 --- a/lib/squib/args/transform.rb +++ b/lib/squib/args/transform.rb @@ -7,8 +7,20 @@ module Squib class Transform include ArgLoader + def initialize(deck = nil) + @deck = deck + end + 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 def self.expanding_parameters @@ -19,6 +31,28 @@ module Squib parameters.keys # all of them 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 diff --git a/lib/squib/graphics/image.rb b/lib/squib/graphics/image.rb index 2cad76e..76d23f2 100644 --- a/lib/squib/graphics/image.rb +++ b/lib/squib/graphics/image.rb @@ -30,6 +30,14 @@ module Squib end cc.rotate(trans.angle) 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.operator = paint.blend unless paint.blend == :none if paint.mask.empty? diff --git a/samples/load_images.rb b/samples/load_images.rb index 45fca33..545ef66 100644 --- a/samples/load_images.rb +++ b/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: 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 svg file: 'spanner.svg', id: '#backdrop', x: 50, y: 350, width: 75, height: 75 # Squib prepends a #-sign if one is not specified diff --git a/samples/sprites.png b/samples/sprites.png new file mode 100644 index 0000000..9457264 Binary files /dev/null and b/samples/sprites.png differ diff --git a/spec/args/transform_spec.rb b/spec/args/transform_spec.rb new file mode 100644 index 0000000..0b88c0c --- /dev/null +++ b/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 \ No newline at end of file diff --git a/spec/data/samples/basic.rb.txt b/spec/data/samples/basic.rb.txt index f6b9d6c..9c235b0 100644 --- a/spec/data/samples/basic.rb.txt +++ b/spec/data/samples/basic.rb.txt @@ -182,6 +182,9 @@ cairo: save([]) cairo: translate([620, 75]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) @@ -189,6 +192,9 @@ cairo: save([]) cairo: translate([620, 75]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) diff --git a/spec/data/samples/custom_config.rb.txt b/spec/data/samples/custom_config.rb.txt index f4521a1..94de5a7 100644 --- a/spec/data/samples/custom_config.rb.txt +++ b/spec/data/samples/custom_config.rb.txt @@ -28,6 +28,9 @@ cairo: save([]) cairo: translate([620, 75]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) diff --git a/spec/data/samples/load_images.rb.txt b/spec/data/samples/load_images.rb.txt index 82036a7..aad79b7 100644 --- a/spec/data/samples/load_images.rb.txt +++ b/spec/data/samples/load_images.rb.txt @@ -18,6 +18,9 @@ cairo: save([]) cairo: translate([620, 75]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) @@ -38,6 +41,9 @@ cairo: translate([305, 50]) cairo: scale([1.953125, 1.953125]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) @@ -58,6 +64,9 @@ cairo: translate([240, 350]) cairo: scale([0.2734375, 0.2734375]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) @@ -66,10 +75,53 @@ cairo: translate([240, 390]) cairo: scale([0.2734375, 0.2734375]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) 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: rotate([0]) cairo: scale([0.5859375, 0.5859375]) @@ -97,6 +149,9 @@ cairo: save([]) cairo: translate([50, 700]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) @@ -104,6 +159,9 @@ cairo: save([]) cairo: translate([70, 750]) cairo: rotate([0]) 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: operator=([:color_burn]) cairo: paint([0.75]) @@ -112,6 +170,9 @@ cairo: save([]) cairo: translate([300, 700]) cairo: rotate([0.0]) 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: paint([1.0]) cairo: restore([]) @@ -119,6 +180,9 @@ cairo: save([]) cairo: translate([300, 800]) cairo: rotate([0.7853981633974483]) 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: paint([1.0]) cairo: restore([]) @@ -148,6 +212,9 @@ cairo: save([]) cairo: translate([650, 950]) cairo: rotate([0]) 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_color(["magenta"]) cairo: mask([ImageSurface, 650, 950]) diff --git a/spec/data/samples/tgc_proofs.rb.txt b/spec/data/samples/tgc_proofs.rb.txt index 163b49e..bccaddb 100644 --- a/spec/data/samples/tgc_proofs.rb.txt +++ b/spec/data/samples/tgc_proofs.rb.txt @@ -62,6 +62,9 @@ cairo: save([]) cairo: translate([620, 75]) cairo: rotate([0]) 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: paint([1.0]) cairo: restore([]) @@ -75,6 +78,9 @@ cairo: save([]) cairo: translate([0, 0]) cairo: rotate([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: paint([0.5]) cairo: restore([]) diff --git a/spec/samples/expected/load_images_00.png b/spec/samples/expected/load_images_00.png index 4f52097..d3ac7a0 100644 Binary files a/spec/samples/expected/load_images_00.png and b/spec/samples/expected/load_images_00.png differ diff --git a/spec/samples/samples_regression_spec.rb b/spec/samples/samples_regression_spec.rb index e21fee5..590c10c 100644 --- a/spec/samples/samples_regression_spec.rb +++ b/spec/samples/samples_regression_spec.rb @@ -73,7 +73,7 @@ describe "Squib samples" do log = StringIO.new mock_cairo(log) 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 expect(log.string).to eq(test_file_str) end