lib/psd/layer.rb in psd-1.3.2 vs lib/psd/layer.rb in psd-1.3.3
- old
+ new
@@ -1,354 +1,76 @@
class PSD
# Represents a single layer and all of the data associated with
# that layer.
class Layer
include Section
+ include BlendModes
+ include BlendingRanges
+ include ChannelImage
+ include Exporting
+ include Helpers
+ include Info
+ include Mask
+ include Name
+ include PathComponents
+ include PositionAndChannels
- attr_reader :id, :mask, :blending_ranges, :adjustments, :channels_info
- attr_reader :blend_mode, :layer_type, :blending_mode, :opacity, :fill_opacity
- attr_reader :channels, :image
+ attr_reader :id, :info_keys
+ attr_accessor :group_layer, :node, :file
- attr_accessor :group_layer
- attr_accessor :top, :left, :bottom, :right, :rows, :cols, :ref_x, :ref_y, :node, :file
-
- alias :info :adjustments
- alias :width :cols
- alias :height :rows
-
- # All of the extra layer info sections that we know how to parse.
- LAYER_INFO = {
- type: TypeTool,
- legacy_type: LegacyTypeTool,
- metadata: MetadataSetting,
- layer_name_source: LayerNameSource,
- object_effects: ObjectEffects,
- name: UnicodeName,
- section_divider: LayerSectionDivider,
- reference_point: ReferencePoint,
- layer_id: LayerID,
- fill_opacity: FillOpacity,
- placed_layer: PlacedLayer,
- vector_mask: VectorMask
- }
-
# Initializes all of the defaults for the layer.
def initialize(file)
@file = file
- @image = nil
+
@mask = {}
@blending_ranges = {}
@adjustments = {}
@channels_info = []
@blend_mode = {}
@group_layer = nil
- @layer_type = 'normal'
@blending_mode = 'normal'
@opacity = 255
- @fill_opacity = 255
# Just used for tracking which layer adjustments we're parsing.
# Not essential.
@info_keys = []
end
# Parse the layer and all of it's sub-sections.
def parse(index=nil)
start_section
- @idx = index
+ @id = index
- parse_info
+ parse_position_and_channels
parse_blend_modes
extra_len = @file.read_int
@layer_end = @file.tell + extra_len
parse_mask_data
parse_blending_ranges
parse_legacy_layer_name
- parse_extra_data
+ parse_layer_info
PSD.logger.debug "Layer name = #{name}"
@file.seek @layer_end # Skip over any filler zeros
end_section
return self
end
- # Export the layer to file. May or may not work.
- def export(outfile)
- export_info(outfile)
-
- @blend_mode.write(outfile)
- @file.seek(@blend_mode.num_bytes, IO::SEEK_CUR)
-
- export_mask_data(outfile)
- export_blending_ranges(outfile)
- export_legacy_layer_name(outfile)
- export_extra_data(outfile)
-
- outfile.write @file.read(end_of_section - @file.tell)
- end
-
# We just delegate this to a normal method call.
def [](val)
self.send(val)
end
- def parse_channel_image!(header, parse)
- @image = ChannelImage.new(@file, header, self)
- parse ? @image.parse : @image.skip
- end
-
- # Does this layer represent the start of a folder section?
- def folder?
- return false unless @adjustments.has_key?(:section_divider)
- @adjustments[:section_divider].is_folder
- end
-
- # Does this layer represent the end of a folder section?
- def folder_end?
- return false unless @adjustments.has_key?(:section_divider)
- @adjustments[:section_divider].is_hidden
- end
-
- # Is this layer visible?
- def visible?
- @visible
- end
-
- # Is this layer hidden?
- def hidden?
- !@visible
- end
-
- # Attempt to translate this layer and modify the document.
- def translate(x=0, y=0)
- @left += x
- @right += x
- @top += y
- @bottom += y
-
- @path_components.each{ |p| p.translate(x,y) } if @path_components
- end
-
- # Attempt to scale the path components within this layer.
- def scale_path_components(xr, yr)
- return unless @path_components
-
- @path_components.each{ |p| p.scale(xr, yr) }
- end
-
- # Helper that exports the text data in this layer, if any.
- def text
- return nil unless @adjustments[:type]
- @adjustments[:type].to_hash
- end
-
- # Gets the name of this layer. If the PSD file is from an even remotely
- # recent version of Photoshop, this data is stored as extra layer info and
- # as a UTF-16 name. Otherwise, it's stored in a legacy block.
- def name
- if @adjustments.has_key?(:name)
- return @adjustments[:name].data
- end
-
- return @legacy_name
- end
-
# We delegate all missing method calls to the extra layer info to make it easier
# to access that data.
def method_missing(method, *args, &block)
return @adjustments[method] if @adjustments.has_key?(method)
super
- end
-
- private
-
- def parse_info
- start_section(:info)
-
- @top = @file.read_int
- @left = @file.read_int
- @bottom = @file.read_int
- @right = @file.read_int
- @channels = @file.read_short
-
- @rows = @bottom - @top
- @cols = @right - @left
-
- @channels.times do
- channel_id = @file.read_short
- channel_length = @file.read_int
-
- @channels_info << {id: channel_id, length: channel_length}
- end
-
- end_section(:info)
- end
-
- def export_info(outfile)
- [@top, @left, @bottom, @right].each { |val| outfile.write_int(val) }
- outfile.write_short(@channels)
-
- @channels_info.each do |channel_info|
- outfile.write_short channel_info[:id]
- outfile.write_int channel_info[:length]
- end
-
- @file.seek end_of_section(:info)
- end
-
- def export_mask_data(outfile)
- outfile.write @file.read(@mask_end - @mask_begin + 4)
- end
-
- def export_blending_ranges(outfile)
- length = 4 * 2 # greys
- length += @blending_ranges[:num_channels] * 8
- outfile.write_int length
-
- outfile.write_short @blending_ranges[:grey][:source][:black]
- outfile.write_short @blending_ranges[:grey][:source][:white]
- outfile.write_short @blending_ranges[:grey][:dest][:black]
- outfile.write_short @blending_ranges[:grey][:dest][:white]
-
- @blending_ranges[:num_channels].times do |i|
- outfile.write_short @blending_ranges[:channels][i][:source][:black]
- outfile.write_short @blending_ranges[:channels][i][:source][:white]
- outfile.write_short @blending_ranges[:channels][i][:dest][:black]
- outfile.write_short @blending_ranges[:channels][i][:dest][:white]
- end
-
- @file.seek length + 4, IO::SEEK_CUR
- end
-
- def export_legacy_layer_name(outfile)
- outfile.write @file.read(@legacy_name_end - @legacy_name_start)
- end
-
- def export_extra_data(outfile)
- outfile.write @file.read(@extra_data_end - @extra_data_begin)
- if @path_components && !@path_components.empty?
- outfile.seek @vector_mask_begin
- @file.seek @vector_mask_begin
-
- write_vector_mask(outfile)
- @file.seek outfile.tell
- end
- end
-
- def parse_blend_modes
- @blend_mode = BlendMode.read(@file)
-
- @blending_mode = @blend_mode.mode
- @opacity = @blend_mode.opacity
- @visible = @blend_mode.visible
- end
-
- def parse_mask_data
- @mask_begin = @file.tell
- @mask = Mask.read(@file)
- @mask_end = @file.tell
- end
-
- def parse_blending_ranges
- length = @file.read_int
-
- @blending_ranges[:grey] = {
- source: {
- black: @file.read_short,
- white: @file.read_short
- },
- dest: {
- black: @file.read_short,
- white: @file.read_short
- }
- }
-
- @blending_ranges[:num_channels] = (length - 8) / 8
-
- @blending_ranges[:channels] = []
- @blending_ranges[:num_channels].times do
- @blending_ranges[:channels] << {
- source: {
- black: @file.read_short,
- white: @file.read_short
- },
- dest: {
- black: @file.read_short,
- white: @file.read_short
- }
- }
- end
- end
-
- # The old school layer names are encoded in MacRoman format,
- # not UTF-8. Luckily Ruby kicks ass at character conversion.
- def parse_legacy_layer_name
- @legacy_name_start = @file.tell
-
- len = Util.pad4 @file.read(1).bytes.to_a[0]
- @legacy_name = @file.read_string(len)
-
- @legacy_name_end = @file.tell
- end
-
- # This section is a bit tricky to parse because it represents all of the
- # extra data that describes this layer.
- def parse_extra_data
- @extra_data_begin = @file.tell
-
- while @file.tell < @layer_end
- # Signature, don't need
- @file.seek 4, IO::SEEK_CUR
-
- # Key, very important
- key = @file.read_string(4)
- @info_keys << key
-
- length = Util.pad2 @file.read_int
- pos = @file.tell
-
- info_parsed = false
- LAYER_INFO.each do |name, info|
- next unless info.key == key
-
- PSD.logger.debug "Layer Info: key = #{key}, start = #{pos}, length = #{length}"
-
- begin
- i = info.new(@file, length)
- i.parse
-
- @adjustments[name] = i
- info_parsed = true
- rescue Exception => e
- PSD.logger.error "Parsing error: key = #{key}, message = #{e.message}"
- PSD.logger.error e.backtrace.join("\n")
- end
-
- break
- end
-
- if !info_parsed
- PSD.logger.debug "Skipping: key = #{key}, pos = #{@file.tell}, length = #{length}"
- @file.seek pos + length
- end
-
- @file.seek pos + length if @file.tell != (pos + length)
- end
-
- @extra_data_end = @file.tell
- end
-
- def write_vector_mask(outfile)
- outfile.write @file.read(8)
- # outfile.write_int 3
- # outfile.write_int @vector_tag
-
- @path_components.each{ |pc| pc.write(outfile); @file.seek(26, IO::SEEK_CUR) }
end
end
end
\ No newline at end of file