diff --git a/features/command_line.feature b/features/command_line.feature index 11580985..9b4485a4 100644 --- a/features/command_line.feature +++ b/features/command_line.feature @@ -204,12 +204,14 @@ Feature: Command Line Given I am using the existing project in test/fixtures/stylesheets/compass When I run: compass grid-img 30+10x24 Then a png file images/grid.png is created + And the image images/grid.png has a size of 40x24 Scenario: Generating a grid image to a specified path with custom dimensions Given I am using the existing project in test/fixtures/stylesheets/compass When I run: compass grid-img 50+10x24 assets/wide_grid.png Then a directory assets is created Then a png file assets/wide_grid.png is created + And the image assets/wide_grid.png has a size of 60x24 Scenario: Generating a grid image with invalid dimensions Given I am using the existing project in test/fixtures/stylesheets/compass diff --git a/features/step_definitions/command_line_steps.rb b/features/step_definitions/command_line_steps.rb index d2f6a0d0..d16999aa 100644 --- a/features/step_definitions/command_line_steps.rb +++ b/features/step_definitions/command_line_steps.rb @@ -225,3 +225,10 @@ Then /^I should see the following "([^"]+)" commands:$/ do |kind, table| commands = @last_command_list.map{|c| c =~ /^\s+\* ([^ ]+)\s+- [A-Z].+$/; [$1]} table.diff!(commands) end + + +Then /^the image ([^ ]+) has a size of (\d+)x(\d+)$/ do |file, width, height| + # see http://snippets.dzone.com/posts/show/805 + IO.read(file)[0x10..0x18].unpack('NN').should == [width.to_i, height.to_i] +end + diff --git a/lib/compass/commands/generate_grid_background.rb b/lib/compass/commands/generate_grid_background.rb index 2bd16d9a..426fbc1e 100644 --- a/lib/compass/commands/generate_grid_background.rb +++ b/lib/compass/commands/generate_grid_background.rb @@ -80,11 +80,7 @@ Options: gutter_width = $2.to_i height = $3.to_i if $3 filename = options[:grid_filename] || projectize("#{project_images_subdirectory}/grid.png") - unless GridBuilder.new(options.merge(:column_width => column_width, :gutter_width => gutter_width, :height => height, :filename => filename, :working_path => self.working_path)).generate! - puts "ERROR: Some library dependencies appear to be missing." - puts "Have you installed rmagick? If not, please run:" - puts "sudo gem install rmagick" - end + GridBuilder.new(options.merge(:column_width => column_width, :gutter_width => gutter_width, :height => height, :filename => filename, :working_path => self.working_path)).generate! end end end diff --git a/lib/compass/grid_builder.rb b/lib/compass/grid_builder.rb index e81d1f6e..af025b26 100644 --- a/lib/compass/grid_builder.rb +++ b/lib/compass/grid_builder.rb @@ -1,21 +1,60 @@ -# This file came from the Blueprint Project -begin - require 'rubygems' - gem 'rmagick' - require 'rvg/rvg' -rescue Exception => e -end +require 'zlib' module Compass - # Uses ImageMagick and RMagick to generate grid.png file - class GridBuilder - include Actions - begin - include Magick - rescue Exception => e + # A simple class to represent and create a PNG-File + # No drawing features given + # Just subclass and write [R,G,B]-Byte-Values into the @data matrix + # Build for compactness, so not much error checking! + # + # Code based on seattlerb's png, see http://seattlerb.rubyforge.org/png/ + class PNG + CRC_TABLE = (0..255).map do |n| + 8.times.inject(n){|x,i| x = ((x & 1) == 1) ? 0xedb88320 ^ (x >> 1) : x >> 1} + end + + class << self + def crc(chunkdata='') + chunkdata.bytes.to_a.inject(0xffffffff){|crc, byte| CRC_TABLE[(crc ^ byte) & 0xff] ^ (crc >> 8) } ^ 0xffffffff + end + + def chunk(type, data="") + [data.size, type, data, crc(type + data)].pack("Na*a*N") + end end + # Initiates a new PNG-Object + # * width: Width of the image in pixels + # * height: Height of the image in pixels + # * background: Background-color represented as [R,G,B]-Byte-Array + def initialize(width, height, background = [255,255,255]) + @height = height + @width = width + @data = Array.new(@height) { |x| Array.new(@width, background) } + end + + BITS = 8 + RGB = 2 # Color Types ( RGBA = 6) + NONE = 0 # Filter + + # binary representation of the PNG, write to file with binary mode + def to_blob + blob = [] + blob << [137, 80, 78, 71, 13, 10, 26, 10].pack("C*") + blob << PNG.chunk('IHDR', [@width, @height, BITS, RGB, NONE, NONE, NONE].pack("N2C5")) + blob << PNG.chunk('IDAT', Zlib::Deflate.deflate(self.png_join)) + blob << PNG.chunk('IEND', '') + blob.join + end + + def png_join + @data.map { |row| "\0" + row.map { |p| "%c%c%c" % p}.join }.join + end + end + + class GridBuilder < PNG + include Actions + attr_reader :column_width, :gutter_width, :filename, :able_to_generate, :options # ==== Options @@ -25,13 +64,14 @@ module Compass # * :height -- Height (in pixels) of a row # * :filename -- Output path of grid.png file def initialize(options={}) - @able_to_generate = Magick::Long_version rescue false - return unless @able_to_generate @column_width = options[:column_width] - @gutter_width = options[:gutter_width] - @height = options[:height] || 20 + gutter_width = options[:gutter_width] + + height = options[:height] || 20 @filename = options[:filename] @options = options + + super(@column_width + gutter_width, height, [0xe9,0xe9,0xe9]) end def working_path @@ -40,20 +80,8 @@ module Compass # generates (overwriting if necessary) grid.png image to be tiled in background def generate! - return false unless self.able_to_generate - total_width = self.column_width + self.gutter_width - RVG::dpi = 100 - - rvg = RVG.new((total_width.to_f/RVG::dpi).in, (@height.to_f/RVG::dpi).in).viewbox(0, 0, total_width, @height) do |canvas| - canvas.background_fill = 'white' - - canvas.g do |column| - column.rect(self.column_width - 1, @height).styles(:fill => "#e8effb") - end - - canvas.g do |baseline| - baseline.line(0, (@height - 1), total_width, (@height- 1)).styles(:fill => "#e9e9e9") - end + (0...@height-1).each do |line| + @data[line] = Array.new(@width){|x| x < @column_width ? [0xe8, 0xef, 0xfb] : [0xff,0xff,0xff] } end if File.exists?(filename) @@ -66,8 +94,8 @@ module Compass end directory File.dirname(filename) logger.record((overwrite ? :overwrite : :create), basename(filename)) - unless options[:dry_run] - rvg.draw.write(filename) + unless options[:dry_run] + write_file(filename, self.to_blob) else true end diff --git a/test/compass_png_test.rb b/test/compass_png_test.rb new file mode 100644 index 00000000..b8fd70c8 --- /dev/null +++ b/test/compass_png_test.rb @@ -0,0 +1,46 @@ +require 'test_helper' +require 'fileutils' + +class CommandLineTest < Test::Unit::TestCase + + def test_class_crc_table + assert_equal 256, Compass::PNG::CRC_TABLE.length + {0 => 0, 1 => 1996959894, 22 => 4107580753, 133 => 2647816111, 255 => 755167117}.each do |i, crc| + assert_equal crc, Compass::PNG::CRC_TABLE[i] + end + end + + def test_class_crc + assert_equal 2666930069, Compass::PNG.crc('foobar') + assert_equal 2035837995, Compass::PNG.crc('A721dasdN') + end + + def test_class_chunk + chunk = Compass::PNG.chunk 'IHDR', [10, 10, 8, 6, 0, 0, 0 ].pack('N2C5') + + header_crc = "\2152\317\275" + header_data = "\000\000\000\n\000\000\000\n\b\006\000\000\000" + header_length = "\000\000\000\r" + + header_chunk = "#{header_length}IHDR#{header_data}#{header_crc}" + + assert_equal header_chunk, chunk + end + + def test_class_chunk_empty + chunk = Compass::PNG.chunk 'IHDR' + expected = "#{0.chr * 4}IHDR#{[Compass::PNG.crc("IHDR")].pack 'N'}" + assert_equal expected, chunk + end + + def test_to_blob + png = Compass::PNG.new(5,10, [255,255,255]) + blob = 'iVBORw0KGgoAAAANSUhEUgAAAAUAAAAKCAIAAADzWwNnAAAAD0lEQVR4nGP4jwoYBhkfALRylWv4Dj0LAAAAAElFTkSuQmCC'.unpack('m*').first + assert_equal blob, png.to_blob + + png = Compass::PNG.new(10,5, [32,64,128]) + blob = 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAFCAIAAADzBuo/AAAAEUlEQVR4nGNQcGjAgxgGUBoALT4rwRTA0gkAAAAASUVORK5CYII='.unpack('m*').first + assert_equal blob, png.to_blob + end + +end \ No newline at end of file