Compass/compass

View on GitHub
core/lib/compass/core/sass_extensions/functions/image_size.rb

Summary

Maintainability
A
2 hrs
Test Coverage
module Compass::Core::SassExtensions::Functions::ImageSize
  KNOWN_TYPES = %w(png gif jpg jpeg)

  # Returns the width of the image relative to the images directory
  def image_width(image_file)
    width, _ = image_dimensions(image_file)
    number(width, "px")
  end
  
  # Returns the height of the image relative to the images directory
  def image_height(image_file)
    _, height = image_dimensions(image_file)
    number(height, "px")
  end

  class ImageProperties
    def initialize(file)
      @file = (file.respond_to?(:to_path) ? file.to_path : file)
      @file_type = File.extname(@file)[1..-1].downcase
      unless KNOWN_TYPES.include?(@file_type)
        raise Sass::SyntaxError, "Unrecognized file type: #{@file_type}"
      end
    end

    def size
      @dimensions ||= send(:"get_size_for_#{@file_type}")
    end

  private
    def get_size_for_png
      File.open(@file, "rb") {|io| io.read}[0x10..0x18].unpack('NN')
    end

    def get_size_for_gif
      File.open(@file, "rb") {|io| io.read}[6..10].unpack('SS')
    end

    def get_size_for_jpg
      get_size_for_jpeg
    end

    def get_size_for_jpeg
      jpeg = JPEG.new(@file)
      [jpeg.width, jpeg.height]
    end
  end

private

  def image_dimensions(image_file)
    image_file = image_file.respond_to?(:value) ? image_file.value : image_file
    options[:compass] ||= {}
    options[:compass][:image_dimensions] ||= {}
    options[:compass][:image_dimensions][image_file] = ImageProperties.new(image_path_for_size(image_file)).size
  end
  
  def image_path_for_size(image_file)
    if File.exists?(image_file)
      return image_file 
    end
    real_path(image_file)
  end

  def real_path(image_file)
    # Compute the real path to the image on the file stystem if the images_dir is set.
    if Compass.configuration.images_path
      File.join(Compass.configuration.images_path, image_file)
    else
      File.join(Compass.configuration.project_path, image_file)
    end
  end

  class JPEG
    attr_reader :width, :height, :bits

    def initialize(file)
      if file.kind_of? IO
        examine(file)
      else
        File.open(file, 'rb') { |io| examine(io) }
      end
    end

  private
    def examine(io)
      class << io
        unless method_defined?(:readbyte)
          def readbyte
            getc
          end
        end
        def readint; (readbyte << 8) + readbyte; end
        def readframe; read(readint - 2); end
        def readsof; [readint, readbyte, readint, readint, readbyte]; end
        def next
          c = readbyte while c != 0xFF
          c = readbyte while c == 0xFF
          c
        end
      end

      raise 'malformed JPEG!' unless io.readbyte == 0xFF && io.readbyte == 0xD8 # SOI

      while marker = io.next
        case marker
          when 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF # SOF markers
            length, @bits, @height, @width, components = io.readsof
            raise 'malformed JPEG' unless length == 8 + components * 3
          when 0xD9, 0xDA then  break # EOI, SOS
          when 0xFE then @comment = io.readframe # COM
          when 0xE1 then io.readframe # APP1, contains EXIF tag
          else io.readframe # ignore frame
        end
      end
    end
end
end