Skip to content

Commit

Permalink
Merge pull request #12 from abousquet/background_color
Browse files Browse the repository at this point in the history
Fix #9 Background color
  • Loading branch information
nthock authored Dec 15, 2019
2 parents 1f547e5 + b32bb5e commit dd973ba
Show file tree
Hide file tree
Showing 5 changed files with 220 additions and 42 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ erl_crash.dump
eqrcode-*.tar

**/.DS_Store
.elixir_ls
.elixir_ls

/test/images/
/test/html/
25 changes: 17 additions & 8 deletions lib/eqrcode/png.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule EQRCode.PNG do
You can specify the following attributes of the QR code:
* `background_color`: In binary format or `:transparent`. The default is `<<255, 255, 255>>`
* `color`: In binary format. The default is `<<0, 0, 0>>`
* `width`: The width of the QR code in pixel. (the actual size may vary, due to the number of modules in the code)
Expand All @@ -19,10 +20,14 @@ defmodule EQRCode.PNG do
alias EQRCode.Matrix

@defaults %{
background_color: <<255, 255, 255>>,
color: <<0, 0, 0>>,
module_size: 11
}

@transparent_alpha <<0>>
@opaque_alpha <<255>>

@png_signature <<137, 80, 78, 71, 13, 10, 26, 10>>

@doc """
Expand All @@ -34,7 +39,7 @@ defmodule EQRCode.PNG do
options = normalize_options(options, matrix_size)
pixel_size = matrix_size * options[:module_size]

ihdr = png_chunk("IHDR", <<pixel_size::32, pixel_size::32, 8::8, 2::8, 0::24>>)
ihdr = png_chunk("IHDR", <<pixel_size::32, pixel_size::32, 8::8, 6::8, 0::24>>)
idat = png_chunk("IDAT", pixels(matrix, options))
iend = png_chunk("IEND", "")

Expand Down Expand Up @@ -81,15 +86,19 @@ defmodule EQRCode.PNG do
:binary.copy(<<0>> <> pixels, module_size)
end

defp module_pixels(nil, %{module_size: module_size}) do
:binary.copy(<<255, 255, 255>>, module_size)
end

defp module_pixels(0, %{module_size: module_size}) do
:binary.copy(<<255, 255, 255>>, module_size)
defp module_pixels(value, %{background_color: background_color, module_size: module_size})
when is_nil(value) or value == 0 do
background_color
|> apply_alpha_channel()
|> :binary.copy(module_size)
end

defp module_pixels(1, %{color: color, module_size: module_size}) do
:binary.copy(color, module_size)
color
|> apply_alpha_channel()
|> :binary.copy(module_size)
end

defp apply_alpha_channel(:transparent), do: <<0, 0, 0>> <> @transparent_alpha
defp apply_alpha_channel(color), do: color <> @opaque_alpha
end
92 changes: 59 additions & 33 deletions lib/eqrcode/svg.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ defmodule EQRCode.SVG do
You can specify the following attributes of the QR code:
* `background_color`: In hexadecimal format or `:transparent`. The default is `#FFF`
* `color`: In hexadecimal format. The default is `#000`
* `shape`: Only `square` or `circle`. The default is `square`
* `width`: The width of the QR code in pixel. Without the width attribute, the QR code size will be dynamically generated based on the input string.
Expand Down Expand Up @@ -44,7 +45,7 @@ defmodule EQRCode.SVG do
~s(<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:ev="http://www.w3.org/2001/xml-events" #{
dimension_attrs
}
shape-rendering="crispEdges">)
shape-rendering="crispEdges" style="background-color: #{svg_options[:background_color]}">)

close_tag = ~s(</svg>)

Expand All @@ -62,6 +63,7 @@ defmodule EQRCode.SVG do

defp set_svg_options(options, matrix_size) do
options
|> Map.put_new(:background_color, "#FFF")
|> Map.put_new(:color, "#000")
|> set_module_size(matrix_size)
|> Map.put_new(:shape, "rectangle")
Expand Down Expand Up @@ -92,46 +94,70 @@ defmodule EQRCode.SVG do
|> Enum.to_list()
end

defp substitute(data, row_num, col_num, svg_options) when is_nil(data) do
y = col_num * svg_options[:module_size]
x = row_num * svg_options[:module_size]
defp substitute(data, row_num, col_num, %{module_size: module_size})
when is_nil(data) or data == 0 do
%{}
|> Map.put(:height, module_size)
|> Map.put(:style, "fill: transparent;")
|> Map.put(:width, module_size)
|> Map.put(:x, row_num * module_size)
|> Map.put(:y, col_num * module_size)
|> draw_rect
end

~s(<rect width="#{svg_options[:module_size]}" height="#{svg_options[:module_size]}" x="#{x}" y="#{
y
}" style="fill:#fff"/>)
# This pattern match ensures that the QR Codes positional markers are drawn
# as rectangles, regardless of the shape
defp substitute(1, row_num, col_num, %{color: color, module_size: module_size, size: size})
when (row_num <= 8 and col_num <= 8) or
(row_num >= size - 9 and col_num <= 8) or
(row_num <= 8 and col_num >= size - 9) do
%{}
|> Map.put(:height, module_size)
|> Map.put(:style, "fill:#{color};")
|> Map.put(:width, module_size)
|> Map.put(:x, col_num * module_size)
|> Map.put(:y, row_num * module_size)
|> draw_rect
end

defp substitute(1, row_num, col_num, %{shape: "circle", size: size} = svg_options) do
y = col_num * svg_options[:module_size]
x = row_num * svg_options[:module_size]

if (row_num <= 8 && col_num <= 8) || (row_num >= size - 9 && col_num <= 8) ||
(row_num <= 8 && col_num >= size - 9) do
~s(<rect width="#{svg_options[:module_size]}" height="#{svg_options[:module_size]}" x="#{x}" y="#{
y
}" style="fill:#{svg_options[:color]}"/>)
else
~s(<circle r="#{svg_options[:module_size] / 2.0}" cx="#{x + svg_options[:module_size] / 2.0}" cy="#{
y + svg_options[:module_size] / 2.0
}" style="fill:#{svg_options[:color]};"/>)
end
defp substitute(1, row_num, col_num, %{
color: color,
module_size: module_size,
shape: "circle"
}) do
radius = module_size / 2.0

%{}
|> Map.put(:cx, row_num * module_size + radius)
|> Map.put(:cy, col_num * module_size + radius)
|> Map.put(:r, radius)
|> Map.put(:style, "fill:#{color};")
|> draw_circle
end

defp substitute(1, row_num, col_num, svg_options) do
y = col_num * svg_options[:module_size]
x = row_num * svg_options[:module_size]
defp substitute(1, row_num, col_num, %{color: color, module_size: module_size}) do
%{}
|> Map.put(:height, module_size)
|> Map.put(:style, "fill:#{color};")
|> Map.put(:width, module_size)
|> Map.put(:x, row_num * module_size)
|> Map.put(:y, col_num * module_size)
|> draw_rect
end

~s(<rect width="#{svg_options[:module_size]}" height="#{svg_options[:module_size]}" x="#{x}" y="#{
y
}" style="fill:#{svg_options[:color]}"/>)
defp draw_rect(attribute_map) do
attributes = get_attributes(attribute_map)
~s(<rect #{attributes}/>)
end

defp substitute(0, row_num, col_num, svg_options) do
y = col_num * svg_options[:module_size]
x = row_num * svg_options[:module_size]
defp draw_circle(attribute_map) do
attributes = get_attributes(attribute_map)
~s(<circle #{attributes}/>)
end

~s(<rect width="#{svg_options[:module_size]}" height="#{svg_options[:module_size]}" x="#{x}" y="#{
y
}" style="fill:#fff"/>)
defp get_attributes(attribute_map) do
attribute_map
|> Enum.map(fn {key, value} -> ~s(#{key}="#{value}") end)
|> Enum.join(" ")
end
end
71 changes: 71 additions & 0 deletions test/png_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
defmodule EQRCode.PNGTest do
use ExUnit.Case

defp content, do: "www.google.com"
defp html_path, do: Path.expand("./test/html")
defp image_path, do: Path.expand("./test/images")

setup do
html_path() |> File.mkdir_p!()
image_path() |> File.mkdir_p!()
qr = content() |> EQRCode.encode()
[qr: qr]
end

test "Generate an html page with different types of PNG images", %{qr: qr} do
color = <<0, 0, 255>>
background_color = <<255, 255, 0>>

pngs =
[]
## Test Default
|> build_png(qr, "Default", [])
## Test Foreground Color png
|> build_png(qr, "Foreground", color: color)
## Test Background Color png
|> build_png(qr, "Background", background_color: background_color)
## Test Foreground and Background Color
|> build_png(qr, "Both Colors", background_color: background_color, color: color)
## Test Background Transparency
|> build_png(qr, "Transparent", background_color: :transparent, color: color)

html = gen_html(pngs)

html_path()
|> Path.join("png_test.html")
|> File.write(html)
end

defp write_png_to_file(png_binary, path), do: File.write!(path, png_binary, [:binary])

defp build_png(png_list, qr, label, opts) do
png_path = image_path() |> Path.join(Macro.underscore(label) <> ".png")

EQRCode.png(qr, opts)
|> write_png_to_file(png_path)

png_list ++ [{label, png_path}]
end

defp gen_html(kw_png) when is_list(kw_png) do
png =
Enum.map(kw_png, fn {key, value} ->
~s{<p><strong>#{key}:</strong></p><image src="#{value}">}
end)

"""
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<head>
<title>EQRCode PNG Image Tests</title>
<style>
body { background: rgba(0,255,0); }
</style>
</head>
#{png}
</body>
<html>
"""
end
end
69 changes: 69 additions & 0 deletions test/svg_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
defmodule EQRCode.SVGTest do
use ExUnit.Case

defp content, do: "www.google.com"
defp html_path, do: Path.expand("./test/html")

setup do
html_path() |> File.mkdir_p!()
qr = content() |> EQRCode.encode()
[qr: qr]
end

test "Generate an html page with different types of SVG images", %{qr: qr} do
color = "blue"
background_color = "yellow"

svgs =
[]
## Test default option
|> build_svgs(qr, "Default", [])
## Test foreground color being set
|> build_svgs(qr, "Foreground", color: color)
## Test background being set
|> build_svgs(qr, "Background", background_color: background_color)
## Test both background and foreground color being set
|> build_svgs(qr, "Both Colors", background_color: background_color, color: color)
## Test transparency in HTML
|> build_svgs(qr, "Transparent", background_color: :transparent, color: color)
## Test ViewBox
|> build_svgs(qr, "ViewBox", background_color: background_color, color: color, viewbox: true)
## Test transparent viewbox in HTML
|> build_svgs(qr, "Transparent ViewBox",
background_color: :transparent,
color: color,
viewbox: true
)

html = gen_html(svgs)

html_path()
|> Path.join("svg_test.html")
|> File.write(html)
end

defp build_svgs(svgs, qr, label, opts) do
svg = EQRCode.svg(qr, opts)
svg_c = EQRCode.svg(qr, [shape: "circle"] ++ opts)
svgs ++ [{label, svg <> svg_c}]
end

defp gen_html(kw_svg) when is_list(kw_svg) do
svg = Enum.map(kw_svg, fn {key, value} -> "<p><strong>#{key}:</strong></p>" <> value end)

"""
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<body>
<head>
<title>EQRCode PNG Image Tests</title>
<style>
body { background: rgba(0,255,0); }
</style>
</head>
#{svg}
</body>
<html>
"""
end
end

0 comments on commit dd973ba

Please sign in to comment.