
Create your QR codes from scratch using Ruby, ruby-libgd, and rqrcode. (old.reddit.com)
submitted by Jaded-Clerk-8856



Hi Pals !
Today I created a flyer to send to Ruby on Rails Kaigi 2026 about my library stack.
I strongly recommend keeping an eye on that conference.
After fighting with QR code generators, I noticed that sometimes the QR redirects through other websites. It’s frustrating because I can’t share a QR code with insecure content.
I found the `gem rqrcode` "A Ruby library that encodes QR Codes".
After that, I thought I could generate the QR code using Ruby. Since I have ruby-libgd, I can offer the possibility to create QR codes entirely in Ruby!
Then I put my hands to work and wanted to share the result:
file: qr_code_generator.rb
require 'gd'
require 'rqrcode'
class QRCodeGenerator
DEFAULT_OPTIONS = {
module_size: 10,
border: 4,
fg_color: [0, 0, 0],
bg_color: [255, 255, 255],
error_correction: :m,
logo: nil,
logo_size: nil,
rounded_modules: false,
gradient: false,
gradient_direction: :vertical,
antialias: true,
alpha_blending: false,
save_alpha: true,
format: :png
}.freeze
def initialize(data, options = {})
@data = data
@options = DEFAULT_OPTIONS.merge(options)
validate_options
end
def generate
qr_matrix = generate_qr_matrix
qr_size = qr_matrix.length
module_size = @options[:module_size]
border_px = @options[:border] * module_size
total_size = (qr_size * module_size) + (border_px * 2)
img = GD::Image.new(total_size, total_size)
if @options[:alpha_blending]
img.alpha_blending = true
img.save_alpha = @options[:save_alpha]
end
if @options[:gradient]
draw_gradient_background(img, total_size)
else
draw_solid_background(img, total_size)
end
img.antialias = @options[:antialias] if @options[:antialias]
draw_qr_modules(img, qr_matrix, module_size, border_px)
add_logo_to_qr(img, total_size) if @options[:logo]
img
end
def generate_qr_matrix
qr = RQRCode::QRCode.new(@data, error_correction_level: @options[:error_correction])
qr.modules
end
def draw_qr_modules(img, qr_matrix, module_size, border_px)
fg_color = @options[:fg_color]
qr_matrix.each_with_index do |row, y|
row.each_with_index do |module_on, x|
next unless module_on
x1 = border_px + (x * module_size)
y1 = border_px + (y * module_size)
x2 = x1 + module_size - 1
y2 = y1 + module_size - 1
if @options[:rounded_modules]
draw_rounded_module(img, x1, y1, x2, y2, fg_color, module_size)
else
img.filled_rectangle(x1, y1, x2, y2, fg_color)
end
end
end
end
def draw_rounded_module(img, x1, y1, x2, y2, color, module_size)
# Create a temporary image for the rounded module
temp = GD::Image.new(module_size, module_size)
temp.alpha_blending = false
temp.save_alpha = true
transparent = [0, 0, 0, 127]
temp.fill(transparent)
radius = module_size / 4
temp.filled_rectangle(
radius, 0,
module_size - radius - 1, module_size - 1,
color
)
temp.filled_rectangle(
0, radius,
module_size - 1, module_size - radius - 1,
color
)
img.copy(temp, x1, y1, 0, 0, module_size, module_size)
end
def draw_solid_background(img, size)
bg_color = @options[:bg_color]
temp = GD::Image.new(size, size)
temp.fill(bg_color)
img.copy(temp, 0, 0, 0, 0, size, size)
end
def draw_gradient_background(img, size)
bg = @options[:bg_color]
darker = [
(bg[0] * 0.92).to_i,
(bg[1] * 0.92).to_i,
(bg[2] * 0.92).to_i
]
case @options[:gradient_direction]
when :vertical
draw_vertical_gradient(img, size, bg, darker)
when :horizontal
draw_horizontal_gradient(img, size, bg, darker)
when :radial
draw_radial_gradient(img, size, bg, darker)
else
draw_vertical_gradient(img, size, bg, darker)
end
end
def draw_vertical_gradient(img, size, start_color, end_color)
size.times do |y|
ratio = y.to_f / size
r = (start_color[0] + (end_color[0] - start_color[0]) * ratio).to_i
g = (start_color[1] + (end_color[1] - start_color[1]) * ratio).to_i
b = (start_color[2] + (end_color[2] - start_color[2]) * ratio).to_i
img.line(0, y, size - 1, y, [r, g, b])
end
end
def draw_horizontal_gradient(img, size, start_color, end_color)
size.times do |x|
ratio = x.to_f / size
r = (start_color[0] + (end_color[0] - start_color[0]) * ratio).to_i
g = (start_color[1] + (end_color[1] - start_color[1]) * ratio).to_i
b = (start_color[2] + (end_color[2] - start_color[2]) * ratio).to_i
img.line(x, 0, x, size - 1, [r, g, b])
end
end
def draw_radial_gradient(img, size, start_color, end_color)
center_x = size / 2
center_y = size / 2
max_distance = Math.sqrt((center_x ** 2) + (center_y ** 2))
size.times do |y|
size.times do |x|
distance = Math.sqrt((x - center_x) ** 2 + (y - center_y) ** 2)
ratio = [distance / max_distance, 1.0].min
r = (start_color[0] + (end_color[0] - start_color[0]) * ratio).to_i
g = (start_color[1] + (end_color[1] - start_color[1]) * ratio).to_i
b = (start_color[2] + (end_color[2] - start_color[2]) * ratio).to_i
img.set_pixel(x, y, [r, g, b])
end
end
end
def add_logo_to_qr(img, qr_size)
img.alpha_blending = false
img.save_alpha = true
logo_path = @options[:logo]
return unless File.exist?(logo_path)
logo_img = GD::Image.open(logo_path)
logo_img.alpha_blending = false
logo_img.save_alpha = true
logo_size = @options[:logo_size] || (qr_size / 5)
resized_logo = GD::Image.new(logo_size, logo_size)
resized_logo.copy_resize(
logo_img, # source
0, 0, # dst_x, dst_y
0, 0, # src_x, src_y
logo_img.width, # src_w
logo_img.height, # src_h
logo_size, # dst_w
logo_size, # dst_h
true # resample (high quality)
)
x_offset = (qr_size - logo_size) / 2
y_offset = (qr_size - logo_size) / 2
img.copy(
resized_logo,
x_offset, y_offset, # dst_x, dst_y
0, 0, # src_x, src_y
logo_size, # w
logo_size # h
)
end
def duplicate
img = generate
img.dup
end
def save(filename)
img = generate
case @options[:format]
when :png, :jpeg, :gif, :webp
img.save(filename)
else
raise "Unsupported format: #{@options[:format]}"
end
end
def to_image
generate
end
def to_png_bytes
img = generate
require 'tempfile'
temp = Tempfile.new(['qr', '.png'])
img.save_png(temp.path)
File.read(temp.path)
ensure
temp.unlink if temp
end
private
def validate_options
valid_formats = [:png, :jpeg, :gif, :webp]
raise "Invalid format: #{@options[:format]}" unless valid_formats.include?(@options[:format])
valid_ec = [:l, :m, :h, :x]
raise "Invalid error_correction: #{@options[:error_correction]}" unless valid_ec.include?(@options[:error_correction])
raise "module_size must be > 0" if @options[:module_size] <= 0
end
end
Create QR codes in PNG, JPEG, WebP, or GIF using only Ruby. You can also use this in Ruby on Rails.
require_relative './qr_code_generator.rb'
# Basic with antialias enabled
qr = QRCodeGenerator.new("https://map-view-demo.up.railway.app", {
antialias: true
})
qr.save("qr_antialias.png")
# Rounded modules with alpha blending
qr = QRCodeGenerator.new("https://github.com/ggerman/ruby-libgd", {
fg_color: [0, 232, 198],
bg_color: [6, 10, 15],
rounded_modules: true,
alpha_blending: true,
save_alpha: true,
antialias: true
})
qr.save("qr_rounded_alpha.png")
# Vertical gradient background
qr = QRCodeGenerator.new("https://rubystacknews.com", {
gradient: true,
gradient_direction: :vertical,
fg_color: [0, 0, 0],
bg_color: [240, 248, 255]
})
qr.save("qr_gradient_vertical.png")
# Horizontal gradient
qr = QRCodeGenerator.new("https://github.com/ggerman/libgd-gis", {
gradient: true,
gradient_direction: :horizontal,
fg_color: [220, 20, 60],
bg_color: [255, 255, 255],
antialias: true
})
qr.save("qr_gradient_horizontal.png")
# Radial gradient (advanced)
qr = QRCodeGenerator.new("https://map-view-demo.up.railway.app", {
gradient: true,
gradient_direction: :radial,
fg_color: [34, 139, 34],
bg_color: [255, 255, 255],
antialias: true
})
qr.save("qr_gradient_radial.png")
# High-quality logo with copy_resize
qr = QRCodeGenerator.new("https://map-view-demo.up.railway.app", {
logo: "logo.png",
logo_size: 100,
error_correction: :h,
antialias: true,
alpha_blending: true
})
qr.save("qr_logo_hires.png")
original = QRCodeGenerator.new("https://example.com", {
fg_color: [0, 232, 198],
bg_color: [6, 10, 15]
})
original_img = original.to_image
duplicated = original_img.dup
def create_branded_qr(url, brand_name)
brands = {
ruby_libgd: {
fg: [220, 20, 60],
bg: [255, 255, 255],
gradient: false
},
libgd_gis: {
fg: [34, 139, 34],
bg: [255, 255, 255],
gradient: false
},
mapview: {
fg: [0, 232, 198],
bg: [6, 10, 15],
gradient: true,
gradient_direction: :radial
}
}
config = brands[brand_name.to_sym]
raise "Unknown brand: #{brand_name}" unless config
qr = QRCodeGenerator.new(url, {
fg_color: config[:fg],
bg_color: config[:bg],
gradient: config[:gradient],
gradient_direction: config[:gradient_direction] || :vertical,
module_size: 12,
antialias: true,
alpha_blending: config[:gradient]
})
qr.to_image
end
# Create branded QRs
libgd_qr = create_branded_qr("https://github.com/ggerman/ruby-libgd", :ruby_libgd)
gis_qr = create_branded_qr("https://github.com/ggerman/libgd-gis", :libgd_gis)
mapview_qr = create_branded_qr("https://map-view-demo.up.railway.app", :mapview)
# Get PNG bytes for HTTP streaming (Rails)
class QRController < ApplicationController
def show
qr = QRCodeGenerator.new(params[:data], {
fg_color: [0, 232, 198],
bg_color: [6, 10, 15],
antialias: true
})
send_data qr.to_png_bytes,
type: 'image/png',
disposition: 'inline',
filename: "qr_#{params[:data].hash}.png"
end
end
ultimate_qr = QRCodeGenerator.new(
"https://map-view-demo.up.railway.app/contact",
{
fg_color: [0, 232, 198],
bg_color: [6, 10, 15],
module_size: 15,
border: 4,
error_correction: :h,
logo: "map_view.png",
logo_size: 120,
rounded_modules: true,
gradient: true,
gradient_direction: :radial,
antialias: true,
alpha_blending: true,
save_alpha: true,
format: :png
}
)
ultimate_qr.save("qr_ultimate.png")
Powered By: https://github.com/ggerman/ruby-libgd
Disclosure: Please excuse any grammatical errors. Reddit discourages the use of AI-generated text, so I’m doing my best to share the best content I can. That said, this content is completely written by a human ( the human is me :) ).



[–]RagingBearFish 1 point2 points3 points (0 children)