visual-qrcode 0.1.1 → 0.1.2
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/.rubocop.yml +3 -0
- data/Guardfile +33 -0
- data/README.md +57 -8
- data/lib/visual_qrcode/export.rb +41 -0
- data/lib/visual_qrcode/module_filler.rb +72 -0
- data/lib/visual_qrcode/pixel_tools.rb +26 -0
- data/lib/visual_qrcode/pixels_handler.rb +62 -0
- data/lib/visual_qrcode/qrcode.rb +83 -0
- data/lib/visual_qrcode/version.rb +1 -1
- data/lib/visual_qrcode.rb +1 -1
- metadata +37 -4
- data/lib/visual_qrcode/test.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f61f0ec6cbd27dd795252d141fb7dd3b6b9cfb98efe35e73d19176505d3e1c0e
|
4
|
+
data.tar.gz: 5aeb4d2931b1b574d2824dbcfe8d0c03480e30f3395dcc39ec5b262fa61b27ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f3588aa015afac87a8ef7375b25c033d8a3a39071941ed9e4ffe6dd35ba3e7039c8c05ec1fca29c5b7fc43348a39ab7d209273aff9c9a92f949f809a6d4d6fe
|
7
|
+
data.tar.gz: 1af2b54970af438dae3c551cf7e0f5126723dff1873f0ea0ca53e0f298e1b2c6948ad93ec7cb832801ad783bf209a61a503987dd12bd01d6ec2820e3717c8c71
|
data/.rubocop.yml
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A sample Guardfile
|
4
|
+
# More info at https://github.com/guard/guard#readme
|
5
|
+
|
6
|
+
## Uncomment and set this to only include directories you want to watch
|
7
|
+
# directories %w(lib spec) \
|
8
|
+
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
9
|
+
|
10
|
+
## Note: if you are using the `directories` clause above and you are not
|
11
|
+
## watching the project directory ('.'), then you will want to move
|
12
|
+
## the Guardfile to a watched dir and symlink it back, e.g.
|
13
|
+
#
|
14
|
+
# $ mkdir config
|
15
|
+
# $ mv Guardfile config/
|
16
|
+
# $ ln -s config/Guardfile .
|
17
|
+
#
|
18
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
19
|
+
|
20
|
+
# NOTE: The cmd option is now required due to the increasing number of ways
|
21
|
+
# rspec may be run, below are examples of the most common uses.
|
22
|
+
# * bundler: 'bundle exec rspec'
|
23
|
+
# * bundler binstubs: 'bin/rspec'
|
24
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
25
|
+
# installed the spring binstubs per the docs)
|
26
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
27
|
+
# * 'just' rspec: 'rspec'
|
28
|
+
|
29
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
30
|
+
watch(%r{^spec/.+_spec\.rb$})
|
31
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
32
|
+
watch("spec/spec_helper.rb") { "spec" }
|
33
|
+
end
|
data/README.md
CHANGED
@@ -1,24 +1,67 @@
|
|
1
1
|
# Visual::Qrcode
|
2
2
|
|
3
|
-
|
3
|
+
`Visual::Qrcode` gives you various tools to generate working QRCodes with images in their backgrounds, based on [this research](https://cgv.cs.nthu.edu.tw/Projects/Recreational_Graphics/Halftone_QRCodes/).
|
4
4
|
|
5
|
-
|
5
|
+
Example of the marianne Visual QRCode generated by the tests :
|
6
6
|
|
7
|
-
|
7
|
+

|
8
8
|
|
9
|
-
|
9
|
+
## Installation
|
10
10
|
|
11
11
|
Install the gem and add to the application's Gemfile by executing:
|
12
12
|
|
13
|
-
$ bundle add
|
13
|
+
$ bundle add visual-qrcode
|
14
14
|
|
15
15
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
16
16
|
|
17
|
-
$ gem install
|
17
|
+
$ gem install visual-qrcode
|
18
|
+
|
19
|
+
|
20
|
+
## Dependencies
|
21
|
+
|
22
|
+
`visual-qrcode` depends on two gems :
|
23
|
+
|
24
|
+
- [rqrcode_core](https://github.com/whomwah/rqrcode_core)
|
25
|
+
- [mini_magick](https://github.com/minimagick/minimagick)
|
26
|
+
|
27
|
+
> Make sur that you have the `ImageMagick` CLI installed on your computer to use `mini_magick`. See its [requirements](https://github.com/minimagick/minimagick?tab=readme-ov-file#requirements)
|
28
|
+
|
18
29
|
|
19
30
|
## Usage
|
20
31
|
|
21
|
-
|
32
|
+
The basic usage requires a string for the QRCode content and an `image_path` (or `image_url`).
|
33
|
+
**Default size** : 3x the basic QRCode size generated with a high level of error correction.
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
visual_qr_code = VisualQrcode::Qrcode.new("bonjour", "spec/images/marianne.png")
|
37
|
+
|
38
|
+
# Returns a MiniMagick::Image
|
39
|
+
image = visual_qr_code.as_png
|
40
|
+
|
41
|
+
# MiniMagick::Image has a write method to create an image file
|
42
|
+
image.write("./marianne_visual_qrcode.png")
|
43
|
+
```
|
44
|
+
|
45
|
+
You can also add a size parameter, in pixels. This size can't be smaller than the **default size**.
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
visual_qr_code = VisualQrcode::Qrcode.new("bonjour by 280", "spec/images/marianne.png", size: 280)
|
49
|
+
|
50
|
+
visual_qrcode.as_png.write("./marianne_visual_qrcode_280x280.png")
|
51
|
+
```
|
52
|
+
|
53
|
+
## Design choices
|
54
|
+
|
55
|
+
### Padding
|
56
|
+
|
57
|
+
In order to have a nice visual, a padding is added on the image to keep it inside of the QRCode guide patterns. Also it helps to reckognize that the image _is_ a scannable QRCode and not just some random image.
|
58
|
+
|
59
|
+
### Resize method
|
60
|
+
|
61
|
+
The Visual QRCode will be generated at a mutiple of the **default size**, and then reduced to the expected size to maintain a good background image quality.
|
62
|
+
|
63
|
+
> For example, if the Default size is 140px, and you want a 230px image, it will generate a 280px Visual QRCode and then reduce it to 230px.
|
64
|
+
|
22
65
|
|
23
66
|
## Development
|
24
67
|
|
@@ -26,9 +69,15 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
26
69
|
|
27
70
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
28
71
|
|
72
|
+
### Testing
|
73
|
+
|
74
|
+
Run `bundle exec rake` to run tests and lint. This is what runs in the CI.
|
75
|
+
|
76
|
+
You can also use `bundle exec guard` to run gard and listen to modified files to run the test
|
77
|
+
|
29
78
|
## Contributing
|
30
79
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
80
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/JeSuisUnCaillou/visual-qrcode/issues.
|
32
81
|
|
33
82
|
## License
|
34
83
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mini_magick"
|
4
|
+
require_relative "pixel_tools"
|
5
|
+
require_relative "pixels_handler"
|
6
|
+
|
7
|
+
module VisualQrcode
|
8
|
+
class Export
|
9
|
+
include PixelTools
|
10
|
+
|
11
|
+
def initialize(pixels)
|
12
|
+
@pixels_handler = PixelsHandler.new(pixels: pixels)
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_png(size: nil, margin: 6)
|
16
|
+
@pixels_handler.add_margin(margin, color: :white)
|
17
|
+
image = MiniMagick::Image.get_image_from_pixels(pixels, dimensions, "RGBA", PIXEL_DEPTH, "png")
|
18
|
+
|
19
|
+
image.resize "#{size}x#{size}" if size
|
20
|
+
|
21
|
+
image
|
22
|
+
end
|
23
|
+
|
24
|
+
def as_text(dark: "x", light: " ")
|
25
|
+
pixels.map do |row|
|
26
|
+
row.map do |pixel|
|
27
|
+
is_light = pixel.nil? || pixel.sum < max_depth * 3
|
28
|
+
is_light ? light : dark
|
29
|
+
end.join
|
30
|
+
end.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
def pixels
|
34
|
+
@pixels_handler.pixels
|
35
|
+
end
|
36
|
+
|
37
|
+
def dimensions
|
38
|
+
@pixels_handler.dimensions
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModuleFiller
|
4
|
+
PIXELS_PER_MODULE = 3
|
5
|
+
|
6
|
+
def fill_vqr_module(x_index, y_index)
|
7
|
+
if @common_patterns[x_index][y_index].nil?
|
8
|
+
fill_vqr_module_with_image(x_index, y_index)
|
9
|
+
fill_vqr_module_with_basic_qrcode(x_index, y_index)
|
10
|
+
else
|
11
|
+
fill_vqr_module_with_pattern(x_index, y_index)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def fill_vqr_module_with_basic_qrcode(x_index, y_index)
|
18
|
+
multiplied_range_each(x_index, y_index) do |new_x, new_y, x_offset, y_offset|
|
19
|
+
if central_pixel?(x_offset, y_offset) || @image_handler.pixels[new_x][new_y][3].zero?
|
20
|
+
value = @basic_qrcode.modules[x_index][y_index]
|
21
|
+
@vqr_pixels[new_x][new_y] = pixel_of(value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def fill_vqr_module_with_image(x_index, y_index)
|
27
|
+
multiplied_range_each(x_index, y_index) do |new_x, new_y, x_offset, y_offset|
|
28
|
+
next if central_pixel?(x_offset, y_offset)
|
29
|
+
|
30
|
+
pixel = @image_handler.pixels[new_x][new_y]
|
31
|
+
@vqr_pixels[new_x][new_y] = pixel
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def fill_vqr_module_with_pattern(x_index, y_index)
|
36
|
+
multiplied_range_each(x_index, y_index) do |new_x, new_y|
|
37
|
+
value = @common_patterns[x_index][y_index]
|
38
|
+
@vqr_pixels[new_x][new_y] = pixel_of(value)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def multiplied_range_each(x_index, y_index, &block)
|
43
|
+
module_range.each do |x_offset|
|
44
|
+
module_range.each do |y_offset|
|
45
|
+
new_x = (PIXELS_PER_MODULE * x_index * size_multiplier) + x_offset
|
46
|
+
new_y = (PIXELS_PER_MODULE * y_index * size_multiplier) + y_offset
|
47
|
+
block.call(new_x, new_y, x_offset, y_offset)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def module_size
|
53
|
+
@module_size ||= PIXELS_PER_MODULE * size_multiplier
|
54
|
+
end
|
55
|
+
|
56
|
+
# [0, 1, 2] : all pixels of a module
|
57
|
+
def module_range
|
58
|
+
@module_range ||= 0..(module_size - 1)
|
59
|
+
end
|
60
|
+
|
61
|
+
# 1 : the central pixels of a module
|
62
|
+
def central_range
|
63
|
+
@central_range ||= begin
|
64
|
+
third_of_range = module_range.max / 3
|
65
|
+
(third_of_range + 1)..((third_of_range * 2) + 1)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def central_pixel?(x_offset, y_offset)
|
70
|
+
central_range.include?(x_offset) && central_range.include?(y_offset)
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PixelTools
|
4
|
+
PIXEL_DEPTH = 8
|
5
|
+
|
6
|
+
def max_depth
|
7
|
+
@max_depth ||= (2**PIXEL_DEPTH) - 1
|
8
|
+
end
|
9
|
+
|
10
|
+
def pixel_of(value)
|
11
|
+
pixel_value = value ? 0 : max_depth
|
12
|
+
[pixel_value, pixel_value, pixel_value, max_depth]
|
13
|
+
end
|
14
|
+
|
15
|
+
def transparent_pixel
|
16
|
+
@transparent_pixel ||= [0] * 4
|
17
|
+
end
|
18
|
+
|
19
|
+
def white_pixel
|
20
|
+
@white_pixel ||= [255] * 4
|
21
|
+
end
|
22
|
+
|
23
|
+
def pixel_of_color(color)
|
24
|
+
send(:"#{color}_pixel")
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mini_magick"
|
4
|
+
require_relative "pixel_tools"
|
5
|
+
|
6
|
+
module VisualQrcode
|
7
|
+
class PixelsHandler
|
8
|
+
include PixelTools
|
9
|
+
|
10
|
+
attr_accessor :pixels
|
11
|
+
|
12
|
+
def initialize(pixels: nil, image_path: nil)
|
13
|
+
if pixels
|
14
|
+
@pixels = pixels
|
15
|
+
else
|
16
|
+
@image = MiniMagick::Image.open(image_path)
|
17
|
+
set_pixels_from_image
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def size
|
22
|
+
@pixels.length
|
23
|
+
end
|
24
|
+
|
25
|
+
def dimensions
|
26
|
+
[size] * 2
|
27
|
+
end
|
28
|
+
|
29
|
+
def resize_with_padding(new_size, padding)
|
30
|
+
padded_size = new_size - (2 * padding)
|
31
|
+
resize(padded_size)
|
32
|
+
add_margin(padding)
|
33
|
+
end
|
34
|
+
|
35
|
+
def resize(new_size)
|
36
|
+
image.resize "#{new_size}x#{new_size}"
|
37
|
+
set_pixels_from_image
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_margin(margin, color: :transparent)
|
41
|
+
pixel = pixel_of_color(color)
|
42
|
+
row_margin = [pixel] * margin
|
43
|
+
col_margin = [Array.new(size + (2 * margin), pixel)] * margin
|
44
|
+
|
45
|
+
margined_rows = @pixels.map do |row|
|
46
|
+
row_margin + row + row_margin
|
47
|
+
end
|
48
|
+
|
49
|
+
@pixels = col_margin + margined_rows + col_margin
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def set_pixels_from_image
|
55
|
+
@pixels = @image.get_pixels("RGBA")
|
56
|
+
end
|
57
|
+
|
58
|
+
def image
|
59
|
+
@image ||= MiniMagick::Image.get_image_from_pixels(@pixels, [size, size], "RGBA", PIXEL_DEPTH, "png")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rqrcode_core"
|
4
|
+
require_relative "pixels_handler"
|
5
|
+
require_relative "export"
|
6
|
+
require_relative "pixel_tools"
|
7
|
+
require_relative "module_filler"
|
8
|
+
|
9
|
+
module VisualQrcode
|
10
|
+
class Qrcode
|
11
|
+
include PixelTools
|
12
|
+
include ModuleFiller
|
13
|
+
|
14
|
+
PADDING_MODULES = 7
|
15
|
+
|
16
|
+
attr_reader :content, :basic_qrcode, :image_handler, :vqr_pixels
|
17
|
+
|
18
|
+
def initialize(content, image_path, size: nil)
|
19
|
+
@content = content
|
20
|
+
@size = size
|
21
|
+
|
22
|
+
@basic_qrcode = RQRCodeCore::QRCode.new(content, level: :h)
|
23
|
+
@image_handler = VisualQrcode::PixelsHandler.new(image_path: image_path)
|
24
|
+
@common_patterns = @basic_qrcode.instance_variable_get(:@common_patterns)
|
25
|
+
end
|
26
|
+
|
27
|
+
def as_png(margin: default_margin)
|
28
|
+
make
|
29
|
+
VisualQrcode::Export.new(@vqr_pixels).as_png(size: @size, margin: margin)
|
30
|
+
end
|
31
|
+
|
32
|
+
def basic_qrcode_as_png(margin: default_margin)
|
33
|
+
make
|
34
|
+
basic_qrcode_pixels = @basic_qrcode.modules.map do |module_row|
|
35
|
+
pixels_row = module_row.map { |value| [pixel_of(value)] * PIXELS_PER_MODULE }.flatten
|
36
|
+
([pixels_row] * PIXELS_PER_MODULE)
|
37
|
+
end.flatten(1)
|
38
|
+
|
39
|
+
VisualQrcode::Export.new(basic_qrcode_pixels).as_png(size: @size, margin: margin)
|
40
|
+
end
|
41
|
+
|
42
|
+
def make
|
43
|
+
intit_vqr_pixels
|
44
|
+
resize_image
|
45
|
+
fill_vqr_pixels
|
46
|
+
end
|
47
|
+
|
48
|
+
def intit_vqr_pixels
|
49
|
+
@vqr_length = min_length * size_multiplier
|
50
|
+
@vqr_pixels = Array.new(@vqr_length) { Array.new(@vqr_length) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def resize_image
|
54
|
+
padding_size = PADDING_MODULES * module_size
|
55
|
+
@image_handler.resize_with_padding(@vqr_length, padding_size)
|
56
|
+
end
|
57
|
+
|
58
|
+
def fill_vqr_pixels
|
59
|
+
@basic_qrcode.modules.each_with_index do |module_row, x_index|
|
60
|
+
module_row.each_index do |y_index|
|
61
|
+
fill_vqr_module(x_index, y_index)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def default_margin
|
69
|
+
module_size * 2
|
70
|
+
end
|
71
|
+
|
72
|
+
def size_multiplier
|
73
|
+
return 1 if @size.nil?
|
74
|
+
raise "Minimum size for your content is #{min_length}px" if @size < min_length
|
75
|
+
|
76
|
+
@size_multiplier ||= 1 + (@size / min_length)
|
77
|
+
end
|
78
|
+
|
79
|
+
def min_length
|
80
|
+
@min_length ||= @basic_qrcode.modules.length * PIXELS_PER_MODULE
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/visual_qrcode.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: visual-qrcode
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Caillou
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
12
|
-
dependencies:
|
11
|
+
date: 2024-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mini_magick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rqrcode_core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
13
41
|
description: A gem to generate working QRCodes with images in their background
|
14
42
|
email:
|
15
43
|
- 6117264+JeSuisUnCaillou@users.noreply.github.com
|
@@ -21,11 +49,16 @@ files:
|
|
21
49
|
- ".rubocop.yml"
|
22
50
|
- ".ruby-version"
|
23
51
|
- CHANGELOG.md
|
52
|
+
- Guardfile
|
24
53
|
- LICENSE.txt
|
25
54
|
- README.md
|
26
55
|
- Rakefile
|
27
56
|
- lib/visual_qrcode.rb
|
28
|
-
- lib/visual_qrcode/
|
57
|
+
- lib/visual_qrcode/export.rb
|
58
|
+
- lib/visual_qrcode/module_filler.rb
|
59
|
+
- lib/visual_qrcode/pixel_tools.rb
|
60
|
+
- lib/visual_qrcode/pixels_handler.rb
|
61
|
+
- lib/visual_qrcode/qrcode.rb
|
29
62
|
- lib/visual_qrcode/version.rb
|
30
63
|
- sig/visual_qrcode.rbs
|
31
64
|
homepage: https://github.com/JeSuisUnCaillou/visual-qrcode
|