koxelaにページ切り替えエフェクトをつけてみた。

ということで一応リリース(?)
ソースほとんど変わってないけど、もういいや。めんどい。

ソースはこちら
あとデモ用のkxlファイルも上げておいたのであわせてどぞ。デモファイル


ということで、性懲りもせずにテキストです。
紹介自体は前回のエントリに書いたんで、切り替えエフェクトの説明のみで。

とりあえず実装したエフェクトが四種類。

まず、なんもせずただ順に書いていくcutin。
カットインとはいっても結局ターミナルの描画速度に依存するのでそんな「カット」ってイメージがない...(д

あとランダムディゾルブ...っていうのカナ?
各点をひたすらにランダムに埋めていく方法。

ライン毎ランダム。多分ランダムストライプ(横)とか名前だった気がする。pptでは。

フェードは...えっとなんと書けばいいのやら。

AAAA
AAAA
AAAA

ABAB
BABA
ABAB

になって

BBBB
BBBB
BBBB

になる、と。
なぜかsortで実装しているキモさをソース読んで堪能ください(w


どうでもいいけど、デモファイルの背景がグラデのところ。
id:Hossyに98時代っぽいと言われた...orz



そーす。

#!/usr/bin/env ruby

# koxela Ver.0.0.1 - こけら
# テキストプレゼンテーションツール
#
# usage:
# $ koxela demo.kxl
# $ ./demo
#
#	Warning:
#	Koxela creates file "demo" in this case without any alert
#	even if file "demo" is already exits.
#
# - 8<- - - - - - - - - - - - - - - - -
# Copyright (C) 2008 TAKEI Yuya All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

require "cairo"
require "yaml"

class Display
end

class FileDisplay < Display
	def initialize fp, col, row
		@fp = fp
		@fp.puts %<#!/bin/sh>
		@fp.puts %<# col:#{col} row:#{row}>
		@fp.puts %<STTY_DEFAULT=`stty -g`>
		@fp.puts %<stty -echo>
	end
	def finish
		@fp.puts %<stty ${STTY_DEFAULT}>
	end
	def clear
		@fp.puts %<echo -en "\033[2J">
	end
	def rewind
		@fp.puts %<echo -en "\033[1;1H">
	end
	def keywait
		@fp.puts %<read>
	end
	def wait n=1
		@fp.puts %<usleep #{n*1000*1000}>
	end
	def right n=1
		@fp.puts %<echo -en "\033[#{n}C">
	end
	def reset
		@fp.puts %<reset>
	end
	def putStr str
		str.gsub!( '"', %<\"> )
		@fp.puts %<echo -en "#{str}">
	end
	def putStrLn str
		str.gsub!( '"', %<\"> )
		@fp.puts %<echo -e "#{str}"> 
	end
	def move x, y
		@fp.puts %<echo -en "\033[#{y};#{x}H">
	end
	def color r,g,b
	end
end

def rgb2y rr,gg,bb
	rr * 0.299 + gg * 0.587 +  bb * 0.114
end

def rgb2esc rr,gg,bb
	"\033[38;5;%dm" % (
		(rr * 6 / 256) * 36 +
		(gg * 6 / 256) * 6 +
		(bb * 6 / 256) +
		16
	)
end

def grids col,row
	points = []
	row.times { |y|
		col.times { |x|
			points += [[x,y]]
		}
	}
	points
end

TIOCGWINSZ = 0x00005413
TIOCSWINSZ = 0x00005414

str = ""
$stdin.ioctl( TIOCGWINSZ, str )
(row, col) = str.unpack("S!4")

if ARGF.filename == "-" then
	exit
end
yaml = YAML.load( ARGF.read )
width = yaml["common"]["width"]			|| 600
height = yaml["common"]["height"]		|| 400
bg = yaml["common"]["background"]		|| [0, 0, 0]
fg = yaml["common"]["foreground"]		|| [1, 1, 1]
chars = yaml["common"]["chars"]			|| " .,~=+?I7Z08DNM#"
fontName = yaml["common"]["font"]["name"]	|| ""
fontSize = yaml["common"]["font"]["size"]	|| 400
lineWidth = yaml["common"]["line"]["width"]	|| 10
effect = yaml["common"]["effect"]		|| "cutin"
delay = yaml["common"]["delay"]			|| 0.1
pages = yaml["pages"]				|| []

surface = Cairo::ImageSurface.new( col, row )
context = Cairo::Context.new( surface )

context.scale( col / width.to_f, row / height.to_f )

basename = File.basename ARGF.filename, ".*"

File.open( "#{basename}", "w" ){ |fp|
	disp = FileDisplay.new( fp, col, row )

	disp.clear
	pages.each_with_index{ |page,i|
		context.set_source_rgb( bg ).rectangle( 0, 0, width, height ).fill
		context.save
		page["objects"].each{ |obj|
			case obj["type"]
			when "line"
				context.set_source_rgb( obj["color"] || fg )
				context.move_to( obj["pos1"][0], obj["pos1"][1] )
				context.line_to( obj["pos2"][0], obj["pos2"][1] )
				context.set_line_width( obj["width"] || lineWidth )
				case obj["method"] 
				when "fill"
					context.fill
				when "stroke"
					context.stroke
				else
					context.stroke
				end
			when "circle"
				context.set_source_rgb( obj["color"] || fg )
				context.circle( obj["pos"][0], obj["pos"][1], obj["radius"] )
				context.set_line_width( obj["width"] || lineWidth )
				case obj["method"] 
				when "fill"
					context.fill
				when "stroke"
					context.stroke
				else
					context.fill
				end
			when "rect"
				context.set_source_rgb( obj["color"] || fg )
				context.rectangle( *obj["rect"] )
				context.set_line_width( obj["width"] || lineWidth )
				case obj["method"] 
				when "fill"
					context.fill
				when "stroke"
					context.stroke
				else
					context.stroke
				end
			when "string"
				context.select_font_face( obj["font"] || fontName, 0, 0 )
				context.set_font_size( obj["size"] || fontSize )
				context.set_source_rgb( obj["color"] || fg )
				context.set_line_width( obj["width"] || lineWidth )

				ext = context.text_extents( obj["string"] )
				extWidth = ext.x_advance - ext.x_bearing
				extHeight = ext.y_advance - ext.y_bearing

				context.move_to( obj["pos"][0] - extWidth / 2, obj["pos"][1] + extHeight / 2)
				context.text_path( obj["string"] )
				case obj["method"] 
				when "fill"
					context.fill
				when "stroke"
					context.stroke
				else
					context.fill
				end
			else
			end
		}

		# jabanner化
		jabanner = []
		row.times { |y|
			jabanner[y] = []
			col.times { |x|
				jabanner[y][x] = ""

				rr = surface.data[4*(x + y * col) + 2]
				gg = surface.data[4*(x + y * col) + 1]
				bb = surface.data[4*(x + y * col) + 0]

				jabanner[y][x] += rgb2esc( rr, gg, bb )

				jabanner[y][x] += chars[chars.length * rgb2y( rr, gg, bb ) / 256].chr
			}
		}

		animationDelay = page["delay"] || delay
		case (page["effect"] || effect)
		when "fade"
			# フェード
			{}
			grids( col, row ).sort_by{|tmp0,tmp1|(tmp0+tmp1)%2}.each{ |x,y|
				disp.move x, y
				disp.putStr jabanner[y][x]
			}
			disp.wait animationDelay
		when "rand"
			# ランダム
			{}
			grids( col, row ).sort_by{rand}.each{ |x,y|
				disp.move x, y
				disp.putStr jabanner[y][x]
			}
			disp.wait animationDelay
		when "randline"
			# ランダムライン
			{}
			(0..(row-1)).sort_by{rand}.each { |y|
				disp.move 0, y
				col.times { |x|
					disp.putStr jabanner[y][x]
				}
				disp.wait animationDelay
			}
		else
			# カットイン
			disp.rewind
			row.times { |y|
				col.times { |x|
					disp.putStr jabanner[y][x]
				}
			}
		end

		disp.keywait
		context.restore
	}

	surface.finish
	disp.reset
}

# vim: sw=2:ts=2:sts=2:foldmethod=syntax