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.
- checksums.yaml +7 -0
- data/ext/special_input_device/_screen.c +25 -0
- data/ext/special_input_device/_screen.h +16 -0
- data/ext/special_input_device/_system_time.c +10 -0
- data/ext/special_input_device/_system_time.h +6 -0
- data/ext/special_input_device/_vendor__interception.c +331 -0
- data/ext/special_input_device/_vendor__interception.h +196 -0
- data/ext/special_input_device/color.c +25 -0
- data/ext/special_input_device/color.h +10 -0
- data/ext/special_input_device/extconf.rb +6 -0
- data/ext/special_input_device/interception_connector.c +75 -0
- data/ext/special_input_device/interception_connector.h +13 -0
- data/ext/special_input_device/keyboard.c +152 -0
- data/ext/special_input_device/mouse.c +1137 -0
- data/ext/special_input_device/point.c +17 -0
- data/ext/special_input_device/point.h +10 -0
- data/ext/special_input_device/rectangle.c +25 -0
- data/ext/special_input_device/rectangle.h +10 -0
- data/ext/special_input_device/ruby_macro.h +84 -0
- data/ext/special_input_device/screen.c +302 -0
- data/ext/special_input_device/special_input_device.c +40 -0
- data/ext/special_input_device/special_input_device.h +42 -0
- data/ext/special_input_device/win32error.c +69 -0
- data/ext/special_input_device/win32error.h +8 -0
- data/ext/special_input_device/window.c +1108 -0
- data/lib/special_input_device/attributes_equal_checker.rb +13 -0
- data/lib/special_input_device/color.rb +156 -0
- data/lib/special_input_device/image.rb +170 -0
- data/lib/special_input_device/image/bmp.rb +89 -0
- data/lib/special_input_device/image/error/damaged_image_error.rb +4 -0
- data/lib/special_input_device/image/error/image_error.rb +3 -0
- data/lib/special_input_device/image/error/unsupported_image_format_error.rb +4 -0
- data/lib/special_input_device/key_code.rb +268 -0
- data/lib/special_input_device/point.rb +48 -0
- data/lib/special_input_device/rectangle.rb +187 -0
- data/lib/special_input_device/special_input_device.rb +67 -0
- data/lib/special_input_device/table_2d.rb +157 -0
- data/stab/keyboard.rb +35 -0
- data/stab/mouse.rb +189 -0
- data/stab/screen.rb +56 -0
- data/stab/win32_error.rb +20 -0
- data/stab/window.rb +398 -0
- 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
|