frameit 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 994d66e0107d1487eae24acaeb5bd997849a7055
4
- data.tar.gz: c8e9b01111ee2435795097129d1d243141bb4cfb
3
+ metadata.gz: 39874df0b50f3d2b85da9a8e15fd1a63639cc090
4
+ data.tar.gz: e0ef4228c227cadd7557cab83bc3a17c09361e9a
5
5
  SHA512:
6
- metadata.gz: 8feb6bb7edb9952d64a35dea949c63401d73ddd72307db8e0c128621a606c517df4e63f776973ac9be3fa1d263a5cebb3ee359def50e5a52af012ae72b91d466
7
- data.tar.gz: 26ab1ba67b648f4e466f50e236ce7dd36c113a702465ece6e2f4b63a09ef3d29ae859d5afce6c676dfb8b6c265113aac8eaadd1508899f93e27131d0417a0228
6
+ metadata.gz: ffb3757b70c4a004f0a69462526ac341e676bc26c011d23164733e9217b30a4c80714419babd61bce104b8d47985a76f0e50924cda6fbc9b7f8d4e8ddc7dd362
7
+ data.tar.gz: ad69ccef5a466dbe23ba6a1533372c351a191961df49084ade21683ecff7024b0ff9bcc08dafa4ec198222366a5d0f8b7da11c83ab9c5b74b06edadfc2696998
data/LICENSE CHANGED
@@ -19,3 +19,6 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
+
23
+ All screenshots located inside the 'assets' folder of this directory are excluded
24
+ from this license.
data/README.md CHANGED
@@ -27,11 +27,10 @@ frameit
27
27
  [![Twitter: @KauseFx](https://img.shields.io/badge/contact-@KrauseFx-blue.svg?style=flat)](https://twitter.com/KrauseFx)
28
28
  [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/KrauseFx/frameit/blob/master/LICENSE)
29
29
  [![Gem](https://img.shields.io/gem/v/frameit.svg?style=flat)](http://rubygems.org/gems/frameit)
30
+ [![Build Status](https://img.shields.io/travis/fastlane/frameit/master.svg?style=flat)](https://travis-ci.org/fastlane/frameit)
30
31
 
31
32
  ###### Quickly put your screenshots into the right device frames
32
33
 
33
- This tool is not supposed to be used for App Store screenshots. Instead it should be used to prepare screenshots for websites, emails and prints.
34
-
35
34
  Get in contact with the developer on Twitter: [@KrauseFx](https://twitter.com/KrauseFx)
36
35
 
37
36
 
@@ -60,10 +59,7 @@ Put a gorgeous device frame around your iOS screenshots just by running one simp
60
59
 
61
60
  Here is a nice gif, that shows ```frameit``` in action:
62
61
 
63
- ![assets/FrameitGit.gif](assets/FrameitGit.gif)
64
-
65
- Here is how the result can look like
66
- ![assets/Overview.png](assets/Overview.png)
62
+ ![assets/FrameitGit.gif](assets/FrameitGit.gif?raw=1)
67
63
 
68
64
  # Installation
69
65
 
@@ -103,6 +99,8 @@ To run the setup process again to add new frames use:
103
99
 
104
100
  frameit setup
105
101
 
102
+ When using `frameit` without titles on top, the screenshots will have the full resolution, which means they can't be uploaded to the App Store directly. They are supposed to be used for websites, print media and emails. Check out the section below to use the screenshots for the App Store.
103
+
106
104
  # Tips
107
105
 
108
106
  ## [`fastlane`](https://fastlane.tools) Toolchain
@@ -118,8 +116,12 @@ To run the setup process again to add new frames use:
118
116
 
119
117
  ##### [Like this tool? Be the first to know about updates and new fastlane tools](https://tinyletter.com/krausefx)
120
118
 
121
- ## Generate screenshots
122
- Check out [```Snapshot```](https://github.com/KrauseFx/snapshot) to automatically generate screenshots using ```UI Automation```.
119
+ ## Generate localized screenshots
120
+ Check out [`snapshot`](https://github.com/KrauseFx/snapshot) to automatically generate screenshots using ```UI Automation```.
121
+
122
+ ## White background of frames
123
+
124
+ Some stock images provided by Apple still have a white background instead of a transparent one. You'll have to edit the Photoshop file to remove the white background, delete the generated `.png` file and run `frameit` again.
123
125
 
124
126
  ## Use a clean status bar
125
127
  You can use [SimulatorStatusMagic](https://github.com/shinydevelopment/SimulatorStatusMagic) to clean up the status bar.
data/bin/frameit CHANGED
@@ -29,7 +29,7 @@ class FrameItApplication
29
29
  c.description = "Adds a black frame around all screenshots."
30
30
 
31
31
  c.action do |args, options|
32
- Frameit::Editor.new.run('.', Frameit::Editor::Color::BLACK)
32
+ Frameit::Runner.new.run('.', Frameit::Color::BLACK)
33
33
  end
34
34
  end
35
35
 
@@ -38,7 +38,7 @@ class FrameItApplication
38
38
  c.description = "Adds a silver frame around all screenshots."
39
39
 
40
40
  c.action do |args, options|
41
- Frameit::Editor.new.run('.', Frameit::Editor::Color::SILVER)
41
+ Frameit::Runner.new.run('.', Frameit::Color::SILVER)
42
42
  end
43
43
  end
44
44
 
@@ -60,6 +60,7 @@ end
60
60
 
61
61
  begin
62
62
  FastlaneCore::UpdateChecker.start_looking_for_update('frameit')
63
+ Frameit::DependencyChecker.check_dependencies
63
64
  FrameItApplication.new.run
64
65
  ensure
65
66
  FastlaneCore::UpdateChecker.show_update_status('frameit', Frameit::VERSION)
Binary file
data/lib/frameit.rb CHANGED
@@ -1,13 +1,27 @@
1
1
  require 'mini_magick'
2
2
  require 'frameit/version'
3
3
  require 'frameit/frame_converter'
4
+ require 'frameit/device_types'
5
+ require 'frameit/runner'
6
+ require 'frameit/screenshot'
7
+ require 'frameit/config_parser'
8
+ require 'frameit/offsets'
4
9
  require 'frameit/editor'
10
+ require 'frameit/template_finder'
11
+ require 'frameit/strings_parser'
12
+ require 'frameit/mac_editor'
5
13
  require 'frameit/dependency_checker'
6
14
 
7
15
  require 'fastlane_core'
8
16
 
9
17
  module Frameit
10
18
  Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
11
-
12
- Frameit::DependencyChecker.check_dependencies
13
19
  end
20
+
21
+
22
+ class ::Hash
23
+ def fastlane_deep_merge(second)
24
+ merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
25
+ self.merge(second, &merger)
26
+ end
27
+ end
@@ -0,0 +1,83 @@
1
+ module Frameit
2
+ class ConfigParser
3
+ def load(path)
4
+ return nil unless File.exists?(path) # we are okay with no config at all
5
+ Helper.log.info "Parsing config file '#{path}'".yellow if $verbose
6
+ @path = path
7
+ self.parse(File.read(path))
8
+ end
9
+
10
+
11
+
12
+ # @param data (String) the JSON data to be parsed
13
+ def parse(data)
14
+ begin
15
+ @data = JSON.parse(data)
16
+ rescue => ex
17
+ Helper.log.fatal ex
18
+ raise "Invalid JSON file at path '#{@path}'. Make sure it's a valid JSON file".red
19
+ end
20
+
21
+ self
22
+ end
23
+
24
+ # Fetches the finished configuration for a given path. This will try to look for a specific value
25
+ # and fallback to a default value if nothing was found
26
+ def fetch_value(path)
27
+ specific = @data['data'].find { |a| path.include?a['filter'] }
28
+
29
+ default = @data['default']
30
+
31
+ values = default.fastlane_deep_merge(specific || {})
32
+
33
+ change_paths_to_absolutes!(values)
34
+ validate_values(values)
35
+
36
+ values
37
+ end
38
+
39
+ # Use absolute paths instead of relative
40
+ def change_paths_to_absolutes!(values)
41
+ values.each do |key, value|
42
+ if value.kind_of?Hash
43
+ change_paths_to_absolutes!(value) # recursive call
44
+ else
45
+ if ['font', 'background'].include?key
46
+ # Change the paths to relative ones
47
+ # `replace`: to change the content of the string, so it's actually stored
48
+
49
+ if @path # where is the config file. We don't have a config file in tests
50
+ containing_folder = File.expand_path('..', @path)
51
+ value.replace File.join(containing_folder, value)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ # Make sure the paths/colors are valid
59
+ def validate_values(values)
60
+ values.each do |key, value|
61
+ if value.kind_of?Hash
62
+ validate_values(value) # recursive call
63
+ else
64
+ if key == 'font'
65
+ raise "Could not find font at path '#{File.expand_path(value)}'" unless File.exists?value
66
+ end
67
+
68
+ if key == 'background'
69
+ raise "Could not find background image at path '#{File.expand_path(value)}'" unless File.exists?value
70
+ end
71
+
72
+ if key == 'color'
73
+ raise "Invalid color '#{value}'. Must be valid Hex #123123" unless value.include?"#"
74
+ end
75
+
76
+ if key == 'padding'
77
+ raise "padding must be type integer" unless value.kind_of?Integer
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -1,6 +1,8 @@
1
1
  module Frameit
2
2
  class DependencyChecker
3
3
  def self.check_dependencies
4
+ return if Helper.is_test?
5
+
4
6
  self.check_image_magick
5
7
  end
6
8
 
@@ -0,0 +1,11 @@
1
+ module Frameit
2
+ module Color
3
+ BLACK = "SpaceGray"
4
+ SILVER = "Slvr"
5
+ end
6
+
7
+ module Orientation
8
+ PORTRAIT = "Vert"
9
+ LANDSCAPE = "Horz"
10
+ end
11
+ end
@@ -1,172 +1,239 @@
1
- require 'deliver'
2
- require 'fastimage'
3
-
4
1
  module Frameit
5
2
  class Editor
6
- module Color
7
- BLACK = "SpaceGray"
8
- SILVER = "Slvr"
9
- end
3
+ attr_accessor :screenshot # reference to the screenshot object to fetch the path, title, etc.
4
+ attr_accessor :frame # the frame of the device
5
+ attr_accessor :image # the current image used for editing
6
+ attr_accessor :top_space_above_device
10
7
 
11
- module Orientation
12
- PORTRAIT = "Vert"
13
- LANDSCAPE = "Horz"
14
- end
8
+ def frame!(screenshot)
9
+ self.screenshot = screenshot
10
+ prepare_image
15
11
 
12
+ if load_frame # e.g. Mac doesn't need a frame
13
+ self.frame = MiniMagick::Image.open(load_frame)
14
+ end
16
15
 
17
- def initialize
18
- converter = FrameConverter.new
19
- unless converter.frames_exist?
20
- # First run
21
- converter.run
16
+ if should_add_title?
17
+ @image = complex_framing
22
18
  else
23
- # Just make sure, the PSD files are converted to PNG
24
- converter.convert_frames
19
+ # easy mode from 1.0 - no title or background
20
+ width = offset['width']
21
+ image.resize width # resize the image to fit the frame
22
+ put_into_frame # put it in the frame
25
23
  end
24
+
25
+ store_result # write to file system
26
26
  end
27
27
 
28
- def run(path, color = Color::BLACK)
29
- @color = color
30
-
31
- screenshots = Dir.glob("#{path}/**/*.{png,PNG}")
32
-
33
- if screenshots.count > 0
34
- screenshots.each do |screenshot|
35
- next if screenshot.include?"_framed.png"
36
- next if screenshot.include?".itmsp/" # a package file, we don't want to modify that
37
-
38
- begin
39
- template_path = get_template(screenshot)
40
- if template_path
41
- template = MiniMagick::Image.open(template_path)
42
- image = MiniMagick::Image.open(screenshot)
43
-
44
- offset_information = image_offset(screenshot)
45
- raise "Could not find offset_information for '#{screenshot}'" unless (offset_information and offset_information[:width])
46
- width = offset_information[:width]
47
- image.resize width
48
-
49
- result = template.composite(image, "png") do |c|
50
- c.compose "Over"
51
- c.geometry offset_information[:offset]
52
- end
53
-
54
- output_path = screenshot.gsub('.png', '_framed.png').gsub('.PNG', '_framed.png')
55
- result.format "png"
56
- result.write output_path
57
- Helper.log.info "Added frame: '#{File.expand_path(output_path)}'".green
58
- end
59
- rescue => ex
60
- Helper.log.error ex
61
- end
28
+ def load_frame
29
+ TemplateFinder.get_template(screenshot)
30
+ end
31
+
32
+ def prepare_image
33
+ @image = MiniMagick::Image.open(screenshot.path)
34
+ end
35
+
36
+
37
+ private
38
+ def store_result
39
+ output_path = screenshot.path.gsub('.png', '_framed.png').gsub('.PNG', '_framed.png')
40
+ image.format "png"
41
+ image.write output_path
42
+ Helper.log.info "Added frame: '#{File.expand_path(output_path)}'".green
43
+ end
44
+
45
+ # puts the screenshot into the frame
46
+ def put_into_frame
47
+ @image = frame.composite(image, "png") do |c|
48
+ c.compose "Over"
49
+ c.geometry offset['offset']
62
50
  end
63
- else
64
- Helper.log.error "Could not find screenshots"
65
51
  end
66
- end
67
52
 
68
- # This will detect the screen size and choose the correct template
69
- def get_template(path)
70
- parts = [
71
- device_name(screen_size(path)),
72
- orientation_name(path),
73
- @color
74
- ]
75
-
76
- templates_path = [ENV['HOME'], FrameConverter::FRAME_PATH].join('/')
77
- templates = Dir["#{templates_path}/**/#{parts.join('_')}*.png"]
78
-
79
- if templates.count == 0
80
- if screen_size(path) == Deliver::AppScreenshot::ScreenSize::IOS_35
81
- Helper.log.warn "Unfortunately 3.5\" device frames were discontinued. Skipping screen '#{path}'".yellow
82
- else
83
- Helper.log.error "Could not find a valid template for screenshot '#{path}'".red
84
- Helper.log.error "You can download new templates from '#{FrameConverter::DOWNLOAD_URL}'"
85
- Helper.log.error "and store them in '#{templates_path}'"
86
- Helper.log.error "Missing file: '#{parts.join('_')}.psd'".red
53
+ def offset
54
+ return @offset_information if @offset_information
55
+
56
+ @offset_information = fetch_config['offset'] || Offsets.image_offset(screenshot)
57
+
58
+ if @offset_information and (@offset_information['offset'] or @offset_information['offset'])
59
+ return @offset_information
87
60
  end
88
- return nil
89
- else
90
- # Helper.log.debug "Found template '#{templates.first}' for screenshot '#{path}'"
91
- return templates.first.gsub(" ", "\ ")
61
+ raise "Could not find offset_information for '#{screenshot}'"
92
62
  end
93
- end
94
63
 
95
- private
96
- def screen_size(path)
97
- Deliver::AppScreenshot.calculate_screen_size(path)
98
- end
99
-
100
- def device_name(screen_size)
101
- size = Deliver::AppScreenshot::ScreenSize
102
- case screen_size
103
- when size::IOS_55
104
- return 'iPhone_6_Plus'
105
- when size::IOS_47
106
- return 'iPhone_6'
107
- when size::IOS_40
108
- return 'iPhone_5s'
109
- when size::IOS_IPAD
110
- return 'iPad_mini'
64
+ #########################################################################################
65
+ # Everything below is related to title, background, etc. and is not used in the easy mode
66
+ #########################################################################################
67
+
68
+ # this is used to correct the 1:1 offset information
69
+ # the offset information is stored to work for the template images
70
+ # since we resize the template images to have higher quality screenshots
71
+ # we need to modify the offset information by a certain factor
72
+ def modify_offset(multiplicator)
73
+ # Format: "+133+50"
74
+ hash = offset['offset']
75
+ x = hash.split("+")[1].to_f * multiplicator
76
+ y = hash.split("+")[2].to_f * multiplicator
77
+ new_offset = "+#{x.round}+#{y.round}"
78
+ @offset_information['offset'] = new_offset
79
+ end
80
+
81
+ # Do we add a background and title as well?
82
+ def should_add_title?
83
+ return (fetch_config['background'] and (fetch_config['title'] or fetch_config['keyword']))
84
+ end
85
+
86
+ # more complex mode: background, frame and title
87
+ def complex_framing
88
+ background = generate_background
89
+
90
+ if self.frame # we have no frame on le mac
91
+ resize_frame!
92
+ @image = put_into_frame
93
+
94
+ # Decrease the size of the framed screenshot to fit into the defined padding + background
95
+ frame_width = background.width - fetch_config['padding'] * 2
96
+ image.resize "#{frame_width}x"
97
+ end
98
+
99
+ @@image = put_device_into_background(background)
100
+
101
+ if fetch_config['title']
102
+ @image = add_title
103
+ end
104
+
105
+ image
106
+ end
107
+
108
+ # Returns a correctly sized background image
109
+ def generate_background
110
+ background = MiniMagick::Image.open(fetch_config['background'])
111
+
112
+ if background.height != screenshot.size[1]
113
+ background.resize "#{screenshot.size[0]}x#{screenshot.size[1]}!" # `!` says it should ignore the ratio
114
+ end
115
+ background
116
+ end
117
+
118
+ def put_device_into_background(background)
119
+ left_space = (background.width / 2.0 - image.width / 2.0).round
120
+ bottom_space = -(image.height / 10).round # to be just a bit below the image bottom
121
+ bottom_space -= 40 if screenshot.is_portrait? # even more for portrait mode
122
+
123
+ self.top_space_above_device = background.height - image.height - bottom_space
124
+
125
+ @image = background.composite(image, "png") do |c|
126
+ c.compose "Over"
127
+ c.geometry "+#{left_space}+#{top_space_above_device}"
111
128
  end
129
+
130
+ return image
112
131
  end
113
132
 
114
- def orientation_name(path)
115
- size = FastImage.size(path)
116
- return Orientation::PORTRAIT if size[0] < size[1]
117
- return Orientation::LANDSCAPE
118
- end
119
-
120
- def image_offset(path)
121
- size = Deliver::AppScreenshot::ScreenSize
122
- case orientation_name(path)
123
- when Orientation::PORTRAIT
124
- case screen_size(path)
125
- when size::IOS_55
126
- return {
127
- offset: '+42+147',
128
- width: 539
129
- }
130
- when size::IOS_47
131
- return {
132
- offset: '+41+154',
133
- width: 530
134
- }
135
- when size::IOS_40
136
- return {
137
- offset: "+54+197",
138
- width: 543
139
- }
140
- when size::IOS_IPAD
141
- return {
142
- offset: '+50+134',
143
- width: 792
144
- }
145
- end
146
- when Orientation::LANDSCAPE
147
- case screen_size(path)
148
- when size::IOS_55
149
- return {
150
- offset: "+146+41",
151
- width: 960
152
- }
153
- when size::IOS_47
154
- return {
155
- offset: "+153+41",
156
- width: 946
157
- }
158
- when size::IOS_40
159
- return {
160
- offset: "+201+48",
161
- width: 970
162
- }
163
- when size::IOS_IPAD
164
- return {
165
- offset: '+133+50',
166
- width: 1058
167
- }
168
- end
133
+ # Resize the frame as it's too low quality by default
134
+ def resize_frame!
135
+ multiplicator = (screenshot.size[0].to_f / offset['width'].to_f) # by how much do we have to change this?
136
+ new_frame_width = multiplicator * frame.width # the new width for the frame
137
+ frame.resize "#{new_frame_width.round}x" # resize it to the calculated witdth
138
+ modify_offset(multiplicator) # modify the offset to properly insert the screenshot into the frame later
139
+ end
140
+
141
+ # Add the title above the device
142
+ def add_title
143
+ title_images = build_title_images(image.width)
144
+ keyword = title_images[:keyword]
145
+ title = title_images[:title]
146
+
147
+ sum_width = (keyword.width rescue 0) + title.width + keyword_padding
148
+ top_space = (top_space_above_device / 2.0 - actual_font_size / 2.0).round # centered
149
+
150
+ left_space = (image.width / 2.0 - sum_width / 2.0).round
151
+ if keyword
152
+ @image = image.composite(keyword, "png") do |c|
153
+ c.compose "Over"
154
+ c.geometry "+#{left_space}+#{top_space}"
155
+ end
169
156
  end
157
+
158
+ left_space += (keyword.width rescue 0) + keyword_padding
159
+ @image = image.composite(title, "png") do |c|
160
+ c.compose "Over"
161
+ c.geometry "+#{left_space}+#{top_space}"
162
+ end
163
+ image
164
+ end
165
+
166
+ def actual_font_size
167
+ (screenshot.size[0] / 20.0).round # depends on the width of the screenshot
168
+ end
169
+
170
+ def keyword_padding
171
+ (actual_font_size / 2.0).round
170
172
  end
173
+
174
+ # This will build 2 individual images with the title, which will then be added to the real image
175
+ def build_title_images(max_width)
176
+ words = [:keyword, :title].keep_if{ |a| fetch_text(a) } # optional keyword/title
177
+ results = {}
178
+ words.each do |key|
179
+ # Create empty background
180
+ empty_path = File.join(Helper.gem_path('frameit'), "lib/assets/empty.png")
181
+ title_image = MiniMagick::Image.open(empty_path)
182
+ image_height = actual_font_size * 2 # gets trimmed afterwards anyway, and on the iPad the `y` would get cut
183
+ title_image.combine_options do |i|
184
+ i.resize "#{max_width}x#{image_height}!" # `!` says it should ignore the ratio
185
+ end
186
+
187
+ # Add the actual title
188
+ font = fetch_config[key.to_s]['font']
189
+ title_image.combine_options do |i|
190
+ i.font font if font
191
+ i.gravity "Center"
192
+ i.pointsize actual_font_size
193
+ i.draw "text 0,0 '#{fetch_text(key)}'"
194
+ i.fill fetch_config[key.to_s]['color']
195
+ end
196
+ title_image.trim # remove white space
197
+
198
+ results[key] = title_image
199
+ end
200
+ results
201
+ end
202
+
203
+ # Loads the config (colors, background, texts, etc.)
204
+ # Don't use this method to access the actual text and use `fetch_texts` instead
205
+ def fetch_config
206
+ return @config if @config
207
+
208
+ config_path = File.join(File.expand_path("..", screenshot.path), "Framefile.json")
209
+ config_path = File.join(File.expand_path("../..", screenshot.path), "Framefile.json") unless File.exists?config_path
210
+ file = ConfigParser.new.load(config_path)
211
+ return {} unless file # no config file at all
212
+ @config = file.fetch_value(screenshot.path)
213
+ end
214
+
215
+ # Fetches the title + keyword for this particular screenshot
216
+ def fetch_text(type)
217
+ raise "Valid parameters :keyword, :title" unless [:keyword, :title].include?type
218
+
219
+ # Try to get it from a keyword.strings or title.strings file
220
+ strings_path = File.join(File.expand_path("..", screenshot.path), "#{type.to_s}.strings")
221
+ if File.exists?strings_path
222
+ parsed = StringsParser.parse(strings_path)
223
+ result = parsed.find { |k, v| screenshot.path.include?k }
224
+ return result.last if result
225
+ end
226
+
227
+ # No string files, fallback to Framefile config
228
+ result = fetch_config[type.to_s]['text']
229
+
230
+ if !result and type == :title
231
+ # title is mandatory
232
+ raise "Could not get title for screenshot #{screenshot.path}. Please provide one in your Framefile.json".red
233
+ end
234
+
235
+ return result
236
+ end
237
+
171
238
  end
172
- end
239
+ end
@@ -45,7 +45,7 @@ module Frameit
45
45
  end
46
46
 
47
47
  def frames_exist?
48
- Dir["#{templates_path}/**/*.psd"].count > 0
48
+ (Dir["#{templates_path}/**/*.psd"].count + Dir["../**/*sRGB.png"].count) > 0
49
49
  end
50
50
 
51
51
  def templates_path
@@ -0,0 +1,33 @@
1
+ module Frameit
2
+ # Responsible for framing Mac Screenshots
3
+ class MacEditor < Editor
4
+
5
+ def prepare_image
6
+ image = super
7
+ image.resize("#{offset['width']}x") if offset['width']
8
+ end
9
+
10
+ def put_device_into_background(background)
11
+ self.top_space_above_device = offset['titleHeight'] # needed for centering the title
12
+
13
+ @image = background.composite(image, "png") do |c|
14
+ c.compose "Over"
15
+ c.geometry offset['offset']
16
+ end
17
+
18
+ return image
19
+ end
20
+
21
+ def load_frame
22
+ nil # Macs don't need frames - backgrounds only
23
+ end
24
+
25
+ def should_add_title?
26
+ true # Mac screenshots always need a background
27
+ end
28
+
29
+ def generate_background
30
+ MiniMagick::Image.open(fetch_config['background']) # no resizing on the Mac
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ module Frameit
2
+ class Offsets
3
+ # Returns the image offset needed for a certain device type for a given orientation
4
+ # uses deliver to detect the screen size
5
+ def self.image_offset(screenshot)
6
+ size = Deliver::AppScreenshot::ScreenSize
7
+ case screenshot.orientation_name
8
+ when Orientation::PORTRAIT
9
+ case screenshot.screen_size
10
+ when size::IOS_55
11
+ return {
12
+ 'offset' => '+42+147',
13
+ 'width' => 539
14
+ }
15
+ when size::IOS_47
16
+ return {
17
+ 'offset' => '+41+154',
18
+ 'width' => 530
19
+ }
20
+ when size::IOS_40
21
+ return {
22
+ 'offset' => "+54+197",
23
+ 'width' => 543
24
+ }
25
+ when size::IOS_IPAD
26
+ return {
27
+ 'offset' => '+50+134',
28
+ 'width' => 792
29
+ }
30
+ end
31
+ when Orientation::LANDSCAPE
32
+ case screenshot.screen_size
33
+ when size::IOS_55
34
+ return {
35
+ 'offset' => "+146+41",
36
+ 'width' => 960
37
+ }
38
+ when size::IOS_47
39
+ return {
40
+ 'offset' => "+153+41",
41
+ 'width' => 946
42
+ }
43
+ when size::IOS_40
44
+ return {
45
+ 'offset' => "+201+48",
46
+ 'width' => 970
47
+ }
48
+ when size::IOS_35
49
+ return {
50
+ 'offset' => "+258+52",
51
+ 'width' => 966
52
+ }
53
+ when size::IOS_IPAD
54
+ return {
55
+ 'offset' => '+133+50',
56
+ 'width' => 1058
57
+ }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ require 'deliver'
2
+ require 'fastimage'
3
+
4
+ module Frameit
5
+ class Runner
6
+ def initialize
7
+ converter = FrameConverter.new
8
+ unless converter.frames_exist?
9
+ # First run
10
+ converter.run
11
+ else
12
+ # Just make sure, the PSD files are converted to PNG
13
+ converter.convert_frames
14
+ end
15
+ end
16
+
17
+ def run(path, color = Color::BLACK)
18
+ screenshots = Dir.glob("#{path}/**/*.{png,PNG}")
19
+
20
+ if screenshots.count > 0
21
+ screenshots.each do |full_path|
22
+ next if full_path.include?"_framed.png"
23
+ next if full_path.include?".itmsp/" # a package file, we don't want to modify that
24
+ next if full_path.include?"device_frames/" # these are the device frames the user is using
25
+
26
+ begin
27
+ screenshot = Screenshot.new(full_path, color)
28
+ screenshot.frame!
29
+ rescue => ex
30
+ Helper.log.error ex
31
+ end
32
+ end
33
+ else
34
+ Helper.log.error "Could not find screenshots"
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module Frameit
2
+ # Represents one screenshot
3
+ class Screenshot
4
+ attr_accessor :path # path to the screenshot
5
+ attr_accessor :size # size in px array of 2 elements: height and width
6
+ attr_accessor :screen_size # deliver screen size type, is unique per device type, used in device_name
7
+ attr_accessor :color # the color to use for the frame
8
+
9
+ # path: Path to screenshot
10
+ # color: Color to use for the frame
11
+ def initialize(path, color)
12
+ raise "Couldn't find file at path '#{path}'".red unless File.exists?path
13
+ @color = color
14
+ @path = path
15
+ @size = FastImage.size(path)
16
+
17
+ @screen_size = ENV["FRAMEIT_FORCE_DEVICE_TYPE"] || Deliver::AppScreenshot.calculate_screen_size(path)
18
+ end
19
+
20
+ # Device name for a given screen size. Used to use the correct template
21
+ def device_name
22
+ sizes = Deliver::AppScreenshot::ScreenSize
23
+ case @screen_size
24
+ when sizes::IOS_55
25
+ return 'iPhone_6_Plus'
26
+ when sizes::IOS_47
27
+ return 'iPhone_6'
28
+ when sizes::IOS_40
29
+ return 'iPhone_5s'
30
+ when sizes::IOS_35
31
+ return 'iPhone_4'
32
+ when sizes::IOS_IPAD
33
+ return 'iPad_mini'
34
+ when sizes::MAC
35
+ return 'Mac'
36
+ end
37
+ end
38
+
39
+ def is_mac?
40
+ return device_name == 'Mac'
41
+ end
42
+
43
+ # The name of the orientation of a screenshot. Used to find the correct template
44
+ def orientation_name
45
+ return Orientation::PORTRAIT if size[0] < size[1]
46
+ return Orientation::LANDSCAPE
47
+ end
48
+
49
+ def is_portrait?
50
+ return (orientation_name == Orientation::PORTRAIT)
51
+ end
52
+
53
+ def to_s
54
+ self.path
55
+ end
56
+
57
+ # Add the device frame, this will also call the method that adds the background + title
58
+ def frame!
59
+ if self.is_mac?
60
+ MacEditor.new.frame!(self)
61
+ else
62
+ Editor.new.frame!(self)
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,31 @@
1
+ module Frameit
2
+ # This class will parse the .string files
3
+ class StringsParser
4
+ def self.parse(path)
5
+ raise "Couldn't find strings file at path '#{path}'".red unless File.exists?path
6
+ raise "Must be .strings file, only got '#{path}'".red unless path.end_with?".strings"
7
+
8
+ result = {}
9
+
10
+ # A .strings file is UTF-16 encoded. We only want to deal with UTF-8
11
+ content = `iconv -f UTF-16 -t UTF-8 '#{path}'`
12
+
13
+ content.split("\n").each do |line|
14
+ begin
15
+ # We don't care about comments and empty lines
16
+ if line.start_with?'"'
17
+ key = line.match(/"(.*)" \= /)[1]
18
+ value = line.match(/ \= "(.*)"/)[1]
19
+
20
+ result[key] = value
21
+ end
22
+ rescue => ex
23
+ Helper.log.error ex
24
+ Helper.log.error line
25
+ end
26
+ end
27
+
28
+ result
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ module Frameit
2
+ # Responsible for finding the correct device
3
+ class TemplateFinder
4
+
5
+ # This will detect the screen size and choose the correct template
6
+ def self.get_template(screenshot)
7
+ return nil if screenshot.is_mac?
8
+ parts = [
9
+ screenshot.device_name,
10
+ screenshot.orientation_name,
11
+ screenshot.color
12
+ ]
13
+ parts << "sRGB" if screenshot.device_name == 'iPad_mini'
14
+
15
+
16
+ templates_path = [ENV['HOME'], FrameConverter::FRAME_PATH].join('/')
17
+ templates = Dir["../**/#{parts.join('_')}*.{png,jpg}"] # local directory
18
+ templates = templates + Dir["#{templates_path}/**/#{parts.join('_')}*.{png,jpg}"] # ~/.frameit folder
19
+
20
+
21
+ if templates.count == 0
22
+ if screenshot.screen_size == Deliver::AppScreenshot::ScreenSize::IOS_35
23
+ Helper.log.warn "Unfortunately 3.5\" device frames were discontinued. Skipping screen '#{screenshot.path}'".yellow
24
+ Helper.log.debug "Looked for: '#{parts.join('_')}.png'".red
25
+ else
26
+ Helper.log.error "Could not find a valid template for screenshot '#{screenshot.path}'".red
27
+ Helper.log.error "You can download new templates from '#{FrameConverter::DOWNLOAD_URL}'"
28
+ Helper.log.error "and store them in '#{templates_path}'"
29
+ Helper.log.error "Missing file: '#{parts.join('_')}.png'".red
30
+ end
31
+ return nil
32
+ else
33
+ return templates.first.gsub(" ", "\ ")
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,3 +1,3 @@
1
1
  module Frameit
2
- VERSION = "1.0.1"
2
+ VERSION = "2.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: frameit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Felix Krause
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-22 00:00:00.000000000 Z
11
+ date: 2015-05-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fastlane_core
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '>='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.5.0
19
+ version: 0.7.2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '>='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.5.0
26
+ version: 0.7.2
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: fastimage
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.8.7.4
139
139
  - !ruby/object:Gem::Dependency
140
- name: codeclimate-test-reporter
140
+ name: coveralls
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - '>='
@@ -161,10 +161,19 @@ files:
161
161
  - LICENSE
162
162
  - README.md
163
163
  - bin/frameit
164
+ - lib/assets/empty.png
164
165
  - lib/frameit.rb
166
+ - lib/frameit/config_parser.rb
165
167
  - lib/frameit/dependency_checker.rb
168
+ - lib/frameit/device_types.rb
166
169
  - lib/frameit/editor.rb
167
170
  - lib/frameit/frame_converter.rb
171
+ - lib/frameit/mac_editor.rb
172
+ - lib/frameit/offsets.rb
173
+ - lib/frameit/runner.rb
174
+ - lib/frameit/screenshot.rb
175
+ - lib/frameit/strings_parser.rb
176
+ - lib/frameit/template_finder.rb
168
177
  - lib/frameit/version.rb
169
178
  homepage: https://fastlane.tools
170
179
  licenses: