image_intensities 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 +7 -0
- data/.gitignore +7 -0
- data/Gemfile +3 -0
- data/ext/Rakefile +19 -0
- data/ext/image_intensities/Makefile +14 -0
- data/ext/image_intensities/definitions.h +49 -0
- data/ext/image_intensities/intensities.c +108 -0
- data/ext/image_intensities/jpeg.c +71 -0
- data/ext/image_intensities/png.c +75 -0
- data/image_intensities.gemspec +17 -0
- data/lib/image_intensities.rb +29 -0
- data/lib/image_intensities/native.rb +24 -0
- data/lib/image_intensities/version.rb +5 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f39c65351e014f893378d86f9c4228992806523aa89c416b67e7f337b7ddc2fb
|
4
|
+
data.tar.gz: d935cc5c3ed468f4413f6e932fe451d0f71ce82ee52b163a8e403f7d02d94a5b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9a185c393800449e75e372b0a39d984859952074a5bfdce1666a09f2e01cdbdb7b88135529295265d9204057b55d2dcfc12dca64f03cdddf198ef27846f845d3
|
7
|
+
data.tar.gz: 4cab25ae49fc490400b688436fa3879c3f393bd80331d127e390ab2ad68f4f182bbb702fff4fa47a4abef124012fb87a20063ea1b82cac0f14a0bdd48f3877b1
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/ext/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :image_intensities do
|
4
|
+
desc 'Compile libimage_intensities'
|
5
|
+
task :compile do
|
6
|
+
lib_path = if Dir.pwd.end_with?('/ext')
|
7
|
+
'image_intensities'
|
8
|
+
else
|
9
|
+
'ext/image_intensities'
|
10
|
+
end
|
11
|
+
|
12
|
+
cd lib_path do
|
13
|
+
make_program = ENV['MAKE'] || 'make'
|
14
|
+
sh "#{make_program} lib/libimage_intensities.so"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
task default: 'image_intensities:compile'
|
@@ -0,0 +1,14 @@
|
|
1
|
+
CC ?= cc
|
2
|
+
RM ?= rm -rf
|
3
|
+
MKDIR ?= mkdir
|
4
|
+
|
5
|
+
.PHONY: all clean
|
6
|
+
|
7
|
+
all: lib/libimage_intensities.so
|
8
|
+
|
9
|
+
lib/libimage_intensities.so: definitions.h intensities.c png.c jpeg.c
|
10
|
+
$(MKDIR) -p lib
|
11
|
+
$(CC) intensities.c png.c jpeg.c -fPIC -shared -o lib/libimage_intensities.so -ljpeg -lpng
|
12
|
+
|
13
|
+
clean:
|
14
|
+
rm -rf lib
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#ifndef _JPEG_H
|
2
|
+
#define _JPEG_H
|
3
|
+
|
4
|
+
#include "definitions.h"
|
5
|
+
|
6
|
+
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
|
7
|
+
|
8
|
+
typedef struct rgb_pixel {
|
9
|
+
uint8_t r;
|
10
|
+
uint8_t g;
|
11
|
+
uint8_t b;
|
12
|
+
} rgb_pixel;
|
13
|
+
|
14
|
+
typedef struct intensity_sum {
|
15
|
+
uint64_t r;
|
16
|
+
uint64_t g;
|
17
|
+
uint64_t b;
|
18
|
+
} intensity_sum;
|
19
|
+
|
20
|
+
typedef struct quadrant_sums {
|
21
|
+
intensity_sum nw;
|
22
|
+
intensity_sum ne;
|
23
|
+
intensity_sum sw;
|
24
|
+
intensity_sum se;
|
25
|
+
} quadrant_sums;
|
26
|
+
|
27
|
+
typedef struct raster_data {
|
28
|
+
uint32_t width;
|
29
|
+
uint32_t height;
|
30
|
+
rgb_pixel *pixels;
|
31
|
+
int error;
|
32
|
+
} raster_data;
|
33
|
+
|
34
|
+
typedef struct intensity_data {
|
35
|
+
double nw;
|
36
|
+
double ne;
|
37
|
+
double sw;
|
38
|
+
double se;
|
39
|
+
int error;
|
40
|
+
} intensity_data;
|
41
|
+
|
42
|
+
raster_data read_jpeg_file(const char *file_name);
|
43
|
+
raster_data read_png_file(const char *file_name);
|
44
|
+
quadrant_sums rgb_sums(rgb_pixel *restrict pixels, uint32_t width, uint32_t height);
|
45
|
+
|
46
|
+
intensity_data jpeg_intensities(const char *file_name);
|
47
|
+
intensity_data png_intensities(const char *file_name);
|
48
|
+
|
49
|
+
#endif
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdint.h>
|
3
|
+
#include <stdlib.h>
|
4
|
+
#include <stddef.h>
|
5
|
+
#include <fcntl.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <jpeglib.h>
|
8
|
+
|
9
|
+
#include "definitions.h"
|
10
|
+
|
11
|
+
quadrant_sums rgb_sums(rgb_pixel *restrict pixels, uint32_t width, uint32_t height)
|
12
|
+
{
|
13
|
+
quadrant_sums sums = {{}};
|
14
|
+
|
15
|
+
for (uint32_t i = 0; i < height; ++i) {
|
16
|
+
for (uint32_t j = 0; j < width; ++j) {
|
17
|
+
int nw = (i <= height / 2) && (j <= width / 2);
|
18
|
+
int ne = (i <= height / 2) && (j >= width / 2);
|
19
|
+
int sw = (i >= height / 2) && (j <= width / 2);
|
20
|
+
int se = (i >= height / 2) && (j >= width / 2);
|
21
|
+
|
22
|
+
rgb_pixel pix = pixels[i * width + j];
|
23
|
+
|
24
|
+
if (nw) {
|
25
|
+
sums.nw.r += pix.r;
|
26
|
+
sums.nw.g += pix.g;
|
27
|
+
sums.nw.b += pix.b;
|
28
|
+
}
|
29
|
+
|
30
|
+
if (ne) {
|
31
|
+
sums.ne.r += pix.r;
|
32
|
+
sums.ne.g += pix.g;
|
33
|
+
sums.ne.b += pix.b;
|
34
|
+
}
|
35
|
+
|
36
|
+
if (sw) {
|
37
|
+
sums.sw.r += pix.r;
|
38
|
+
sums.sw.g += pix.g;
|
39
|
+
sums.sw.b += pix.b;
|
40
|
+
}
|
41
|
+
|
42
|
+
if (se) {
|
43
|
+
sums.se.r += pix.r;
|
44
|
+
sums.se.g += pix.g;
|
45
|
+
sums.se.b += pix.b;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
return sums;
|
51
|
+
}
|
52
|
+
|
53
|
+
static intensity_data rgb_to_luma(quadrant_sums sums, raster_data data)
|
54
|
+
{
|
55
|
+
double dim = MAX(data.width * data.height / 4.0, 1);
|
56
|
+
|
57
|
+
double nw_luma = ((sums.nw.r / dim * 0.2126) +
|
58
|
+
(sums.nw.g / dim * 0.7152) +
|
59
|
+
(sums.nw.b / dim * 0.0772)) / 3.0;
|
60
|
+
|
61
|
+
double ne_luma = ((sums.ne.r / dim * 0.2126) +
|
62
|
+
(sums.ne.g / dim * 0.7152) +
|
63
|
+
(sums.ne.b / dim * 0.0772)) / 3.0;
|
64
|
+
|
65
|
+
double sw_luma = ((sums.sw.r / dim * 0.2126) +
|
66
|
+
(sums.sw.g / dim * 0.7152) +
|
67
|
+
(sums.sw.b / dim * 0.0772)) / 3.0;
|
68
|
+
|
69
|
+
double se_luma = ((sums.se.r / dim * 0.2126) +
|
70
|
+
(sums.se.g / dim * 0.7152) +
|
71
|
+
(sums.se.b / dim * 0.0772)) / 3.0;
|
72
|
+
|
73
|
+
return (struct intensity_data) {
|
74
|
+
.nw = nw_luma,
|
75
|
+
.ne = ne_luma,
|
76
|
+
.sw = sw_luma,
|
77
|
+
.se = se_luma,
|
78
|
+
.error = 0
|
79
|
+
};
|
80
|
+
}
|
81
|
+
|
82
|
+
intensity_data jpeg_intensities(const char *file_name)
|
83
|
+
{
|
84
|
+
raster_data data = read_jpeg_file(file_name);
|
85
|
+
if (data.error)
|
86
|
+
return (struct intensity_data) { .error = 1 };
|
87
|
+
|
88
|
+
quadrant_sums sums = rgb_sums(data.pixels, data.width, data.height);
|
89
|
+
intensity_data ins = rgb_to_luma(sums, data);
|
90
|
+
|
91
|
+
free(data.pixels);
|
92
|
+
|
93
|
+
return ins;
|
94
|
+
}
|
95
|
+
|
96
|
+
intensity_data png_intensities(const char *file_name)
|
97
|
+
{
|
98
|
+
raster_data data = read_png_file(file_name);
|
99
|
+
if (data.error)
|
100
|
+
return (struct intensity_data) { .error = 1 };
|
101
|
+
|
102
|
+
quadrant_sums sums = rgb_sums(data.pixels, data.width, data.height);
|
103
|
+
intensity_data ins = rgb_to_luma(sums, data);
|
104
|
+
|
105
|
+
free(data.pixels);
|
106
|
+
|
107
|
+
return ins;
|
108
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#include <stdio.h>
|
2
|
+
#include <stdint.h>
|
3
|
+
#include <stdlib.h>
|
4
|
+
#include <stddef.h>
|
5
|
+
#include <fcntl.h>
|
6
|
+
#include <string.h>
|
7
|
+
#include <jpeglib.h>
|
8
|
+
#include <setjmp.h>
|
9
|
+
|
10
|
+
#include "definitions.h"
|
11
|
+
|
12
|
+
struct my_error_mgr {
|
13
|
+
struct jpeg_error_mgr pub;
|
14
|
+
jmp_buf setjmp_buffer;
|
15
|
+
};
|
16
|
+
|
17
|
+
static void my_error_exit(j_common_ptr cinfo)
|
18
|
+
{
|
19
|
+
struct my_error_mgr *myerr = (struct my_error_mgr *) cinfo->err;
|
20
|
+
longjmp(myerr->setjmp_buffer, 1);
|
21
|
+
}
|
22
|
+
|
23
|
+
raster_data read_jpeg_file(const char *file_name)
|
24
|
+
{
|
25
|
+
struct jpeg_decompress_struct cinfo;
|
26
|
+
struct my_error_mgr jerr;
|
27
|
+
FILE *file;
|
28
|
+
JSAMPARRAY buffer;
|
29
|
+
int row_stride;
|
30
|
+
|
31
|
+
raster_data data = {};
|
32
|
+
|
33
|
+
file = fopen(file_name, "rb");
|
34
|
+
if (file == NULL) {
|
35
|
+
data.error = 1;
|
36
|
+
return data;
|
37
|
+
}
|
38
|
+
|
39
|
+
cinfo.err = jpeg_std_error(&jerr.pub);
|
40
|
+
jerr.pub.error_exit = my_error_exit;
|
41
|
+
|
42
|
+
if (setjmp(jerr.setjmp_buffer)) {
|
43
|
+
jpeg_destroy_decompress(&cinfo);
|
44
|
+
fclose(file);
|
45
|
+
|
46
|
+
return data;
|
47
|
+
}
|
48
|
+
|
49
|
+
jpeg_create_decompress(&cinfo);
|
50
|
+
jpeg_stdio_src(&cinfo, file);
|
51
|
+
jpeg_read_header(&cinfo, TRUE);
|
52
|
+
jpeg_start_decompress(&cinfo);
|
53
|
+
|
54
|
+
data.width = cinfo.output_width;
|
55
|
+
data.height = cinfo.output_height;
|
56
|
+
data.pixels = malloc(cinfo.output_components * cinfo.output_width * cinfo.output_height);
|
57
|
+
|
58
|
+
row_stride = cinfo.output_width * cinfo.output_components;
|
59
|
+
buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
|
60
|
+
|
61
|
+
for (size_t i = 0; cinfo.output_scanline < cinfo.output_height; ++i) {
|
62
|
+
jpeg_read_scanlines(&cinfo, buffer, 1);
|
63
|
+
memcpy(&data.pixels[i * cinfo.output_width], buffer[0], row_stride);
|
64
|
+
}
|
65
|
+
|
66
|
+
jpeg_finish_decompress(&cinfo);
|
67
|
+
jpeg_destroy_decompress(&cinfo);
|
68
|
+
fclose(file);
|
69
|
+
|
70
|
+
return data;
|
71
|
+
}
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#include <png.h>
|
2
|
+
#include <stdio.h>
|
3
|
+
#include <stdint.h>
|
4
|
+
#include <stdlib.h>
|
5
|
+
#include <fcntl.h>
|
6
|
+
|
7
|
+
#include "definitions.h"
|
8
|
+
|
9
|
+
raster_data read_png_file(const char *file_name)
|
10
|
+
{
|
11
|
+
FILE *fp = NULL;
|
12
|
+
png_bytep *row_pointers = NULL;
|
13
|
+
int error = 0;
|
14
|
+
raster_data data = {};
|
15
|
+
|
16
|
+
fp = fopen(file_name, "rb");
|
17
|
+
if (fp == NULL) {
|
18
|
+
error = 1;
|
19
|
+
goto cleanup;
|
20
|
+
}
|
21
|
+
|
22
|
+
// These lines will not fail under any usual circumstances, so it's okay
|
23
|
+
// to leak the png ptr if the info ptr can't be allocated
|
24
|
+
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
25
|
+
png_infop info_ptr = png_create_info_struct(png_ptr);
|
26
|
+
|
27
|
+
if (!png_ptr || !info_ptr) {
|
28
|
+
error = 1;
|
29
|
+
goto cleanup;
|
30
|
+
}
|
31
|
+
|
32
|
+
// Set up error handler
|
33
|
+
if (setjmp(png_jmpbuf(png_ptr))) {
|
34
|
+
error = 1;
|
35
|
+
goto cleanup;
|
36
|
+
}
|
37
|
+
|
38
|
+
png_init_io(png_ptr, fp);
|
39
|
+
png_set_sig_bytes(png_ptr, 0);
|
40
|
+
|
41
|
+
png_read_info(png_ptr, info_ptr);
|
42
|
+
|
43
|
+
data.width = png_get_image_width(png_ptr, info_ptr);
|
44
|
+
data.height = png_get_image_height(png_ptr, info_ptr);
|
45
|
+
|
46
|
+
data.pixels = malloc(png_get_rowbytes(png_ptr, info_ptr) * data.height);
|
47
|
+
|
48
|
+
png_set_strip_16(png_ptr);
|
49
|
+
png_set_strip_alpha(png_ptr);
|
50
|
+
png_set_gray_to_rgb(png_ptr);
|
51
|
+
png_set_expand(png_ptr);
|
52
|
+
png_set_interlace_handling(png_ptr);
|
53
|
+
png_read_update_info(png_ptr, info_ptr);
|
54
|
+
|
55
|
+
row_pointers = png_malloc(png_ptr, data.height * sizeof(png_bytep));
|
56
|
+
for (size_t i = 0; i < data.height; ++i)
|
57
|
+
row_pointers[i] = (png_bytep) &data.pixels[data.width * i];
|
58
|
+
|
59
|
+
png_read_image(png_ptr, row_pointers);
|
60
|
+
png_free(png_ptr, row_pointers);
|
61
|
+
png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
62
|
+
|
63
|
+
fclose(fp);
|
64
|
+
|
65
|
+
return data;
|
66
|
+
|
67
|
+
cleanup:
|
68
|
+
|
69
|
+
if (fp) fclose(fp);
|
70
|
+
if (row_pointers) png_free(png_ptr, row_pointers);
|
71
|
+
if (png_ptr && info_ptr) png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
|
72
|
+
|
73
|
+
data.error = error;
|
74
|
+
return data;
|
75
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'lib/image_intensities/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'image_intensities'
|
5
|
+
s.version = ImageIntensities::VERSION
|
6
|
+
s.summary = ''
|
7
|
+
s.author = 'Liam P. White'
|
8
|
+
s.files = `git ls-files`.split("\n")
|
9
|
+
s.license = 'MIT'
|
10
|
+
s.extensions = %w[ext/Rakefile]
|
11
|
+
s.require_paths = %w[lib]
|
12
|
+
|
13
|
+
s.add_dependency 'ffi'
|
14
|
+
s.add_dependency 'rake'
|
15
|
+
s.add_dependency 'rb-libmagic'
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'magic'
|
4
|
+
|
5
|
+
module ImageIntensities
|
6
|
+
def self.file(path)
|
7
|
+
case magic.file(path)
|
8
|
+
when 'image/png'
|
9
|
+
ins = Native.png_intensities(path)
|
10
|
+
raise 'Processing error' if ins[:error] != 0
|
11
|
+
|
12
|
+
{ nw: ins[:nw], ne: ins[:ne], sw: ins[:sw], se: ins[:se] }
|
13
|
+
when 'image/jpeg'
|
14
|
+
ins = Native.jpeg_intensities(path)
|
15
|
+
raise 'Processing error' if ins[:error] != 0
|
16
|
+
|
17
|
+
{ nw: ins[:nw], ne: ins[:ne], sw: ins[:sw], se: ins[:se] }
|
18
|
+
else
|
19
|
+
raise "Unsupported file type `#{mime}'"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.magic
|
24
|
+
@magic ||= Magic.new(Magic::MIME_TYPE)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'image_intensities/native'
|
29
|
+
require 'image_intensities/version'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
|
5
|
+
module ImageIntensities
|
6
|
+
module Native
|
7
|
+
extend FFI::Library
|
8
|
+
|
9
|
+
spec = Gem.loaded_specs['image_intensities']
|
10
|
+
gem_root = spec.gem_dir
|
11
|
+
ffi_lib "#{gem_root}/ext/image_intensities/lib/libimage_intensities.so"
|
12
|
+
|
13
|
+
class IntensityData < FFI::Struct
|
14
|
+
layout :nw, :double,
|
15
|
+
:ne, :double,
|
16
|
+
:sw, :double,
|
17
|
+
:se, :double,
|
18
|
+
:error, :int
|
19
|
+
end
|
20
|
+
|
21
|
+
attach_function :jpeg_intensities, [:string], IntensityData.by_value
|
22
|
+
attach_function :png_intensities, [:string], IntensityData.by_value
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: image_intensities
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Liam P. White
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-08-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rb-libmagic
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
executables: []
|
58
|
+
extensions:
|
59
|
+
- ext/Rakefile
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- Gemfile
|
64
|
+
- ext/Rakefile
|
65
|
+
- ext/image_intensities/Makefile
|
66
|
+
- ext/image_intensities/definitions.h
|
67
|
+
- ext/image_intensities/intensities.c
|
68
|
+
- ext/image_intensities/jpeg.c
|
69
|
+
- ext/image_intensities/png.c
|
70
|
+
- image_intensities.gemspec
|
71
|
+
- lib/image_intensities.rb
|
72
|
+
- lib/image_intensities/native.rb
|
73
|
+
- lib/image_intensities/version.rb
|
74
|
+
homepage:
|
75
|
+
licenses:
|
76
|
+
- MIT
|
77
|
+
metadata: {}
|
78
|
+
post_install_message:
|
79
|
+
rdoc_options: []
|
80
|
+
require_paths:
|
81
|
+
- lib
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
requirements: []
|
93
|
+
rubygems_version: 3.0.3
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: ''
|
97
|
+
test_files: []
|