pixelflut 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: accec3b9c3471899c3d8f9b9ff5b1e2f8f60506aa674acc9ff5e76dd8411ad15
4
- data.tar.gz: ef229296c199b0e12dcd6f2f7a3c48a265c7d6dae9757b1518e299f117dfa01f
3
+ metadata.gz: b91c341d7ba55f35b968b3a35c31cb4e676b2055c2f86bd6a94e3f5a4847c788
4
+ data.tar.gz: 700e3fae41c985852236339eca7381f05d383e55c74e9948b2f7935b2f7bf69e
5
5
  SHA512:
6
- metadata.gz: 8634a2c74929588c18e9c1c7b391f0557811fcf342c7d1297511ab78ae1318fff00c49a4e398c99c6c7b576cfeafd9589543297ce5d1bca7a862a77434d9fac2
7
- data.tar.gz: f7dbf586cf87531e11eca5938c3807cbd1a92d7881c21bde15d29b9f3b1301069b4c47de9cec7ae87a3b59049aded33169109f07a70d80f4d503e3a55c73b820
6
+ metadata.gz: 68bbebdd53d083cf10e6bc5c76a18b81ac43347f929166d61493a4e8a54b07263f7ae18c1b23eb5c26317a8d02a33aea18f26740c0cb3f9f387338bb4322a2f5
7
+ data.tar.gz: c045bd205847f450c924c2e0f2485737af956f6f419e99b78dadd7b8ab101972f0b251bd09eb62bd39024f60c4f00b34830ccf367a718e71395d037696f735ea
data/README.md CHANGED
@@ -12,8 +12,8 @@ This gem is an open experiment. You are welcome to fork or create pull requests
12
12
 
13
13
  Use [Bundler](http://gembundler.com/) to install the gem:
14
14
 
15
- ```bash
16
- $ gem install pixelflut
15
+ ```sh
16
+ gem install pixelflut
17
17
  ```
18
18
 
19
19
  Now the `pxf` command offers the complete functionality.
@@ -22,18 +22,19 @@ Now the `pxf` command offers the complete functionality.
22
22
 
23
23
  You'll find some help on the command line:
24
24
 
25
- ```bash
25
+ ```
26
26
  $ pxf
27
- Usage: pxf [OPTIONS] IMAGE
27
+ Usage: pxf [options] <image>
28
28
 
29
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
30
+ --host <address> target host address (default 127.0.0.1)
31
+ -p, --port <port> target port (default 1337)
32
+ -c, --connections <count> count of connections (default 4)
33
+ -x, --transpose-x <x> transpose image <x> pixels
34
+ -y, --transpose-y <y> transpose image <y> pixels
35
+ -m, --mode <mode> select pixel encoding (TEXT | BIN)
36
+ -t, --threads use threads instead of processes
37
+ -h, --help print this help
38
+ -v, --version print version information
39
+
39
40
  ```
data/bin/pxf CHANGED
@@ -2,151 +2,132 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  $stdout.sync = $stderr.sync = true
5
- Process.setproctitle($name = 'pxf')
5
+ $name = Process.setproctitle(File.basename(Process.argv0))
6
6
 
7
7
  if ARGV.index('-h') || ARGV.index('--help')
8
8
  puts <<~HELP
9
- Usage: #{$name} <image> [options]
9
+ Usage: #{$name} [options] <png_image>
10
10
 
11
11
  Options:
12
- --host <address> target host address
12
+ --host <address> target host address (default 127.0.0.1)
13
13
  -p, --port <port> target port (default 1234)
14
14
  -c, --connections <count> count of connections (default 4)
15
- -b, --bytes <bytes> send junks of <bytes> size
15
+ -m, --mode <mode> select pixel encoding (TEXT | BIN)
16
+ -t, --threads use threads instead of processes
16
17
  -x, --transpose-x <x> transpose image <x> pixels
17
18
  -y, --transpose-y <y> transpose image <y> pixels
18
- -s, --scale <scale> scale image by factor
19
- -m, --pixel <mode> select pixel coding (RGBX | RGBA | RGB)
20
19
  -h, --help print this help
20
+ -v, --version print version information
21
21
  HELP
22
22
  exit
23
23
  end
24
24
 
25
- def error(msg, code: 1)
26
- $stderr.puts("#{$name}-error: #{msg}")
25
+ if ARGV.index('-v') || ARGV.index('--version')
26
+ require_relative('../lib/pixelflut/version')
27
+ puts("#{$name} - Pixelflut v#{Pixelflut::VERSION}")
28
+ exit
29
+ end
30
+
31
+ def die!(msg, code: 1)
32
+ $stderr.puts("#{$name}: #{msg}")
27
33
  exit(code)
28
34
  end
29
35
 
30
- error('Argument missing') if ARGV.empty?
31
-
32
- Configuration =
33
- Class
34
- .new do
35
- attr_reader :image
36
- attr_reader :num_connections, :bytes
37
- attr_reader :trans_x, :trans_y, :scale, :mode
38
-
39
- def initialize
40
- @address = '127.0.0.1'
41
- @port = 1234
42
- @num_connections = 4
43
- @bytes = 0
44
- @trans_x = 0
45
- @trans_y = 0
46
- @mode = :rgbx
47
- end
48
-
49
- def parse!(argv)
50
- argv = Array.new(argv)
51
- until argv.empty?
52
- case arg = argv.shift
53
- when '--host'
54
- @address = as_str(argv.shift, 'host')
55
- when '-p', '--port'
56
- @port = as_uint(argv.shift, 'port')
57
- when '-c', '--connections'
58
- @num_connections = as_uint(argv.shift, 'connections')
59
- when '-b', '--bytes'
60
- @bytes = as_uint(argv.shift, 'bytes')
61
- when '-x', '--transpose-x'
62
- @trans_x = argv.shift.to_i
63
- when '-y', '--transpose-y'
64
- @trans_y = argv.shift.to_i
65
- when '-s', '--scale'
66
- @scale = as_float(argv.shift, 'scale')
67
- when '-m', '--pixel'
68
- @mode = as_mode(argv.shift, 'mode')
69
- else
70
- raise(ArgumentError, "Invalid option - #{arg}") if @image
71
- @image = arg
72
- end
73
- end
74
- end
75
-
76
- def address
77
- Pixelflut::Sender.address(@address, @port)
78
- end
79
-
80
- def options
81
- { source: @image, x: @trans_x, y: @trans_y, scale: @scale, mode: @mode }
82
- end
83
-
84
- private
85
-
86
- def invalid(value, name)
87
- raise(ArgumentError, "Value for #{name} missing") if value.nil?
88
- raise(ArgumentError, "Invalid value for #{name} - '#{value}'")
89
- end
90
-
91
- def as_uint(value, name)
92
- ret = value.to_i
93
- ret.positive? ? ret : invalid(value, name)
94
- end
95
-
96
- def as_str(value, name)
97
- value.nil? || value.empty? ? invalid(value, name) : value
98
- end
99
-
100
- def as_float(value, name)
101
- ret = value.to_f
102
- ret.positive? ? ret : invalid(value, name)
103
- end
104
-
105
- def as_mode(value, name)
106
- case value.downcase
107
- when 'rgbx'
108
- :rgbx
109
- when 'rgba'
110
- :rgba
111
- when 'rgb'
112
- :rgb
113
- else
114
- invalid(value, name)
115
- end
116
- end
36
+ die!('argument missing') if ARGV.empty?
37
+
38
+ require_relative('../lib/pixelflut')
39
+
40
+ module Configuration
41
+ class << self
42
+ attr_reader :use_threads
43
+
44
+ def address
45
+ require_relative('../lib/pixelflut/sender')
46
+ Pixelflut::Sender.as_address(@host || '127.0.0.1', @port || 1234)
47
+ end
48
+
49
+ private
50
+
51
+ def invalid(value, name)
52
+ die!("value for #{name} missing") if value.nil?
53
+ die!("invalid value for #{name} - '#{value}'")
54
+ end
55
+
56
+ def as_str(value, name)
57
+ value.nil? || value.empty? ? invalid(value, name) : value
58
+ end
59
+
60
+ def as_uint(value, name)
61
+ (ret = value.to_i).positive? ? ret : invalid(value, name)
62
+ end
63
+
64
+ def as_mode(value, name)
65
+ %w[text bin].include?(v = value.downcase) ? v : invalid(value, name)
117
66
  end
118
- .new
67
+ end
119
68
 
120
- def create_junks
121
- lines = Pixelflut.convert(**Configuration.options)
122
- if Configuration.bytes.zero?
123
- return Pixelflut.slices(lines, count: Configuration.num_connections)
69
+ until ARGV.empty?
70
+ case arg = ARGV.shift
71
+ when '--host'
72
+ @host = as_str(ARGV.shift, 'host')
73
+ when '-p', '--port'
74
+ @port = as_uint(ARGV.shift, 'port')
75
+ when '-c', '--connections'
76
+ Pixelflut.count = as_uint(ARGV.shift, 'connections')
77
+ die!("too many connections - #{Pixelflut.count}") if Pixelflut.count > 255
78
+ when '-m', '--mode', '--pixel'
79
+ Pixelflut.mode = as_mode(ARGV.shift, 'mode')
80
+ when '-t', '--use-threads'
81
+ @use_threads = true
82
+ when '-x', '--transpose-x'
83
+ Pixelflut.delta_x = ARGV.shift.to_i
84
+ when '-y', '--transpose-y'
85
+ Pixelflut.delta_y = ARGV.shift.to_i
86
+ else
87
+ die!("invalid option - #{arg}") if Pixelflut.file_name
88
+ Pixelflut.file_name = arg
89
+ end
124
90
  end
125
- Pixelflut.junks(lines, bytes: Configuration.bytes)
91
+
92
+ die!('<image> missing') unless Pixelflut.file_name
126
93
  end
127
94
 
128
- begin
129
- Configuration.parse!(ARGV)
130
- require_relative('../lib/pixelflut')
131
- data = create_junks
132
- error("Too many subprocesses - #{data.size}") if data.size > 256
133
- print("spawn #{data.size}")
95
+ def use_processes(address, data)
96
+ puts("#{$name}: start #{data.size} processes for #{data.sum(&:size)} bytes")
134
97
  data.size.times do |i|
135
98
  next unless fork
136
- Process.setproctitle($name = format("#{$name}-%02d", i + 1))
137
- data = data[i].join
99
+ data = data[i]
138
100
  GC.start
139
- GC.disable
140
- Pixelflut::Sender.send(Configuration.address, data) { print('.') }
101
+ $name = Process.setproctitle("#{$name}-#{'0' if i < 9}#{i + 1}")
102
+ Pixelflut::Sender.send(address, data) do |size|
103
+ puts("#{$name}: #{size} bytes")
104
+ end
105
+ end
106
+ end
107
+
108
+ def use_threads(address, data)
109
+ puts("#{$name}: start #{data.size} threads for #{data.sum(&:size)} bytes")
110
+ Thread.report_on_exception = false
111
+ data.map! { |slice| Thread.start { Pixelflut::Sender.send(address, slice) } }
112
+ GC.start
113
+ data.each(&:join)
114
+ end
115
+
116
+ begin
117
+ if Configuration.use_threads
118
+ use_threads(Configuration.address, Pixelflut.data)
119
+ else
120
+ use_processes(Configuration.address, Pixelflut.data)
141
121
  end
142
- rescue ArgumentError => e
143
- error(e)
144
- rescue LoadError => e
145
- error(e, code: 4)
146
122
  rescue Errno::ECONNREFUSED
147
- error('unable to connect', code: 2)
123
+ die!('unable to connect', code: 2)
148
124
  rescue Errno::EPIPE
149
- error('connection lost', code: 2)
125
+ die!('connection lost', code: 2)
150
126
  rescue SocketError => e
151
- error(e, code: 3)
127
+ die!(e, code: 3)
128
+ rescue LoadError => e
129
+ die!(e, code: 4)
130
+ rescue Interrupt
131
+ print("\b\b") if $stdout.tty?
132
+ die!('aborted', code: 130)
152
133
  end
@@ -1,31 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
-
5
3
  module Pixelflut
6
4
  module Sender
7
- def self.address(host, port)
8
- Addrinfo.tcp(host, port)
5
+ def self.as_address(host, port)
6
+ require('socket') unless defined?(Addrinfo)
7
+ info = Addrinfo.tcp(host, port)
8
+ Address.new(
9
+ Socket.pack_sockaddr_in(info.ip_port, info.ip_address),
10
+ info.ipv6? ? :INET6 : :INET
11
+ )
9
12
  end
10
13
 
11
14
  def self.send(address, data)
12
15
  socket = create_socket(address)
13
- yield(socket) if block_given?
14
- loop { socket.write(data) }
16
+ yield(data.bytesize) if block_given?
17
+ socket.write(data) while true
15
18
  end
16
19
 
17
20
  def self.create_socket(address)
18
- Socket
19
- .new(address.ipv6? ? :INET6 : :INET, :STREAM)
20
- .tap do |socket|
21
- socket.sync = true
22
- socket.setsockopt(:TCP, :NODELAY, 0)
23
- socket.setsockopt(:SOCKET, :KEEPALIVE, 0)
24
- socket.do_not_reverse_lookup = true
25
- socket.connect(
26
- Socket.pack_sockaddr_in(address.ip_port, address.ip_address)
27
- )
28
- end
21
+ socket = Socket.new(address.type, :STREAM)
22
+ socket.connect(address.sockaddr_in)
23
+ socket.setsockopt(:TCP, :NODELAY, 1)
24
+ socket.setsockopt(:SOCKET, :KEEPALIVE, 0)
25
+ socket.sync = socket.do_not_reverse_lookup = true
26
+ socket
29
27
  end
28
+
29
+ Address = Struct.new(:sockaddr_in, :type)
30
30
  end
31
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pixelflut
4
- VERSION = '0.2.2'
4
+ VERSION = '1.0.0'
5
5
  end
data/lib/pixelflut.rb CHANGED
@@ -1,60 +1,99 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'pixelflut/image'
4
- require_relative 'pixelflut/sender'
5
-
6
3
  module Pixelflut
7
4
  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))
5
+ attr_accessor :file_name, :count, :mode, :delta_x, :delta_y
6
+
7
+ def data
8
+ sliced(convert(*load).shuffle!, @count).map!(&:join)
10
9
  end
11
10
 
12
- def slices(lines, count: 4)
13
- Array
14
- .new(count) { [] }
15
- .tap do |ret|
16
- lines.each_with_index { |line, idx| ret[idx % count] << line }
17
- end
11
+ private
12
+
13
+ def load
14
+ require('chunky_png') unless defined?(ChunkyPNG)
15
+ image = ChunkyPNG::Canvas.from_file(@file_name)
16
+ [image.width, image.height, image.pixels.pack("N#{image.pixels.size}")]
17
+ rescue Errno::ENOENT => e
18
+ raise(LoadError, e.message, cause: e)
18
19
  end
19
20
 
20
- def junks(lines, bytes:)
21
- size, ret = 0, [current = []]
22
- lines.each do |line|
23
- next current << line if (size += line.bytesize) < bytes
24
- ret << (current = [line])
25
- size = line.bytesize
21
+ # def load_rmagick
22
+ # require('rmagick') unless defined?(Magick)
23
+ # image = Magick::Image.read(@file_name).first
24
+ # image.scale!(@scale) if @scale
25
+ # [
26
+ # image.columns,
27
+ # image.rows,
28
+ # image.export_pixels_to_str(0, 0, image.columns, image.rows, 'rgba')
29
+ # ]
30
+ # rescue Magick::ImageMagickError => e
31
+ # raise(LoadError, e.message, cause: e)
32
+ # end
33
+
34
+ def sliced(array, number)
35
+ division = array.size / number
36
+ modulo = array.size % number
37
+ pos = 0
38
+ Array.new(number) do |index|
39
+ length = division + (modulo > 0 && modulo > index ? 1 : 0)
40
+ slice = array.slice(pos, length)
41
+ pos += length
42
+ slice
26
43
  end
27
- ret
28
44
  end
29
45
 
30
- private
31
-
32
- def _convert(image, dx, dy)
33
- image
34
- .each_pixel
35
- .to_a
36
- .map! { |x, y, px| "PX #{x + dx} #{y + dy} #{yield(px)}\n" }
37
- .shuffle!
46
+ def convert(width, height, blob)
47
+ if @mode == 'bin'
48
+ to_bin_format(width, height, blob)
49
+ else
50
+ to_text_format(width, height, blob)
51
+ end
38
52
  end
39
53
 
40
- def as_image(source, scale)
41
- Image.new(source).tap { |image| image.scale(scale) if scale }
54
+ def to_text_format(width, height, blob)
55
+ ret = []
56
+ pos = -1
57
+ height.times do |y|
58
+ width.times do |x|
59
+ next if (a = blob.getbyte(pos += 4)) == 0
60
+ ret << format(
61
+ (
62
+ if a == 255
63
+ "PX %d %d %02x%02x%02x\n"
64
+ else
65
+ "PX %d %d %02x%02x%02x%02x\n"
66
+ end
67
+ ),
68
+ x + @delta_x,
69
+ y + @delta_y,
70
+ blob.getbyte(pos - 3),
71
+ blob.getbyte(pos - 2),
72
+ blob.getbyte(pos - 1),
73
+ a
74
+ )
75
+ end
76
+ end
77
+ ret
42
78
  end
