From f49bff2388454e3df33979bb4d85238b9eba0e1f Mon Sep 17 00:00:00 2001 From: Andy Meneely Date: Sat, 28 Feb 2015 19:34:46 -0500 Subject: [PATCH] Add antialias config option Closes #41 --- CHANGELOG.md | 6 ++++-- README.md | 1 + benchmarks/antialias_best.rb | 13 +++++++++++++ benchmarks/antialias_best.yml | 1 + benchmarks/antialias_fast.rb | 13 +++++++++++++ benchmarks/antialias_fast.yml | 1 + lib/squib/card.rb | 1 + lib/squib/constants.rb | 1 + lib/squib/deck.rb | 4 +++- lib/squib/graphics/cairo_context_wrapper.rb | 3 ++- lib/squib/project_template/config.yml | 6 ++++-- spec/data/samples/autoscale_font.rb.txt | 3 +++ spec/data/samples/basic.rb.txt | 3 +++ spec/data/samples/cairo_access.rb.txt | 2 ++ spec/data/samples/csv_import.rb.txt | 2 ++ spec/data/samples/custom_config.rb.txt | 1 + spec/data/samples/draw_shapes.rb.txt | 1 + spec/data/samples/excel.rb.txt | 3 +++ spec/data/samples/gradients.rb.txt | 1 + spec/data/samples/hello_world.rb.txt | 2 ++ spec/data/samples/load_images.rb.txt | 1 + spec/data/samples/portrait-landscape.rb.txt | 2 ++ spec/data/samples/ranges.rb.txt | 3 +++ spec/data/samples/saves.rb.txt | 16 ++++++++++++++++ spec/data/samples/showcase.rb.txt | 4 ++++ spec/data/samples/text_options.rb.txt | 3 +++ spec/data/samples/tgc_proofs.rb.txt | 1 + spec/data/samples/units.rb.txt | 1 + spec/graphics/graphics_images_spec.rb | 4 ++++ spec/graphics/graphics_save_doc_spec.rb | 3 ++- spec/graphics/graphics_shapes_spec.rb | 5 +++++ spec/graphics/graphics_text_spec.rb | 3 +++ spec/spec_helper.rb | 3 ++- 33 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 benchmarks/antialias_best.rb create mode 100644 benchmarks/antialias_best.yml create mode 100644 benchmarks/antialias_fast.rb create mode 100644 benchmarks/antialias_fast.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 3811028..13bd090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ # v0.4.0 * SVG backend support! You can now set the deck's back end to work with SVGs instead of images, making the resulting PDFs vectorized. (You can still save to PNGs too.) This was a big change for Squib, and it's got a few known issues here and there. See discussion on the README for more details. -* Bugfix: Stray stroke on circles (#35) +* Added config option for antialiasing method. My benchmarks showed that 'best' is only 10% slower than 'fast' on extremely alias-intensive tasks, so that's the Squib default now. +* Bugfix: Stray stroke on circles after text (#35) * Bugfix: Progress bar increment error (#34) Known issues -* +* Masking SVGs onto an SVG backend will rasterize as an intermediate step. +* Scale on vectorized PDFs is not perfect. ## v0.3.0 * 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. diff --git a/README.md b/README.md index 2936254..a01d872 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,7 @@ Squib supports various configuration properties that can be specified in an exte * `dpi` (Integer, default: 300). Used in calculations when units are used (e.g. for PDF rendering and unit conversion). * `hint` (ColorString, default: off). Text hints are used to show the boundaries of text boxes. Can be enabled/disabled for individual commands, or set globally with the `set` command. This setting is overriden by `set` and individual commands. * `custom_colors` (Hash of Colors, default: {}). Defines globally-available colors available to the deck that can be specified in commands. +* `antialias` (`fast, good, best, none`, default: best). Set the algorithm that Cairo will use for antialiasing. Using our benchmarks on large decks, `best` is only X% slower anyway. For more info see the [Cairo docs](http://www.cairographics.org/manual/cairo-cairo-t.html#cairo-antialias-t). The following sample demonstrates the config file. diff --git a/benchmarks/antialias_best.rb b/benchmarks/antialias_best.rb new file mode 100644 index 0000000..cbd883b --- /dev/null +++ b/benchmarks/antialias_best.rb @@ -0,0 +1,13 @@ +require 'squib' + +Squib::Deck.new(cards: 200, config: 'antialias_best.yml') do + background color: :white + # alphabet = 'a'.upto('z').to_a.join + ' ' + 'A'.upto('Z').to_a.join + ' ' + # text str: alphabet * 36 , font: 'Sans Bold 18', width: 825, height: 1125, hint: :red + 0.upto(500).each do |i| + circle radius: 50, + x: (i % 17) * 50, + y: (i / 17) * 50 + end + save_png prefix: 'antialias_best_' +end \ No newline at end of file diff --git a/benchmarks/antialias_best.yml b/benchmarks/antialias_best.yml new file mode 100644 index 0000000..08891ce --- /dev/null +++ b/benchmarks/antialias_best.yml @@ -0,0 +1 @@ +antialias: best \ No newline at end of file diff --git a/benchmarks/antialias_fast.rb b/benchmarks/antialias_fast.rb new file mode 100644 index 0000000..57e5157 --- /dev/null +++ b/benchmarks/antialias_fast.rb @@ -0,0 +1,13 @@ +require 'squib' + +Squib::Deck.new(cards: 200, config: 'antialias_fast.yml') do + background color: :white + # alphabet = 'a'.upto('z').to_a.join + ' ' + 'A'.upto('Z').to_a.join + ' ' + # text str: alphabet * 36 , font: 'Sans Bold 18', width: 825, height: 1125, hint: :red + 0.upto(500).each do |i| + circle radius: 50, + x: (i % 17) * 50, + y: (i / 17) * 50 + end + save_png prefix: 'antialias_fast_' +end \ No newline at end of file diff --git a/benchmarks/antialias_fast.yml b/benchmarks/antialias_fast.yml new file mode 100644 index 0000000..5f56341 --- /dev/null +++ b/benchmarks/antialias_fast.yml @@ -0,0 +1 @@ +antialias: fast \ No newline at end of file diff --git a/lib/squib/card.rb b/lib/squib/card.rb index f0c0a9e..60478e1 100644 --- a/lib/squib/card.rb +++ b/lib/squib/card.rb @@ -25,6 +25,7 @@ module Squib @svgfile = "#{deck.dir}/#{deck.prefix}#{deck.count_format % index}.svg" @cairo_surface = make_surface(@svgfile, backend) @cairo_context = Squib::Graphics::CairoContextWrapper.new(Cairo::Context.new(@cairo_surface)) + @cairo_context.antialias = @deck.antialias end def make_surface(svgfile, backend) diff --git a/lib/squib/constants.rb b/lib/squib/constants.rb index 8afc749..8b1ad10 100644 --- a/lib/squib/constants.rb +++ b/lib/squib/constants.rb @@ -63,6 +63,7 @@ module Squib # # @api public CONFIG_DEFAULTS = { + 'antialias' => 'best', 'backend' => 'memory', 'count_format' => SYSTEM_DEFAULTS[:count_format], 'custom_colors' => {}, diff --git a/lib/squib/deck.rb b/lib/squib/deck.rb index 693e68d..f047d15 100644 --- a/lib/squib/deck.rb +++ b/lib/squib/deck.rb @@ -30,7 +30,7 @@ module Squib # :nodoc: # @api private - attr_reader :text_hint + attr_reader :text_hint, :antialias # :nodoc: # @api private @@ -58,6 +58,7 @@ module Squib # @param block [Block] the main body of the script. # @api public def initialize(width: 825, height: 1125, cards: 1, dpi: 300, config: 'config.yml', layout: nil, &block) + @antialias = CONFIG_DEFAULTS['antialias'] @dpi = dpi @font = SYSTEM_DEFAULTS[:default_font] @cards = [] @@ -109,6 +110,7 @@ module Squib @dir = config['dir'] @prefix = config['prefix'] @count_format = config['count_format'] + @antialias = config['antialias'] end end diff --git a/lib/squib/graphics/cairo_context_wrapper.rb b/lib/squib/graphics/cairo_context_wrapper.rb index b65bbd0..b745f57 100644 --- a/lib/squib/graphics/cairo_context_wrapper.rb +++ b/lib/squib/graphics/cairo_context_wrapper.rb @@ -15,7 +15,8 @@ module Squib :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, :clip, :transform, :mask, :create_pango_layout + :operator=, :show_page, :clip, :transform, :mask, :create_pango_layout, + :antialias= def set_source_squibcolor(arg) if match = arg.match(LINEAR_GRADIENT) diff --git a/lib/squib/project_template/config.yml b/lib/squib/project_template/config.yml index 9b0b983..6ffa218 100644 --- a/lib/squib/project_template/config.yml +++ b/lib/squib/project_template/config.yml @@ -4,6 +4,9 @@ # Default: 300 #dpi: 72 +#antialias: best #recommended. Only about 10% slower than fast +#antialias: default # set the anti-aliasing algorithm. default defers to the underlying graphics device. See http://www.cairographics.org/manual/cairo-cairo-t.html#cairo-antialias-t + # Text hints are used to show the boundaries of text boxes. # Can be enabled/disabled at the command-level, or set globally with `set` #text_hint: '#F00' @@ -21,5 +24,4 @@ # Use a SVG cairo back end, instead of an in-memory buffer # backend: :memory # default -# backend: :svg # slower, but can create scalable pdfs -# tmp_dir: _tmp # for SVG backend, you MUST specify a tmp_dir +# backend: :svg # can create scalable pdfs, but rendering done at the printer level is not as good as Cairo. diff --git a/spec/data/samples/autoscale_font.rb.txt b/spec/data/samples/autoscale_font.rb.txt index 7d3a9ae..c1637e3 100644 --- a/spec/data/samples/autoscale_font.rb.txt +++ b/spec/data/samples/autoscale_font.rb.txt @@ -1,3 +1,6 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/basic.rb.txt b/spec/data/samples/basic.rb.txt index 4613f72..8c0d01b 100644 --- a/spec/data/samples/basic.rb.txt +++ b/spec/data/samples/basic.rb.txt @@ -1,3 +1,6 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/cairo_access.rb.txt b/spec/data/samples/cairo_access.rb.txt index 21cd171..f4d1ae1 100644 --- a/spec/data/samples/cairo_access.rb.txt +++ b/spec/data/samples/cairo_access.rb.txt @@ -1,3 +1,5 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/csv_import.rb.txt b/spec/data/samples/csv_import.rb.txt index 02f6e7c..49560d5 100644 --- a/spec/data/samples/csv_import.rb.txt +++ b/spec/data/samples/csv_import.rb.txt @@ -1,3 +1,5 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/custom_config.rb.txt b/spec/data/samples/custom_config.rb.txt index 0f260d5..f1cf298 100644 --- a/spec/data/samples/custom_config.rb.txt +++ b/spec/data/samples/custom_config.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color(["#ccc"]) cairo: paint([]) diff --git a/spec/data/samples/draw_shapes.rb.txt b/spec/data/samples/draw_shapes.rb.txt index 9753850..c036206 100644 --- a/spec/data/samples/draw_shapes.rb.txt +++ b/spec/data/samples/draw_shapes.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: rounded_rectangle([300, 300, 400, 400, 0, 0]) cairo: set_source_color([:red]) diff --git a/spec/data/samples/excel.rb.txt b/spec/data/samples/excel.rb.txt index eaef22e..be26316 100644 --- a/spec/data/samples/excel.rb.txt +++ b/spec/data/samples/excel.rb.txt @@ -1,3 +1,6 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/gradients.rb.txt b/spec/data/samples/gradients.rb.txt index 1eea627..1b45ff3 100644 --- a/spec/data/samples/gradients.rb.txt +++ b/spec/data/samples/gradients.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source([LinearPattern]) cairo: paint([]) diff --git a/spec/data/samples/hello_world.rb.txt b/spec/data/samples/hello_world.rb.txt index 0889219..fb05142 100644 --- a/spec/data/samples/hello_world.rb.txt +++ b/spec/data/samples/hello_world.rb.txt @@ -1,3 +1,5 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:black]) cairo: translate([0, 0]) diff --git a/spec/data/samples/load_images.rb.txt b/spec/data/samples/load_images.rb.txt index 1d7a912..7302a6b 100644 --- a/spec/data/samples/load_images.rb.txt +++ b/spec/data/samples/load_images.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color(["#0b7c8e"]) cairo: paint([]) diff --git a/spec/data/samples/portrait-landscape.rb.txt b/spec/data/samples/portrait-landscape.rb.txt index d373add..1c30cdf 100644 --- a/spec/data/samples/portrait-landscape.rb.txt +++ b/spec/data/samples/portrait-landscape.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color(["#aaa"]) cairo: paint([]) @@ -20,6 +21,7 @@ cairo: update_pango_layout([MockDouble]) cairo: show_pango_layout([MockDouble]) cairo: restore([]) surface: write_to_png(["_output/portrait_00.png"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color(["#aaa"]) cairo: paint([]) diff --git a/spec/data/samples/ranges.rb.txt b/spec/data/samples/ranges.rb.txt index fdf1af4..3ad9ee2 100644 --- a/spec/data/samples/ranges.rb.txt +++ b/spec/data/samples/ranges.rb.txt @@ -1,3 +1,6 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/saves.rb.txt b/spec/data/samples/saves.rb.txt index bcb1d50..be85d73 100644 --- a/spec/data/samples/saves.rb.txt +++ b/spec/data/samples/saves.rb.txt @@ -1,3 +1,19 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:gray]) cairo: paint([]) diff --git a/spec/data/samples/showcase.rb.txt b/spec/data/samples/showcase.rb.txt index 8f5b61a..e5a2d28 100644 --- a/spec/data/samples/showcase.rb.txt +++ b/spec/data/samples/showcase.rb.txt @@ -1,3 +1,7 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color(["#CE534D"]) cairo: paint([]) diff --git a/spec/data/samples/text_options.rb.txt b/spec/data/samples/text_options.rb.txt index 6a98ccd..44614d3 100644 --- a/spec/data/samples/text_options.rb.txt +++ b/spec/data/samples/text_options.rb.txt @@ -1,3 +1,6 @@ +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/tgc_proofs.rb.txt b/spec/data/samples/tgc_proofs.rb.txt index 86853a9..9fe175b 100644 --- a/spec/data/samples/tgc_proofs.rb.txt +++ b/spec/data/samples/tgc_proofs.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color([:white]) cairo: paint([]) diff --git a/spec/data/samples/units.rb.txt b/spec/data/samples/units.rb.txt index 1c166d7..983345d 100644 --- a/spec/data/samples/units.rb.txt +++ b/spec/data/samples/units.rb.txt @@ -1,3 +1,4 @@ +cairo: antialias=(["best"]) cairo: save([]) cairo: set_source_color(["#ddd"]) cairo: paint([]) diff --git a/spec/graphics/graphics_images_spec.rb b/spec/graphics/graphics_images_spec.rb index 97350f2..4677b0b 100644 --- a/spec/graphics/graphics_images_spec.rb +++ b/spec/graphics/graphics_images_spec.rb @@ -15,10 +15,13 @@ describe Squib::Card do allow(@deck).to receive(:dir).and_return('_output') allow(@deck).to receive(:count_format).and_return('%02d') allow(@deck).to receive(:prefix).and_return('card_') + allow(@deck).to receive(:antialias).and_return('best') + end context '#png' do it 'makes all the expected calls on a smoke test' do + expect(@context).to receive(:antialias=).with('best') expect(@context).to receive(:save).once expect(@context).to receive(:translate).with(-37, -38).once expect(@context).to receive(:rotate).with(0.0).once @@ -45,6 +48,7 @@ describe Squib::Card do it 'makes all the expected calls on a smoke test' do expect(@svg).to receive(:width).and_return(100).twice expect(@svg).to receive(:height).and_return(100).twice + expect(@context).to receive(:antialias=).with('best').once expect(@context).to receive(:save).once expect(@context).to receive(:rotate).with(0.0).once expect(@context).to receive(:translate).with(37, 38).once diff --git a/spec/graphics/graphics_save_doc_spec.rb b/spec/graphics/graphics_save_doc_spec.rb index 0d5bd01..5249d58 100644 --- a/spec/graphics/graphics_save_doc_spec.rb +++ b/spec/graphics/graphics_save_doc_spec.rb @@ -20,13 +20,14 @@ describe Squib::Deck, '#save_pdf' do before(:each) do allow(Cairo::PDFSurface).to receive(:new).and_return(nil) #don't create the file allow(Cairo::Context).to receive(:new).and_return(cxt) + allow(cxt).to receive(:antialias=) end it 'make all the expected calls on a smoke test' do num_cards = 9 deck = Squib::Deck.new(cards: 9, width: 825, height: 1125) - expect(Squib.logger).to receive(:debug).at_least(:once) expect(deck).to receive(:dirify) { |arg| arg } #don't create the dir + expect(Squib.logger).to receive(:debug).at_least(:once) expect_card_place(75, 75) expect_card_place(831, 75) diff --git a/spec/graphics/graphics_shapes_spec.rb b/spec/graphics/graphics_shapes_spec.rb index 1a33c6b..1f8dba8 100644 --- a/spec/graphics/graphics_shapes_spec.rb +++ b/spec/graphics/graphics_shapes_spec.rb @@ -18,10 +18,12 @@ describe Squib::Card do allow(@deck).to receive(:dir).and_return('_output') allow(@deck).to receive(:count_format).and_return('%02d') allow(@deck).to receive(:prefix).and_return('card_') + allow(@deck).to receive(:antialias).and_return('best') end context 'rect' do it 'make all the expected calls on a smoke test' do + expect(@context).to receive(:antialias=).with('best') expect(@context).to receive(:save).once expect(@context).to receive(:rounded_rectangle).with(37, 38, 50, 100, 10, 15).twice expect_stroke('#fff', '#f00', 2.0) @@ -36,6 +38,7 @@ describe Squib::Card do context 'circle' do it 'make all the expected calls on a smoke test' do + expect(@context).to receive(:antialias=).with('best') expect(@context).to receive(:save).once expect(@context).to receive(:move_to).with(137, 38) expect(@context).to receive(:circle).with(37, 38, 100).twice @@ -51,6 +54,7 @@ describe Squib::Card do context 'triangle' do it 'make all the expected calls on a smoke test' do + expect(@context).to receive(:antialias=).with('best') expect(@context).to receive(:save).once expect(@context).to receive(:triangle).with(1, 2, 3, 4, 5, 6).twice expect_stroke('#fff', '#f00', 2.0) @@ -63,6 +67,7 @@ describe Squib::Card do context 'line' do it 'make all the expected calls on a smoke test' do + expect(@context).to receive(:antialias=).with('best') expect(@context).to receive(:save).once expect(@context).to receive(:move_to).with(1, 2).once expect(@context).to receive(:line_to).with(3, 4).once diff --git a/spec/graphics/graphics_text_spec.rb b/spec/graphics/graphics_text_spec.rb index 11584f6..3630051 100644 --- a/spec/graphics/graphics_text_spec.rb +++ b/spec/graphics/graphics_text_spec.rb @@ -14,11 +14,13 @@ describe Squib::Card, '#text' do allow(deck).to receive(:dir).and_return('_output') allow(deck).to receive(:count_format).and_return('%02d') allow(deck).to receive(:prefix).and_return('card_') + allow(deck).to receive(:antialias).and_return('best') end it 'make all the expected calls on a smoke test' do extent = Pango::Rectangle.new(50,60,100,200) expect(Squib.logger).to receive(:debug).once + expect(context).to receive(:antialias=).with('best').once expect(context).to receive(:save).once expect(context).to receive(:set_source_color).once expect(context).to receive(:move_to).with(10, 15).once @@ -67,6 +69,7 @@ describe Squib::Card, '#text' do allow(deck).to receive(:dir).and_return('_output') allow(deck).to receive(:count_format).and_return('%02d') allow(deck).to receive(:prefix).and_return('card_') + allow(deck).to receive(:antialias).and_return('best') end it 'aligns right with strings' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f7c491c..3c1e078 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -76,7 +76,8 @@ 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 clip transform mask rectangle reset_clip).each do |m| + triangle line_to operator= show_page clip transform mask rectangle + reset_clip antialias=).each do |m| allow(cxt).to receive(m) { |*args| strio << scrub_hex("cairo: #{m}(#{args})\n") } end