visual-qrcode 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ff65d953dd617026b54fb67139962d89a0447ed0d0e7366ed73a3a8a6d9834d
4
- data.tar.gz: 8df829a7254d9ce0b60aac4aee8193f9d71a516386ef829ce32ea423b1fcbb87
3
+ metadata.gz: 0ed1452bc8167ca44fd547c65998487c46f259967c28edc10ea7730f3e968b62
4
+ data.tar.gz: e6f33b6faf3d491657f13db6218260ab68888d15e046286bac339267451f79f8
5
5
  SHA512:
6
- metadata.gz: fdbaf267baf3d51d34871ce631ee7be59b728307e839db6d9bb65d045fa9b83fed6c38d225ddf6a4613e757ca7d90bac285af369161bf94f18a28d3b8e8f5e74
7
- data.tar.gz: ae5b2a40c7c9d1d6165f3c7e22255ff019b6a7a4d7b1f5fbb3eddffcd6dd4af0c38c8d6b9976d8cf7807b66ec65cbb821baea950aaf9b1f5a8204ff7737d74c5
6
+ metadata.gz: 429de2eb4cfa170258b51f814d452df963a32bf54afa6dc2ce8c3ccaf65fc2c8d0da1e490aa9bb2705881cddaf3c511da975610c6f198ae53930793610d6a99a
7
+ data.tar.gz: d9b1f0e0e9c16592a44a99cb4272adb605189c6902d8e42cf5ef9c4aa0e11ec4c11b209e39fd54ab3b2a4287ca2cf415929e36af742975f3979e5b24cf30f41e
data/.rubocop.yml CHANGED
@@ -14,3 +14,6 @@ Style/StringLiteralsInInterpolation:
14
14
 
15
15
  Style/Documentation:
16
16
  Enabled: false
