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.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +63 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +16 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +161 -0
- data/LICENSE.txt +21 -0
- data/README.rdoc +59 -0
- data/Rakefile +54 -0
- data/bin/tileset_tooling +7 -0
- data/features/insert_bleed.feature +56 -0
- data/features/step_definitions/file_steps.rb +19 -0
- data/features/step_definitions/help_steps.rb +10 -0
- data/features/step_definitions/insert_bleed_steps.rb +28 -0
- data/features/support/configuration.rb +18 -0
- data/features/support/env.rb +19 -0
- data/features/support/test_data.rb +16 -0
- data/features/tileset_tooling.feature +7 -0
- data/lib/tileset_tooling.rb +4 -0
- data/lib/tileset_tooling/app.rb +58 -0
- data/lib/tileset_tooling/commands.rb +22 -0
- data/lib/tileset_tooling/commands/insert_bleed.rb +126 -0
- data/lib/tileset_tooling/data.rb +13 -0
- data/lib/tileset_tooling/data/tile.rb +30 -0
- data/lib/tileset_tooling/data/tileset.rb +96 -0
- data/lib/tileset_tooling/data/types.rb +11 -0
- data/lib/tileset_tooling/utils.rb +26 -0
- data/lib/tileset_tooling/version.rb +7 -0
- data/test/commands/insert_bleed_test.rb +76 -0
- data/test/data/UIpackSheet_transparent.png +0 -0
- data/test/data/expected/simple_with_bad_specs.png +0 -0
- data/test/data/expected/simple_with_margin.png +0 -0
- data/test/data/expected/simple_with_specs.png +0 -0
- data/test/data/simple_no_margin.png +0 -0
- data/test/data/simple_with_bad_specs.png +0 -0
- data/test/data/simple_with_bad_specs.specs +2 -0
- data/test/data/simple_with_margin.png +0 -0
- data/test/data/simple_with_specs.png +0 -0
- data/test/data/simple_with_specs.specs +7 -0
- data/test/data/source/simple_no_margin.xcf +0 -0
- data/test/data/source/simple_with_margin.xcf +0 -0
- data/test/test_helper.rb +23 -0
- data/test/utils_test.rb +12 -0
- data/tileset_tooling.gemspec +34 -0
- data/tileset_tooling.rdoc +48 -0
- 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,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
|