#============================================================================== # ■ Gifcat #------------------------------------------------------------------------------ #  複数のgifファイルをアニメーションgifとして連結させるクラスです。 # Copyright (c) 1997,2002,2007 http://www.tohoho-web.com/ # # 基本的な使い方 # gifcat = Gifcat.new # # gifcat.cat_type = 数値 # gifcat.lock_on = true or false # gifcat.base_delay = 数値 # gifcat.looptime = 数値 # # gifcat.gifcat("イメージファイル(拡張子省略)", ["イメージファイル(拡張子省略)"]) # #============================================================================== class Gifcat #-------------------------------------------------------------------------- # ● 公開インスタンス変数 #-------------------------------------------------------------------------- attr_accessor :cat_type # 0なら固定連結、1なら横連結、2なら縦連結 attr_accessor :lock_on # ファイルロックをするか attr_accessor :base_delay # 設定無しの場合の遅延時間(0.01秒単位) attr_accessor :loop_time # ループ回数 #-------------------------------------------------------------------------- # ● オブジェクト初期化 #-------------------------------------------------------------------------- def initialize @cat_type = 1 @lock_on = true @base_delay = 10 @loop_time = 1 end #-------------------------------------------------------------------------- # ● gifファイル連結 #-------------------------------------------------------------------------- def gifcat(*files) # 配列が入っている場合を考慮して、平面化 files = files.flatten # 各種変数を初期化 @width = 0 @height = 0 @first_width = 0 @first_height = 0 @margin_w = [] @margin_h = [] @gct = [] @lct = [] @local_width = [] @local_height = [] @images = [] @controls = [] @gct_use = true @bytes = [] # ファイルを順次処理する for fs in 0...files.size # ファイルサイズ用の変数を初期化 @size = 0 # スクロール判定を初期化 @scroll_fix = false gif = files[fs] # ファイルが存在する場合 if FileTest.exist?("#{gif}.gif") # ファイルをバイナリモードで読み出す File.open("#{gif}.gif", "rb"){|f| f.flock(File::LOCK_SH) if @lock_on @file = f.read } # ヘッダの取得 head_size = gif_header # ヘッダが正しい場合 if head_size != nil @size += head_size else # 正しくない場合、次のファイルに進む @controls.push(nil) @images.push(nil) next end # コントロールブロックを control_exist = false # ガラフ・ドゥ loop do case @file[@size] # 次のブロックが拡張部分の場合 when 33 case @file[@size + 1] # Graphic Control Extensionの場合 when 249 # ブロックの取得 gce_size = gif_control # ブロックが正しい場合 if gce_size != nil control_exist = true @size += gce_size else # 正しくない場合、次のファイルに進む @images.push(nil) break end # Application Extensionの場合 when 255 # ブロックの取得 app_size = gif_application # ブロックが正しい場合 if app_size != nil @size += app_size else # 正しくない場合、次のファイルに進む @images.push(nil) break end # Comment Extensionの場合 when 254 # ブロックの取得 com_size = gif_comment # ブロックが正しい場合 if com_size != nil @size += com_size else # 正しくない場合、次のファイルに進む @images.push(nil) break end # Plain Text Extensionの場合 when 1 # ブロックの取得 pte_size = gif_plain # ブロックが正しい場合 if pte_size != nil @size += pte_size else # 正しくない場合、次のファイルに進む @images.push(nil) break end end # 次のブロックがImage Blockの場合 when 44 # イメージの取得 image_size = gif_image # イメージが正しい場合 if image_size != nil @size += image_size @scroll_fix = true else # 正しくない場合、次のファイルに進む @images.push(nil) break end else break end end @controls.push(nil) if control_exist == false end end # アニメーションgifを生成する return create_image end # 以下、外部クラスから使えないようにプロテクト protected #-------------------------------------------------------------------------- # ● ヘッダの取得 #-------------------------------------------------------------------------- def gif_header # 正式なGIFヘッダであるか判定 return nil unless @file[0, 6] =~ /GIF8[79]a/ # ついでにフッタもここで判定 return nil unless @file[-1] == 59 # 大きさを加算 case @cat_type when 0 @width = [(@file[7] * 256 + @file[6]), @width].max @height = [(@file[9] * 256 + @file[8]), @height].max when 1 @width += (@file[7] * 256 + @file[6]) @height = [(@file[9] * 256 + @file[8]), @height].max when 2 @width = [(@file[7] * 256 + @file[6]), @width].max @height += (@file[9] * 256 + @file[8]) end @first_width = @width if @first_width == 0 @first_height = @height if @first_height == 0 flags = @file[10] # カラーテーブルの有無を取得 gct_use = (flags[7] == 1) # カラーテーブルの大きさを取得 gct_size = flags & 7 # 背景色の取得 @bg_color = @file[11] if @bg_color == nil # カラーテーブルの取得 head_size = 13 ct_temp = [] if gct_use # 一色ずつ地道に読み取る for i in 0...(2 ** (gct_size + 1)) color = @file[13 + i * 3, 3] ct_temp.push(color) head_size += 3 end # カラーテーブルが一つでも違う場合、 if @gct_use and !@gct.empty? and @gct[0] != ct_temp @gct_use = false end @gct.push(ct_temp) end # ヘッダの大きさを返す return head_size end #-------------------------------------------------------------------------- # ● イメージブロックの取得 #-------------------------------------------------------------------------- def gif_image # ローカルサイズを取得 @local_width.push(@file[@size + 6] * 256 + @file[@size + 5]) @local_height.push(@file[@size + 8] * 256 + @file[@size + 7]) # マージンを取得 case @cat_type when 0 @margin_w.push(@file[@size + 2] * 256 + @file[@size + 1]) @margin_h.push(@file[@size + 4] * 256 + @file[@size + 3]) when 1 @margin_w.push(@width - @first_width + @file[@size + 2] * 256 + @file[@size + 1]) @margin_h.push(@file[@size + 4] * 256 + @file[@size + 3]) @width += (@file[@size + 2] * 256 + @file[@size + 1]) unless @scroll_fix when 2 @margin_w.push(@file[@size + 3] * 256 + @file[@size + 1]) @margin_h.push(@height - @first_height + @file[@size + 4] * 256 + @file[@size + 3]) @height += (@file[@size + 4] * 256 + @file[@size + 3]) unless @scroll_fix end flags = @file[@size + 9] # カラーテーブルの大きさを取得 lct_size = flags & 7 image_size = 10 # カラーテーブルが存在する場合 if flags[7] == 1 @gct_use = false ct_temp = [] # 一色ずつ地道に読み取る for i in 0...(2 ** (lct_size + 1)) color = @file[@size + 10 + i * 3, 3] ct_temp.push(color) image_size += 3 end @lct.push(ct_temp) else @lct.push(@gct.last) end @bytes.push(@file[@size + image_size]) image_size += 1 # ここまでのサイズを記録 noi_size = image_size temp = 0 # 法則に従って、イメージを読み出す loop do temp = @file[@size + image_size] image_size += (temp + 1) break if temp == 0 end # イメージだけを保存 @images.push(@file[@size + noi_size, image_size - noi_size - 1]) return image_size end #-------------------------------------------------------------------------- # ● コントロールブロックの取得 #-------------------------------------------------------------------------- def gif_control # 絶対に欠けたGIFなんて使わないだろうが一応確認 return nil if @file[@size + 2] != 4 return nil if @file[@size + 7] != 0 # ブロックをそのまま保存 result = @file[@size, 3] flag = @file[@size + 3] if @cat_type == 0 result += ((flag + 8).chr) else result += flag.chr end delay = @file[@size + 5] * 256 + @file[@size + 4] delay = @base_delay if delay == 0 result += ([delay].pack("v")) result += @file[@size + 6, 2] @controls.push(result) return 8 end #-------------------------------------------------------------------------- # ● gifイメージの作成 #-------------------------------------------------------------------------- def create_image return "" if @images.empty? # ヘッダの生成 result = "GIF89a" result += [@width, @height].pack("v2") # グローバルカラーテーブルを使う場合 if @gct_use table = @gct.shift flags = 128 + ((Math.log10(table.size) / Math.log10(2) - 1).ceil) result += flags.chr else result += "\x00" end result += @bg_color.chr result += "\x00" # グローバルカラーテーブルを使う場合 if @gct_use result += table.join("") end # アニメブロックを生成 result += "\x21\xff\x0bNETSCAPE2.0\x03\x01" result += [@loop_time].pack("v") result += "\x00" # 各自ブロックを生成 for image in 0...@images.size if @images[image] != nil # コントロールブロックの確認 if @controls[image] != nil result += @controls[image] else # 無ければ、その場で生成 result += "!\xf9\x04" if @cat_type == 0 result += "\x08" else result += "\x00" end result += [@base_delay].pack("v") result += "\x00\x00" end # イメージブロックの生成 result += "," result += [@margin_w[image], @margin_h[image]].pack("v2") result += [@local_width[image], @local_height[image]].pack("v2") if @gct_use result += "\x00" else ct = @lct.shift flag = 128 + ((Math.log10(ct.size) / Math.log10(2) - 1).ceil) result += flag.chr result += ct.join("") end result += @bytes[image].chr result += @images[image] result += "\x00" end end # 終端バイトをつけて、返す result += ";" return result end #-------------------------------------------------------------------------- # ● アプリケーションブロックの作成 #-------------------------------------------------------------------------- def gif_application # (破棄決定) return nil if @file[@size + 2] != 11 return (@file[@size + 14] == 0 ? 15 : 16 + @file[@size + 14]) end #-------------------------------------------------------------------------- # ● コメントブロックの作成 #-------------------------------------------------------------------------- def gif_comment # (破棄決定) return (@file[@size + 2] == 0 ? 3 : 4 + @file[@size + 2]) end #-------------------------------------------------------------------------- # ● プレーンブロックの作成 #-------------------------------------------------------------------------- def gif_plain # (破棄決定) return nil if @file[@size + 2] != 12 return (@file[@size + 15] == 0 ? 16 : 17 + @file[@size + 15]) end end