17
+
18
+ Rspec/MultipleMemoizedHelpers:
19
+ Enabled: false
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,115 @@
1
- # Visual::Qrcode
1
+ # VisualQrcode
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
3
+ `VisualQrcode` gives you various tools to generate working QR codes with images in their backgrounds, based on [this research](https://cgv.cs.nthu.edu.tw/Projects/Recreational_Graphics/Halftone_QRCodes/).
4
4
 
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/visual_qrcode`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ Example of some `VisualQrcode` generated by the tests :
6
6
 
7
- ## Installation
7
+ ![image](/spec/images/marianne_visual_qrcode.png)
8
+ ![image](/spec/images/zidane_visual_qrcode.png)
9
+ ![image](/spec/images/leaf_visual_qrcode.png)
10
+
11
+ Basically, each QR Code module is transformed into 9 pixels. The central pixel is the QR Code data, and the 8 other pixels around are used to display the background image.
8
12
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
13
+ ![image](/docs/basic_to_visual_sample.png)
14
+
15
+ ## Installation
10
16
 
11
17
  Install the gem and add to the application's Gemfile by executing:
12
18
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
19
+ $ bundle add visual-qrcode
14
20
 
15
21
  If bundler is not being used to manage dependencies, install the gem by executing:
16
22
 
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
23
+ $ gem install visual-qrcode
24
+
25
+
26
+ ## Dependencies
27
+
28
+ `visual-qrcode` depends on two gems :
29
+
30
+ - [rqrcode_core](https://github.com/whomwah/rqrcode_core)
31
+ - [mini_magick](https://github.com/minimagick/minimagick)
32
+
33
+ > 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)
34
+
18
35
 
19
36
  ## Usage
20
37
 
21
- TODO: Write usage instructions here
38
+ The basic usage requires a string for the QRCode content and an `image_path` (or `image_url`).
39
+
40
+ ```ruby
41
+ visual_qr_code = VisualQrcode::Qrcode.new(
42
+ "Taataaaa Yoyoyooooo ! Qu'est-ce que tu caches sous ton grand chapeauuuuuu !",
43
+ "spec/images/marianne.png"
44
+ )
45
+
46
+ # Returns a MiniMagick::Image
47
+ image = visual_qr_code.as_png
48
+
49
+ # MiniMagick::Image has a write method to create an image file
50
+ image.write("./marianne_visual_qrcode.png")
51
+ ```
52
+
53
+ **Default size** : Because we need 9 pixels in each QR Code module, the **minimum size** is **3x the minimum basic QRCode size** with a high level of error correction. It varies with the amount of content you want to encode in the QR Code.
54
+
55
+ You can also add a size parameter, in pixels. This size can't be smaller than the **minimum size**.
56
+
57
+ ```ruby
58
+ visual_qr_code = VisualQrcode::Qrcode.new(
59
+ "This is a leaf. Yeah. Big surprise, isn't it ?",
60
+ "spec/images/leaf.png",
61
+ size: 280
62
+ )
63
+
64
+ visual_qrcode.as_png.write("./leaf_visual_qrcode_280x280.png")
65
+ ```
66
+
67
+ If you choose a size too small, you'll get an error informing you of the minimum size necessary for your content.
68
+
69
+ If your content is small and produces a QR Code of small size (big patterns, few modules), you can increase the amount of modules with the `qr_size` parameter. It corresponds to the [size option of RQRCodeCore](https://github.com/whomwah/rqrcode_core/tree/master?tab=readme-ov-file#options)
70
+
71
+ ```ruby
72
+ visual_qr_code = VisualQrcode::Qrcode.new(
73
+ "eh",
74
+ "spec/images/zidane.png",
75
+ qr_size: 10
76
+ )
77
+
78
+ visual_qrcode.as_png.write("./zidane_visual_qrcode_size_10.png")
79
+ ```
80
+
81
+ ## Design choices
82
+
83
+ ### Padding
84
+
85
+ In order to have a nice visual, a padding is added on the image to keep it inside of the QRCode line patterns on top and on the left. Also it helps to reckognize that the image _is_ a scannable QRCode and not just some random image.
86
+
87
+ **By default, the padding is equal to 7 modules.**
88
+
89
+ If your image has enough transparency to dodge the QRCode lines, you can remove the padding with the `padding_modules: 0` option.
90
+
91
+ ```ruby
92
+ visual_qr_code = VisualQrcode::Qrcode.new(
93
+ "My leaf don't need no padding, it's a strong and independant leaf",
94
+ "spec/images/leaf.png",
95
+ size: 280,
96
+ padding_modules: 0
97
+ )
98
+ ```
99
+
100
+ You can also customize the padding if you want more or less modules than the default value.
101
+
102
+ ### Resize method
103
+
104
+ The Visual QRCode will be generated at a mutiple of the **minimum size**, and then reduced to the expected size to maintain a good background image quality.
105
+
106
+ > For example, if the minimum size is 140px, and you want a 230px image, it will generate a 280px Visual QRCode and then reduce it to 230px.
107
+
108
+ ### QRCode minimum Size
109
+
110
+ The minimum [size of RQRCodeCore](https://github.com/whomwah/rqrcode_core/tree/master?tab=readme-ov-file#options) used is 6 by default, to get enough space for the image to be visible inside the Visual QRCode.
111
+
112
+ But you can force it to a lower value if you want, with the `qr_size` option.
22
113
 
23
114
  ## Development
24
115
 
@@ -26,9 +117,15 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
26
117
 
27
118
  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
119
 
120
+ ### Testing
121
+
122
+ Run `bundle exec rake` to run tests and lint. This is what runs in the CI.
123
+
124
+ You can also use `bundle exec guard` to run gard and listen to modified files to run the test
125
+
29
126
  ## Contributing
30
127
 
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/visual-qrcode.
128
+ Bug reports and pull requests are welcome on GitHub at https://github.com/JeSuisUnCaillou/visual-qrcode/issues.
32
129
 
33
130
  ## License
34
131
 
Binary file
@@ -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) || @pixels_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 = @pixels_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,64 @@
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
+ raise "Image should be a square" if @image.width != @image.height
18
+
19
+ set_pixels_from_image
20
+ end
21
+ end
22
+
23
+ def size
24
+ @pixels.length
25
+ end
26
+
27
+ def dimensions
28
+ [size] * 2
29
+ end
30
+
31
+ def resize_with_padding(new_size, padding)
32
+ padded_size = new_size - (2 * padding)
33
+ resize(padded_size)
34
+ add_margin(padding)
35
+ end
36
+
37
+ def resize(new_size)
38
+ image.resize "#{new_size}x#{new_size}"
39
+ set_pixels_from_image
40
+ end
41
+
42
+ def add_margin(margin, color: :transparent)
43
+ pixel = pixel_of_color(color)
44
+ row_margin = [pixel] * margin
45
+ col_margin = [Array.new(size + (2 * margin), pixel)] * margin
46
+
47
+ margined_rows = @pixels.map do |row|
48
+ row_margin + row + row_margin
49
+ end
50
+
51
+ @pixels = col_margin + margined_rows + col_margin
52
+ end
53
+
54
+ private
55
+
56
+ def set_pixels_from_image
57
+ @pixels = @image.get_pixels("RGBA")
58
+ end
59
+
60
+ def image
61
+ @image ||= MiniMagick::Image.get_image_from_pixels(@pixels, [size, size], "RGBA", PIXEL_DEPTH, "png")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,84 @@
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
+ DEFAULT_PADDING_MODULES = 7
15
+
16
+ attr_reader :content, :basic_qrcode, :pixels_handler, :vqr_pixels
17
+
18
+ def initialize(content, image_path, size: nil, padding_modules: nil, qr_size: nil)
19
+ @content = content
20
+ @size = size
21
+ @padding_modules = padding_modules || DEFAULT_PADDING_MODULES
22
+ @qr_size = qr_size || 6
23
+
24
+ initialize_basic_qr_code_and_qr_size(content)
25
+ @pixels_handler = VisualQrcode::PixelsHandler.new(image_path: image_path)
26
+ @common_patterns = @basic_qrcode.instance_variable_get(:@common_patterns)
27
+ end
28
+
29
+ def as_png(margin: default_margin)
30
+ make
31
+ VisualQrcode::Export.new(@vqr_pixels).as_png(size: @size, margin: margin)
32
+ end
33
+
34
+ def make
35
+ intit_vqr_pixels
36
+ resize_image
37
+ fill_vqr_pixels
38
+ end
39
+
40
+ def intit_vqr_pixels
41
+ @vqr_length = min_length * size_multiplier
42
+ @vqr_pixels = Array.new(@vqr_length) { Array.new(@vqr_length) }
43
+ end
44
+
45
+ def resize_image
46
+ padding_size = @padding_modules * module_size
47
+ @pixels_handler.resize_with_padding(@vqr_length, padding_size)
48
+ end
49
+
50
+ def fill_vqr_pixels
51
+ @basic_qrcode.modules.each_with_index do |module_row, x_index|
52
+ module_row.each_index do |y_index|
53
+ fill_vqr_module(x_index, y_index)
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def initialize_basic_qr_code_and_qr_size(content)
61
+ @basic_qrcode = RQRCodeCore::QRCode.new(content, level: :h, size: @qr_size)
62
+ rescue RQRCodeCore::QRCodeRunTimeError => e
63
+ raise e unless e.message =~ /^code length overflow./
64
+
65
+ @qr_size += 1
66
+ initialize_basic_qr_code_and_qr_size(content)
67
+ end
68
+
69
+ def default_margin
70
+ module_size * 2
71
+ end
72
+
73
+ def size_multiplier
74
+ return 1 if @size.nil?
75
+ raise "Minimum size for your content is #{min_length}px" if @size < min_length
76
+
77
+ @size_multiplier ||= 1 + (@size / min_length)
78
+ end
79
+
80
+ def min_length
81
+ @min_length ||= @basic_qrcode.modules.length * PIXELS_PER_MODULE
82
+ end
83
+ end
84
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module VisualQrcode
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/visual_qrcode.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "visual_qrcode/version"
4
- require_relative "visual_qrcode/test"
4
+ require_relative "visual_qrcode/qrcode"
5
5
 
6
6
  module VisualQrcode
7
7
  class Error < StandardError; end
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.1
4
+ version: 0.1.3
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-11 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-07-15 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,17 @@ 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
56
+ - docs/basic_to_visual_sample.png
27
57
  - lib/visual_qrcode.rb
28
- - lib/visual_qrcode/test.rb
58
+ - lib/visual_qrcode/export.rb
59
+ - lib/visual_qrcode/module_filler.rb
60
+ - lib/visual_qrcode/pixel_tools.rb
61
+ - lib/visual_qrcode/pixels_handler.rb
62
+ - lib/visual_qrcode/qrcode.rb
29
63
  - lib/visual_qrcode/version.rb
30
64
  - sig/visual_qrcode.rbs
31
65
  homepage: https://github.com/JeSuisUnCaillou/visual-qrcode
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module VisualQrcode
4
- class Test
5
- attr_accessor :input
6
-
7
- def initialize(input)
8
- @input = input
9
- end
10
- end
11
- end