pixelflut 0.0.13 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46bc6d8430a0a37a766f925558d1279fbf69457f859f9f6286c2fb4a3f3a9e81
4
- data.tar.gz: f634ffe53ec48385c8c9b42f032f3fa0e56d91155cf075a05b2b9c15794d8dbb
3
+ metadata.gz: a4b1d4d4f5be08766e694539cf82507427f901f71ae1ec4c5568cec7344d1345
4
+ data.tar.gz: 65d9ebde1aa48a5989c28340d3da137b8d99d3952a85fbfb904ad7af64420df8
5
5
  SHA512:
6
- metadata.gz: 53bb74856603e0d46563b707726cdb8d5d96f5192ebd641c00ebba463e45ba5165536f4b95ea60d28e0468f8735acb32d3439802541791faf9a2dcbe13b94dab
7
- data.tar.gz: 2f26150a3d40045e6030018cbcbf807dcbb1f415f193f156d1c45d8c4eea5e5c6d56e5205d764b701b6d644346d0892042f5fb1cdd9ce0d9cd6ee5ba4fbad099
6
+ metadata.gz: 377feb36cdde2221e3d6c7739acc9773d771d15cf319e93e379c6f86248189c286af91a25e13acda88fc40e030b956761ffa205215e4bb37ddd9ee64e805e748
7
+ data.tar.gz: 910cd772cedf70ddd4cb01445821ccf360f54d96a3c4076b9845886a0e5e1a232a0b153e640b574e6540c490c77691bd515a6b42bb74ade60f0970e2e201c9f9
data/.gitignore CHANGED
@@ -1,4 +1,3 @@
1
- _local/
2
1
  tmp/
3
2
  pkg/
4
3
  gems.locked
data/README.md CHANGED
@@ -1,14 +1,12 @@
1
1
  # Pixelflut
2
2
 
3
- A Pixelflut server & client tool collection written in Ruby.
3
+ A Pixelflut client written in Ruby.
4
4
 
5
5
  ## Description
6
6
 
