abstracted out engine for chunkypng

This commit is contained in:
Scott Davis 2011-02-24 01:02:31 -05:00
parent 1c71df3b41
commit 03f4c23c04
4 changed files with 171 additions and 154 deletions

View File

@ -1,7 +1,7 @@
PATH PATH
remote: . remote: .
specs: specs:
compass (0.11.beta.2.153582f) compass (0.11.beta.2.1c71df3)
chunky_png (~> 0.12.0) chunky_png (~> 0.12.0)
sass (>= 3.1.0.alpha.218) sass (>= 3.1.0.alpha.218)

View File

@ -1,6 +1,7 @@
require 'digest/md5' require 'digest/md5'
require 'compass/sass_extensions/sprites/image' require 'compass/sass_extensions/sprites/image'
require 'compass/sass_extensions/sprites/base' require 'compass/sass_extensions/sprites/base'
require 'compass/sass_extensions/sprites/engines/chunky_png_engine'
module Compass::SassExtensions::Functions::Sprites module Compass::SassExtensions::Functions::Sprites
ZERO = Sass::Script::Number::new(0) ZERO = Sass::Script::Number::new(0)
@ -14,63 +15,8 @@ module Compass::SassExtensions::Functions::Sprites
end end
end end
class SpriteMap < Compass::SassExtensions::Sprites::Base class SpriteMap < Compass::SassExtensions::Sprites::ChunkyPngEngine
# Calculates the overal image dimensions
# collects image sizes and input parameters for each sprite
def compute_image_metadata!
@width = 0
init_images
compute_image_positions!
@height = @images.last.top + @images.last.height
end
def init_images
@images = image_names.collect do |relative_file|
image = Compass::SassExtensions::Sprites::Image.new(relative_file, options)
@width = [ @width, image.width + image.offset ].max
image
end
end
def compute_image_positions!
@images.each_with_index do |image, index|
image.left = image.position.unit_str == "%" ? (@width - image.width) * (image.position.value / 100) : image.position.value
next if index == 0
last_image = @images[index-1]
image.top = last_image.top + last_image.height + [image.spacing, last_image.spacing].max
end
end
def image_for(name)
@images.detect { |img| img.name == name}
end
def require_png_library!
begin
require 'oily_png'
rescue LoadError
require 'chunky_png'
end
end
# Returns a PNG object
def construct_sprite
require_png_library!
output_png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
images.each do |image|
input_png = ChunkyPNG::Image.from_file(image.file)
if image.repeat == "no-repeat"
output_png.replace input_png, image.left, image.top
else
x = image.left - (image.left / image.width).ceil * image.width
while x < width do
output_png.replace input_png, x, image.top
x += image.width
end
end
end
output_png
end
end end
# Creates a SpriteMap object. A sprite map, when used in a property is the same # Creates a SpriteMap object. A sprite map, when used in a property is the same

View File

