basic image thumbnails

This commit is contained in:
Emi Matchu 2012-07-16 16:47:28 -04:00
parent 7c015e2d88
commit cf2546d832
6 changed files with 65 additions and 16 deletions

View file

@ -15,6 +15,8 @@ class Outfit < ActiveRecord::Base
mount_uploader :image, OutfitImageUploader mount_uploader :image, OutfitImageUploader
after_commit :enqueue_image!
def as_json(more_options={}) def as_json(more_options={})
serializable_hash :only => [:id, :name, :pet_state_id, :starred], serializable_hash :only => [:id, :name, :pet_state_id, :starred],
:methods => [:color_id, :species_id, :worn_and_unworn_item_ids] :methods => [:color_id, :species_id, :worn_and_unworn_item_ids]
@ -68,23 +70,35 @@ class Outfit < ActiveRecord::Base
end end
# Returns the array of SwfAssets representing each layer of the output image, # Returns the array of SwfAssets representing each layer of the output image,
# ordered from bottom to top. # ordered from bottom to top. Careful: this method is memoized, so if the
def layered_assets # image layers change after its first call we'll get bad results.
visible_assets.sort { |a, b| a.zone.depth <=> b.zone.depth } def image_layers
@image_layers ||= visible_assets_with_images.sort { |a, b| a.zone.depth <=> b.zone.depth }
end end
# Creates and writes the thumbnail images for this outfit. (Writes to file in # Creates and writes the thumbnail images for this outfit iff the new image
# development, S3 in production.) Runs #save! on the record, so any other # would be different than the current one. (Writes to file in development,
# changes will also be saved. # S3 in production.) If the image is updated, updates the image layers hash
# and runs #save! on the record, so any other changes will also be saved.
def write_image! def write_image!
if image_layers_dirty?
Tempfile.open(['outfit_image', '.png']) do |image| Tempfile.open(['outfit_image', '.png']) do |image|
create_image! image create_image! image
self.image_layers_hash = generate_image_layers_hash
self.image = image self.image = image
save! save!
end end
end
self.image self.image
end end
# Enqueue an image write iff the new image would be different than the
# current one.
def enqueue_image!
Resque.enqueue(OutfitImageUpdate, id) if image_layers_dirty?
end
def s3_key(size) def s3_key(size)
URI.encode("#{id}/#{size.join 'x'}.png") URI.encode("#{id}/#{size.join 'x'}.png")
end end
@ -108,13 +122,13 @@ class Outfit < ActiveRecord::Base
# Creates a 600x600 PNG image of this outfit, writing to the given output # Creates a 600x600 PNG image of this outfit, writing to the given output
# file. # file.
def create_image!(output) def create_image!(output)
layers = self.layered_assets base_layer = image_layers.first
base_layer = layers.shift above_layers = image_layers[1..-1]
write_temp_swf_asset_image! base_layer, output write_temp_swf_asset_image! base_layer, output
output.close output.close
Tempfile.open(['outfit_overlay', '.png']) do |overlay| Tempfile.open(['outfit_overlay', '.png']) do |overlay|
layers.each do |layer| above_layers.each do |layer|
overlay.open overlay.open
write_temp_swf_asset_image! layer, overlay write_temp_swf_asset_image! layer, overlay
overlay.close overlay.close
@ -151,6 +165,21 @@ class Outfit < ActiveRecord::Base
all_assets.select { |a| (1 << (a.zone_id - 1)) & restricted_zones_mask == 0 } all_assets.select { |a| (1 << (a.zone_id - 1)) & restricted_zones_mask == 0 }
end end
def visible_assets_with_images
visible_assets.select(&:has_image?)
end
# Generate 8-char hex digest representing visible image layers for this outfit.
# Hash function should be decently collision-resistant.
def generate_image_layers_hash
@generated_image_layers_hash ||=
Digest::MD5.hexdigest(image_layers.map(&:id).join(',')).first(8)
end
def image_layers_dirty?
generate_image_layers_hash != self.image_layers_hash
end
IMAGE_BASE_SIZE = [600, 600] IMAGE_BASE_SIZE = [600, 600]
def write_temp_swf_asset_image!(swf_asset, file) def write_temp_swf_asset_image!(swf_asset, file)
key = swf_asset.s3_key(IMAGE_BASE_SIZE) key = swf_asset.s3_key(IMAGE_BASE_SIZE)

View file

@ -0,0 +1,8 @@
class OutfitImageUpdate
@queue = :outfit_image_updates
def self.perform(id)
Outfit.find(id).write_image!
end
end

View file

@ -3,7 +3,7 @@
# this to use S3 on all environments for those images only.) # this to use S3 on all environments for those images only.)
CarrierWave.configure do |config| CarrierWave.configure do |config|
if Rails.env.production? || true # REMOVE if Rails.env.production?
s3_config = YAML.load_file Rails.root.join('config', 'aws_s3.yml') s3_config = YAML.load_file Rails.root.join('config', 'aws_s3.yml')
access_key_id = s3_config['access_key_id'] access_key_id = s3_config['access_key_id']
secret_access_key = s3_config['secret_access_key'] secret_access_key = s3_config['secret_access_key']

View file

@ -0,0 +1,9 @@
class AddImageLayersHashToOutfit < ActiveRecord::Migration
def self.up
add_column :outfits, :image_layers_hash, :string, :length => 8
end
def self.down
remove_column :outfits, :image_layers_hash
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20120521164652) do ActiveRecord::Schema.define(:version => 20120716193946) do
create_table "auth_servers", :force => true do |t| create_table "auth_servers", :force => true do |t|
t.string "short_name", :limit => 10, :null => false t.string "short_name", :limit => 10, :null => false
@ -118,6 +118,7 @@ ActiveRecord::Schema.define(:version => 20120521164652) do
t.string "name" t.string "name"
t.boolean "starred", :default => false, :null => false t.boolean "starred", :default => false, :null => false
t.string "image" t.string "image"
t.string "image_layers_hash"
end end
add_index "outfits", ["pet_state_id"], :name => "index_outfits_on_pet_state_id" add_index "outfits", ["pet_state_id"], :name => "index_outfits_on_pet_state_id"

2
public/outfits/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore