special_input_device 0.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/ext/special_input_device/_screen.c +25 -0
  3. data/ext/special_input_device/_screen.h +16 -0
  4. data/ext/special_input_device/_system_time.c +10 -0
  5. data/ext/special_input_device/_system_time.h +6 -0
  6. data/ext/special_input_device/_vendor__interception.c +331 -0
  7. data/ext/special_input_device/_vendor__interception.h +196 -0
  8. data/ext/special_input_device/color.c +25 -0
  9. data/ext/special_input_device/color.h +10 -0
  10. data/ext/special_input_device/extconf.rb +6 -0
  11. data/ext/special_input_device/interception_connector.c +75 -0
  12. data/ext/special_input_device/interception_connector.h +13 -0
  13. data/ext/special_input_device/keyboard.c +152 -0
  14. data/ext/special_input_device/mouse.c +1137 -0
  15. data/ext/special_input_device/point.c +17 -0
  16. data/ext/special_input_device/point.h +10 -0
  17. data/ext/special_input_device/rectangle.c +25 -0
  18. data/ext/special_input_device/rectangle.h +10 -0
  19. data/ext/special_input_device/ruby_macro.h +84 -0
  20. data/ext/special_input_device/screen.c +302 -0
  21. data/ext/special_input_device/special_input_device.c +40 -0
  22. data/ext/special_input_device/special_input_device.h +42 -0
  23. data/ext/special_input_device/win32error.c +69 -0
  24. data/ext/special_input_device/win32error.h +8 -0
  25. data/ext/special_input_device/window.c +1108 -0
  26. data/lib/special_input_device/attributes_equal_checker.rb +13 -0
  27. data/lib/special_input_device/color.rb +156 -0
  28. data/lib/special_input_device/image.rb +170 -0
  29. data/lib/special_input_device/image/bmp.rb +89 -0
  30. data/lib/special_input_device/image/error/damaged_image_error.rb +4 -0
  31. data/lib/special_input_device/image/error/image_error.rb +3 -0
  32. data/lib/special_input_device/image/error/unsupported_image_format_error.rb +4 -0
  33. data/lib/special_input_device/key_code.rb +268 -0
  34. data/lib/special_input_device/point.rb +48 -0
  35. data/lib/special_input_device/rectangle.rb +187 -0
  36. data/lib/special_input_device/special_input_device.rb +67 -0
  37. data/lib/special_input_device/table_2d.rb +157 -0
  38. data/stab/keyboard.rb +35 -0
  39. data/stab/mouse.rb +189 -0
  40. data/stab/screen.rb +56 -0
  41. data/stab/win32_error.rb +20 -0
  42. data/stab/window.rb +398 -0
  43. metadata +85 -0
