visual-qrcode 0.1.1 → 0.1.2

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: f61f0ec6cbd27dd795252d141fb7dd3b6b9cfb98efe35e73d19176505d3e1c0e
4
+ data.tar.gz: 5aeb4d2931b1b574d2824dbcfe8d0c03480e30f3395dcc39ec5b262fa61b27ac
5
5
  SHA512:
6
- metadata.gz: fdbaf267baf3d51d34871ce631ee7be59b728307e839db6d9bb65d045fa9b83fed6c38d225ddf6a4613e757ca7d90bac285af369161bf94f18a28d3b8e8f5e74
7
- data.tar.gz: ae5b2a40c7c9d1d6165f3c7e22255ff019b6a7a4d7b1f5fbb3eddffcd6dd4af0c38c8d6b9976d8cf7807b66ec65cbb821baea950aaf9b1f5a8204ff7737d74c5
6
+ metadata.gz: 5f3588aa015afac87a8ef7375b25c033d8a3a39071941ed9e4ffe6dd35ba3e7039c8c05ec1fca29c5b7fc43348a39ab7d209273aff9c9a92f949f809a6d4d6fe
7
+ data.tar.gz: 1af2b54970af438dae3c551cf7e0f5126723dff1873f0ea0ca53e0f298e1b2c6948ad93ec7cb832801ad783bf209a61a503987dd12bd01d6ec2820e3717c8c71
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,67 @@
1
1
  # Visual::Qrcode
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
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
- 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 the marianne Visual QRCode generated by the tests :
6
6
 
7
- ## Installation
7
+ ![image](/spec/images/marianne_visual_qrcode.png)
8
8
 
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.
9
+ ## Installation
10
10
 
11
11
  Install the gem and add to the application's Gemfile by executing:
12
12
 
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
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 UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
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
- TODO: Write usage instructions here
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/[USERNAME]/visual-qrcode.
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
@@ -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.2"
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.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-11 00:00:00.000000000 Z
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/test.rb
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
@@ -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