@ -1,121 +1,156 @@
module Compass module Compass
module SassExtensions module SassExtensions
module Sprites module Sprites
class Base < Sass::Script::Literal module Base
# Changing this string will invalidate all previously generated sprite images. module ClassMethods
# We should do so only when the packing algorithm changes def from_uri(uri, context, kwargs)
SPRITE_VERSION = "1" path, name = Compass::Sprites.path_and_name(uri.value)
sprites = Compass::Sprites.discover_sprites(uri.value).map do |sprite|
attr_accessor :image_names, :path, :name, :options sprite.gsub(Compass.configuration.images_path+"/", "")
attr_accessor :images, :width, :height end
new(sprites, path, name, context, kwargs)
def self.from_uri(uri, context, kwargs)
path, name = Compass::Sprites.path_and_name(uri.value)
sprites = Compass::Sprites.discover_sprites(uri.value).map do |sprite|
sprite.gsub(Compass.configuration.images_path+"/", "")
end end
new(sprites, path, name, context, kwargs)
end end
#instance Methods
module InstanceMethods
def initialize(image_names, path, name, context, options) def self.included(base)
@image_names, @path, @name, @options = image_names, path, name, options base.extend(ClassMethods)
@images = nil end
@width = nil
@height = nil
@evaluation_context = context
validate!
compute_image_metadata!
end
# Calculate the size of the sprite # Changing this string will invalidate all previously generated sprite images.
def size # We should do so only when the packing algorithm changes
[width, height] SPRITE_VERSION = "1"
end
def sprite_names attr_accessor :image_names, :path, :name, :options
image_names.map{|f| Compass::Sprites.sprite_name(f) } attr_accessor :images, :width, :height
end
def validate!
for sprite_name in sprite_names def initialize(image_names, path, name, context, options)
unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/ @image_names, @path, @name, @options = image_names, path, name, options
raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier" @images = nil
@width = nil
@height = nil
@evaluation_context = context
validate!
compute_image_metadata!
end
# Calculate the size of the sprite
def size
[width, height]
end
# Calculates the overal image dimensions
# collects image sizes and input parameters for each sprite
def compute_image_metadata!
@width = 0
init_images
compute_image_positions!
@height = @images.last.top + @images.last.height
end
def init_images
@images = image_names.collect do |relative_file|
image = Compass::SassExtensions::Sprites::Image.new(relative_file, options)
@width = [ @width, image.width + image.offset ].max
image
end end
end end
end # Calculates the overal image dimensions
# collects image sizes and input parameters for each sprite
# The on-the-disk filename of the sprite def compute_image_positions!
def filename @images.each_with_index do |image, index|
File.join(Compass.configuration.images_path, "#{path}-#{uniqueness_hash}.png") image.left = image.position.unit_str == "%" ? (@width - image.width) * (image.position.value / 100) : image.position.value
end next if index == 0
last_image = @images[index-1]
# Calculates the overal image dimensions image.top = last_image.top + last_image.height + [image.spacing, last_image.spacing].max
# collects image sizes and input parameters for each sprite end
def compute_image_metadata!
end
# Generate a sprite image if necessary
def generate
if generation_required?
sprite_data = construct_sprite
save!(sprite_data)
Compass.configuration.run_callback(:sprite_generated, sprite_data)
end end
end
def generation_required? def image_for(name)
!File.exists?(filename) || outdated? @images.detect { |img| img.name == name}
end end
def uniqueness_hash def sprite_names
@uniqueness_hash ||= begin image_names.map{|f| Compass::Sprites.sprite_name(f) }
sum = Digest::MD5.new end
sum << SPRITE_VERSION
sum << path def validate!
images.each do |image| for sprite_name in sprite_names
[:relative_file, :height, :width, :repeat, :spacing, :position, :digest].each do |attr| unless sprite_name =~ /\A#{Sass::SCSS::RX::IDENT}\Z/
sum << image.send(attr).to_s raise Sass::SyntaxError, "#{sprite_name} must be a legal css identifier"
end end
end end
sum.hexdigest[0...10]
end end
@uniqueness_hash
end
# saves the sprite for later retrieval # The on-the-disk filename of the sprite
def save!(output_png) def filename
saved = output_png.save filename File.join(Compass.configuration.images_path, "#{path}-#{uniqueness_hash}.png")
Compass.configuration.run_callback(:sprite_saved, filename) end
saved
end
# All the full-path filenames involved in this sprite # Generate a sprite image if necessary
def image_filenames def generate
image_names.map do |image_name| if generation_required?
File.join(Compass.configuration.images_path, image_name) sprite_data = construct_sprite
save!(sprite_data)
Compass.configuration.run_callback(:sprite_generated, sprite_data)
end
end
def generation_required?
!File.exists?(filename) || outdated?
end
def uniqueness_hash
@uniqueness_hash ||= begin
sum = Digest::MD5.new
sum << SPRITE_VERSION
sum << path
images.each do |image|
[:relative_file, :height, :width, :repeat, :spacing, :position, :digest].each do |attr|
sum << image.send(attr).to_s
end
end
sum.hexdigest[0...10]
end
@uniqueness_hash
end
# saves the sprite for later retrieval
def save!(output_png)
saved = output_png.save filename
Compass.configuration.run_callback(:sprite_saved, filename)
saved
end
# All the full-path filenames involved in this sprite
def image_filenames
image_names.map do |image_name|
File.join(Compass.configuration.images_path, image_name)
end
end
def inspect
to_s
end
def to_s(options = self.options)
sprite_url(self).value
end
def respond_to?(meth)
super || @evaluation_context.respond_to?(meth)
end
def method_missing(meth, *args, &block)
if @evaluation_context.respond_to?(meth)
@evaluation_context.send(meth, *args, &block)
else
super
end
end end
end end
def inspect
to_s
end
def to_s(options = self.options)
sprite_url(self).value
end
def respond_to?(meth)
super || @evaluation_context.respond_to?(meth)
end
def method_missing(meth, *args, &block)
if @evaluation_context.respond_to?(meth)
@evaluation_context.send(meth, *args, &block)
else
super
end
end
end end
end end
end end

View File

@ -0,0 +1,36 @@
module Compass
module SassExtensions
module Sprites
class ChunkyPngEngine < Sass::Script::Literal
include Compass::SassExtensions::Sprites::Base::InstanceMethods
def require_png_library!
begin
require 'oily_png'
rescue LoadError
require 'chunky_png'
end
end
# Returns a PNG object
def construct_sprite
require_png_library!
output_png = ChunkyPNG::Image.new(width, height, ChunkyPNG::Color::TRANSPARENT)
images.each do |image|
input_png = ChunkyPNG::Image.from_file(image.file)
if image.repeat == "no-repeat"
output_png.replace input_png, image.left, image.top
else
x = image.left - (image.left / image.width).ceil * image.width
while x < width do
output_png.replace input_png, x, image.top
x += image.width
end
end
end
output_png
end
end
end
end
end