@@ -0,0 +1,13 @@
1
+ # The <code>AttributesEqualChecker</code> mixin is used by classes who need to compare two object by comparing all their
2
+ # attributes.
3
+ module SpecialInputDevice::AttributesEqualChecker
4
+
5
+ # Compares two objects by using all the attributes of the receiver, return true if all attributes are equal(using
6
+ # <code>==</code> operator).
7
+ # @param [Object] other other object
8
+ # @return [FalseClass, TrueClass]
9
+ def ==(other)
10
+ instance_variables.all? { |x| instance_variable_get(x) == other.instance_variable_get(x) }
11
+ end
12
+
13
+ end
@@ -0,0 +1,156 @@
1
+ require_relative('attributes_equal_checker')
2
+
3
+ # <code>Color</code> object contain 8-bit value of the levels of RGB, can represent a full color.
4
+ class SpecialInputDevice::Color
5
+
6
+ include(SpecialInputDevice::AttributesEqualChecker)
7
+
8
+ # @return [Fixnum] the level of alpha
9
+ attr_reader :alpha
10
+ # @param [Fixnum] value the level of alpha
11
+ def alpha=(value)
12
+ @alpha = [value.to_i, 0, 255].sort[1];
13
+ end
14
+
15
+ # @return [Fixnum] the level of blue
16
+ attr_reader :blue
17
+ # @param [Fixnum] value the level of blue
18
+ def blue=(value)
19
+ @blue = [value.to_i, 0, 255].sort[1];
20
+ end
21
+
22
+ # @return [Fixnum] the level of green
23
+ attr_reader :green
24
+ # @param [Fixnum] value the level of green
25
+ def green=(value)
26
+ @green = [value.to_i, 0, 255].sort[1];
27
+ end
28
+
29
+ # @return [Fixnum] the level of red
30
+ attr_reader :red
31
+ # @param [Fixnum] value the level of red
32
+ def red=(value)
33
+ @red = [value.to_i, 0, 255].sort[1];
34
+ end
35
+
36
+ # @!scope class
37
+ # @overload new(red, green, blue, alpha = 255)
38
+ # Returns a new instance of <code>Color</code>.
39
+ # @param [Fixnum] red the level of red
40
+ # @param [Fixnum] green the level of green
41
+ # @param [Fixnum] blue the level of blue
42
+ # @param [Fixnum] alpha the level of alpha
43
+ # @return [SpecialInputDevice::Color] a new instance of <code>Color</code>
44
+ def initialize(red, green, blue, alpha = 255)
45
+ self.red = red
46
+ self.green = green
47
+ self.blue = blue
48
+ self.alpha = alpha
49
+ end
50
+
51
+ # Calculate the similarity between two colors. There are difference methods to calculate.
52
+ # @see SpecialInputDevice.color_compare_method
53
+ # @param [SpecialInputDevice::Color] other other color
54
+ # @return [Float] the similarity between 0.0 and 1.0
55
+ def =~(other)
56
+ raise(TypeError, "Expecting SpecialInputDevice::Color, #{other.class} gavin") unless other.is_a?(SpecialInputDevice::Color)
57
+ case SpecialInputDevice.color_compare_method
58
+ when SpecialInputDevice::COLOR_COMPARE_METHOD::EUCLIDEAN_METRIC_IN_HSL_BIPYRAMIDAL
59
+ self_hsl = self.hsl
60
+ other_hsl = other.hsl
61
+ self_hue, self_saturation, self_lightness = self_hsl
62
+ other_hue, other_saturation, other_lightness = other_hsl
63
+ self_radius = 0.5 - (self_lightness - 0.5).abs
64
+ other_radius = 0.5 - (other_lightness - 0.5).abs
65
+ self_xyz = [self_saturation * Math.cos(self_hue) * self_radius, self_saturation * Math.sin(self_hue) * self_radius, self_lightness]
66
+ other_xyz = [other_saturation * Math.cos(other_hue) * other_radius, other_saturation * Math.sin(other_hue) * other_radius, other_lightness]
67
+ 1 - self_xyz.zip(other_xyz).map { |x| (x[0] - x[1]) ** 2 }.reduce(:+) ** 0.5
68
+ when SpecialInputDevice::COLOR_COMPARE_METHOD::EUCLIDEAN_METRIC_IN_HSV_CONE
69
+ self_hsv = self.hsv
70
+ other_hsv = other.hsv
71
+ self_hue, self_saturation, self_value = self_hsv
72
+ other_hue, other_saturation, other_value = other_hsv
73
+ self_radius = self_value / 2
74
+ other_radius = other_value / 2
75
+ self_xyz = [self_saturation * Math.cos(self_hue) * self_radius, self_saturation * Math.sin(self_hue) * self_radius, self_value]
76
+ other_xyz = [other_saturation * Math.cos(other_hue) * other_radius, other_saturation * Math.sin(other_hue) * other_radius, other_value]
77
+ 1 - (self_xyz.zip(other_xyz).map { |x| (x[0] - x[1]) ** 2 }.reduce(:+) / 1.5) ** 0.5
78
+ when SpecialInputDevice::COLOR_COMPARE_METHOD::EUCLIDEAN_METRIC_IN_RGB_CUBE
79
+ 1 - (rgb.zip(other.rgb).map { |x| (x[0] - x[1]) ** 2 }.reduce(:+) / 3) ** 0.5
80
+ else
81
+ raise(RuntimeError, "Undefined color compare method '#{SpecialInputDevice.color_compare_method.to_s}'.")
82
+ end
83
+ end
84
+
85
+ # @return [Array(Float, Float, Float)] the three value of RGB model in floating point representation
86
+ # @see https://en.wikipedia.org/wiki/RGB_color_model Wikipedia - RGB color model
87
+ def rgb
88
+ [red, green, blue].map { |x| x / 255.0 }
89
+ end
90
+
91
+ # @return [Array(Float, Float, Float)] the three value of HSL model in radius and floating point representation
92
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV Wikipedia - HSL and HSV
93
+ def hsl
94
+ rgb = self.rgb
95
+ red, green, blue = rgb
96
+ max = rgb.max
97
+ min = rgb.min
98
+ different = max - min
99
+ if different > 0
100
+ hue = case max
101
+ when red
102
+ ((green - blue) / different % 6) * (Math::PI / 3)
103
+ when green
104
+ ((blue - red) / different + 2) * (Math::PI / 3)
105
+ when blue
106
+ ((red - green) / different + 4) * (Math::PI / 3)
107
+ else
108
+ raise
109
+ end
110
+ saturation = different / (1 - (max + min - 1).abs)
111
+ else
112
+ hue = 0
113
+ saturation = 0
114
+ end
115
+ lightness = (max + min) / 2
116
+ [hue, saturation, lightness]
117
+ end
118
+
119
+ # @return [Array(Float, Float, Float)] the three value of HSV model in radius and floating point representation
120
+ # @see https://en.wikipedia.org/wiki/HSL_and_HSV Wikipedia - HSL and HSV
121
+ def hsv
122
+ rgb = self.rgb
123
+ red, green, blue = rgb
124
+ max = rgb.max
125
+ min = rgb.min
126
+ different = max - min
127
+ if different > 0
128
+ hue = case max
129
+ when red
130
+ (green - blue) / different % 6 * Math::PI / 3
131
+ when green
132
+ (blue - red) / different + 2 * Math::PI / 3
133
+ when blue
134
+ (red - green) / different + 4 * Math::PI / 3
135
+ else
136
+ raise
137
+ end
138
+ else
139
+ hue = 0
140
+ end
141
+ saturation = max > 0 ? different / max : 0
142
+ value = max
143
+ [hue, saturation, value]
144
+ end
145
+
146
+ # @!visibility private
147
+ def inspect
148
+ '#%02x%02x%02x%02X' % [alpha, red, green, blue]
149
+ end
150
+
151
+ # @!visibility private
152
+ def to_s
153
+ '#%02x%02x%02x%02X' % [alpha, red, green, blue]
154
+ end
155
+
156
+ end
@@ -0,0 +1,170 @@
1
+ require_relative('table_2d')
2
+
3
+ # <code>SpecialInputDevice::Image</code> object represent an image.
4
+ class SpecialInputDevice::Image < SpecialInputDevice::Table2D
5
+
6
+ # A container of all return methods used by <code>#find</code> method.
7
+ # @see #find
8
+ module RETURN_METHOD
9
+ # Returning the first match.
10
+ FIRST = :first
11
+ # Returning the highest similarity match.
12
+ CLOSEST = :closest
13
+ end
14
+
15
+ # Load an image from a file.
16
+ # @param [File, String] argument the file need to load
17
+ # @return [SpecialInputDevice::Image] a new image
18
+ def self.load(argument)
19
+ case argument
20
+ when String
21
+ file = File.open(argument, 'rb')
22
+ when File
23
+ file = argument
24
+ file.binmode
25
+ else
26
+ raise(TypeError, "Expecting String or File, #{argument.class} gavin")
27
+ end
28
+ extension = file.path[/(?<=\.)(?<!\/\.)(?<!\\\.)\w+\Z/].upcase
29
+ # noinspection RubyResolve
30
+ image = const_get(extension).load(file)
31
+ file.close unless argument == file
32
+ image
33
+ end
34
+
35
+ # Save this image to a file.
36
+ # @param [File, String] argument the target file
37
+ # @return [SpecialInputDevice::Image] self
38
+ def save(argument)
39
+ case argument
40
+ when String
41
+ file = File.open(argument, 'wb')
42
+ when File
43
+ file = argument
44
+ file.binmode
45
+ else
46
+ raise(TypeError, "Expecting String or File, #{argument.class} gavin")
47
+ end
48
+ extension = file.path[/(?<=\.)(?<!\/\.)(?<!\\\.)\w+\Z/].upcase
49
+ SpecialInputDevice::Image.const_get(extension).save(self, file)
50
+ file.close unless argument == file
51
+ self
52
+ end
53
+
54
+ # Calculate similarity between a section of this image and another smaller image which provided by parameter
55
+ # <code>image</code>. The parameters <code>x</code> and <code>y</code> indicate the position of the upper-left corner
56
+ # of the section. The size of section is same as the size of smaller image. This method compares each pixel in two
57
+ # image at the same position.
58
+ # @param [Integer] x the x-coordinate of the upper-left corner of the section
59
+ # @param [Integer] y the y-coordinate of the upper-left corner of the section
60
+ # @param [SpecialInputDevice::Image] image another image, should be smaller
61
+ # @param [Float] minimum_similarity minimum limit of similarity
62
+ # @return [Float, NilClass] similarity the similarity or nil if similarity is lower than minimum limit
63
+ def similarity_at(x, y, image, minimum_similarity = 0.0)
64
+ image_width = image.width
65
+ image_height = image.height
66
+ return nil unless width - x >= image_width && height - y >= image_height
67
+ current_similarity = 1.0
68
+ image_size = image_width * image_height
69
+ (0...image_width).each do |x0|
70
+ (0...image_height).each do |y0|
71
+ current_similarity -= (1 - (self[x + x0, y + y0] =~ image[x0, y0])) / image_size
72
+ return nil if current_similarity < minimum_similarity
73
+ end
74
+ end
75
+ current_similarity
76
+ end
77
+
78
+ # Find the position of another smaller image in this image.
79
+ # @param [SpecialInputDevice::Image] image another image, should be smaller
80
+ # @param [Float] similarity minimum limit of similarity
81
+ # @param [Symbol] _return indicate the behavior of this method, see SpecialInputDevice::Image::RETURN_METHOD
82
+ # @return [SpecialInputDevice::Rectangle, NilClass] the position
83
+ # @see SpecialInputDevice::Image::RETURN_METHOD
84
+ def find(image, similarity: 1.0, _return: RETURN_METHOD::FIRST)
85
+ methods = RETURN_METHOD.constants(false).map { |x| RETURN_METHOD.const_get(x) }
86
+ raise("Undefined return method '#{_return.to_s}' passed, Defined methods are [#{methods.join(', ')}].") unless methods.include?(_return)
87
+ closest_similarity = 0.0
88
+ closest_similarity_x = nil
89
+ closest_similarity_y = nil
90
+ find_path(image) do |x0, y0|
91
+ next unless (current_similarity = similarity_at(x0, y0, image, similarity))
92
+ case _return
93
+ when RETURN_METHOD::FIRST
94
+ return SpecialInputDevice::Rectangle.new(x0, y0, image.width, image.height)
95
+ when RETURN_METHOD::CLOSEST
96
+ return SpecialInputDevice::Rectangle.new(x0, y0, image.width, image.height) if current_similarity == 1.0
97
+ if current_similarity > closest_similarity
98
+ closest_similarity = current_similarity
99
+ closest_similarity_x = x0
100
+ closest_similarity_y = y0
101
+ end
102
+ else
103
+ raise(RuntimeError, "Undefined return method '#{_return.to_s}'.")
104
+ end
105
+ end
106
+ return nil unless closest_similarity_x || closest_similarity_y
107
+ SpecialInputDevice::Rectangle.new(closest_similarity_x, closest_similarity_y, image.width, image.height)
108
+ end
109
+
110
+ # Override this method if validation is needed. In this method, raise an <code>Exception</code> if it is not valid.
111
+ # @param [Integer] x the x coordinate
112
+ # @param [Integer] y the y coordinate
113
+ # @param [Object] value the object at the specific coordinate
114
+ # @return [Object] the value to be stored
115
+ def validate(x, y, value)
116
+ raise(TypeError, "Expecting SpecialInputDevice::Color, #{value.class} gavin") if value && !value.is_a?(SpecialInputDevice::Color)
117
+ value
118
+ end
119
+
120
+ protected(:validate)
121
+
122
+ def find_path(image)
123
+ return nil unless image.width <= width && image.height <= height
124
+ width_difference = width - image.width
125
+ height_difference = height - image.height
126
+ smaller_difference = [width_difference, height_difference].min
127
+ half_difference = smaller_difference / 2
128
+ need_special_handle = smaller_difference.even?
129
+ if need_special_handle
130
+ if width_difference < height_difference
131
+ (height_difference - half_difference).downto(half_difference) do |x0|
132
+ yield half_difference, x0
133
+ end
134
+ elsif width_difference > height_difference
135
+ (width_difference - half_difference).downto(half_difference) do |x0|
136
+ yield x0, half_difference
137
+ end
138
+ else
139
+ yield half_difference, half_difference
140
+ end
141
+ end
142
+ (half_difference - (need_special_handle ? 1 : 0)).downto(0) do |x0|
143
+ left = x0
144
+ right = width_difference - x0
145
+ top = x0
146
+ bottom = height_difference - x0
147
+ (x0 + 1).upto(bottom) do |x1|
148
+ yield left, x1
149
+ end
150
+ (x0 + 1).upto(right) do |x1|
151
+ yield x1, bottom
152
+ end
153
+ (bottom - 1).downto(top) do |x1|
154
+ yield right, x1
155
+ end
156
+ (right - 1).downto(left) do |x1|
157
+ yield x1, top
158
+ end
159
+ end
160
+ end
161
+
162
+ private(:find_path)
163
+
164
+ end
165
+
166
+ require_relative('image/error/image_error')
167
+ require_relative('image/error/damaged_image_error')
168
+ require_relative('image/error/unsupported_image_format_error')
169
+
170
+ require_relative('image/bmp')
@@ -0,0 +1,89 @@
1
+ # <code>SpecialInputDevice::Image::BMP</code> is an image decoder for bmp files.
2
+ module SpecialInputDevice::Image::BMP
3
+
4
+ # The header identifiers of bitmap.
5
+ # This should be the first two bytes in the bitmap file.
6
+ BITMAP_HEADER_IDENTIFIERS = ['BM', 'BA', 'CI', 'CP', 'IC', 'PT']
7
+
8
+ # Load a image from a bmp file.
9
+ # @param [File] file the file need to load
10
+ # @return [SpecialInputDevice::Image] the image
11
+ def self.load(file)
12
+ filename = file.is_a?(File) ? "The path '#{file.path}'" : 'This bitmap file'
13
+ bitmap_header_identifier = file.read(2)
14
+ raise(ArgumentError, "#{filename} does not point to a bitmap file.") unless BITMAP_HEADER_IDENTIFIERS.include?(bitmap_header_identifier)
15
+ system_file_size = file.size if file.is_a?(File)
16
+ bitmap_file_size = file.read(4).unpack('L<')[0]
17
+ raise(SpecialInputDevice::DamagedImageError, "The size of the file at '#{path}' should be #{bitmap_file_size} bytes, but the image only has #{system_file_size} bytes.") if file.is_a?(File) && bitmap_file_size != system_file_size
18
+ file.read(4) # Reserved field
19
+ image_data_address = file.read(4).unpack('L<')[0]
20
+ dib_header_size = file.read(4).unpack('L<')[0]
21
+ raise(SpecialInputDevice::UnsupportedImageFormatError, "#{filename} is using an unsupported DIB header.") unless dib_header_size == 40
22
+ width = file.read(4).unpack('l<')[0]
23
+ height = file.read(4).unpack('l<')[0]
24
+ number_of_color_planes = file.read(2).unpack('S<')[0]
25
+ raise(SpecialInputDevice::DamagedImageError, "#{filename} has been damaged.") unless number_of_color_planes == 1
26
+ color_depth = file.read(2).unpack('S<')[0]
27
+ compression_method = file.read(4).unpack('L<')[0]
28
+ raise(SpecialInputDevice::UnsupportedImageFormatError, "#{filename} is using an unsupported compression method.") unless compression_method == 0
29
+ image_size = file.read(4).unpack('L<')[0]
30
+ file.read(4) # Horizontal resolution
31
+ file.read(4) # Vertical resolution
32
+ file.read(4) # Number of colors in the color palette
33
+ file.read(4) # Number of important colors used
34
+ file.read(image_data_address - 14 - dib_header_size) # Palette
35
+ raise(SpecialInputDevice::DamagedImageError, "#{filename} has been damaged.") unless image_size == (color_depth * width.abs + 31) / 32 * 4 * height.abs
36
+ padding_size = -color_depth * width % 32 / 8
37
+ image = SpecialInputDevice::Image.new(width.abs, height.abs)
38
+ if color_depth == 24 || color_depth == 32
39
+ byte_size = color_depth / 8
40
+ (height > 0 ? (height - 1).downto(0) : height.upto(-1)).each do |x0|
41
+ (width > 0 ? 0.upto(width - 1) : -1.downto(width)).each do |x1|
42
+ color = file.read(byte_size).unpack("C#{byte_size}")
43
+ color[0], color[2] = color[2], color[0]
44
+ image[x1, x0] = SpecialInputDevice::Color.new(*color)
45
+ end
46
+ file.read(padding_size)
47
+ end
48
+ else
49
+ raise(SpecialInputDevice::UnsupportedImageFormatError, "#{filename} is using an unsupported number of bits per pixel.")
50
+ end
51
+ image
52
+ end
53
+
54
+ # Save the image to a bmp file.
55
+ # @param [SpecialInputDevice::Image] image the file need to load
56
+ # @param [File] file the file need to load
57
+ # @return [SpecialInputDevice::Image] the image
58
+ def self.save(image, file)
59
+ bmp_header_size = 14
60
+ dib_header_size = 40
61
+ headers_size = bmp_header_size + dib_header_size
62
+ color_depth = 32
63
+ width = image.width
64
+ height = image.height
65
+ row_size = color_depth / 8 * width
66
+ image_size = row_size * height
67
+ bitmap_file_size = headers_size + image_size
68
+ pack_format = "C#{row_size}"
69
+
70
+ file.write(BITMAP_HEADER_IDENTIFIERS[0])
71
+ file.write([bitmap_file_size].pack('L<')) # Bitmap file size
72
+ file.write([0].pack('L<')) # Reserved field
73
+ file.write([headers_size].pack('L<')) # Image data address
74
+ file.write([dib_header_size].pack('L<')) # DIB header size
75
+ file.write([width].pack('L<')) # Width
76
+ file.write([height].pack('L<')) # Height
77
+ file.write([1].pack('S<')) # Number of color planes
78
+ file.write([color_depth].pack('S<')) # Color depth
79
+ file.write([0].pack('L<')) # Compression method
80
+ file.write([image_size].pack('L<')) # Image size
81
+ file.write([0].pack('L<')) # Horizontal resolution
82
+ file.write([0].pack('L<')) # Vertical resolution
83
+ file.write([0].pack('L<')) # Number of colors in the color palette
84
+ file.write([0].pack('L<')) # Number of important colors used
85
+ image.each_row.reverse_each { |row| file.write((row.map { |x| [x.blue, x.green, x.red, x.alpha] }.flatten).pack(pack_format)) }
86
+ file
87
+ end
88
+
89
+ end
@@ -0,0 +1,4 @@
1
+ # <code>SpecialInputDevice::DamagedImageError</code> occur when loading a image which contain contradiction. For
2
+ # example, the image size from header of BMP file and file system do not match.
3
+ class SpecialInputDevice::DamagedImageError < SpecialInputDevice::ImageError
4
+ end
@@ -0,0 +1,3 @@
1
+ # <code>SpecialInputDevice::ImageError</code> is the base error class of all image error.
2
+ class SpecialInputDevice::ImageError < RuntimeError
3
+ end
@@ -0,0 +1,4 @@
1
+ # <code>SpecialInputDevice::UnsupportedImageFormatError</code> occur when loading a image file and that file's format is
2
+ # not supported yet.
3
+ class SpecialInputDevice::UnsupportedImageFormatError < SpecialInputDevice::ImageError
4
+ end