forked from OpenNeo/impress
Remove outfit image saving
This has already been moved to Impress 2020 too, so we can delete all the image generation and saving!
This commit is contained in:
parent
e121d8bba2
commit
8ea74b737e
3 changed files with 0 additions and 172 deletions
|
@ -13,13 +13,6 @@ class Outfit < ActiveRecord::Base
|
|||
|
||||
scope :wardrobe_order, -> { order('starred DESC', :name) }
|
||||
|
||||
# NOTE: We no longer save images, but we've left the code here for now.
|
||||
# The `image` method below simulates the previous API for the rest
|
||||
# of the app!
|
||||
# mount_uploader :image, OutfitImageUploader
|
||||
# before_save :update_enqueued_image
|
||||
# after_commit :enqueue_image!
|
||||
|
||||
class OutfitImage
|
||||
def initialize(image_versions)
|
||||
@image_versions = image_versions
|
||||
|
@ -126,49 +119,6 @@ class Outfit < ActiveRecord::Base
|
|||
end
|
||||
self.item_outfit_relationships = new_rels
|
||||
end
|
||||
|
||||
# Returns the array of SwfAssets representing each layer of the output image,
|
||||
# ordered from bottom to top. Careful: this method is memoized, so if the
|
||||
# image layers change after its first call we'll get bad results.
|
||||
def image_layers
|
||||
@image_layers ||= visible_assets_with_images.sort { |a, b| a.depth <=> b.depth }
|
||||
end
|
||||
|
||||
# Creates and writes the thumbnail images for this outfit iff the new image
|
||||
# would be different than the current one. (Writes to file in development,
|
||||
# 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!
|
||||
if image_layers_dirty?
|
||||
image = Tempfile.open(['outfit_image', '.png'])
|
||||
begin
|
||||
create_image! image
|
||||
self.image_layers_hash = generate_image_layers_hash
|
||||
self.image = image
|
||||
self.image_enqueued = false
|
||||
save!
|
||||
ensure
|
||||
image.close!
|
||||
end
|
||||
end
|
||||
|
||||
self.image
|
||||
end
|
||||
|
||||
# Enqueue an image write iff the new image would be different than the
|
||||
# current one.
|
||||
def enqueue_image!
|
||||
Resque.enqueue(OutfitImageUpdate, id)
|
||||
end
|
||||
|
||||
def update_enqueued_image
|
||||
self.image_enqueued = (image_layers_dirty?)
|
||||
true
|
||||
end
|
||||
|
||||
def s3_key(size)
|
||||
URI.encode("#{id}/#{size.join 'x'}.png")
|
||||
end
|
||||
|
||||
def self.build_for_user(user, params)
|
||||
Outfit.new.tap do |outfit|
|
||||
|
@ -183,94 +133,4 @@ class Outfit < ActiveRecord::Base
|
|||
outfit.attributes = params
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Creates a 600x600 PNG image of this outfit, writing to the given output
|
||||
# file.
|
||||
def create_image!(output)
|
||||
unless image_layers.empty?
|
||||
temp_image_files = Parallel.map(image_layers, :in_threads => 8) do |swf_asset|
|
||||
image_file = Tempfile.open(['outfit_layer', '.png'])
|
||||
begin
|
||||
write_temp_swf_asset_image!(swf_asset, image_file)
|
||||
rescue RightAws::AwsError
|
||||
nil # skip broken images
|
||||
else
|
||||
image_file
|
||||
ensure
|
||||
image_file.close
|
||||
end
|
||||
end.compact # remove nils for broken images
|
||||
|
||||
# Here we do some awkwardness to get the exact ImageMagick command we
|
||||
# want, though it's still less awkward than handling the command
|
||||
# ourselves. Give all of the temporary images as input, flatten them and
|
||||
# write them to the output path.
|
||||
command = MiniMagick::CommandBuilder.new('convert')
|
||||
temp_image_files.each { |image_file| command.push image_file.path }
|
||||
command.layers 'flatten'
|
||||
command.push output.path
|
||||
|
||||
# Though the above command really is sufficient, we still need a dummy
|
||||
# image to handle execution.
|
||||
output_image = MiniMagick::Image.new(output.path)
|
||||
output_image.run(command)
|
||||
|
||||
temp_image_files.each(&:unlink)
|
||||
else
|
||||
output.close
|
||||
end
|
||||
end
|
||||
|
||||
def visible_assets
|
||||
biology_assets = pet_state.swf_assets.includes(:zone)
|
||||
object_assets = SwfAsset.object_assets.
|
||||
fitting_body_id(pet_state.pet_type.body_id).for_item_ids(worn_item_ids).
|
||||
includes(:zone)
|
||||
|
||||
# Now for fun with bitmasks! Rather than building a bunch of integer arrays
|
||||
# here, we instead go low-level and use bit-level operations. Build the
|
||||
# bitmask by parsing the binary string (reversing it to get the lower zone
|
||||
# numbers on the right), then OR them all together to get the mask
|
||||
# representing all the restricted zones. (Note to self: why not just store
|
||||
# in this format in the first place?)
|
||||
restrictors = biology_assets + worn_items
|
||||
restricted_zones_mask = restrictors.inject(0) do |mask, restrictor|
|
||||
mask | restrictor.zones_restrict.reverse.to_i(2)
|
||||
end
|
||||
|
||||
# Now, check each asset's zone is not restricted in the bitmask using
|
||||
# bitwise operations: shift 1 to the zone_id position, then AND it with
|
||||
# the restricted zones mask. If we get 0, then the bit for that zone ID was
|
||||
# not turned on, so the zone is not restricted and this asset is visible.
|
||||
all_assets = biology_assets + object_assets
|
||||
all_assets.select { |a| (1 << (a.zone_id - 1)) & restricted_zones_mask == 0 }
|
||||
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]
|
||||
def write_temp_swf_asset_image!(swf_asset, file)
|
||||
key = swf_asset.s3_key(IMAGE_BASE_SIZE)
|
||||
bucket = SwfAsset::IMAGE_BUCKET
|
||||
data = bucket.get(key)
|
||||
file.binmode # write in binary mode
|
||||
file.truncate(0) # clear the file
|
||||
file.write data # write the new data
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
require 'timeout'
|
||||
|
||||
class OutfitImageUpdate
|
||||
TIMEOUT_IN_SECONDS = 30
|
||||
|
||||
@queue = :outfit_image_updates
|
||||
|
||||
def self.perform(id)
|
||||
Timeout::timeout(TIMEOUT_IN_SECONDS) do
|
||||
Outfit.find(id).write_image!
|
||||
end
|
||||
end
|
||||
|
||||
# Represents an outfit image update for an outfit that existed before this
|
||||
# feature was built. Its queue has a lower priority, so new outfits will
|
||||
# be updated before retroactively converted outfits.
|
||||
class Retroactive < OutfitImageUpdate
|
||||
@queue = :retroactive_outfit_image_updates
|
||||
end
|
||||
end
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
namespace :outfits do
|
||||
desc 'Retroactively enqueue image updates for outfits saved to user accounts'
|
||||
task :retroactively_enqueue => :environment do
|
||||
outfits = Outfit.select([:id]).where('image IS NULL AND user_id IS NOT NULL')
|
||||
puts "Enqueuing #{outfits.count} outfits"
|
||||
outfits.find_each do |outfit|
|
||||
Resque.enqueue(OutfitImageUpdate::Retroactive, outfit.id)
|
||||
end
|
||||
puts "Successfully enqueued."
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue