ascii_pngfy 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/ascii_pngfy.rb +40 -0
- data/lib/ascii_pngfy/aabb.rb +49 -0
- data/lib/ascii_pngfy/color_rgba.rb +56 -0
- data/lib/ascii_pngfy/exceptions.rb +29 -0
- data/lib/ascii_pngfy/glyphs.rb +118 -0
- data/lib/ascii_pngfy/pngfyer.rb +43 -0
- data/lib/ascii_pngfy/rendering_rules.rb +198 -0
- data/lib/ascii_pngfy/result.rb +25 -0
- data/lib/ascii_pngfy/settings.rb +6 -0
- data/lib/ascii_pngfy/settings/color_setting.rb +44 -0
- data/lib/ascii_pngfy/settings/font_height_setting.rb +74 -0
- data/lib/ascii_pngfy/settings/horizontal_spacing_setting.rb +46 -0
- data/lib/ascii_pngfy/settings/setable_getable.rb +35 -0
- data/lib/ascii_pngfy/settings/setable_getable_settings.rb +85 -0
- data/lib/ascii_pngfy/settings/settings_snapshot.rb +35 -0
- data/lib/ascii_pngfy/settings/text_setting.rb +210 -0
- data/lib/ascii_pngfy/settings/vertical_spacing_setting.rb +46 -0
- data/lib/ascii_pngfy/settings_renderer.rb +41 -0
- data/lib/ascii_pngfy/vec2i.rb +23 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1d0d17138de3a7998ba69e9cdb8ddb45bc4b0aa259f570b5b76e3f32824512b0
|
4
|
+
data.tar.gz: '08a2046403c7c76e8d0d150224d3ce1df4776539be57b8a0a56cdc16930ceab3'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4d7bb12da4c527081fa049e940e3af6e98333ff2850fe796371a6465579f8586409d27b157547abdf9daedcd48ba087d2afba3fe022c159ac42b5a2a4185d4b
|
7
|
+
data.tar.gz: 2345644e3d1f5bf09dbf0f366743dacd555943e6b8e5cbd72e3db9fddb1a5f96fdc491e01e1d7cdd9b4b3990d9ce5568b30b6bfddfce1d60f26bc207e285394e
|
data/lib/ascii_pngfy.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Reponsibilities
|
4
|
+
# - Top level namespace that contains all AsciiPngfy
|
5
|
+
# sub-namespaces and general constants
|
6
|
+
# - Requires all files needed to use the AsciiPngfy Gem
|
7
|
+
module AsciiPngfy
|
8
|
+
MAX_RESULT_PNG_IMAGE_WIDTH = 3840
|
9
|
+
MAX_RESULT_PNG_IMAGE_HEIGHT = 2160
|
10
|
+
GLYPH_DESIGN_WIDTH = 5
|
11
|
+
GLYPH_DESIGN_HEIGHT = 9
|
12
|
+
SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE_RANGE = (32..126).freeze
|
13
|
+
SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE = SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE_RANGE.to_a.freeze
|
14
|
+
SUPPORTED_ASCII_CODES = ([10] + SUPPORTED_ASCII_CODES_WITHOUT_NEWLINE).freeze
|
15
|
+
SUPPORTED_ASCII_CHARACTERS = SUPPORTED_ASCII_CODES.map(&:chr).freeze
|
16
|
+
end
|
17
|
+
|
18
|
+
require 'ascii_pngfy/pngfyer'
|
19
|
+
|
20
|
+
require 'ascii_pngfy/exceptions'
|
21
|
+
|
22
|
+
require 'ascii_pngfy/settings'
|
23
|
+
require 'ascii_pngfy/settings/settings_snapshot'
|
24
|
+
require 'ascii_pngfy/settings/setable_getable_settings'
|
25
|
+
|
26
|
+
require 'ascii_pngfy/settings/setable_getable'
|
27
|
+
require 'ascii_pngfy/settings/color_setting'
|
28
|
+
require 'ascii_pngfy/settings/font_height_setting'
|
29
|
+
require 'ascii_pngfy/settings/horizontal_spacing_setting'
|
30
|
+
require 'ascii_pngfy/settings/vertical_spacing_setting'
|
31
|
+
require 'ascii_pngfy/settings/text_setting'
|
32
|
+
|
33
|
+
require 'ascii_pngfy/vec2i'
|
34
|
+
require 'ascii_pngfy/aabb'
|
35
|
+
require 'ascii_pngfy/rendering_rules'
|
36
|
+
require 'ascii_pngfy/settings_renderer'
|
37
|
+
|
38
|
+
require 'ascii_pngfy/glyphs'
|
39
|
+
require 'ascii_pngfy/color_rgba'
|
40
|
+
require 'ascii_pngfy/result'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsciiPngfy
|
4
|
+
# Reponsibilities
|
5
|
+
# - Represents an axis aligned bounding box through a minimum and
|
6
|
+
# maximum coordinate pair
|
7
|
+
# - Public getters for the min and max coordinate pair
|
8
|
+
# - Provides a way to iterate all the pixel coordinates in
|
9
|
+
# the respective bounding box with and without the pixel index
|
10
|
+
#
|
11
|
+
# This pixel index follows the conventions used for the glyph
|
12
|
+
# design string where the index increases based on the iterated
|
13
|
+
# pixel from:
|
14
|
+
# - topmost row to bottommost row
|
15
|
+
# - leftmost pixel to rightmost pixel for each of these rows
|
16
|
+
class AABB
|
17
|
+
attr_reader(:min, :max)
|
18
|
+
|
19
|
+
def initialize(min_x, min_y, max_x, max_y)
|
20
|
+
self.min = Vec2i.new(min_x, min_y)
|
21
|
+
self.max = Vec2i.new(max_x, max_y)
|
22
|
+
end
|
23
|
+
|
24
|
+
def each_pixel(&yielder)
|
25
|
+
min.y.upto(max.y) do |pixel_y|
|
26
|
+
min.x.upto(max.x) do |pixel_x|
|
27
|
+
yielder.call(pixel_x, pixel_y)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def each_pixel_with_index(&yielder)
|
33
|
+
pixel_index = 0
|
34
|
+
each_pixel do |pixel_x, pixel_y|
|
35
|
+
yielder.call(pixel_x, pixel_y, pixel_index)
|
36
|
+
|
37
|
+
pixel_index += 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_s
|
42
|
+
"min#{min} max#{max}"
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_writer(:min, :max)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsciiPngfy
|
4
|
+
# Reponsibilities
|
5
|
+
# - Provides RGBA color handling and validation
|
6
|
+
class ColorRGBA
|
7
|
+
VALID_RGBA_COLOR_RANGE = (0..255).freeze
|
8
|
+
attr_reader(:red, :green, :blue, :alpha)
|
9
|
+
|
10
|
+
def initialize(red, green, blue, alpha)
|
11
|
+
self.red = red
|
12
|
+
self.green = green
|
13
|
+
self.blue = blue
|
14
|
+
self.alpha = alpha
|
15
|
+
end
|
16
|
+
|
17
|
+
def red=(new_red)
|
18
|
+
@red = validate_color_value(new_red, :red)
|
19
|
+
end
|
20
|
+
|
21
|
+
def green=(new_green)
|
22
|
+
@green = validate_color_value(new_green, :green)
|
23
|
+
end
|
24
|
+
|
25
|
+
def blue=(new_blue)
|
26
|
+
@blue = validate_color_value(new_blue, :blue)
|
27
|
+
end
|
28
|
+
|
29
|
+
def alpha=(new_alpha)
|
30
|
+
@alpha = validate_color_value(new_alpha, :alpha)
|
31
|
+
end
|
32
|
+
|
33
|
+
def ==(other)
|
34
|
+
other.red == red &&
|
35
|
+
other.green == green &&
|
36
|
+
other.blue == blue &&
|
37
|
+
other.alpha == alpha
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def validate_color_value(color_value, color_component)
|
43
|
+
return color_value if valid_color_value?(color_value)
|
44
|
+
|
45
|
+
error_message = String.new
|
46
|
+
error_message << "#{color_value.inspect} is not a valid #{color_component} color component value. "
|
47
|
+
error_message << "Must be an Integer in the range (#{VALID_RGBA_COLOR_RANGE})."
|
48
|
+
|
49
|
+
raise Exceptions::InvalidRGBAColorValueError, error_message
|
50
|
+
end
|
51
|
+
|
52
|
+
def valid_color_value?(color_value)
|
53
|
+
color_value.is_a?(Integer) && VALID_RGBA_COLOR_RANGE.cover?(color_value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsciiPngfy
|
4
|
+
# Provides a custom AsciiPngfy Exceptions hierarchy
|
5
|
+
module Exceptions
|
6
|
+
# Base class to classify AsciiPngfy errors under StandardError
|
7
|
+
class AsciiPngfyError < StandardError; end
|
8
|
+
|
9
|
+
class InvalidRGBAColorValueError < AsciiPngfyError; end
|
10
|
+
|
11
|
+
class InvalidFontHeightError < AsciiPngfyError; end
|
12
|
+
|
13
|
+
class InvalidSpacingError < AsciiPngfyError; end
|
14
|
+
|
15
|
+
class InvalidHorizontalSpacingError < InvalidSpacingError; end
|
16
|
+
|
17
|
+
class InvalidVerticalSpacingError < InvalidSpacingError; end
|
18
|
+
|
19
|
+
class InvalidReplacementTextError < AsciiPngfyError; end
|
20
|
+
|
21
|
+
class InvalidCharacterError < AsciiPngfyError; end
|
22
|
+
|
23
|
+
class EmptyTextError < AsciiPngfyError; end
|
24
|
+
|
25
|
+
class TextLineTooLongError < AsciiPngfyError; end
|
26
|
+
|
27
|
+
class TooManyTextLinesError < AsciiPngfyError; end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsciiPngfy
|
4
|
+
# Reponsibilities
|
5
|
+
# - Provides pixel plotting designs for all supported non-control
|
6
|
+
# ASCII characters
|
7
|
+
# - Decides which design character, '.' or '#', represents the
|
8
|
+
# font layer or the background layer
|
9
|
+
# rubocop: disable Metrics/ModuleLength
|
10
|
+
module Glyphs
|
11
|
+
DESIGNS = {
|
12
|
+
' ' => '.............................................',
|
13
|
+
'!' => '..#....#....#....#....#.........#............',
|
14
|
+
'"' => '.#.#..#.#..#.#...............................',
|
15
|
+
'#' => '......#.#.#####.#.#..#.#.#####.#.#...........',
|
16
|
+
'$' => '..#...#####.#...###...#.#####...#............',
|
17
|
+
'%' => '#...##...#...#...#...#...#...##...#..........',
|
18
|
+
'&' => '.##..#..#.#..#..#####..#.#..#..##.#..........',
|
19
|
+
"'" => '..#....#....#................................',
|
20
|
+
'(' => '...#...#....#....#....#....#.....#...........',
|
21
|
+
')' => '..#.....#....#....#....#....#...#............',
|
22
|
+
'*' => '.......#..#.#.#.###.#.#.#..#.................',
|
23
|
+
'+' => '.......#....#..#####..#....#.................',
|
24
|
+
',' => '...........................#....#...#........',
|
25
|
+
'-' => '...............#####.........................',
|
26
|
+
'.' => '...........................#....#............',
|
27
|
+
'/' => '....#....#...#...#...#...#....#..............',
|
28
|
+
'0' => '.###.#...##..###.#.###..##...#.###...........',
|
29
|
+
'1' => '..#...##....#....#....#....#..#####..........',
|
30
|
+
'2' => '.###.#...#....#...#...#...#...#####..........',
|
31
|
+
'3' => '.###.#...#....#.###.....##...#.###...........',
|
32
|
+
'4' => '.#..#.#..##...######....#....#....#..........',
|
33
|
+
'5' => '######....#....####.....##...#.###...........',
|
34
|
+
'6' => '.###.#....#....####.#...##...#.###...........',
|
35
|
+
'7' => '#####....#....#...#...#....#....#............',
|
36
|
+
'8' => '.###.#...##...#.###.#...##...#.###...........',
|
37
|
+
'9' => '.###.#...##...#.####....##...#.###...........',
|
38
|
+
':' => '.......#....#..............#....#............',
|
39
|
+
';' => '.......#....#..............#....#...#........',
|
40
|
+
'<' => '........##.##..#.....##.....##...............',
|
41
|
+
'=' => '..........#####.....#####....................',
|
42
|
+
'>' => '.....##.....##.....#..##.##..................',
|
43
|
+
'?' => '.###.#...#....#...#...#.........#............',
|
44
|
+
'@' => '.###.#..###.#.##.#.##..###.....###...........',
|
45
|
+
'A' => '.###.#...##...##...#######...##...#..........',
|
46
|
+
'B' => '####.#...##...#####.#...##...#####...........',
|
47
|
+
'C' => '.###.#...##....#....#....#...#.###...........',
|
48
|
+
'D' => '####.#...##...##...##...##...#####...........',
|
49
|
+
'E' => '######....#....####.#....#....#####..........',
|
50
|
+
'F' => '######....#....####.#....#....#..............',
|
51
|
+
'G' => '.###.#...##....#.####...##...#.###...........',
|
52
|
+
'H' => '#...##...##...#######...##...##...#..........',
|
53
|
+
'I' => '#####..#....#....#....#....#..#####..........',
|
54
|
+
'J' => '....#....#....#....##...##...#.###...........',
|
55
|
+
'K' => '#...##..#.#.#..##...#.#..#..#.#...#..........',
|
56
|
+
'L' => '#....#....#....#....#....#....#####..........',
|
57
|
+
'M' => '#...###.###.#.##...##...##...##...#..........',
|
58
|
+
'N' => '#...##...###..##.#.##..###...##...#..........',
|
59
|
+
'O' => '.###.#...##...##...##...##...#.###...........',
|
60
|
+
'P' => '####.#...##...#####.#....#....#..............',
|
61
|
+
'Q' => '.###.#...##...##...##...##...#.###....##.....',
|
62
|
+
'R' => '####.#...##...#####.#...##...##...#..........',
|
63
|
+
'S' => '.###.#...##.....###.....##...#.###...........',
|
64
|
+
'T' => '#####..#....#....#....#....#....#............',
|
65
|
+
'U' => '#...##...##...##...##...##...#.###...........',
|
66
|
+
'V' => '#...##...##...##...#.#.#..#.#...#............',
|
67
|
+
'W' => '#...##...##...##...##.#.###.###...#..........',
|
68
|
+
'X' => '#...##...#.#.#...#...#.#.#...##...#..........',
|
69
|
+
'Y' => '#...##...#.#.#...#....#....#....#............',
|
70
|
+
'Z' => '#####....#...#...#...#...#....#####..........',
|
71
|
+
'[' => '..###..#....#....#....#....#....###..........',
|
72
|
+
'\\' => '#....#.....#.....#.....#.....#....#..........',
|
73
|
+
']' => '###....#....#....#....#....#..###............',
|
74
|
+
'^' => '..#...#.#.#...#..............................',
|
75
|
+
'_' => '..............................#####..........',
|
76
|
+
'`' => '.#.....#.....................................',
|
77
|
+
'a' => '...........#####...##...##...#.####..........',
|
78
|
+
'b' => '#....#....####.#...##...##...#####...........',
|
79
|
+
'c' => '...........###.#...##....#...#.###...........',
|
80
|
+
'd' => '....#....#.#####...##...##...#.####..........',
|
81
|
+
'e' => '...........###.#...#######.....###...........',
|
82
|
+
'f' => '..##..#..#.#...####..#....#....#.............',
|
83
|
+
'g' => '...........#####...##...##...#.####....#.###.',
|
84
|
+
'h' => '#....#....####.#...##...##...##...#..........',
|
85
|
+
'i' => '..#........##....#....#....#..#####..........',
|
86
|
+
'j' => '....#........##....#....#....#....##...#.###.',
|
87
|
+
'k' => '#....#....#...##..#.###..#..#.#...#..........',
|
88
|
+
'l' => '##....#....#....#....#....#.....###..........',
|
89
|
+
'm' => '..........####.#.#.##.#.##.#.##.#.#..........',
|
90
|
+
'n' => '..........####.#...##...##...##...#..........',
|
91
|
+
'o' => '...........###.#...##...##...#.###...........',
|
92
|
+
'p' => '..........####.#...##...##...#####.#....#....',
|
93
|
+
'q' => '...........#####...##...##...#.####....#....#',
|
94
|
+
'r' => '..........#.##.##..###..##....#..............',
|
95
|
+
's' => '...........#####.....###.....#####...........',
|
96
|
+
't' => '.#....#...####..#....#....#.....###..........',
|
97
|
+
'u' => '..........#...##...##...##...#.####..........',
|
98
|
+
'v' => '..........#...##...##...#.#.#...#............',
|
99
|
+
'w' => '..........#...##...##.#.##.#.#.#.#...........',
|
100
|
+
'x' => '..........#...#.#.#...#...#.#.#...#..........',
|
101
|
+
'y' => '..........#...##...##...##...#.####....#.###.',
|
102
|
+
'z' => '..........#####...#...#...#...#####..........',
|
103
|
+
'{' => '...#...#....#...#.....#....#.....#...........',
|
104
|
+
'|' => '..#....#....#....#....#....#....#............',
|
105
|
+
'}' => '.#.....#....#.....#...#....#...#.............',
|
106
|
+
'~' => '................#..##.##.....................'
|
107
|
+
}.freeze
|
108
|
+
|
109
|
+
def self.font_layer_design_character?(some_design_character)
|
110
|
+
some_design_character == '#'
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.background_layer_design_character?(some_design_character)
|
114
|
+
some_design_character == '.'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
# rubocop: enable Metrics/ModuleLength
|
118
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsciiPngfy
|
4
|
+
# Reponsibilities
|
5
|
+
# - Provide the complete interface of this gem dynamically
|
6
|
+
# in order to force the interface of the Settings onto the caller
|
7
|
+
# - Orchestrates the Settings and the SettingsRenderer
|
8
|
+
class Pngfyer
|
9
|
+
def initialize(use_glyph_designs: true)
|
10
|
+
self.settings_renderer = SettingsRenderer.new(use_glyph_designs: use_glyph_designs)
|
11
|
+
self.settings = Settings::SetableGetableSettings.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def respond_to_missing?(method_name, _)
|
15
|
+
setter?(method_name) || super
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method_name, *arguments)
|
19
|
+
# forward only set_* calls to the settings so that the respective setting
|
20
|
+
# can enforce it's interface and any unsupported setting setters results
|
21
|
+
# in an undefined method error
|
22
|
+
if setter?(method_name)
|
23
|
+
setting_call = method_name
|
24
|
+
settings.public_send(setting_call, *arguments)
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def pngfy
|
31
|
+
settings_snapshot = settings.snapshot
|
32
|
+
settings_renderer.render_result(settings_snapshot)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_accessor(:settings, :settings_renderer)
|
38
|
+
|
39
|
+
def setter?(method_name)
|
40
|
+
method_name.start_with?('set_')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AsciiPngfy
|
4
|
+
# Reponsibilities
|
5
|
+
# - The reason for this particulare interface with singleton methods
|
6
|
+
# at this point, is to avoid coupling between the settings themselves
|
7
|
+
# and because the architecture is about to change in terms of the
|
8
|
+
# current implementation of the Settings
|
9
|
+
#
|
10
|
+
# - Provide a single point of reference for all computations that
|
11
|
+
# contribute to the renderer result of the #pngfy process
|
12
|
+
#
|
13
|
+
# - Computations based on setting values such as color;
|
14
|
+
# font height; spacing and text
|
15
|
+
#
|
16
|
+
# - General enough so that any settings related context can
|
17
|
+
# use this functionality
|
18
|
+
# rubocop: disable Metrics/ModuleLength
|
19
|
+
module RenderingRules
|
20
|
+
def self.text_lines(text)
|
21
|
+
text.split("\n", -1)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.longest_text_line(text)
|
25
|
+
text_lines(text).max_by(&:length)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.png_width(settings, override_text = nil)
|
29
|
+
use_text = override_text || settings.text
|
30
|
+
|
31
|
+
longest_text_line_length = longest_text_line(use_text).length
|
32
|
+
horizontal_spacing_count = longest_text_line_length - 1
|
33
|
+
|
34
|
+
(longest_text_line_length * GLYPH_DESIGN_WIDTH) + (horizontal_spacing_count * settings.horizontal_spacing)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.png_height(settings, override_text = nil)
|
38
|
+
use_text = override_text || settings.text
|
39
|
+
|
40
|
+
text_line_count = text_lines(use_text).size
|
41
|
+
vertical_spacing_count = text_line_count - 1
|
42
|
+
|
43
|
+
(text_line_count * GLYPH_DESIGN_HEIGHT) + (vertical_spacing_count * settings.vertical_spacing)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.font_multiplier(settings)
|
47
|
+
settings.font_height / GLYPH_DESIGN_HEIGHT
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.render_width(settings)
|
51
|
+
png_width(settings) * font_multiplier(settings)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.render_height(settings)
|
55
|
+
png_height(settings) * font_multiplier(settings)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.text_lines_characters(settings)
|
59
|
+
text_lines(settings.text).map(&:chars)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.font_region(settings, character_column_index, character_row_index)
|
63
|
+
font_region_top_left_x = character_column_index * (settings.horizontal_spacing + GLYPH_DESIGN_WIDTH)
|
64
|
+
font_region_top_left_y = character_row_index * (settings.vertical_spacing + GLYPH_DESIGN_HEIGHT)
|
65
|
+
font_region_bottom_right_x = font_region_top_left_x + (GLYPH_DESIGN_WIDTH - 1)
|
66
|
+
font_region_bottom_right_y = font_region_top_left_y + (GLYPH_DESIGN_HEIGHT - 1)
|
67
|
+
|
68
|
+
AABB.new(
|
69
|
+
font_region_top_left_x,
|
70
|
+
font_region_top_left_y,
|
71
|
+
font_region_bottom_right_x,
|
72
|
+
font_region_bottom_right_y
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.each_font_region_with_associated_character(settings, &yielder)
|
77
|
+
text_lines_characters(settings).each_with_index do |line_characters, row_index|
|
78
|
+
line_characters.each_with_index do |character, column_index|
|
79
|
+
font_region = font_region(settings, column_index, row_index)
|
80
|
+
|
81
|
+
yielder.call(font_region, character)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.each_font_region(settings, &yielder)
|
87
|
+
each_font_region_with_associated_character(settings) do |font_region, _font_region_character|
|
88
|
+
yielder.call(font_region)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.color_rgba_to_chunky_png_integer(color_rgba)
|
93
|
+
ChunkyPNG::Color.rgba(
|
94
|
+
color_rgba.red,
|
95
|
+
color_rgba.green,
|
96
|
+
color_rgba.blue,
|
97
|
+
color_rgba.alpha
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.straight_alpha_composite_color_value(over_component, over_alpha, under_component, under_alpha)
|
102
|
+
# over refers to the top layer, i.e. the font layer
|
103
|
+
ca = over_component
|
104
|
+
aa = over_alpha.fdiv(255)
|
105
|
+
|
106
|
+
# under refers to the bottom layer, i.e. the background layer
|
107
|
+
cb = under_component
|
108
|
+
ab = under_alpha.fdiv(255)
|
109
|
+
|
110
|
+
# return alpha composited color component as integer in range 0..255
|
111
|
+
# avoid divisions by zero
|
112
|
+
numerator = (ca * aa + cb * ab * (1 - aa))
|
113
|
+
denumerator = (aa + ab * (1 - aa))
|
114
|
+
|
115
|
+
return 0 if denumerator.zero? || numerator.zero?
|
116
|
+
|
117
|
+
(numerator / denumerator).to_i
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.straight_alpha_composite_alpha_value(over_alpha, under_alpha)
|
121
|
+
aa = over_alpha.fdiv(255)
|
122
|
+
ab = under_alpha.fdiv(255)
|
123
|
+
|
124
|
+
# return alpha composited alpha component as integer in range 0..255
|
125
|
+
((aa + ab * (1 - aa)) * 255).to_i
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.straight_alpha_composite_color(over_color, under_color)
|
129
|
+
over_color_alpha = over_color.alpha
|
130
|
+
under_color_alpha = under_color.alpha
|
131
|
+
|
132
|
+
AsciiPngfy::ColorRGBA.new(
|
133
|
+
straight_alpha_composite_color_value(over_color.red, over_color_alpha, under_color.red, under_color_alpha),
|
134
|
+
straight_alpha_composite_color_value(over_color.green, over_color_alpha, under_color.green, under_color_alpha),
|
135
|
+
straight_alpha_composite_color_value(over_color.blue, over_color_alpha, under_color.blue, under_color_alpha),
|
136
|
+
straight_alpha_composite_alpha_value(over_color_alpha, under_color_alpha)
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
def self.possibly_blended_font_and_background_color(settings)
|
141
|
+
case settings.font_color.alpha
|
142
|
+
when 255
|
143
|
+
settings.font_color
|
144
|
+
else
|
145
|
+
straight_alpha_composite_color(settings.font_color, settings.background_color)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.design_character_pixel_color(settings, design_character)
|
150
|
+
if AsciiPngfy::Glyphs.font_layer_design_character?(design_character)
|
151
|
+
possibly_blended_font_and_background_color(settings)
|
152
|
+
elsif AsciiPngfy::Glyphs.background_layer_design_character?(design_character)
|
153
|
+
settings.background_color
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.plot_font_regions_with_design(settings, png)
|
158
|
+
each_font_region_with_associated_character(settings) do |font_region, character|
|
159
|
+
# the only ASCII character that has an empty glyph desgn is the space
|
160
|
+
# so avoid unnecessary work for spaces
|
161
|
+
next if character == ' '
|
162
|
+
|
163
|
+
# mirror the font design for each font region into the png
|
164
|
+
font_region_character_design = AsciiPngfy::Glyphs::DESIGNS[character]
|
165
|
+
|
166
|
+
font_region.each_pixel_with_index do |font_pixel_x, font_pixel_y, font_pixel_index|
|
167
|
+
png_pixel_design_character = font_region_character_design[font_pixel_index]
|
168
|
+
|
169
|
+
png_pixel_plot_color = design_character_pixel_color(settings, png_pixel_design_character)
|
170
|
+
png_pixel_plot_color_as_integer = color_rgba_to_chunky_png_integer(png_pixel_plot_color)
|
171
|
+
|
172
|
+
png[font_pixel_x, font_pixel_y] = png_pixel_plot_color_as_integer
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.plot_font_regions_without_design(settings, png)
|
178
|
+
final_font_color = possibly_blended_font_and_background_color(settings)
|
179
|
+
final_font_color_as_integer = color_rgba_to_chunky_png_integer(final_font_color)
|
180
|
+
|
181
|
+
each_font_region(settings) do |font_region|
|
182
|
+
# fill every font region entirely with, the potentially mixed, font and backround color
|
183
|
+
font_region.each_pixel do |font_region_x, font_region_y|
|
184
|
+
png[font_region_x, font_region_y] = final_font_color_as_integer
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.plot_settings(settings, use_glyph_designs, png)
|
190
|
+
if use_glyph_designs
|
191
|
+
plot_font_regions_with_design(settings, png)
|
192
|
+
else
|
193
|
+
plot_font_regions_without_design(settings, png)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
# rubocop: enable Metrics/ModuleLength
|