lib/yamg.rb in yamg-0.0.5 vs lib/yamg.rb in yamg-0.0.7
- old
+ new
@@ -1,400 +1,66 @@
require 'yaml'
require 'rainbow'
+require 'screencap'
require 'mini_magick'
MiniMagick.processor = :gm if ENV['gm']
#
# Yet Another Media Generator
#
-class YAMG
- attr_accessor :config
+module YAMG
+ autoload :CLI, 'yamg/cli'
+ autoload :Icon, 'yamg/icon'
+ autoload :Splash, 'yamg/splash'
+ autoload :Screenshot, 'yamg/screenshot'
- TEMPLATES = {
+ # Load template works
+ TEMPLATES = YAML.load_file(
+ File.join(File.dirname(__FILE__), 'yamg', 'templates.yaml')
+ )
- #
- # Android Cordova/Phonegap
- #
- android: {
- # TODO: check if we really need this duplication on android
- icons: {
- 'res/drawable/icon.png' => 96,
- 'res/drawable-ldpi/icon.png' => 36,
- 'res/drawable-mdpi/icon.png' => 48,
- 'res/drawable-hdpi/icon.png' => 72,
- 'res/drawable-xhdpi/icon.png' => 96,
- 'res/drawable-xxhdpi/icon.png' => 256,
- 'bin/res/drawable/icon.png' => 96,
- 'bin/res/drawable-ldpi/icon.png' => 36,
- 'bin/res/drawable-mdpi/icon.png' => 48,
- 'bin/res/drawable-hdpi/icon.png' => 72,
- 'bin/res/drawable-xhdpi/icon.png' => 96,
- 'ant-build/res/drawable/icon.png' => 96,
- 'ant-build/res/drawable-ldpi/icon.png' => 36,
- 'ant-build/res/drawable-mdpi/icon.png' => 48,
- 'ant-build/res/drawable-hdpi/icon.png' => 72,
- 'ant-build/res/drawable-xhdpi/icon.png' => 96
- },
- splash: {
- 'res/drawable-port-ldpi/screen.png' => [200, 320],
- 'res/drawable-port-mdpi/screen.png' => [320, 480],
- 'res/drawable-port-hdpi/screen.png' => [480, 800],
- 'res/drawable-port-xhdpi/screen.png' => [720, 1280],
- 'res/drawable-land-ldpi/screen.png' => [320, 200],
- 'res/drawable-land-mdpi/screen.png' => [480, 320],
- 'res/drawable-land-hdpi/screen.png' => [800, 480],
- 'res/drawable-land-xhdpi/screen.png' => [1280, 720],
- 'bin/res/drawable-port-ldpi/screen.png' => [200, 320],
- 'bin/res/drawable-port-mdpi/screen.png' => [320, 480],
- 'bin/res/drawable-port-hdpi/screen.png' => [480, 800],
- 'bin/res/drawable-port-xhdpi/screen.png' => [720, 1280],
- 'bin/res/drawable-land-ldpi/screen.png' => [320, 200],
- 'bin/res/drawable-land-mdpi/screen.png' => [480, 320],
- 'bin/res/drawable-land-hdpi/screen.png' => [800, 480],
- 'bin/res/drawable-land-xhdpi/screen.png' => [1280, 720],
- 'ant-build/res/drawable-port-ldpi/screen.png' => [200, 320],
- 'ant-build/res/drawable-port-mdpi/screen.png' => [320, 480],
- 'ant-build/res/drawable-port-hdpi/screen.png' => [480, 800],
- 'ant-build/res/drawable-port-xhdpi/screen.png' => [720, 1280],
- 'ant-build/res/drawable-land-ldpi/screen.png' => [320, 200],
- 'ant-build/res/drawable-land-mdpi/screen.png' => [480, 320],
- 'ant-build/res/drawable-land-hdpi/screen.png' => [800, 480],
- 'ant-build/res/drawable-land-xhdpi/screen.png' => [1280, 720]
- }
- },
+ # def initialize(conf = './.yamg.yml')
+ # load_config(conf)
+ # end
+ class << self
+ attr_accessor :config
- #
- # iOS cordova/phonegap
- #
- ios: {
- icons: {
- 'Resources/icons/icon.png' => 57,
- 'Resources/icons/icon@2x.png' => 114,
- 'Resources/icons/icon-40.png' => 40,
- 'Resources/icons/icon-40@2x.png' => 80,
- 'Resources/icons/icon-40@3x.png' => 120,
- 'Resources/icons/icon-50.png' => 50,
- 'Resources/icons/icon-50@2x.png' => 100,
- 'Resources/icons/icon-60.png' => 60,
- 'Resources/icons/icon-60@2x.png' => 120,
- 'Resources/icons/icon-60@3x.png' => 180,
- 'Resources/icons/icon-72.png' => 72,
- 'Resources/icons/icon-72@2x.png' => 144,
- 'Resources/icons/icon-76.png' => 76,
- 'Resources/icons/icon-76@2x.png' => 152,
- 'Resources/icons/icon-120.png' => 120,
- 'Resources/icons/icon-small.png' => 29,
- 'Resources/icons/icon-small@2x.png' => 58,
- 'Resources/icons/icon-small@3x.png' => 87
- },
- splash: {
- 'Resources/splash/Default~iphone.png' => [320, 480],
- 'Resources/splash/Default@2x~iphone.png' => [640, 960],
- 'Resources/splash/Default-Landscape@2x~ipad.png' => [2048, 1536],
- 'Resources/splash/Default-Landscape~ipad.png' => [1024, 768],
- 'Resources/splash/Default-Portrait@2x~ipad.png' => [1536, 2048],
- 'Resources/splash/Default-Portrait~ipad.png' => [768, 1024],
- 'Resources/splash/Default-568h@2x~iphone.png' => [640, 1136],
- 'Resources/splash/Default-667h.png' => [750, 1344],
- 'Resources/splash/Default-736h.png' => [1242, 2208],
- 'Resources/splash/Default-Landscape-736h.png' => [2208, 1242]
- }
- },
-
- #
- # Phonegap www/res/
- #
- phonegap: {
- icon: {
- 'icon/android/icon-36-ldpi.png' => 36,
- 'icon/android/icon-48-mdpi.png' => 48,
- 'icon/android/icon-72-hdpi.png' => 72,
- 'icon/android/icon-96-xhdpi.png' => 96,
- 'icon/blackberry/icon-80.png' => 80,
- 'icon/ios/icon-57.png' => 57,
- 'icon/ios/icon-57-2x.png' => 114,
- 'icon/ios/icon-72.png' => 72,
- 'icon/webos/icon-64.png' => 64,
- 'icon/windows-phone/icon-173-tile.png' => 173,
- 'icon/windows-phone/icon-62-tile.png' => 62,
- 'icon/windows-phone/icon-48.png' => 48
- },
- splash: {
- 'screen/android/screen-ldpi-landscape.png' => [200, 320],
- 'screen/android/screen-mdpi-landscape.png' => [320, 480],
- 'screen/android/screen-hdpi-landscape.png' => [480, 800],
- 'screen/android/screen-xhdpi-landscape.png' => [720, 1280],
- 'screen/android/screen-ldpi-portrait.png' => [320, 200],
- 'screen/android/screen-mdpi-portrait.png' => [480, 320],
- 'screen/android/screen-hdpi-portrait.png' => [800, 480],
- 'screen/android/screen-xhdpi-portrait.png' => [1280, 720],
- 'screen/blackberry/screen-225' => [225],
- 'screen/ios/screen-ipad-landscape-2x.png' => [2048, 1536],
- 'screen/ios/screen-ipad-landscape.png' => [1024, 768],
- 'screen/ios/screen-ipad-portrait-2x.png' => [1536, 2048],
- 'screen/ios/screen-ipad-portrait.png' => [768, 1024],
- 'screen/ios/screen-iphone-landscape.png' => [480, 320],
- 'screen/ios/screen-iphone-landscape-2x.png' => [960, 640],
- 'screen/ios/screen-iphone-portrait-2x.png' => [640, 960],
- 'screen/ios/screen-iphone-portrait-568h-2x.png' => [640, 1136],
- 'screen/ios/screen-iphone-portrait.png' => [320, 480],
- 'screen/ios/screen-iDefault~iphone.png' => [320, 480],
- 'screen/ios/screen-iDefault-667h.png' => [750, 1344],
- 'screen/ios/screen-iDefault-736h.png' => [1242, 2208],
- 'screen/ios/screen-iDefault-Landscape-736h.png' => [2208, 1242],
- 'screen/windows-phone/screen-portrait.jpg' => [720, 1280]
- }
- },
-
- #
- # WWW
- #
- web: {
- icons: {
- 'icon.png' => 256
- },
- media: {
- 'media.png' => 256
- }
- },
-
- rails: {
- icons: {
- 'public/favicon.png' => 16,
- 'public/favicon.ico' => 16,
- 'public/icon.png' => 512,
- 'app/assets/images/icon.png' => 512,
- 'app/assets/images/favicon.png' => 16
- },
- media: {
- 'public/logo.png' => 512
- }
- },
-
- twitter: {
- icons: {
- 'icon.png' => 256
- }
- },
-
- #
- # Play Store
- #
- google: {
- icons: {
- 'icon.png' => 512,
- 'icon1024.png' => 1024, # [1024, 500]
- '180.png' => 180, # 180, 120
- },
- splash: {
- 'splash.png' => [1024, 768]
- }
- },
-
- #
- # App Store
- #
- apple: {
- icons: {
- 'icon.png' => 1024,
- 'icon16.png' => 16
- },
- splash: {
- 'splash.png' => [1024, 768]
- }
- },
-
- facebook: {
- icons: {
- 'icon.png' => 1024,
- 'icon16.png' => 16
- },
- splash: {
- 'splash.png' => [1024, 768]
- }
- }
- }
-
- def initialize
- load_config
- end
-
- def load_config
- self.config = YAML.load_file('./.yamg.yml').freeze
- rescue Errno::ENOENT
- puts 'Create config!'
- exit 1
- end
-
- def setup_for(opts)
- case opts
- when Hash then opts
- when String then { 'path' => opts }
- when TrueClass then { 'path' => './media' }
- else fail
- end
- end
-
- #
- # Writes image to disk
- #
- def write_out(img, path)
- img.format File.extname(path)
- FileUtils.mkdir_p File.dirname(path)
- img.write(path)
- rescue Errno::ENOENT
- puts
- puts Rainbow("Path not found '#{path}'").red
- exit 1
- end
-
- def compile_work(scope, opts)
- puts Rainbow("Working on #{scope}'").blue
- setup = setup_for(opts)
-
- if (t = TEMPLATES[scope.to_sym])
- Thread.new do # 200% speed up with 8 cores
- icon_work(t[:icons], setup)
- splash_work(t[:splash], setup) if t[:splash]
+ def init
+ file = './.yamg.yml'
+ if File.exist?(file)
+ puts "File exists: '#{file}'"
+ exit 1
end
- else
- puts 'Custom job!'
+ puts Rainbow('Creating your configuration').black
+ src = File.join(File.dirname(__FILE__), 'yamg', 'yamg.yml')
+ FileUtils.cp(src, file)
end
- end
- def compile(scope = nil)
- time = Time.now
- works = config['compile']
- works.select! { |w| w =~ scope } if scope
- works.each { |out, opts| compile_work(out, opts) }
- Thread.list.reject { |t| t == Thread.current }.each(&:join)
- puts Rainbow("Done compile #{Time.now - time}").red
- end
-
- #
- #
- # ICONS
- #
- #
- def load_icons(path)
- return [path] unless File.extname(path).empty?
- Dir["#{path}/*.png"].map { |f| File.basename(f) }
- end
-
- def find_closest_gte_icon(size, icons)
- return icons.max_by(&:to_i) if icons.map(&:to_i).max < size
- icons.min_by do |f|
- # n = x.match(/\d+/).to_s.to_i
- n = f.to_i
- size > n ? Float::INFINITY : n
+ def load_config(conf = './.yamg.yml')
+ self.config = YAML.load_file(conf).freeze
+ rescue Errno::ENOENT
+ puts Rainbow('Create config! Run: `yamg init`').red
+ exit 1
end
- end
- def icon_work(files, setup)
- path = setup['icon'] || config['icon']['path']
- rounded = setup['rounded'] || config['icon']['rounded']
- icons = load_icons(path)
- puts Rainbow("Starting in #{path} with #{icons} | #{setup}").blue
- files.each do |file, size|
- from = File.join(path, find_closest_gte_icon(size, icons))
- to = File.join(setup['path'], file)
- puts "#{File.basename from} -> #{to} (#{size}px)"
- image = MiniMagick::Image.open(from)
- image.resize size # "NxN"
- image = round(image) if rounded
- write_out(image, to)
+ def load_images(dir)
+ return [dir] unless File.extname(dir).empty?
+ Dir["#{dir}/*.png"].map { |f| File.basename(f) }
end
- end
- # https://gist.github.com/artemave/c20e7450af866f5e7735
- def round(img, r = 14)
- size = img.dimensions.join(',')
- r = img.dimensions.max / r
- radius = [r, r].join(',')
-
- mask = MiniMagick::Image.open(img.path)
- mask.format 'png'
-
- mask.combine_options do |m|
- m.alpha 'transparent'
- m.background 'none'
- m.draw "roundrectangle 0,0,#{size},#{radius}"
+ #
+ # Writes image to disk
+ #
+ def write_out(img, path)
+ img.format File.extname(path)
+ FileUtils.mkdir_p File.dirname(path)
+ img.write(path)
+ rescue Errno::ENOENT
+ puts
+ puts Rainbow("Path not found '#{path}'").red
+ exit 1
end
-
- overlay = ::MiniMagick::Image.open img.path
- overlay.format 'png'
-
- overlay.combine_options do |o|
- o.alpha 'transparent'
- o.background 'none'
- o.draw "roundrectangle 0,0,#{size},#{radius}"
- end
-
- masked = img.composite(mask, 'png') do |i|
- i.alpha 'set'
- i.compose 'DstIn'
- end
-
- masked.composite(overlay, 'png') do |i|
- i.compose 'Over'
- end
- masked
-
- # convert
- # -size 512x512 xc:none
- # -draw "roundrectangle 0,0,512,512,55,55" mask.png
- # convert icon.png
- # -matte mask.png
- # -compose DstIn
- # -composite picture_with_rounded_corners.png
- end
-
- #
- #
- # SPLASH
- #
- #
- def splash_center(path, size, bg = config['splash']['background'])
- icon_size = size.max / 4
- image = MiniMagick::Image.open(path)
- image.resize icon_size if image.dimensions.max >= icon_size
- image.background bg if bg
- image.combine_options do |o|
- o.gravity 'center'
- o.extent size.join('x') # "WxH"
- end
- end
-
- def splash_composite(base, icons)
- max = base.dimensions.min / 9
- icons.reduce(base) do |img, over|
- oimg = MiniMagick::Image.open(File.join(config['splash']['path'], over))
- oimg.resize(max) if oimg.dimensions.max >= max
- img.composite(oimg) do |o|
- o.compose 'Over'
- o.gravity File.basename(over, '.*')
- o.geometry '+40%+40%'
- end
- end
- end
-
- def splash_work(screens, setup)
- path = config['splash']['path']
- icons = load_icons(path)
- center = icons.find { |i| i =~ /center/ }
- icons.delete(center)
-
- puts Rainbow("Starting splashes | #{setup}").blue
- screens.each do |file, size|
- to = File.join(setup['path'], file)
- image = splash_center(File.join(path, center), size)
- write_out(splash_composite(image, icons), to)
- end
- end
-
- def screenshot
- puts 'SS'
end
end
# },
# "customImages": [