7
- Based on the idea of a simple server protocol to collaborate on a shared canvas named [Pixel Flut](https://cccgoe.de/wiki/Pixelflut) this gem implements a Ruby version.
7
+ Based on the idea of a simple server protocol to collaborate on a shared canvas named [Pixelflut](https://cccgoe.de/wiki/Pixelflut) this gem implements a Ruby version of a client.
8
8
 
9
- This gem is an open experiment to write a fast server and canvas in Ruby. You are welcome to fork or create pull requests to find the fastest solution!
10
-
11
- The gem includes some tools to develop Pixelflut clients and pre-processors.
9
+ This gem is an open experiment. You are welcome to fork or create pull requests to find the fastest solution!
12
10
 
13
11
  ### Installation
14
12
 
@@ -26,76 +24,16 @@ You'll find some help on the command line:
26
24
 
27
25
  ```bash
28
26
  $ pxf
29
- usage: pxf command [options]
30
-
31
- valid commands:
32
- convert Convert given IMAGE file to Pixelflut ASCII format.
33
- generate Execute given generator FILEs.
34
- help Print help for given COMMAND.
35
- send Send given Pixelflut ASCII file to a server.
36
- server Start Pixelflut server.
37
- version Print version information.
38
- ```
39
-
40
- ### Start a Sserver
41
-
42
- Starting the server on default port `1234` and open a drawing screen is quite simple:
43
-
44
- ```bash
45
- $ pxf server
46
- ```
47
-
48
- With these options you can configure the server:
49
-
50
- ```
51
- -b, --bind ADDR bind to given address
52
- -p, --port PORT select port(default: 1234)
53
- -k, --keep-alive set maximum keep-alive time
54
- -r, --read_buffer SIZE set read buffer size (default: 1024)
55
- -w, --width WIDTH set canvas width (default: 800)
56
- -h, --height HEIGHT set canvas height (default: 600)
57
- -f, --[no-]fullscreen run in fullscreen mode
27
+ Usage: pxf [OPTIONS] IMAGE
28
+
29
+ Options:
30
+ --host ADDRESS target host address
31
+ -p, --port PORT target port (default 1234)
32
+ -c, --connections CONN count of connections (default 4)
33
+ -b, --bytes BYTES send junks of BYTES size
34
+ -x, --transpose-x X transpose image X pixels
35
+ -y, --transpose-y Y transpose image Y pixels
36
+ -s, --scale SCALE scale image by SCALE factor
37
+ -m, --pixel MODE select pixel coding (RGBX | RGBA | RGB)
38
+ -h, --help print this help
58
39
  ```
59
-
60
- ### Convert an Image
61
-
62
- There is a conversion command which can be used to convert a given image to the Pixelflut ASCII text format:
63
-
64
- ```bash
65
- $ pxf convert image.png
66
- ```
67
-
68
- The result can send directly to a server (sample assumes the server runs on localhost port 1234):
69
-
70
- ```bash
71
- $ pxf convert image.png | pxf send
72
- ```
73
-
74
- The converter can help you to resize and positioning the image at the servers canvas. These options are avail:
75
-
76
- ```
77
- -x, --transpose-x X transpose image X pixels
78
- -y, --transpose-y Y transpose image Y pixels
79
- -w, --width WIDTH resize the image to given WIDTH
80
- -h, --height HEIGHT resize the image to given HEIGHT
81
- ```
82
-
83
- It maybe faster to pre-process your images and send them later:
84
-
85
- ```bash
86
- $ pxf convert -x 50 -y 100 --width 100 image1.png > image1.px
87
- $ pxf convert -x 150 --width 2100 image2.png > image2.px
88
- $ pxf send -h pixel_host image1.px image2.px
89
- ```
90
-
91
- ### Send Pixelflut ASCII Text
92
-
93
- The `pxf send` command can be used to send given Pixelflut ASCII text files to a server. It uses several connections at the same time to fasten the transmission.
94
-
95
- Next sample will send the Pixelflut ASCII text file named `pixels.px` to the server on port 1234 at address `pixelhost` using 12 connections:
96
-
97
- ```bash
98
- $ pxf send -h pixelhost -p 1234 -c 12 pixels.px
99
- ```
100
-
101
- If no file is given the STDIN will be read and send afterwards to the server.
data/bin/pxf CHANGED
@@ -1,33 +1,69 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- def bin_root
5
- File.realdirpath('../', __FILE__).freeze
4
+ help <<~end, 'IMAGE'
5
+ --host ADDRESS target host address
6
+ -p, --port PORT target port (default 1234)
7
+ -c, --connections CONN count of connections (default 4)
8
+ -b, --bytes BYTES send junks of BYTES size
9
+ -x, --transpose-x X transpose image X pixels
10
+ -y, --transpose-y Y transpose image Y pixels
11
+ -s, --scale SCALE scale image by SCALE factor
12
+ -m, --pixel MODE select pixel coding (RGBX | RGBA | RGB)
13
+ -h, --help print this help
6
14
  end
7
15
 
8
- def find_commands
9
- Hash[Dir.glob(File.join(bin_root, 'pxf-*')).map!{ |cmd| [File.basename(cmd)[4..-1], cmd] }]
16
+ main do |opts|
17
+ data = create_junks(opts)
18
+ print("spawn #{data.size}")
19
+ data.size.times do |i|
20
+ next unless fork
21
+ Process.setproctitle(name(format("#{name}-%02d", i + 1)))
22
+ data = data[i].join
23
+ GC.start
24
+ GC.disable
25
+ Pixelflut::Sender.send(opts.address, data) { print('.') }
26
+ end
27
+ rescue SocketError => e
28
+ error(3, e)
29
+ rescue LoadError => e
30
+ error(4, e)
31
+ rescue Errno::ECONNREFUSED
32
+ error(2, 'unable to connect')
33
+ rescue Errno::EPIPE
34
+ error(2, 'connection lost')
10
35
  end
11
36
 
12
- def err(msg)
13
- $stderr.puts("pxf: #{msg}")
14
- exit 1
37
+ def create_junks(opts)
38
+ lines = Pixelflut.convert(**opts.to_h)
39
+ return Pixelflut.slices(lines, count: opts.count) if opts.bytes.zero?
40
+ Pixelflut.junks(lines, bytes: opts.bytes)
15
41
  end
16
42
 
17
- def general_help
18
- puts('usage: pxf command [options]', nil, 'valid commands:')
19
- map = find_commands.transform_values!{ |cmd| %x(#{cmd} --short-help) }
20
- cmds = map.keys.sort!
21
- len = cmds.max_by(&:size).size + 3
22
- cmds.each do |cmd|
23
- print cmd.ljust(len)
24
- puts(map[cmd])
25
- end
26
- exit
43
+ before do
44
+ $stderr.sync = $stdout.sync = true
45
+ Process.setproctitle(name)
46
+ require_relative('../lib/pixelflut')
47
+ end
48
+
49
+ parse_argv do |args|
50
+ Struct.new(:address, :count, :bytes, :x, :y, :mode, :scale, :source) do
51
+ def to_h
52
+ { source: source, x: x, y: y, scale: scale, mode: mode }
53
+ end
54
+ end.new(
55
+ Pixelflut::Sender.address(
56
+ args['ADDRESS'] || '127.0.0.1',
57
+ (args['PORT'] || 1234).to_i
58
+ ),
59
+ (args['CONN'] || 4).to_i,
60
+ args['BYTES'].to_i,
61
+ args['X'].to_i,
62
+ args['Y'].to_i,
63
+ { 'RGBA' => :rgba, 'RGB' => :rgb }[args['MODE']] || :rgbx,
64
+ args.key?('SCALE') ? args['SCALE'].to_f : nil,
65
+ args['IMAGE']
66
+ )
27
67
  end
28
68
 
29
- general_help if ARGV.empty? || ARGV[0] == '--help' || ARGV[0] == '-h'
30
- cmd = ARGV.shift
31
- fname = File.join(bin_root, 'pxf-' + cmd)
32
- err("no such command - #{cmd}") unless File.executable?(fname)
33
- exec(fname, *ARGV)
69
+ BEGIN { require('mini-cli') and include(MiniCli) }
data/bin/pxf-info CHANGED
@@ -1,55 +1,32 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
1
+ #! /bin/sh
3
2
 
4
- def options(cfg)
5
- require 'optparse'
6
- OptionParser.new do |opts|
7
- opts.summary_indent = ' '
8
- opts.banner = 'usage: pxf info [options]'
9
- opts.separator(nil)
10
- opts.separator('valid options:')
11
- opts.on('-h', '--host HOST', String, 'target host name (default localhost)'){ |v| cfg[:host] = v }
12
- opts.on('-p', '--port PORT', Integer, 'target port (default 1234)'){ |v| cfg[:port] = v }
13
- end
14
- end
3
+ set -e
15
4
 
16
- def help(short)
17
- puts(options(nil), nil) unless short
18
- puts('Get info about a Pixelflut server.')
19
- exit
20
- end
5
+ if [ "$1" = '--help' ]
6
+ then
7
+ echo "Usage: ${0##*/} [-p|--port PORT] ADDRESS
21
8
 
22
- def err(msg, code = 1)
23
- $stderr.puts("pxf: #{msg}")
24
- exit(code)
25
- end
9
+ Requests screen size of Pixelflut server at ADDRESS.
10
+ "
11
+ exit 0
12
+ fi
26
13
 
27
- def create_options
28
- {port: 1234}.tap{ |cfg| options(cfg).parse! }
29
- rescue OptionParser::ParseError => e
30
- err(e)
31
- end
14
+ PORT="1234"
15
+ ADDRESS=""
32
16
 
33
- help(false) if '--help' == ARGV[0]
34
- help(true) if '--short-help' == ARGV[0]
35
- options = create_options
36
- require File.realdirpath('../../lib/pixelflut/client/socket.rb', __FILE__)
37
- begin
38
- socket = Pixelflut::Client::Socket.new(Addrinfo.tcp(options[:host], options[:port]))
17
+ while [ $# -gt 0 ]
18
+ do
19
+ key="$1"
20
+ case $key in
21
+ -p|--port)
22
+ PORT="$2"
23
+ shift; shift
24
+ ;;
25
+ *)
26
+ ADDRESS="$key"
27
+ shift
28
+ ;;
29
+ esac
30
+ done
39
31
 
40
-
41
- err("unable to connect - #{options[:host]}:options[:port])", 2) unless socket.connect? || socket.wait_writable(5)
42
- err("server busy - #{options[:host]}:options[:port])", 3) unless socket.write_with_timout("SIZE\n", 5)
43
- line = socket.readline_with_timeout(5)
44
- socket.close
45
- err("server does not respond - #{options[:host]}:options[:port])", 4) unless line
46
- mark, width, height = line.split(' ', 3)
47
- width = width.to_i
48
- height = height.to_i
49
- err("no Pixelflut server - #{options[:host]}:options[:port])", 5) if mark != 'SIZE' || width <= 0 || height <= 0
50
- puts "canvas size: #{width}x#{height}"
51
- rescue SocketError, SystemCallError => e
52
- err(e.message, 2)
53
- rescue Interrupt
54
- exit
55
- end
32
+ echo "SIZE\nQUIT\n" | nc "$ADDRESS" "$PORT"
data/gems.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- source('https://rubygems.org'){ gemspec }
3
+ source('https://rubygems.org') { gemspec }
data/lib/pixelflut.rb CHANGED
@@ -1,8 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'pixelflut/image'
4
+ require_relative 'pixelflut/sender'
5
+
1
6
  module Pixelflut
2
- LIB_DIR = File.realdirpath('../pixelflut', __FILE__).freeze
3
- autoload :App, File.join(LIB_DIR, 'app.rb')
4
- autoload :Server, File.join(LIB_DIR, 'server.rb')
5
- autoload :Canvas, File.join(LIB_DIR, 'canvas.rb')
6
- autoload :Converter, File.join(LIB_DIR, 'converter.rb')
7
- autoload :VERSION, File.join(LIB_DIR, 'version.rb')
7
+ class << self
8
+ def convert(source:, x: 0, y: 0, scale: nil, mode: :rgbx)
9
+ _convert(as_image(source, scale), x, y, &as_cvt(mode))
10
+ end
11
+
12
+ def slices(lines, count: 4)
13
+ Array.new(count) { [] }.tap do |ret|
14
+ lines.each_with_index { |line, idx| ret[idx % count] << line }
15
+ end
16
+ end
17
+
18
+ def junks(lines, bytes:)
19
+ size, ret = 0, [current = []]
20
+ lines.each do |line|
21
+ next current << line if (size += line.bytesize) < bytes
22
+ ret << (current = [line])
23
+ size = line.bytesize
24
+ end
25
+ ret
26
+ end
27
+ alias packages junks # backward compatibility
28
+
29
+ private
30
+
31
+ def _convert(image, dx, dy)
32
+ image.each_pixel.to_a.map! do |x, y, px|
33
+ "PX #{x + dx} #{y + dy} #{yield(px)}\n"
34
+ end.shuffle!
35
+ end
36
+
37
+ def as_image(source, scale)
38
+ Image.new(source).tap { |image| image.scale(scale) if scale }
39
+ end
40
+
41
+ def as_cvt(mode)
42
+ case mode
43
+ when :rgb
44
+ lambda { |px| px.to_color(Magick::AllCompliance, false, 8, true)[1, 6] }
45
+ when :rgba
46
+ lambda { |px| px.to_color(Magick::AllCompliance, true, 8, true)[1, 8] }
47
+ else
48
+ lambda do |px|
49
+ px.to_color(Magick::AllCompliance, false, 8, true)[
50
+ 1,
51
+ px.alpha >= 65_535 ? 6 : 8
52
+ ]
53
+ end
54
+ end
55
+ end
56
+ end
8
57
  end
@@ -0,0 +1,32 @@
1
+ require 'rmagick'
2
+
3
+ module Pixelflut
4
+ class Image
5
+ def initialize(file_name)
6
+ @image = Magick::ImageList.new(file_name)[0]
7
+ rescue Magick::ImageMagickError => e
8
+ raise(LoadError, e.message, cause: e)
9
+ end
10
+
11
+ def width
12
+ @image.columns
13
+ end
14
+
15
+ def height
16
+ @image.rows
17
+ end
18
+
19
+ def resize_to(width, height = nil)
20
+ @image.resize_to_fit!(width, height)
21
+ end
22
+
23
+ def scale(factor)
24
+ @image.scale!(factor)
25
+ end
26
+
27
+ def each_pixel
28
+ return to_enum(__method__) unless block_given?
29
+ @image.each_pixel { |px, x, y| 0 != px.alpha and yield(x, y, px) }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,29 @@
1
+ require 'socket'
2
+
3
+ module Pixelflut
4
+ module Sender
5
+ def self.address(host, port)
6
+ Addrinfo.tcp(host, port)
7
+ end
8
+
9
+ def self.send(address, data)
10
+ socket = create_socket(address)
11
+ yield(socket) if block_given?
12
+ loop { socket.write(data) }
13
+ end
14
+
15
+ def self.create_socket(address)
16
+ Socket
17
+ .new(address.ipv6? ? :INET6 : :INET, :STREAM)
18
+ .tap do |socket|
19
+ socket.sync = false
20
+ socket.setsockopt(:TCP, :NODELAY, 0)
21
+ socket.setsockopt(:SOCKET, :KEEPALIVE, 0)
22
+ socket.do_not_reverse_lookup = true
23
+ socket.connect(
24
+ Socket.pack_sockaddr_in(address.ip_port, address.ip_address)
25
+ )
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Pixelflut
2
- VERSION = '0.0.13'.freeze
4
+ VERSION = '0.2.1'
3
5
  end
data/pixelflut.gemspec CHANGED
@@ -2,40 +2,33 @@
2
2
 
3
3
  require_relative 'lib/pixelflut/version'
4
4
 
5
- GemSpec = Gem::Specification.new do |spec|
5
+ Gem::Specification.new do |spec|
6
6
  spec.name = 'pixelflut'
7
7
  spec.version = Pixelflut::VERSION
8
- spec.summary = 'A Pixelflut server & client tool collection written in Ruby.'
8
+ spec.author = 'Mike Blumtritt'
9
+
10
+ spec.required_ruby_version = '>= 2.7.2'
11
+
12
+ spec.summary = 'A fast Pixelflut client written in Ruby.'
9
13
  spec.description = <<~DESCRIPTION
10
- Based on the idea of a simple server protocol to collaborate on a shared canvas named
11
- [Pixel Flut](https://cccgoe.de/wiki/Pixelflut) this gem implements a Ruby version.
14
+ Based on the idea of a simple server protocol to collaborate on a shared
15
+ canvas named [Pixelflut](https://cccgoe.de/wiki/Pixelflut) this gem
16
+ implements a fast Ruby client version.
12
17
  DESCRIPTION
13
- spec.author = 'Mike Blumtritt'
14
- spec.email = 'mike.blumtritt@invision.de'
15
18
  spec.homepage = 'https://github.com/mblumtritt/pixelflut'
16
- spec.metadata = {
17
- 'issue_tracker' => 'https://github.com/mblumtritt/pixelflut/issues'
18
- }
19
- spec.rubyforge_project = spec.name
20
19
 
21
- spec.add_runtime_dependency 'gosu', '>= 0.13.2'
22
- spec.add_development_dependency 'bundler', '>= 1.16.0'
23
- spec.add_development_dependency 'rake', '>= 12.3.0'
20
+ spec.metadata['source_code_uri'] = 'https://github.com/mblumtritt/pixelflut'
21
+ spec.metadata['bug_tracker_uri'] =
22
+ 'https://github.com/mblumtritt/pixelflut/issues'
24
23
 
25
- spec.platform = Gem::Platform::RUBY
26
- spec.required_rubygems_version = Gem::Requirement.new('>= 1.3.6')
27
- spec.required_ruby_version = '>= 2.5.0'
24
+ spec.add_runtime_dependency 'rmagick'
25
+ spec.add_runtime_dependency 'mini-cli', '>= 0.4.0'
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
28
28
 
29
- spec.require_paths = %w[lib]
30
29
  spec.bindir = 'bin'
31
- spec.executables =
32
- Dir
33
- .glob(File.expand_path('../bin/*', __FILE__))
34
- .map!{ |fn| File.basename(fn) }
35
-
36
- all_files = %x(git ls-files -z).split(0.chr)
37
- spec.test_files = all_files.grep(%r{^(spec|test)/})
38
- spec.files = all_files - spec.test_files
30
+ spec.executables = %w[pxf pxf-info]
39
31
 
32
+ spec.files = Dir.chdir(__dir__) { `git ls-files -z`.split(0.chr) }
40
33
  spec.extra_rdoc_files = %w[README.md]
41
34
  end