=begin $Id: main.rb,v 1.2 2006-03-15 04:37:46 matju Exp $ GridFlow Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. See file ../COPYING for further informations on licensing terms. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. =end require "socket" require "fcntl" module GridFlow class<= 0 : frame number of frame read. false : no frame was read : end of sequence. nil : a frame was read, but can't say its number. note that trying to read a nonexistent frame should no longer rewind automatically (@in handles that part), nor re-read the last frame (mpeg/quicktime used to do this) def seek(Integer i) : select one frame to be read next (by number) def length() : ^Integer returns number of frames (never implemented ?) def close() : close a handler inlet 0 : grid : frame to write other : special options outlet 0 : grid : frame just read outlet 1 : everything else =end def initialize(mode,*) super @cast = :int32 @colorspace = :rgb @mode = mode @frame = 0 @parent = nil @stream = nil flags = self.class.instance_eval{if defined?@flags then @flags else 6 end} # FF_W, FF_R, FF_RW case mode when :in; flags[2]==1 when :out; flags[1]==1 else raise "Format opening mode is incorrect" end or raise \ "Format '#{self.class.instance_eval{@symbol_name}}'"\ " does not support mode '#{mode}'" end def close @stream.close if defined? @stream and @stream end def self.suffixes_are(*suffixes) suffixes.map{|s|s.split(/[, ]/)}.flatten.each {|suffix| Format.suffixes[suffix] = self } end class< @prod end # the header def frame1 data post "----- frame1" head,@bpv,reserved,@n_dim = data.unpack "a5ccc" @endian = case head when "\x7fGRID"; ENDIAN_BIG when "\x7fgrid"; ENDIAN_LITTLE else raise "grid header: invalid (#{data.inspect})" end case bpv when 8, 16, 32; # ok else raise "unsupported bpv (#{@bpv})" end if reserved!=0 raise "reserved field is not zero" end if @n_dim > GridFlow.max_rank raise "too many dimensions (#{@n_dim})" end on_read(4*@n_dim) {|data| frame2 data } end # the dimension list def frame2 data post "----- frame2" @dim = data.unpack(if @endian==ENDIAN_LITTLE then "V*" else "N*" end) set_bufsize if @prod > GridFlow.max_size raise "dimension list: invalid prod (#{@prod})" end send_out_grid_begin 0, @dim, @cast on_read(bufsize) {|data| frame3 data } @dex = 0 end attr_reader :bufsize # for each slice of the body def frame3 data post "----- frame3 with dex=#{@dex.inspect}, prod=#{@prod.inspect}" n = data.length nn = n*8/@bpv # is/was there a problem with the size of the data being read? case @bpv when 8 @bp = BitPacking.new(@endian,1,[0xff]) send_out_grid_flow(0, @bp.unpack(data)) @dex += data.length when 16 @bp = BitPacking.new(@endian,2,[0xffff]) send_out_grid_flow(0, @bp.unpack(data)) @dex += data.length/2 when 32 data.swap32! if @endian!=OurByteOrder send_out_grid_flow 0, data @dex += data.length/4 end if @dex >= @prod @clock.unset else on_read(bufsize) {|data| frame3 data } end end def _0_rgrid_begin if not @stream raise "can't send frame when there is no connection" end @dim = inlet_dim 0 post "@dim=#{@dim.inspect}" return if @headerless # header @stream.write( [if @endian==ENDIAN_LITTLE then "\x7fgrid" else "\x7fGRID" end, @bpv,0,@dim.length].pack("a5ccc")) # dimension list @stream.write( @dim.to_a.pack(if @endian==ENDIAN_LITTLE then "V*" else "N*" end)) end def _0_rgrid_flow data case @bpv when 8, 16 @stream.write @bp.pack(data) when 32 data.swap32! if GridFlow::OurByteOrder != @endian @stream.write data end end def _0_rgrid_end; @stream.flush end def endian(a) @endian = case a when :little; ENDIAN_LITTLE when :big; ENDIAN_BIG when :same; ENDIAN_SAME else raise "argh" end end def headerless(*args) args=args[0] if Array===args[0] args.map! {|a| Numeric===a or raise "expecting dimension list..." a.to_i } @headerless = args end def headerful; @headerless = nil end #!@#$ method name conflict ? def type(nt) #!@#$ bug: should not be able to modify this _during_ a transfer case nt when :uint8; @bpv= 8; @bp=BitPacking.new(ENDIAN_LITTLE,1,[0xff]) when :int16; @bpv=16; @bp=BitPacking.new(ENDIAN_LITTLE,1,[0xffff]) when :int32; @bpv=32; @bp=nil else raise "unsupported number type" end end } module PPMandTarga # "and false" disables features that may cause crashes and don't # accelerate gridflow that much. def frame_read_body height, width, channels bs = width*channels n = bs*height bs = (self.class.buffersize/bs)*bs+bs # smallest multiple of bs over BufferSize buf = "" if RUBY_VERSION >= "1.8.0" and false data = "x"*bs # must preallocate (bug in 1.8.0.pre1-3) while n>0 do bs=n if bs>n @stream.read(bs,data) or raise EOFError if @bp then send_out_grid_flow 0, @bp.unpack(data,buf) else send_out_grid_flow 0, data, :uint8 end n-=bs end else nothing = "" while n>0 do bs=n if bs>n data = @stream.read(bs) or raise EOFError if @bp then send_out_grid_flow 0, @bp.unpack(data,buf) else send_out_grid_flow 0, data, :uint8 end data.replace nothing and false # prevent clogging memory n-=bs end end end end Format.subclass("#io:ppm",1,1) { install_rgrid 0 @comment = "Portable PixMap (PPM) File Format" suffixes_are "ppm" include EventIO, PPMandTarga def initialize(mode,source,*args) @bp = if mode==:out BitPacking.new(ENDIAN_LITTLE,3,[0x0000ff,0x00ff00,0xff0000]) else nil end super raw_open mode,source,*args end def frame #@stream.sync = false metrics=[] return false if eof? line = @stream.gets (rewind; line = @stream.gets) if not line # hack line.chomp! if line != "P6" then raise "Wrong format (needing PPM P6)" end while metrics.length<3 line = @stream.gets next if line =~ /^#/ metrics.push(*(line.split(/\s+/).map{|x| Integer x })) end metrics[2]==255 or raise "Wrong color depth (max_value=#{metrics[2]} instead of 255)" send_out_grid_begin 0, [metrics[1], metrics[0], 3], @cast frame_read_body metrics[1], metrics[0], 3 super end def _0_rgrid_begin dim = inlet_dim 0 raise "expecting (rows,columns,channels)" if dim.length!=3 raise "expecting channels=3" if dim[2]!=3 @stream.write "P6\n" @stream.write "# generated using GridFlow #{GF_VERSION}\n" @stream.write "#{dim[1]} #{dim[0]}\n255\n" @stream.flush inlet_set_factor 0, 3 end def _0_rgrid_flow(data) @stream.write @bp.pack(data) end def _0_rgrid_end; @stream.flush end self }.subclass("#io:tk",1,1) { install_rgrid 0 def initialize(mode) if mode!=:out then raise "only #out" end super(mode,:file,"/tmp/tk-#{$$}-#{object_id}.ppm") GridFlow.gui "toplevel .#{object_id}\n" GridFlow.gui "wm title . GridFlow/Tk\n" GridFlow.gui "image create photo #{object_id} -width 320 -height 240\n" GridFlow.gui "pack [label .#{object_id}.im -image #{object_id}]\n" end def _0_rgrid_end super @stream.seek 0,IO::SEEK_SET GridFlow.gui "image create photo #{object_id} -file /tmp/tk-#{$$}-#{object_id}.ppm\n" end def delete GridFlow.gui "destroy .#{object_id}\n" GridFlow.gui "image delete #{object_id}\n" end alias close delete } Format.subclass("#io:targa",1,1) { install_rgrid 0 @comment = "TrueVision Targa" suffixes_are "tga" include EventIO, PPMandTarga =begin targa header is like: [:comment, Uint8, :length], [:colortype, Uint8], [:colors, Uint8], 5, [:origin_x, Int16], [:origin_y, Int16], [:w, Uint16], [:h, Uint16], [:depth, Uint8], 1, [:comment, String8Unpadded, :data], =end def initialize(mode,source,*args) super raw_open mode,source,*args end def set_bitpacking depth @bp = case depth #!@#$ endian here doesn't seem to be changing much ? when 24; BitPacking.new(ENDIAN_LITTLE,3,[0xff0000,0x00ff00,0x0000ff]) when 32; BitPacking.new(ENDIAN_LITTLE,4, [0x00ff0000,0x0000ff00,0x000000ff,0xff000000]) else raise "tga: unsupported colour depth: #{depth}\n" end end def frame return false if eof? head = @stream.read(18) comment_length,colortype,colors,w,h,depth = head.unpack("cccx9vvcx") comment = @stream.read(comment_length) raise "unsupported color format: #{colors}" if colors != 2 # post "tga: size y=#{h} x=#{w} depth=#{depth} colortype=#{colortype}" # post "tga: comment: \"#{comment}\"" set_bitpacking depth send_out_grid_begin 0, [ h, w, depth/8 ], @cast frame_read_body h, w, depth/8 super end def _0_rgrid_begin dim = inlet_dim 0 raise "expecting (rows,columns,channels)" if dim.length!=3 raise "expecting channels=3 or 4" if dim[2]!=3 and dim[2]!=4 # comment = "created using GridFlow" #!@#$ why did i use that comment again? comment = "generated using GridFlow #{GF_VERSION}" @stream.write [comment.length,colortype=0,colors=2,"\0"*9, dim[1],dim[0],8*dim[2],(8*(dim[2]-3))|32,comment].pack("ccca9vvcca*") set_bitpacking 8*dim[2] inlet_set_factor 0, dim[2] end def _0_rgrid_flow data; @stream.write @bp.pack(data) end def _0_rgrid_end; @stream.flush end } end # module GridFlow