cuterb 0.1.0 → 0.2.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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +8 -0
- data/exe/cuterb +7 -2
- data/lib/cuterb.rb +68 -48
- data/lib/cuterb/version.rb +1 -1
- data/lib/utils.rb +76 -53
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54a6a1d3dfe28a92a4ab84de75e2103bb7ef9cc809df6bedd74f65eda8087aed
|
4
|
+
data.tar.gz: 62c6f5dcf4000786583e31539ce6b4bb81ef7c1dd78470fcc2c6d312e9ff11c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 879568b92cfe196013f82fc0915b074132c71da09d2e9e4888f2a4d34dcf8595a9b726e81ca7404e5e9bf26c4e9eb77bfb64795436bb08f4cd605a19909fe3ff
|
7
|
+
data.tar.gz: cf0ecb6153746b7e2dc12a962cd669c4533fde762d9786fd34a03b34870c393ca62d31c304c87bc2de3604e2d98634612fa01e4b985c47a78e265a8a09c770b2
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
# CuteRB
|
2
|
+
[sample/](https://github.com/lrks/cuteRB/tree/master/sample)
|
3
|
+
|
4
|
+
<img src="https://raw.githubusercontent.com/lrks/cuteRB/master/sample/puhaa_qr.png" width="50%" />
|
5
|
+
|
2
6
|
```
|
3
7
|
$ cuterb
|
4
8
|
cuterb TEXT INPUT_IMAGE_FILE OUTPUT_IMAGE_FILE
|
@@ -10,3 +14,7 @@ $ cuterb "hogehoge" sample/image.png output/qr.png
|
|
10
14
|
```
|
11
15
|
$ gem instlal cuterb
|
12
16
|
```
|
17
|
+
|
18
|
+
## Inspired by
|
19
|
+
* [chinuno\-usami/CuteR](https://github.com/chinuno-usami/CuteR)
|
20
|
+
* [kciter/qart\.js: Generate artistic QR code\. 🎨](https://github.com/kciter/qart.js)
|
data/exe/cuterb
CHANGED
@@ -1,9 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require "cuterb"
|
3
3
|
|
4
|
+
if ['-v', '--version'].include?(ARGV[0])
|
5
|
+
puts CuteRB::VERSION
|
6
|
+
exit 0
|
7
|
+
end
|
8
|
+
|
4
9
|
if ARGV.length != 3
|
5
|
-
|
6
|
-
|
10
|
+
puts "#{$0} TEXT INPUT_IMAGE_FILE OUTPUT_IMAGE_FILE"
|
11
|
+
exit 1
|
7
12
|
end
|
8
13
|
|
9
14
|
c = CuteRB::CLI.new(ARGV[0], ARGV[1], ARGV[2])
|
data/lib/cuterb.rb
CHANGED
@@ -2,77 +2,97 @@ require 'cuterb/version'
|
|
2
2
|
require 'rqrcode'
|
3
3
|
require_relative './utils.rb'
|
4
4
|
require 'rmagick'
|
5
|
+
require 'tempfile'
|
5
6
|
|
6
7
|
module CuteRB
|
7
8
|
class Error < StandardError; end
|
8
9
|
|
9
10
|
class CLI
|
10
|
-
|
11
|
-
|
11
|
+
def initialize(text, image, output)
|
12
|
+
@qr = RQRCode::QRCode.new(text, :level => :h)
|
12
13
|
|
13
14
|
@image = Magick::ImageList.new(image).first
|
14
|
-
|
15
|
-
|
16
|
-
@image = @image.quantize(256, Magick::GRAYColorspace)
|
17
|
-
bg = Magick::Image.new(@image.columns, @image.columns) { self.background_color = "white" }
|
18
|
-
@image = bg.composite(@image, 0, 0, Magick::OverCompositeOp).normalize.contrast(true).contrast(true)
|
15
|
+
short = [@image.columns, @image.rows].min
|
16
|
+
raise "#{image} too small." if short < @qr.module_count
|
19
17
|
|
18
|
+
# crop image
|
19
|
+
if @image.columns != @image.rows
|
20
|
+
begin
|
21
|
+
tmp = Tempfile.new(['cuterb-canny', '.png'])
|
22
|
+
system("convert #{image} -canny 0x1+10%+30% #{tmp.path}")
|
23
|
+
raise 'convert error' unless $?.success?
|
24
|
+
canny = Magick::ImageList.new(tmp.path).first
|
25
|
+
start = Utils.contents_area(canny)
|
26
|
+
rescue RuntimeError, Magick::ImageMagickError, Magick::FatalImageMagickError, Magick::DestroyedImageError => e
|
27
|
+
start = 0
|
28
|
+
end
|
29
|
+
|
30
|
+
if @image.columns == short
|
31
|
+
@image = @image.crop(0, start, short, start+short)
|
32
|
+
else
|
33
|
+
@image = @image.crop(start, 0, start+short, short)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@image = @image.quantize(256, Magick::GRAYColorspace)
|
37
|
+
|
38
|
+
bg = Magick::Image.new(@image.columns, @image.columns) { self.background_color = "white" }
|
39
|
+
@image = bg.composite(@image, 0, 0, Magick::OverCompositeOp).normalize.contrast(true).contrast(true)
|
20
40
|
@output = output
|
21
41
|
end
|
22
42
|
|
23
43
|
def run()
|
24
|
-
|
25
|
-
|
26
|
-
|
44
|
+
pixel = qrpixel()
|
45
|
+
overlay(pixel)
|
46
|
+
@image.write(@output)
|
27
47
|
return 0
|
28
48
|
end
|
29
49
|
|
30
50
|
|
31
51
|
private
|
32
|
-
|
52
|
+
def qrpixel()
|
33
53
|
data = @qr.to_s(:dark => 'b', :light => 'w').delete("\n").chars
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
54
|
+
Utils.place_position_probe_pattern(data, 0, 0)
|
55
|
+
Utils.place_position_probe_pattern(data, @qr.module_count - 7, 0)
|
56
|
+
Utils.place_position_probe_pattern(data, 0, @qr.module_count - 7)
|
57
|
+
Utils.place_position_adjust_pattern(data, @qr.version)
|
58
|
+
return data
|
59
|
+
end
|
40
60
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
61
|
+
def overlay(pixel, size=0.2, black_threshold=75, white_threshold=185)
|
62
|
+
scale = @image.columns / @qr.module_count.to_f
|
63
|
+
offset = (1 - size) * 0.5 * scale
|
64
|
+
size = size * scale
|
45
65
|
|
46
|
-
|
47
|
-
|
48
|
-
|
66
|
+
for row in 0...@qr.module_count
|
67
|
+
for col in 0...@qr.module_count
|
68
|
+
cell = pixel[row * @qr.module_count + col]
|
49
69
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
70
|
+
x = col * scale
|
71
|
+
y = row * scale
|
72
|
+
if cell.downcase == cell
|
73
|
+
x1 = (x + offset).round
|
74
|
+
y1 = (y + offset).round
|
75
|
+
xy = size.ceil
|
56
76
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
77
|
+
mean = @image.export_pixels(x1, y1, xy, xy, 'R').map{ |px| px/257.0 }.sum(0.0) / (xy * xy)
|
78
|
+
next if ((cell == 'b' && mean < black_threshold) || (cell == 'w' && mean > white_threshold))
|
79
|
+
else
|
80
|
+
x1 = x.round
|
81
|
+
y1 = y.round
|
82
|
+
xy = scale.ceil
|
83
|
+
end
|
64
84
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
85
|
+
draw = Magick::Draw.new
|
86
|
+
draw.fill((cell.downcase == 'w') ? '#ffffff' : '#000000')
|
87
|
+
draw.rectangle(x1, y1, x1+xy, y1+xy)
|
88
|
+
draw.draw(@image)
|
89
|
+
end
|
90
|
+
end
|
71
91
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
92
|
+
quiet = 4 * scale
|
93
|
+
width = (@image.columns + quiet * 2).ceil
|
94
|
+
bg = Magick::Image.new(width, width) { self.background_color = "white" }
|
95
|
+
@image = bg.composite(@image, quiet.round, quiet.round, Magick::OverCompositeOp)
|
96
|
+
end
|
77
97
|
end
|
78
98
|
end
|
data/lib/cuterb/version.rb
CHANGED
data/lib/utils.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
module CuteRB
|
2
2
|
class Utils
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
# rqrcode: /lib/rqrcode/qrcode/qr_util.rb L16 -- L58
|
4
|
+
#--
|
5
|
+
# Copyright 2004 by Duncan Robertson (duncan@whomwah.com).
|
6
|
+
# All rights reserved.
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
# Permission is granted for use, copying, modification, distribution,
|
9
|
+
# and distribution of modified versions of this work as long as the
|
10
|
+
# above copyright notice is included.
|
11
|
+
#++
|
12
12
|
PATTERN_POSITION_TABLE = [
|
13
13
|
[],
|
14
14
|
[6, 18],
|
@@ -52,17 +52,17 @@ module CuteRB
|
|
52
52
|
[6, 30, 58, 86, 114, 142, 170]
|
53
53
|
]
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
# rqrcode: lib/rqrcode/qrcode/qr_code.rb L346 -- L361
|
56
|
+
#--
|
57
|
+
# Copyright 2008 by Duncan Robertson (duncan@whomwah.com).
|
58
|
+
# All rights reserved.
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
60
|
+
# Permission is granted for use, copying, modification, distribution,
|
61
|
+
# and distribution of modified versions of this work as long as the
|
62
|
+
# above copyright notice is included.
|
63
|
+
#++
|
64
64
|
def self.place_position_probe_pattern(dst, row, col)
|
65
|
-
|
65
|
+
length = Math.sqrt(dst.length).to_i
|
66
66
|
(-1..7).each do |r|
|
67
67
|
next if !(row + r).between?(0, length - 1)
|
68
68
|
|
@@ -72,60 +72,83 @@ module CuteRB
|
|
72
72
|
is_vert_line = (r.between?(0, 6) && (c == 0 || c == 6))
|
73
73
|
is_horiz_line = (c.between?(0, 6) && (r == 0 || r == 6))
|
74
74
|
is_square = r.between?(2,4) && c.between?(2, 4)
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
y = row + r
|
76
|
+
x = col + c
|
77
|
+
dst[y * length + x] = (is_vert_line || is_horiz_line || is_square) ? 'B' : 'W'
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
# rqrcode: lib/rqrcode/qrcode/qr_code.rb L388 -- L403
|
83
|
+
#--
|
84
|
+
# Copyright 2008 by Duncan Robertson (duncan@whomwah.com).
|
85
|
+
# All rights reserved.
|
86
86
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
87
|
+
# Permission is granted for use, copying, modification, distribution,
|
88
|
+
# and distribution of modified versions of this work as long as the
|
89
|
+
# above copyright notice is included.
|
90
|
+
#++
|
91
91
|
def self.place_position_adjust_pattern(dst, version)
|
92
|
-
|
92
|
+
length = Math.sqrt(dst.length).to_i
|
93
93
|
positions = PATTERN_POSITION_TABLE[version - 1]
|
94
94
|
|
95
95
|
positions.each do |row|
|
96
96
|
positions.each do |col|
|
97
|
-
|
97
|
+
next if dst[row * length + col] == dst[row * length + col].upcase
|
98
98
|
|
99
99
|
( -2..2 ).each do |r|
|
100
100
|
( -2..2 ).each do |c|
|
101
|
-
|
102
|
-
|
103
|
-
|
101
|
+
y = row + r
|
102
|
+
x = col + c
|
103
|
+
dst[y * length + x] = (r.abs == 2 || c.abs == 2 || (r == 0 && c == 0)) ? 'B' : 'W'
|
104
104
|
end
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
110
|
+
def self.dump(data)
|
111
|
+
length = Math.sqrt(data.length).to_i
|
112
|
+
for row in 0...length
|
113
|
+
for col in 0...length
|
114
|
+
case data[row * length + col]
|
115
|
+
when 'b'
|
116
|
+
print "\e[47m \e[m"
|
117
|
+
when 'w'
|
118
|
+
print "\e[40m \e[m"
|
119
|
+
when 'B'
|
120
|
+
print "\e[30m\e[47m--\e[m\e[m"
|
121
|
+
when 'W'
|
122
|
+
print "\e[37m\e[40m--\e[m\e[m"
|
123
|
+
else
|
124
|
+
p data[row * length + col]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
puts ""
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.contents_area(canny)
|
132
|
+
short = [canny.columns, canny.rows].min
|
133
|
+
long = [canny.columns, canny.rows].max
|
134
|
+
integral = Array.new(long)
|
135
|
+
canny.rotate!(90, '>')
|
136
|
+
|
137
|
+
for y in 0...canny.rows
|
138
|
+
integral[y] = canny.export_pixels(0, y, canny.columns, 1, 'R').map{|px| px == 65535 ? 1 : 0}.sum(integral[y-1] || 0)
|
139
|
+
end
|
140
|
+
|
141
|
+
start = 0
|
142
|
+
count = 0
|
143
|
+
for offset in 0...(long-short)
|
144
|
+
c = integral[short + offset] - (integral[offset - 1] || 0)
|
145
|
+
if c > count
|
146
|
+
start = offset
|
147
|
+
count = c
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
return start
|
152
|
+
end
|
130
153
|
end
|
131
154
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cuterb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- lrks
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|