43
79
 
44
- def as_cvt(mode)
45
- case mode
46
- when :rgb
47
- ->(px) { px.to_color(Magick::AllCompliance, false, 8, true)[1, 6] }
48
- when :rgba
49
- ->(px) { px.to_color(Magick::AllCompliance, true, 8, true)[1, 8] }
50
- else
51
- lambda do |px|
52
- px.to_color(Magick::AllCompliance, false, 8, true)[
53
- 1,
54
- px.alpha >= 65_535 ? 6 : 8
55
- ]
80
+ def to_bin_format(width, height, blob)
81
+ ret = []
82
+ pos = 0
83
+ height.times do |y|
84
+ width.times do |x|
85
+ if blob[pos + 3] != "\x0"
86
+ ret << "PB#{[x + @delta_x, y + @delta_y].pack('v2')}#{blob[pos, 4]}"
87
+ end
88
+ pos += 4
56
89
  end
57
90
  end
91
+ ret
58
92
  end
59
93
  end
94
+
95
+ @count = ENV['TC'].to_i
96
+ @count = 4 unless @count.positive?
97
+ @mode = 'text'
98
+ @delta_x = @delta_y = 0
60
99
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pixelflut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-16 00:00:00.000000000 Z
11
+ date: 2023-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rmagick
14
+ name: chunky_png
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -31,16 +31,13 @@ description: |
31
31
  email:
32
32
  executables:
33
33
  - pxf
34
- - pxf-info
35
34
  extensions: []
36
35
  extra_rdoc_files:
37
36
  - README.md
38
37
  files:
39
38
  - README.md
40
39
  - bin/pxf
41
- - bin/pxf-info
42
40
  - lib/pixelflut.rb
43
- - lib/pixelflut/image.rb
44
41
  - lib/pixelflut/sender.rb
45
42
  - lib/pixelflut/version.rb
46
43
  homepage: https://github.com/mblumtritt/pixelflut
@@ -48,6 +45,7 @@ licenses: []
48
45
  metadata:
49
46
  source_code_uri: https://github.com/mblumtritt/pixelflut
50
47
  bug_tracker_uri: https://github.com/mblumtritt/pixelflut/issues
48
+ rubygems_mfa_required: 'true'
51
49
  post_install_message:
52
50
  rdoc_options: []
53
51
  require_paths:
@@ -63,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
61
  - !ruby/object:Gem::Version
64
62
  version: '0'
65
63
  requirements: []
66
- rubygems_version: 3.3.7
64
+ rubygems_version: 3.4.22
67
65
  signing_key:
68
66
  specification_version: 4
69
67
  summary: A fast Pixelflut client written in Ruby.
data/bin/pxf-info DELETED
@@ -1,32 +0,0 @@
1
- #! /bin/sh
2
-
3
- set -e
4
-
5
- if [ "$1" = '--help' ]
6
- then
7
- echo "Usage: ${0##*/} [-p|--port PORT] ADDRESS
8
-
9
- Requests screen size of Pixelflut server at ADDRESS.
10
- "
11
- exit 0
12
- fi
13
-
14
- PORT="1234"
15
- ADDRESS=""
16
-
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
31
-
32
- echo "SIZE\nQUIT\n" | nc "$ADDRESS" "$PORT"
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rmagick'
4
-
5
- module Pixelflut
6
- class Image
7
- def initialize(file_name)
8
- @image = Magick::ImageList.new(file_name).first
9
- rescue Magick::ImageMagickError => e
10
- raise(LoadError, e.message, cause: e)
11
- end
12
-
13
- def width
14
- @image.columns
15
- end
16
-
17
- def height
18
- @image.rows
19
- end
20
-
21
- def resize_to(width, height = nil)
22
- @image.resize_to_fit!(width, height)
23
- end
24
-
25
- def scale(factor)
26
- @image.scale!(factor)
27
- end
28
-
29
- def each_pixel
30
- return to_enum(__method__) unless block_given?
31
- @image.each_pixel { |px, x, y| 0 != px.alpha and yield(x, y, px) }
32
- end
33
- end
34
- end