tileset_tooling 0.0.2

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ruby.yml +35 -0
  3. data/.gitignore +8 -0
  4. data/.rubocop.yml +63 -0
  5. data/.ruby-version +1 -0
  6. data/.vscode/settings.json +16 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +161 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.rdoc +59 -0
  11. data/Rakefile +54 -0
  12. data/bin/tileset_tooling +7 -0
  13. data/features/insert_bleed.feature +56 -0
  14. data/features/step_definitions/file_steps.rb +19 -0
  15. data/features/step_definitions/help_steps.rb +10 -0
  16. data/features/step_definitions/insert_bleed_steps.rb +28 -0
  17. data/features/support/configuration.rb +18 -0
  18. data/features/support/env.rb +19 -0
  19. data/features/support/test_data.rb +16 -0
  20. data/features/tileset_tooling.feature +7 -0
  21. data/lib/tileset_tooling.rb +4 -0
  22. data/lib/tileset_tooling/app.rb +58 -0
  23. data/lib/tileset_tooling/commands.rb +22 -0
  24. data/lib/tileset_tooling/commands/insert_bleed.rb +126 -0
  25. data/lib/tileset_tooling/data.rb +13 -0
  26. data/lib/tileset_tooling/data/tile.rb +30 -0
  27. data/lib/tileset_tooling/data/tileset.rb +96 -0
  28. data/lib/tileset_tooling/data/types.rb +11 -0
  29. data/lib/tileset_tooling/utils.rb +26 -0
  30. data/lib/tileset_tooling/version.rb +7 -0
  31. data/test/commands/insert_bleed_test.rb +76 -0
  32. data/test/data/UIpackSheet_transparent.png +0 -0
  33. data/test/data/expected/simple_with_bad_specs.png +0 -0
  34. data/test/data/expected/simple_with_margin.png +0 -0
  35. data/test/data/expected/simple_with_specs.png +0 -0
  36. data/test/data/simple_no_margin.png +0 -0
  37. data/test/data/simple_with_bad_specs.png +0 -0
  38. data/test/data/simple_with_bad_specs.specs +2 -0
  39. data/test/data/simple_with_margin.png +0 -0
  40. data/test/data/simple_with_specs.png +0 -0
  41. data/test/data/simple_with_specs.specs +7 -0
  42. data/test/data/source/simple_no_margin.xcf +0 -0
  43. data/test/data/source/simple_with_margin.xcf +0 -0
  44. data/test/test_helper.rb +23 -0
  45. data/test/utils_test.rb +12 -0
  46. data/tileset_tooling.gemspec +34 -0
  47. data/tileset_tooling.rdoc +48 -0
  48. metadata +317 -0
@@ -0,0 +1,56 @@
1
+ Feature: App inserts bleed correctly
2
+ Simple test to validate that the bleed around tiles
3
+ is added correctly when running the full executable
4
+
5
+ Scenario: Insert bleed has help
6
+ When I get help for bleed insert
7
+ Then the exit status should be 0
8
+
9
+ Scenario: Insert bleed without margin
10
+ When I insert bleed to test data "simple_no_margin.png"
11
+ Then I type "16"
12
+ Then I type "16"
13
+ Then I type "0"
14
+ Then I type "0"
15
+ Then I type "0"
16
+ Then the exit status should be 1
17
+ Then the output should contain "Current implementation needs an existing margin"
18
+
19
+ Scenario: Insert bleed generate default output
20
+ When I insert bleed to test data "simple_with_margin.png"
21
+ Then I type "16"
22
+ Then I type "16"
23
+ Then I type "1"
24
+ Then I type "0"
25
+ Then I type "0"
26
+ Then the exit status should be 0
27
+ Then generated file should be the same as data result "simple_with_margin.png"
28
+
29
+ Scenario: Insert bleed specific output
30
+ When I insert bleed to test data "simple_with_margin.png" and output to "test_output.png"
31
+ Then I type "16"
32
+ Then I type "16"
33
+ Then I type "1"
34
+ Then I type "0"
35
+ Then I type "0"
36
+ Then the exit status should be 0
37
+ Then generated file should be the same as data result "simple_with_margin.png"
38
+
39
+ Scenario: Insert bleed with specs
40
+ When I insert bleed to test data "simple_with_specs.png"
41
+ Then the exit status should be 0
42
+ Then generated file should be the same as data result "simple_with_specs.png"
43
+
44
+ Scenario: Insert bleed with bad specs
45
+ When I insert bleed to test data "simple_with_bad_specs.png"
46
+ Then the exit status should be 1
47
+
48
+ Scenario: Insert bleed with skipped specs
49
+ When I insert bleed to test data "simple_with_bad_specs.png" and skip specs loading
50
+ Then I type "16"
51
+ Then I type "16"
52
+ Then I type "1"
53
+ Then I type "0"
54
+ Then I type "0"
55
+ Then the exit status should be 0
56
+ Then generated file should be the same as data result "simple_with_bad_specs.png"
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ Then(/^generated file should be the same as data result "([^"]*)"/) do |data_path_name|
5
+ expected_output_path = test_result_path(data_path_name)
6
+
7
+ # Try two paths: normal output, and hacked aruba tmp one
8
+ output_path = @output_path
9
+ aruba_output_path = "#{::Configuration.instance.working_directory}/#{@output_path}"
10
+ output_path = aruba_output_path unless ::File.exist?(output_path)
11
+
12
+ expect(::File.exist?(expected_output_path)).to be true
13
+ expect(::File.exist?(output_path)).to be true
14
+
15
+ expected_signature = image_signature(expected_output_path)
16
+ output_signature = image_signature(output_path)
17
+
18
+ expect(expected_signature).to eq output_signature
19
+ end
@@ -0,0 +1,10 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ When(/^I get help/) do
5
+ step %(I run `#{::Configuration.instance.app_name} help`)
6
+ end
7
+
8
+ When(/^I get help for "([^"]*)"$/) do |command|
9
+ step %(I run `#{::Configuration.instance.app_name} #{command} help`)
10
+ end
@@ -0,0 +1,28 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ When(/^I insert bleed to test data "([^"]*)"$/) do |data_name|
5
+ image_path = test_data_path(data_name)
6
+ file_name = ::File.basename(image_path, '.*')
7
+ extension = ::File.extname(image_path)
8
+ directory = ::File.dirname(image_path)
9
+
10
+ @output_path = "#{directory}/#{file_name}_result#{extension}"
11
+ step %(I run `#{::Configuration.instance.app_name} bleed insert #{image_path}` interactively)
12
+ end
13
+
14
+ When(/^I insert bleed to test data "([^"]*)" and output to "([^"]*)"/) do |data_name, output_path|
15
+ @output_path = output_path
16
+ image_path = test_data_path(data_name)
17
+ step %(I run `#{::Configuration.instance.app_name} bleed insert --output #{@output_path} #{image_path}` interactively)
18
+ end
19
+
20
+ When(/^I insert bleed to test data "([^"]*)" and skip specs loading/) do |data_name|
21
+ image_path = test_data_path(data_name)
22
+ file_name = ::File.basename(image_path, '.*')
23
+ extension = ::File.extname(image_path)
24
+ directory = ::File.dirname(image_path)
25
+
26
+ @output_path = "#{directory}/#{file_name}_result#{extension}"
27
+ step %(I run `#{::Configuration.instance.app_name} bleed insert --skip-specs #{image_path}` interactively)
28
+ end
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ class ::Configuration
5
+ include ::Singleton
6
+
7
+ def initialize
8
+ @app_name = 'tileset_tooling'
9
+ @working_directory = nil
10
+ end
11
+
12
+ attr_reader :app_name
13
+ attr_accessor :working_directory
14
+ end
15
+
16
+ ::Aruba.configure do |config|
17
+ ::Configuration.instance.working_directory = config.working_directory
18
+ end
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'aruba/cucumber'
5
+
6
+ BIN_DIR = ::File.join(__dir__, '..', '..', 'bin')
7
+ LIB_DIR = ::File.join(__dir__, '..', '..', 'lib')
8
+ ::ENV['PATH'] = "#{::File.expand_path(::BIN_DIR)}#{::File::PATH_SEPARATOR}#{::ENV['PATH']}"
9
+
10
+ Before do
11
+ # Using "announce" causes massive warnings on 1.9.2
12
+ @puts = true
13
+ @original_rubylib = ::ENV['RUBYLIB']
14
+ ::ENV['RUBYLIB'] = ::LIB_DIR + ::File::PATH_SEPARATOR + ::ENV['RUBYLIB'].to_s
15
+ end
16
+
17
+ After do
18
+ ::ENV['RUBYLIB'] = @original_rubylib
19
+ end
@@ -0,0 +1,16 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'tileset_tooling/utils'
5
+
6
+ def test_data_path(name)
7
+ "#{__dir__}/../../test/data/#{name}"
8
+ end
9
+
10
+ def test_result_path(name)
11
+ "#{__dir__}/../../test/data/expected/#{name}"
12
+ end
13
+
14
+ def image_signature(path)
15
+ ::TilesetTooling::Utils.image_signature(path)
16
+ end
@@ -0,0 +1,7 @@
1
+ Feature: App executes correctly
2
+ Simple test to validate that the app starts correctly, makes
3
+ debugging of failing tests easier
4
+
5
+ Scenario: App just runs
6
+ When I get help
7
+ Then the exit status should be 0
@@ -0,0 +1,4 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'tileset_tooling/app'
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'gli'
5
+ require 'semantic_logger'
6
+
7
+ require 'tileset_tooling/version'
8
+ require 'tileset_tooling/commands'
9
+ require 'tileset_tooling/utils'
10
+
11
+ # Core module for the project
12
+ module ::TilesetTooling
13
+ end
14
+
15
+ # Core application for the project; sets up the GLI configuration and such
16
+ class ::TilesetTooling::App
17
+ extend ::GLI::App
18
+
19
+ program_desc 'Bits of tooling I use for working with tilesets'
20
+
21
+ version ::TilesetTooling::VERSION
22
+
23
+ subcommand_option_handling :normal
24
+ arguments :strict
25
+
26
+ pre do
27
+ # Set the global default log level and add appender
28
+ ::SemanticLogger.sync!
29
+ ::SemanticLogger.default_level = :trace
30
+ ::SemanticLogger.add_appender(io: $stdout, formatter: :color)
31
+
32
+ true
33
+ end
34
+
35
+ post do
36
+ end
37
+
38
+ on_error do |error|
39
+ puts(error)
40
+ puts(error.backtrace)
41
+ true
42
+ end
43
+
44
+ desc 'Commands relating to bleed around tiles'
45
+ command :bleed do |bleed_command|
46
+ bleed_command.arg(:input_file)
47
+ bleed_command.desc('Inserts a bleed around tiles')
48
+ bleed_command.command(:insert) do |insert_command|
49
+ insert_command.switch([:'skip-specs'], desc: 'Skips the reading of the specs')
50
+ insert_command.flag([:output], default_value: nil, desc: 'Path where to store result', arg_name: 'path')
51
+ insert_command.action do |_, options, args|
52
+ command = ::TilesetTooling::Commands::InsertBleed.new(options, args)
53
+ command.unpack!
54
+ command.run
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'highline'
5
+ require 'semantic_logger'
6
+
7
+ # Module containing all commands
8
+ module ::TilesetTooling::Commands
9
+ end
10
+
11
+ # Basis for all commands
12
+ class ::TilesetTooling::Commands::Command
13
+ # Initializer for command basis
14
+ def initialize(options, args)
15
+ @logger = ::SemanticLogger[self.class.name.split('::').last]
16
+ @cli = ::HighLine.new
17
+ @options = options
18
+ @args = args
19
+ end
20
+ end
21
+
22
+ require 'tileset_tooling/commands/insert_bleed'
@@ -0,0 +1,126 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'tileset_tooling/data'
5
+
6
+ require 'yaml'
7
+
8
+ # Command used to insert bleed around tiles
9
+ class ::TilesetTooling::Commands::InsertBleed < ::TilesetTooling::Commands::Command
10
+ # Validates arguments/options and unpacks them
11
+ def unpack!
12
+ raise(::StandardError, 'Missing argument') unless @args.count == 1
13
+
14
+ @image_path = @args.shift
15
+
16
+ raise(::StandardError, "Could not find image '#{@image_path}'") unless ::File.exist?(@image_path)
17
+ end
18
+
19
+ # Runs this command
20
+ def run
21
+ @logger.info("Adding bleed to tiles in image file '#{@image_path}'")
22
+ tileset = gather_image_information
23
+
24
+ raise(::StandardError, 'Current implementation needs an existing margin') unless tileset.margin.positive?
25
+
26
+ ::MiniMagick::Tool::Convert.new do |convert|
27
+ convert.background('none')
28
+ convert << tileset.original_image_path
29
+
30
+ tileset.for_each_tile do |tile|
31
+ copy_rect(convert, tile.left, tile.top, 1, tile.width, tile.left, tile.top - 1) unless tile.top <= 0
32
+ copy_rect(convert, tile.left, tile.bottom - 1, 1, tile.width, tile.left, tile.bottom) unless tile.bottom >= tileset.height
33
+ copy_rect(convert, tile.left, tile.top, tile.height, 1, tile.left - 1, tile.top) unless tile.left <= 0
34
+ copy_rect(convert, tile.right - 1, tile.top, tile.height, 1, tile.right, tile.top) unless tile.right >= tileset.width
35
+ end
36
+
37
+ convert.flatten
38
+ convert << result_path
39
+ end
40
+
41
+ @logger.info("Result stored in '#{result_path}'")
42
+ end
43
+
44
+ private
45
+
46
+ def result_path
47
+ return @options[:output] if @options[:output]
48
+
49
+ file_name = ::File.basename(@image_path, '.*')
50
+ extension = ::File.extname(@image_path)
51
+ directory = ::File.dirname(@image_path)
52
+
53
+ "#{directory}/#{file_name}_result#{extension}"
54
+ end
55
+
56
+ def ask_specs
57
+ tile_height =
58
+ @cli.ask('Tile height? ', ::Integer) do |q|
59
+ q.default = 0
60
+ q.in = 1..256
61
+ end
62
+ tile_width =
63
+ @cli.ask('Tile width? ', ::Integer) do |q|
64
+ q.default = 0
65
+ q.in = 1..256
66
+ end
67
+ margin = @cli.ask('Margin? ', ::Integer) { |q| q.default = 0 }
68
+ offset_top = @cli.ask('Top Offset? ', ::Integer) { |q| q.default = 0 }
69
+ offset_left = @cli.ask('Left Offset? ', ::Integer) { |q| q.default = 0 }
70
+
71
+ [tile_height, tile_width, margin, offset_top, offset_left]
72
+ end
73
+
74
+ def load_specs_from_file(file_path)
75
+ @logger.info("Extracting specs from '#{file_path}'")
76
+ specs = ::YAML.load_file(file_path)
77
+
78
+ begin
79
+ tile_height = specs['specs']['details']['tile_height']
80
+ tile_width = specs['specs']['details']['tile_width']
81
+ margin = specs['specs']['details']['margin']
82
+ offset_top = specs['specs']['details']['offset_top']
83
+ offset_left = specs['specs']['details']['offset_left']
84
+ rescue ::NoMethodError
85
+ raise(::StandardError, 'Invalid specs file')
86
+ end
87
+ [tile_height, tile_width, margin, offset_top, offset_left]
88
+ end
89
+
90
+ def find_specs
91
+ specs_file = ::TilesetTooling::Utils.image_spec_file_path(@image_path)
92
+ if !@options[:'skip-specs'] && ::File.exist?(specs_file)
93
+ tile_height, tile_width, margin, offset_top, offset_left = load_specs_from_file(specs_file)
94
+ else
95
+ tile_height, tile_width, margin, offset_top, offset_left = ask_specs
96
+ end
97
+ [tile_height, tile_width, margin, offset_top, offset_left]
98
+ end
99
+
100
+ # Asks for information about the image and build a tileset
101
+ def gather_image_information
102
+ tile_height, tile_width, margin, offset_top, offset_left = find_specs
103
+
104
+ ::TilesetTooling::Data::TileSet.new(
105
+ image: ::MiniMagick::Image.open(@image_path),
106
+ original_image_path: @image_path,
107
+ tile_height: tile_height,
108
+ tile_width: tile_width,
109
+ margin: margin,
110
+ offset_top: offset_top,
111
+ offset_left: offset_left
112
+ )
113
+ end
114
+
115
+ def copy_rect(convert, x1, y1, height, width, x2, y2)
116
+ # convert image \( +clone -crop WxH+X1+Y1 +repage \) -geometry +X2+Y2 -compose over -composite result
117
+ convert.stack do |stack|
118
+ stack.clone.+ # rubocop:disable Lint/Void
119
+ stack.crop("#{width}x#{height}+#{x1}+#{y1}")
120
+ stack.repage.+
121
+ end
122
+ convert.geometry("+#{x2}+#{y2}")
123
+ convert.compose('Over')
124
+ convert.composite
125
+ end
126
+ end
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ require 'dry-struct'
5
+ require 'mini_magick'
6
+
7
+ # Module containing all data-types that are used across the project
8
+ module ::TilesetTooling::Data
9
+ end
10
+
11
+ require 'tileset_tooling/data/types'
12
+ require 'tileset_tooling/data/tile'
13
+ require 'tileset_tooling/data/tileset'
@@ -0,0 +1,30 @@
1
+ # Copyright (c) 2020 Jean-Sebastien Gelinas, see LICENSE.txt
2
+ # frozen_string_literal: true
3
+
4
+ # Class representing a TileSet's information
5
+ class ::TilesetTooling::Data::Tile < ::Dry::Struct
6
+ attribute :top, ::TilesetTooling::Data::Types::Integer
7
+ attribute :left, ::TilesetTooling::Data::Types::Integer
8
+ attribute :height, ::TilesetTooling::Data::Types::Integer
9
+ attribute :width, ::TilesetTooling::Data::Types::Integer
10
+
11
+ attribute :margin_top, ::TilesetTooling::Data::Types::Integer
12
+ attribute :margin_left, ::TilesetTooling::Data::Types::Integer
13
+ attribute :margin_bottom, ::TilesetTooling::Data::Types::Integer
14
+ attribute :margin_right, ::TilesetTooling::Data::Types::Integer
15
+
16
+ # Helper to print the information of the tile
17
+ def to_s
18
+ "#{top},#{left} [#{margin_top},#{margin_left},#{margin_bottom},#{margin_right}]"
19
+ end
20
+
21
+ # Helper to get the bottom coordinate
22
+ def bottom
23
+ top + height
24
+ end
25
+
26
+ # Helper to get the right coordinate
27
+ def right
28
+ left + width
29
+ end
30
+ end