eyes_selenium 2.15.0 → 2.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +0 -2
- data/.travis.yml +16 -0
- data/Gemfile +1 -1
- data/README.md +14 -4
- data/Rakefile +8 -1
- data/eyes_selenium.gemspec +26 -17
- data/lib/applitools/base/batch_info.rb +19 -0
- data/lib/applitools/base/dimension.rb +21 -0
- data/lib/applitools/base/environment.rb +19 -0
- data/lib/applitools/base/mouse_trigger.rb +33 -0
- data/lib/applitools/base/point.rb +21 -0
- data/lib/applitools/base/region.rb +77 -0
- data/lib/applitools/base/server_connector.rb +113 -0
- data/lib/applitools/base/session.rb +15 -0
- data/lib/applitools/base/start_info.rb +34 -0
- data/lib/applitools/base/test_results.rb +36 -0
- data/lib/applitools/base/text_trigger.rb +22 -0
- data/lib/applitools/eyes.rb +393 -0
- data/lib/applitools/eyes_logger.rb +40 -0
- data/lib/applitools/method_tracer.rb +22 -0
- data/lib/applitools/selenium/driver.rb +194 -0
- data/lib/applitools/selenium/element.rb +66 -0
- data/lib/applitools/selenium/keyboard.rb +27 -0
- data/lib/applitools/selenium/match_window_data.rb +24 -0
- data/lib/applitools/selenium/match_window_task.rb +190 -0
- data/lib/applitools/selenium/mouse.rb +62 -0
- data/lib/applitools/selenium/viewport_size.rb +128 -0
- data/lib/applitools/utils/image_delta_compressor.rb +150 -0
- data/lib/applitools/utils/image_utils.rb +63 -0
- data/lib/applitools/utils/utils.rb +52 -0
- data/lib/applitools/version.rb +3 -0
- data/lib/eyes_selenium.rb +9 -29
- data/spec/driver_passthrough_spec.rb +25 -25
- data/spec/spec_helper.rb +5 -8
- data/test/appium_example_script.rb +57 -0
- data/{test_script.rb → test/test_script.rb} +7 -9
- data/{watir_test.rb → test/watir_test_script.rb} +6 -4
- metadata +120 -48
- data/appium_eyes_example.rb +0 -56
- data/lib/eyes_selenium/capybara.rb +0 -21
- data/lib/eyes_selenium/eyes/agent_connector.rb +0 -76
- data/lib/eyes_selenium/eyes/batch_info.rb +0 -19
- data/lib/eyes_selenium/eyes/dimension.rb +0 -15
- data/lib/eyes_selenium/eyes/driver.rb +0 -266
- data/lib/eyes_selenium/eyes/element.rb +0 -78
- data/lib/eyes_selenium/eyes/environment.rb +0 -15
- data/lib/eyes_selenium/eyes/eyes.rb +0 -396
- data/lib/eyes_selenium/eyes/eyes_keyboard.rb +0 -25
- data/lib/eyes_selenium/eyes/eyes_mouse.rb +0 -60
- data/lib/eyes_selenium/eyes/failure_reports.rb +0 -4
- data/lib/eyes_selenium/eyes/match_level.rb +0 -8
- data/lib/eyes_selenium/eyes/match_window_data.rb +0 -18
- data/lib/eyes_selenium/eyes/match_window_task.rb +0 -182
- data/lib/eyes_selenium/eyes/mouse_trigger.rb +0 -23
- data/lib/eyes_selenium/eyes/region.rb +0 -72
- data/lib/eyes_selenium/eyes/screenshot_taker.rb +0 -18
- data/lib/eyes_selenium/eyes/session.rb +0 -14
- data/lib/eyes_selenium/eyes/start_info.rb +0 -32
- data/lib/eyes_selenium/eyes/test_results.rb +0 -32
- data/lib/eyes_selenium/eyes/text_trigger.rb +0 -19
- data/lib/eyes_selenium/eyes/viewport_size.rb +0 -105
- data/lib/eyes_selenium/eyes_logger.rb +0 -47
- data/lib/eyes_selenium/utils.rb +0 -6
- data/lib/eyes_selenium/utils/image_delta_compressor.rb +0 -149
- data/lib/eyes_selenium/utils/image_utils.rb +0 -76
- data/lib/eyes_selenium/version.rb +0 -3
@@ -1,32 +0,0 @@
|
|
1
|
-
class Applitools::TestResults
|
2
|
-
attr_accessor :is_new, :url
|
3
|
-
attr_reader :steps, :matches, :mismatches, :missing, :exact_matches, :strict_matches,
|
4
|
-
:content_matches, :layout_matches, :none_matches, :is_passed
|
5
|
-
def initialize(steps=0, matches=0, mismatches=0, missing=0,
|
6
|
-
exact_matches=0, strict_matches=0, content_matches=0,
|
7
|
-
layout_matches=0, none_matches=0)
|
8
|
-
@steps = steps
|
9
|
-
@matches = matches
|
10
|
-
@mismatches = mismatches
|
11
|
-
@missing = missing
|
12
|
-
@exact_matches = exact_matches
|
13
|
-
@strict_matches = strict_matches
|
14
|
-
@content_matches = content_matches
|
15
|
-
@layout_matches = layout_matches
|
16
|
-
@none_matches = none_matches
|
17
|
-
@is_new = nil
|
18
|
-
@url = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
def is_passed
|
22
|
-
!is_new && mismatches == 0 && missing ==0
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_s
|
26
|
-
is_new_str = ""
|
27
|
-
unless is_new.nil?
|
28
|
-
is_new_str = is_new ? "New test" : "Existing test"
|
29
|
-
end
|
30
|
-
"#{is_new_str} [ steps: #{steps}, matches: #{matches}, mismatches: #{mismatches}, missing: #{missing} ], URL: #{url}"
|
31
|
-
end
|
32
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
class Applitools::TextTrigger
|
2
|
-
|
3
|
-
attr_reader :text, :control
|
4
|
-
|
5
|
-
def initialize(text, control)
|
6
|
-
@text = text
|
7
|
-
@control = control
|
8
|
-
end
|
9
|
-
|
10
|
-
def to_hash
|
11
|
-
{
|
12
|
-
triggetType: 'Text', text: text, control: control.to_hash
|
13
|
-
}
|
14
|
-
end
|
15
|
-
|
16
|
-
def to_s
|
17
|
-
"Text [#{@control}] #{@text}"
|
18
|
-
end
|
19
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
class Applitools::ViewportSize
|
2
|
-
|
3
|
-
JS_GET_VIEWPORT_HEIGHT =
|
4
|
-
'var height = undefined;' +
|
5
|
-
' if (window.innerHeight) {height = window.innerHeight;}' +
|
6
|
-
' else if (document.documentElement ' +
|
7
|
-
'&& document.documentElement.clientHeight) ' +
|
8
|
-
'{height = document.documentElement.clientHeight;}' +
|
9
|
-
' else { var b = document.getElementsByTagName("body")[0]; ' +
|
10
|
-
'if (b.clientHeight) {height = b.clientHeight;}' +
|
11
|
-
'};' +
|
12
|
-
'return height;'
|
13
|
-
|
14
|
-
JS_GET_VIEWPORT_WIDTH =
|
15
|
-
'var width = undefined;' +
|
16
|
-
' if (window.innerWidth) {width = window.innerWidth;}' +
|
17
|
-
' else if (document.documentElement ' +
|
18
|
-
'&& document.documentElement.clientWidth) ' +
|
19
|
-
'{width = document.documentElement.clientWidth;}' +
|
20
|
-
' else { var b = document.getElementsByTagName("body")[0]; ' +
|
21
|
-
'if (b.clientWidth) {' +
|
22
|
-
'width = b.clientWidth;}' +
|
23
|
-
'};' +
|
24
|
-
'return width;'
|
25
|
-
|
26
|
-
attr_reader :driver
|
27
|
-
attr_accessor :dimension
|
28
|
-
def initialize(driver, dimension=nil)
|
29
|
-
@driver = driver
|
30
|
-
@dimension = dimension
|
31
|
-
end
|
32
|
-
|
33
|
-
def extract_viewport_width
|
34
|
-
driver.execute_script(JS_GET_VIEWPORT_WIDTH)
|
35
|
-
end
|
36
|
-
|
37
|
-
def extract_viewport_height
|
38
|
-
driver.execute_script(JS_GET_VIEWPORT_HEIGHT)
|
39
|
-
end
|
40
|
-
|
41
|
-
def extract_viewport_from_browser!
|
42
|
-
self.dimension = extract_viewport_from_browser
|
43
|
-
end
|
44
|
-
|
45
|
-
def extract_viewport_from_browser
|
46
|
-
width, height = nil, nil
|
47
|
-
begin
|
48
|
-
width = extract_viewport_width
|
49
|
-
height = extract_viewport_height
|
50
|
-
rescue => e
|
51
|
-
EyesLogger.info "#{__method__}(): Failed to extract viewport size using Javascript: (#{e.message})"
|
52
|
-
end
|
53
|
-
if width.nil? || height.nil?
|
54
|
-
EyesLogger.info "#{__method__}(): Using window size as viewport size."
|
55
|
-
width, height = *browser_size.values
|
56
|
-
width, height = width.ceil, height.ceil
|
57
|
-
if driver.landscape_orientation? && height > width
|
58
|
-
width, height = height, width
|
59
|
-
end
|
60
|
-
end
|
61
|
-
Applitools::Dimension.new(width,height)
|
62
|
-
end
|
63
|
-
alias_method :viewport_size, :extract_viewport_from_browser
|
64
|
-
|
65
|
-
def set
|
66
|
-
if dimension.is_a?(Hash) && dimension.has_key?(:width) && dimension.has_key?(:height)
|
67
|
-
# If dimension is hash of width/height, we convert it to a struct with width/height properties.
|
68
|
-
self.dimension = Struct.new(:width, :height).new(dimension[:width], dimension[:height])
|
69
|
-
elsif !dimension.respond_to?(:width) || !dimension.respond_to?(:height)
|
70
|
-
raise ArgumentError, "expected #{dimension.inspect}:#{dimension.class}" +
|
71
|
-
' to respond to #width and #height, or be a hash with these keys.'
|
72
|
-
end
|
73
|
-
self.browser_size = dimension
|
74
|
-
verify_size(:browser_size)
|
75
|
-
cur_viewport_size = extract_viewport_from_browser
|
76
|
-
self.browser_size = Applitools::Dimension.new(
|
77
|
-
(2 * browser_size.width) - cur_viewport_size.width,
|
78
|
-
(2 * browser_size.height) - cur_viewport_size.height
|
79
|
-
)
|
80
|
-
verify_size(:viewport_size)
|
81
|
-
end
|
82
|
-
|
83
|
-
def verify_size(to_verify, sleep_time=1, retries=3)
|
84
|
-
cur_size = nil
|
85
|
-
retries.times do
|
86
|
-
sleep(sleep_time)
|
87
|
-
cur_size = send(to_verify)
|
88
|
-
return if cur_size.values == dimension.values
|
89
|
-
end
|
90
|
-
EyesLogger.info(err_msg = "Failed setting #{to_verify} to #{dimension.values} (current size: #{cur_size.values})")
|
91
|
-
raise Applitools::TestFailedError.new(err_msg)
|
92
|
-
end
|
93
|
-
|
94
|
-
def browser_size
|
95
|
-
driver.manage.window.size
|
96
|
-
end
|
97
|
-
|
98
|
-
def browser_size=(other)
|
99
|
-
self.driver.manage.window.size = other
|
100
|
-
end
|
101
|
-
|
102
|
-
def to_hash
|
103
|
-
Hash[dimension.each_pair.to_a]
|
104
|
-
end
|
105
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
|
-
module EyesLogger
|
4
|
-
|
5
|
-
class NullLogger
|
6
|
-
attr_accessor :level
|
7
|
-
|
8
|
-
def info(msg)
|
9
|
-
# do nothing
|
10
|
-
end
|
11
|
-
def debug(msg)
|
12
|
-
#do nothing
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
NULL_LOGGER = NullLogger.new
|
17
|
-
|
18
|
-
@@log_handler = NULL_LOGGER
|
19
|
-
|
20
|
-
def self.log_handler=(log_handler)
|
21
|
-
if !log_handler.respond_to?(:info) || !log_handler.respond_to?(:debug)
|
22
|
-
raise Applitools::EyesError.new('log handler must respond to "info" and "debug"!')
|
23
|
-
end
|
24
|
-
@@log_handler = log_handler
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.info(msg)
|
28
|
-
@@log_handler.info(msg)
|
29
|
-
end
|
30
|
-
|
31
|
-
def self.debug(msg)
|
32
|
-
@@log_handler.debug(msg)
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.open
|
36
|
-
if @@log_handler.respond_to?(:open)
|
37
|
-
@@log_handler.open
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.close
|
42
|
-
if @@log_handler.respond_to?(:close)
|
43
|
-
@@log_handler.close
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
end
|
data/lib/eyes_selenium/utils.rb
DELETED
@@ -1,149 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
Applitools SDK class.
|
3
|
-
|
4
|
-
Provides image compression based on image sequences and deflate.
|
5
|
-
=end
|
6
|
-
require 'oily_png'
|
7
|
-
|
8
|
-
class Applitools::Utils::ImageDeltaCompressor
|
9
|
-
|
10
|
-
# Compresses the target image based on the source image.
|
11
|
-
#
|
12
|
-
# +target+:: +ChunkyPNG::Canvas+ The image to compress based on the source image.
|
13
|
-
# +target_encoded+:: +Array+ The uncompressed image as binary string.
|
14
|
-
# +source+:: +ChunkyPNG::Canvas+ The source image used as a base for compressing the target image.
|
15
|
-
# +block_size+:: +Integer+ The width/height of each block.
|
16
|
-
#
|
17
|
-
# Returns +String+ The binary result (either the compressed image, or the uncompressed image if the compression
|
18
|
-
# is greater in length).
|
19
|
-
def self.compress_by_raw_blocks(target, target_encoded, source, block_size = 10)
|
20
|
-
# If we can't compress for any reason, return the target image as is.
|
21
|
-
if source.nil? || (source.height != target.height) || (source.width != target.width)
|
22
|
-
# Returning a COPY of the target binary string
|
23
|
-
return String.new(target_encoded)
|
24
|
-
end
|
25
|
-
|
26
|
-
# Preparing the variables we need
|
27
|
-
target_pixels = target.to_rgb_stream.unpack('C*')
|
28
|
-
source_pixels = source.to_rgb_stream.unpack('C*')
|
29
|
-
image_size = Dimension.new(target.width, target.height)
|
30
|
-
block_columns_count = (target.width / block_size) + ((target.width % block_size) == 0 ? 0 : 1)
|
31
|
-
block_rows_count = (target.height / block_size) + ((target.height % block_size) == 0 ? 0 : 1)
|
32
|
-
|
33
|
-
# IMPORTANT: The "-Zlib::MAX_WBITS" tells ZLib to create raw deflate compression, without the
|
34
|
-
# "Zlib headers" (this isn't documented in the Zlib page, I found this in some internet forum).
|
35
|
-
compressor = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS)
|
36
|
-
|
37
|
-
compression_result = ''
|
38
|
-
|
39
|
-
# Writing the data header
|
40
|
-
compression_result += @@PREAMBLE.encode("UTF-8")
|
41
|
-
compression_result += [@@FORMAT_RAW_BLOCKS].pack("C")
|
42
|
-
compression_result += [0].pack("S>") #Source id, Big Endian
|
43
|
-
compression_result += [block_size].pack("S>") #Big Endian
|
44
|
-
|
45
|
-
|
46
|
-
# We perform the compression for each channel
|
47
|
-
3.times do |channel|
|
48
|
-
block_number = 0
|
49
|
-
block_rows_count.times do |block_row|
|
50
|
-
block_columns_count.times do |block_column|
|
51
|
-
actual_channel_index = 2 - channel # Since the image bytes are BGR and the server expects RGB...
|
52
|
-
compare_result = compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, 3, block_size,
|
53
|
-
block_column, block_row, actual_channel_index)
|
54
|
-
|
55
|
-
if !compare_result.identical
|
56
|
-
channel_bytes = compare_result.channel_bytes
|
57
|
-
string_to_compress = [channel].pack('C')
|
58
|
-
string_to_compress += [block_number].pack('L>')
|
59
|
-
string_to_compress += channel_bytes.pack('C*')
|
60
|
-
|
61
|
-
compression_result += compressor.deflate(string_to_compress)
|
62
|
-
|
63
|
-
# If the compressed data so far is greater than the uncompressed
|
64
|
-
# representation of the target, just return the target.
|
65
|
-
if compression_result.length > target_encoded.length
|
66
|
-
compressor.finish
|
67
|
-
compressor.close
|
68
|
-
# Returning a COPY of the target bytes
|
69
|
-
return String.new(target_encoded)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
block_number += 1
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
# Compress and flush any remaining uncompressed data in the input buffer.
|
77
|
-
compression_result += compressor.finish
|
78
|
-
compressor.close
|
79
|
-
# Returning the compressed result as a byte array
|
80
|
-
return compression_result
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
### PRIVATE
|
85
|
-
private
|
86
|
-
|
87
|
-
@@PREAMBLE = 'applitools'
|
88
|
-
@@FORMAT_RAW_BLOCKS = 3
|
89
|
-
|
90
|
-
Dimension = Struct.new(:width, :height)
|
91
|
-
CompareAndCopyBlockChannelDataResult = Struct.new(:identical, :channel_bytes)
|
92
|
-
|
93
|
-
|
94
|
-
# Computes the width and height of the image data contained in the block
|
95
|
-
# at the input column and row.
|
96
|
-
# +image_size+:: +Dimension+ The image size in pixels.
|
97
|
-
# +block_size+:: The block size for which we would like to compute the image data width and height.
|
98
|
-
# +block_column+:: The block column index.
|
99
|
-
# +block_row+:: The block row index.
|
100
|
-
# ++
|
101
|
-
# Returns the width and height of the image data contained in the block are returned as a +Dimension+.
|
102
|
-
def self.get_actual_block_size(image_size, block_size, block_column, block_row)
|
103
|
-
actual_width = [image_size.width - (block_column * block_size), block_size].min
|
104
|
-
actual_height = [image_size.height - (block_row * block_size), block_size].min
|
105
|
-
Dimension.new(actual_width, actual_height)
|
106
|
-
end
|
107
|
-
|
108
|
-
# Compares a block of pixels between the source and target and copies the target's block bytes to the result.
|
109
|
-
# +source_pixels+:: +Array+ of bytes, representing the pixels of the source image.
|
110
|
-
# +target_pixels+:: +Array+ of bytes, representing the pixels of the target image.
|
111
|
-
# +image_size+:: +Dimension+ The size of the source/target image (remember they must be the same size).
|
112
|
-
# +pixel_length+:: +Integer+ The number of bytes composing a pixel
|
113
|
-
# +block_size+:: +Integer+ The width/height of the block (block is a square, theoretically).
|
114
|
-
# +block_column+:: +Integer+ The block column index (when looking at the images as a grid of blocks).
|
115
|
-
# +block_row+:: +Integer+ The block row index (when looking at the images as a grid of blocks).
|
116
|
-
# +channel+:: +Integer+ The index of the channel we're comparing.
|
117
|
-
# ++
|
118
|
-
# Returns +CompareAndCopyBlockChannelDataResult+ object containing a flag saying whether the blocks are identical
|
119
|
-
# and a copy of the target block's bytes.
|
120
|
-
def self.compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, pixel_length, block_size,
|
121
|
-
block_column, block_row, channel)
|
122
|
-
identical = true
|
123
|
-
|
124
|
-
actual_block_size = get_actual_block_size(image_size, block_size, block_column, block_row)
|
125
|
-
|
126
|
-
# Getting the actual amount of data in the block we wish to copy
|
127
|
-
actual_block_height = actual_block_size.height
|
128
|
-
actual_block_width = actual_block_size.width
|
129
|
-
|
130
|
-
stride = image_size.width * pixel_length
|
131
|
-
|
132
|
-
# Iterating the block's pixels and comparing the source and target
|
133
|
-
channel_bytes = []
|
134
|
-
actual_block_height.times do |h|
|
135
|
-
offset = (((block_size * block_row) + h) * stride) + (block_size * block_column * pixel_length) + channel
|
136
|
-
actual_block_width.times do |w|
|
137
|
-
source_byte = source_pixels[offset]
|
138
|
-
target_byte = target_pixels[offset]
|
139
|
-
if source_byte != target_byte
|
140
|
-
identical = false
|
141
|
-
end
|
142
|
-
channel_bytes << target_byte
|
143
|
-
offset += pixel_length
|
144
|
-
end
|
145
|
-
end
|
146
|
-
# Returning the compare-and-copy result
|
147
|
-
CompareAndCopyBlockChannelDataResult.new(identical, channel_bytes)
|
148
|
-
end
|
149
|
-
end
|
@@ -1,76 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
Applitools SDK class.
|
3
|
-
|
4
|
-
Provides images manipulation functionality.
|
5
|
-
=end
|
6
|
-
require 'oily_png'
|
7
|
-
require 'base64'
|
8
|
-
|
9
|
-
module Applitools::Utils::ImageUtils
|
10
|
-
|
11
|
-
# Creates an image object from the PNG bytes.
|
12
|
-
# +png_bytes+:: +String+ A binary string of the PNG bytes of the image.
|
13
|
-
#
|
14
|
-
# Returns:
|
15
|
-
# +ChunkyPNG::Canvas+ An image object.
|
16
|
-
def self.png_image_from_bytes(png_bytes)
|
17
|
-
EyesLogger.debug "#{__method__}()"
|
18
|
-
image = ChunkyPNG::Image.from_blob(png_bytes)
|
19
|
-
EyesLogger.debug 'Done!'
|
20
|
-
return image
|
21
|
-
end
|
22
|
-
|
23
|
-
# Creates an image instance from a base64 representation of its PNG encoding.
|
24
|
-
#
|
25
|
-
# +png_bytes64+:: +String+ The Base64 representation of a PNG image.
|
26
|
-
#
|
27
|
-
# Returns:
|
28
|
-
# +ChunkyPNG::Canvas+ An image object.
|
29
|
-
def self.png_image_from_base64(png_bytes64)
|
30
|
-
EyesLogger.debug "#{__method__}()"
|
31
|
-
png_bytes = Base64.decode64(png_bytes64)
|
32
|
-
EyesLogger.debug 'Done!'
|
33
|
-
return png_image_from_bytes(png_bytes)
|
34
|
-
end
|
35
|
-
|
36
|
-
# Get the raw PNG bytes of an image.
|
37
|
-
#
|
38
|
-
# +ChunkyPNG::Canvas+ The image object for which to get the PNG bytes.
|
39
|
-
#
|
40
|
-
# Returns:
|
41
|
-
# +String+ The PNG bytes of the image.
|
42
|
-
def self.bytes_from_png_image(image)
|
43
|
-
EyesLogger.debug "#{__method__}()"
|
44
|
-
png_bytes = image.to_blob
|
45
|
-
EyesLogger.debug 'Done!'
|
46
|
-
return png_bytes
|
47
|
-
end
|
48
|
-
|
49
|
-
# Get the Base64 representation of the raw PNG bytes of an image.
|
50
|
-
#
|
51
|
-
# +ChunkyPNG::Canvas+ The image object for which to get the PNG bytes.
|
52
|
-
#
|
53
|
-
# Returns:
|
54
|
-
# +String+ the Base64 representation of the raw PNG bytes of an image.
|
55
|
-
def self.base64_from_png_image(image)
|
56
|
-
EyesLogger.debug "#{__method__}()"
|
57
|
-
png_bytes = bytes_from_png_image(image)
|
58
|
-
EyesLogger.debug 'Encoding as base64...'
|
59
|
-
image64 = Base64.encode64(png_bytes)
|
60
|
-
EyesLogger.debug 'Done!'
|
61
|
-
return image64
|
62
|
-
end
|
63
|
-
|
64
|
-
# Rotates a matrix 90 deg clockwise or counter clockwise (depending whether num_quadrants is positive or negative,
|
65
|
-
# respectively).
|
66
|
-
#
|
67
|
-
# +image+:: +ChunkyPNG::Canvas+ The image to rotate.
|
68
|
-
# +num_quadrants+:: +Integer+ The number of rotations to perform. Positive values are used for clockwise rotation
|
69
|
-
# and negative values are used for counter-clockwise rotation.
|
70
|
-
#
|
71
|
-
def self.quadrant_rotate!(image, num_quadrants)
|
72
|
-
rotate_method = num_quadrants > 0 ? image.method('rotate_right!'.to_sym) : image.method('rotate_left!'.to_sym)
|
73
|
-
(0..(num_quadrants.abs-1)).each { rotate_method.call }
|
74
|
-
return image
|
75
|
-
end
|
76
|
-
end
|