=begin rdoc An Image is a Document with a file that we can view inline. An image can be displayed in various formats (defined through modes). These modes are defined for each Site through Iformat. Default modes: 'tiny' => { :size=>:force, :width=>16, :height=>16, }, 'mini' => { :size=>:force, :width=>32, :ratio=>1.0, }, 'pv' => { :size=>:force, :width=>70, :ratio=>1.0 }, 'med' => { :size=>:limit, :width=>280, :ratio=>2/3.0 }, 'top' => { :size=>:force, :width=>280, :ratio=>2.0/3.0, :gravity=>Magick::NorthGravity}, 'mid' => { :size=>:force, :width=>280, :ratio=>2.0/3.0, :gravity=>Magick::CenterGravity}, 'low' => { :size=>:force, :width=>280, :ratio=>2.0/3.0, :gravity=>Magick::SouthGravity}, 'edit' => { :size=>:limit, :width=>400, :height=>400 }, 'std' => { :size=>:limit, :width=>600, :ratio=>2/3.0 }, 'full' => { :size=>:keep }, To display an image with one of those formats, you use the 'img_tag' helper : img_tag(@node, :mode=>'med') For more information on img_tag, have a look at ApplicationHelper#img_tag. An image can be croped by changing the 'crop' pseudo attribute (see Image#crop= ) : @node.update_attributes(:crop=>{:x=>10, :y=>10, :width=>30, :height=>60}) === Version The version class used by images is the ImageVersion. === Storage File data is managed by the Document Attachment. This class is responsible for storing the file and retrieving the data. == Properties These properties are added to Images : size(format):: file size for the image at the given format width(format):: image width in pixel for the given format height(format):: image height in pixel for the given format === links Default links for Image are: icon_for:: become the unique 'icon' for the linked node. Example on how to use 'icon' with ruby: @node.icon.img_tag('pv') <= display the node's icon with the 'pv' (preview) format. Same example in a zafu template: or to create a link to the article using the icon: =end class Image < Document include Zena::Use::Upload::UploadedFile after_save :clear_new_image property do |t| t.integer 'width' t.integer 'height' t.text 'exif' end safe_property :width, :height # We need ExifData to be loaded now (not a string) because JSON unpacking will not auto-load. safe_method :exif => ExifData class << self def accept_content_type?(content_type) Zena::Use::ImageBuilder.image_content_type?(content_type) end # Class list to which this class can change to def change_to_classes_for_form classes_for_form(:class => 'Image') end end # Return the width in pixels for an image at the given format. def width(format=nil) if format.nil? || format[:size] == :keep prop['width'] elsif format[:size] == :force format[:width] else if img = image_with_format(format) img.width else nil end end end # Return the height in pixels for an image at the given format. def height(format=nil) if format.nil? || format[:size] == :keep prop['height'] elsif format[:size] == :force format[:height] else if img = image_with_format(format) img.height else nil end end end # Return the Exchangeable Image Format (Exif). def exif prop['exif'] || ExifData.new({}) end # Return the size of the image for the given format (see Image for information on format). def filesize(format=nil) version.filesize(format) end # Updaging image attributes and propreties. Accept also :file and :crop keys. def update_attributes(attributes) attributes.stringify_keys! # If file and crop attributes are both present when updating, make sure to run file= before crop=. if attributes['file'] && attributes['crop'] file = attributes.delete('file') crop = attributes.delete('crop') super(attributes) self.file = file self.crop = crop save else super end end # Set content file, will refuse to accept the file if it is not an image. def file=(file) new_file = super if self.class.accept_content_type?(new_file.content_type) @new_image = new_file img = image_with_format(nil) prop['width' ] = img.width prop['height'] = img.height prop['exif'] = img.exif rescue nil end end # Return a file with the data for the given format. It is the receiver's responsability to close the file. def file(format=nil) if format.nil? || format[:size] == :keep super() else if File.exist?(self.filepath(format)) || make_image(format) File.new(self.filepath(format)) else nil end end end def can_crop?(format) x, y, w, h = [format['x'].to_i, 0].max, [format['y'].to_i, 0].max, [format['w'].to_i, width].min, [format['h'].to_i, height].min (format['max_value'] && (format['max_value'].to_f * (format['max_unit'] == 'Mb' ? 1024 : 1) * 1024) < prop['size']) || (format['format'] && format['format'] != prop['ext']) || ((x < width && y < height && w > 0 && h > 0) && !(x==0 && y==0 && w == width && h == height)) end # Crop the image using the 'crop' hash with the top left corner position (:x, :y) and the width and height (:width, :heigt). Example: # @node.crop = {:x=>10, :y=>10, :width=>30, :height=>60} # Be carefull as this method changes the current file. So you should make a backup version before croping the image (the popup editor displays a warning). def crop=(format) if can_crop?(format) # do crop if file = self.cropped_file(format) # crop can return nil, check first. self.file = file end end end # Return a cropped image using the 'crop' hash with the top left corner position (:x, :y) and the width and height (:width, :heigt). def cropped_file(format) original = format['original'] || @loaded_file || self.file x, y, w, h = format['x'].to_f, format['y'].to_f, format['w'].to_f, format['h'].to_f new_type = format['format'] ? Zena::EXT_TO_TYPE[format['format'].downcase][0] : nil max = format['max_value'].to_f * (format['max_unit'] == 'Mb' ? 1024 : 1) * 1024 # crop image img = Zena::Use::ImageBuilder.new(:file => original) img.crop!(x, y, w, h) if x && y && w && h img.format = format['format'] if new_type && new_type != content_type img.max_filesize = max if format['max_value'] && max file = Tempfile.new(filename) File.open(file.path, "wb") { |f| f.syswrite(img.read) } ctype = Zena::EXT_TO_TYPE[img.format.downcase][0] fname = "#{filename}.#{Zena::TYPE_TO_EXT[ctype][0]}" uploaded_file(file, filename, ctype) end # This is called if the image's width and/or height is nil and image builder could # compute the size. def fix_sizes(w, h) prop['width'] = w prop['height'] = h Zena::Db.set_attribute(version, 'properties', encode_properties(@properties)) end private # Set image event date to when the photo was taken def set_defaults super if event_at.blank? self.event_at = self.exif.date_time end end # This is triggered after create (after the image has been saved but # before the properties are saved with the version). def save_version_after_create set_default_text super end def set_default_text if text.blank? self.text = "!#{zip}!" # We need to rebuild the serialized data in version dump_properties end end # Create a new image in memory with the new format def image_with_format(format=nil) if @new_image Zena::Use::ImageBuilder.new(:file => @new_image).transform!(format) elsif !new_record? format ||= Iformat['full'] @formats ||= {} @formats[format[:name]] ||= Zena::Use::ImageBuilder.new(:path => filepath, :width => prop['width'], :height => prop['height'], :node => self).transform!(format) else raise StandardError, "No image to work on" end end # Create an image with the new format. def make_image(format) return nil unless img = image_with_format(format) return nil if img.dummy? make_file(filepath(format),img) end # Create a file without creating a Version and an Attachment. def make_file(path, data) FileUtils::mkpath(File.dirname(path)) unless File.exist?(File.dirname(path)) File.open(path, "wb") { |f| f.syswrite(data.read) } end # We have to clear the @new_image after save because it points to a temporary file # that can be removed after saving and we could crash during rendering if we wanted # to access the image. def clear_new_image @new_image = nil true end end