ascii_pngfy 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|