diff --git a/docs/dsl/png.rst b/docs/dsl/png.rst index ed26d67f..b5313b8a 100644 --- a/docs/dsl/png.rst +++ b/docs/dsl/png.rst @@ -12,6 +12,8 @@ file file(s) to read in. As in :doc:`/arrays`, if this a single file, then it's use for every card in range. If the parameter is an Array of files, then each file is looked up for each card. If any of them are nil or '', nothing is done for that card. +.. include:: /args/anchor.rst + .. include:: /args/xy.rst width diff --git a/docs/dsl/svg.rst b/docs/dsl/svg.rst index f2060583..8c5af579 100644 --- a/docs/dsl/svg.rst +++ b/docs/dsl/svg.rst @@ -19,6 +19,8 @@ file By default, if ``file`` is not found, a warning is logged. This behavior can be configured in :doc:`/config` +.. include:: /args/anchor.rst + .. include:: /args/xy.rst data diff --git a/lib/squib/args/scale_box.rb b/lib/squib/args/scale_box.rb index b48df1b6..1248bd94 100644 --- a/lib/squib/args/scale_box.rb +++ b/lib/squib/args/scale_box.rb @@ -13,7 +13,8 @@ class ScaleBox def self.parameters { x: 0, y: 0, - width: :native, height: :native + width: :native, height: :native, + anchor: :top_left } end @@ -52,6 +53,11 @@ def validate_height(arg, i) raise 'height must be a number, :scale, :native, or :deck' end + def validate_anchor(arg, i) + raise 'anchor must be one of :top_left, :top_right, :bottom_left, :bottom_right, or :center/:middle' unless [:top_left, :top_right, :bottom_left, :bottom_right, :center, :middle].include? arg + arg + end + end end diff --git a/lib/squib/dsl/png.rb b/lib/squib/dsl/png.rb index 5674ffed..f73f684d 100644 --- a/lib/squib/dsl/png.rb +++ b/lib/squib/dsl/png.rb @@ -25,7 +25,7 @@ def initialize(deck, dsl_method) def self.accepted_params %i( file - x y width height + x y width height anchor alpha blend mask angle crop_x crop_y crop_width crop_height crop_corner_radius crop_corner_x_radius crop_corner_y_radius diff --git a/lib/squib/dsl/svg.rb b/lib/squib/dsl/svg.rb index 07d4426c..e347b69b 100644 --- a/lib/squib/dsl/svg.rb +++ b/lib/squib/dsl/svg.rb @@ -26,7 +26,7 @@ def initialize(deck, dsl_method) def self.accepted_params %i( file - x y width height + x y width height anchor blend mask crop_x crop_y crop_width crop_height crop_corner_radius crop_corner_x_radius crop_corner_y_radius diff --git a/lib/squib/graphics/image.rb b/lib/squib/graphics/image.rb index 3b10599e..b6e06636 100644 --- a/lib/squib/graphics/image.rb +++ b/lib/squib/graphics/image.rb @@ -33,19 +33,42 @@ def png(file, box, paint, trans) Squib.logger.debug {"RENDERING PNG: \n file: #{file}\n box: #{box}\n paint: #{paint}\n trans: #{trans}"} return if file.nil? or file.eql? '' png = Squib.cache_load_image(file) + box.width = png.width.to_f if box.width == :native + box.height = png.height.to_f if box.height == :native + box.width = png.width.to_f * box.height.to_f / png.height.to_f if box.width == :scale + box.height = png.height.to_f * box.width.to_f / png.width.to_f if box.height == :scale + + scale_width = box.width.to_f / png.width.to_f + scale_height = box.height.to_f / png.height.to_f + warn_png_scale(file, scale_width, scale_height) + + rotate_offset_x = 0 + rotate_offset_y = 0 + if [:top_right, :bottom_right].include? box.anchor + box.x = box.x - box.width + rotate_offset_x = box.width + end + if [:bottom_left, :bottom_right].include? box.anchor + box.y = box.y - box.height + rotate_offset_y = box.height + end + if [:center, :middle].include? box.anchor + rotate_offset_x = (box.width / 2.0) + box.x = box.x - rotate_offset_x + rotate_offset_y = (box.height / 2.0) + box.y = box.y - rotate_offset_y + end + use_cairo do |cc| cc.translate(box.x, box.y) - box.width = png.width.to_f if box.width == :native - box.height = png.height.to_f if box.height == :native - box.width = png.width.to_f * box.height.to_f / png.height.to_f if box.width == :scale - box.height = png.height.to_f * box.width.to_f / png.width.to_f if box.height == :scale - - scale_width = box.width.to_f / png.width.to_f - scale_height = box.height.to_f / png.height.to_f - warn_png_scale(file, scale_width, scale_height) cc.scale(scale_width, scale_height) - cc.rotate(trans.angle) + if box.anchor == :top_left + # Avoid doing useless translate([0, 0]) in Cairo and break regression tests + cc.rotate(trans.angle) + else + cc.rotate_about(rotate_offset_x, rotate_offset_y, trans.angle) + end cc.flip(trans.flip_vertical, trans.flip_horizontal, box.width / 2, box.height / 2) cc.translate(-box.x, -box.y) @@ -94,10 +117,31 @@ def svg(file, svg_args, box, paint, trans) box.height = svg.height.to_f * box.width.to_f / svg.width.to_f if box.height == :scale scale_width = box.width.to_f / svg.width.to_f scale_height = box.height.to_f / svg.height.to_f + rotate_offset_x = 0 + rotate_offset_y = 0 + if [:top_right, :bottom_right].include? box.anchor + box.x = box.x - box.width + rotate_offset_x = box.width + end + if [:bottom_left, :bottom_right].include? box.anchor + box.y = box.y - box.height + rotate_offset_y = box.height + end + if [:center, :middle].include? box.anchor + rotate_offset_x = (box.width / 2.0) + box.x = box.x - rotate_offset_x + rotate_offset_y = (box.height / 2.0) + box.y = box.y - rotate_offset_y + end use_cairo do |cc| cc.translate(box.x, box.y) cc.flip(trans.flip_vertical, trans.flip_horizontal, box.width / 2, box.height / 2) - cc.rotate(trans.angle) + if box.anchor == :top_left + # Avoid doing useless translate([0, 0]) in Cairo and break regression tests + cc.rotate(trans.angle) + else + cc.rotate_about(rotate_offset_x, rotate_offset_y, trans.angle) + end cc.scale(scale_width, scale_height) trans.crop_width = box.width if trans.crop_width == :native diff --git a/samples/images/_images.rb b/samples/images/_images.rb index 699b42d9..e3250d02 100644 --- a/samples/images/_images.rb +++ b/samples/images/_images.rb @@ -1,7 +1,7 @@ require 'squib' require 'squib/sample_helpers' -Squib::Deck.new(width: 1000, height: 3000) do +Squib::Deck.new(width: 1000, height: 3200) do draw_graph_paper width, height sample "This a PNG.\nNo scaling is done by default." do |x, y| @@ -99,6 +99,16 @@ png file: 'with-alpha.png', mask: mask, x: x + 150, y: y, width: 150, height: :scale end + sample 'PNGs and SVGs are top-left-anchored by default but this can be changed.' do |x,y| + rect x: x, y: y, width: 100, height: 100, # draw the crop line + dash: '3 3', stroke_color: 'red', stroke_width: 3 + png x: x + 50, y: y + 50, width: 100, height: 100, file: 'angler-fish.png', + anchor: :center, angle: - Math::PI / 4 - 0.2 + rect x: x + 150, y: y, width: 100, height: 100, # draw the crop line + dash: '3 3', stroke_color: 'red', stroke_width: 3 + svg x: x + 250, y: y + 100, width: 100, height: 100, file: 'robot-golem.svg', + anchor: :bottom_right, angle: Math::PI / 5 + end save_png prefix: '_images_' end diff --git a/spec/args/scale_box_spec.rb b/spec/args/scale_box_spec.rb index e692b852..da98ea2c 100644 --- a/spec/args/scale_box_spec.rb +++ b/spec/args/scale_box_spec.rb @@ -64,6 +64,17 @@ expect { Squib::Args.extract_scale_box args, deck }.to raise_error('height must be a number, :scale, :native, or :deck') end + it 'allows setting anchors' do + args = { anchor: :middle } + box = Squib::Args.extract_scale_box args, deck + expect(box).to have_attributes(anchor: [:middle]) + end + + it 'disallows setting incorrect anchors' do + args = { anchor: :midle } + expect { Squib::Args.extract_scale_box args, deck }.to raise_error('anchor must be one of :top_left, :top_right, :bottom_left, :bottom_right, or :center/:middle') + end + end