pixelflut 0.2.2 → 1.0.0

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: 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