├── .babelrc ├── .github └── FUNDING.yml ├── .gitignore ├── Icon └── Icon.icns ├── Icon_Generator.provisionprofile ├── LICENSE.md ├── README.md ├── Screenshots ├── Icon.png ├── banner.png ├── gif.gif ├── s1.png ├── s2.png ├── s3.png ├── s4.png └── s5.png ├── afterSignHook.js ├── appstore.sh ├── babel ├── afterSignHook.js ├── components │ ├── Application.js │ └── InputComponent.js ├── entitlements.plist ├── library │ ├── Contents.json │ └── Generator.js ├── main.js └── renderer.js ├── entitlements.plist ├── index.html ├── package-lock.json ├── package.json ├── src ├── components │ ├── Application.js │ └── InputComponent.js ├── library │ ├── Contents.json │ └── Generator.js ├── main.js └── renderer.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-react-jsx-source" 4 | ], 5 | "presets": ["env", "react"], 6 | "ignore": [ 7 | "**/afterSignHook.js" 8 | ] 9 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: onmyway133 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tags* 3 | /.idea/ 4 | /build/ 5 | /dist/ 6 | /external_binaries/ 7 | /out/ 8 | /vendor/download/ 9 | /vendor/debian_jessie_amd64-sysroot/ 10 | /vendor/debian_jessie_arm-sysroot/ 11 | /vendor/debian_jessie_arm64-sysroot/ 12 | /vendor/debian_jessie_i386-sysroot/ 13 | /vendor/debian_wheezy_amd64-sysroot/ 14 | /vendor/debian_wheezy_arm-sysroot/ 15 | /vendor/debian_wheezy_i386-sysroot/ 16 | /vendor/python_26/ 17 | /vendor/npm/ 18 | /vendor/llvm/ 19 | /vendor/llvm-build/ 20 | /vendor/.gclient 21 | node_modules/ 22 | *.xcodeproj 23 | *.swp 24 | *.pyc 25 | *.VC.db 26 | *.VC.VC.opendb 27 | .vs/ 28 | .vscode/ 29 | *.vcxproj 30 | *.vcxproj.user 31 | *.vcxproj.filters 32 | *.sln 33 | *.log 34 | /brightray/brightray.opensdf 35 | /brightray/brightray.sdf 36 | /brightray/brightray.sln 37 | /brightray/brightray.vcxproj* 38 | /brightray/brightray.suo 39 | /brightray/brightray.v12.suo 40 | /brightray/brightray.xcodeproj/ 41 | 42 | IconGenerator-mas-x64 43 | icon_generator-darwin-x64 44 | babel/ -------------------------------------------------------------------------------- /Icon/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Icon/Icon.icns -------------------------------------------------------------------------------- /Icon_Generator.provisionprofile: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Icon_Generator.provisionprofile -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Licensed under the **MIT** license 2 | 3 | > Copyright (c) 2017 Khoa Pham 4 | > 5 | > Permission is hereby granted, free of charge, to any person obtaining 6 | > a copy of this software and associated documentation files (the 7 | > "Software"), to deal in the Software without restriction, including 8 | > without limitation the rights to use, copy, modify, merge, publish, 9 | > distribute, sublicense, and/or sell copies of the Software, and to 10 | > permit persons to whom the Software is furnished to do so, subject to 11 | > the following conditions: 12 | > 13 | > The above copyright notice and this permission notice shall be 14 | > included in all copies or substantial portions of the Software. 15 | > 16 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 𝕴𝖈𝖔𝖓 𝕲𝖊𝖓𝖊𝖗𝖆𝖙𝖔𝖗 2 | 3 | 4 | Buy Me A Coffee 5 | 6 | 7 | Checkout https://indiegoodies.com/ 8 | 9 |
10 | 11 |
12 | 13 | ## Description 14 | 15 | - An `electron.js` app used for generating app icons 16 | - Support macOS 17 | - Follow [Human Interface Guidelines iOS](https://developer.apple.com/ios/human-interface-guidelines/graphics/app-icon/) 18 | - Generate based on latest Xcode [Contents.json](https://developer.apple.com/library/content/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/Contents.html) 19 | 20 | ## How to install 21 | 22 | - Download latest release from https://github.com/onmyway133/IconGenerator/releases 23 | 24 | ## How to use 25 | 26 | - Drag image onto left box 27 | - Support `png`, `jpeg`, `jpg`, `webp`, `tiff`, `gif`, `svg` 28 | - Choose platform to generate: `iPhone`, `iPad`, `macOS`, `tvOS`, `watchOS`, or an `icns file` 29 | - Generated `AppIcon.appiconset` or `AppIcon.icns` and save to `Downloads` folder 30 | 31 |
32 | 33 |
34 | 35 | ## Credit 36 | 37 | - Icon http://emojione.com/ 38 | - Use [sharp](https://github.com/lovell/sharp) to resize images 39 | 40 | ## Author 41 | 42 | Khoa Pham, onmyway133@gmail.com 43 | 44 | ## License 45 | 46 | **IconGenerator** is available under the MIT license. See the [LICENSE](https://github.com/onmyway133/IconGenerator/blob/master/LICENSE.md) file for more info. 47 | -------------------------------------------------------------------------------- /Screenshots/Icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/Icon.png -------------------------------------------------------------------------------- /Screenshots/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/banner.png -------------------------------------------------------------------------------- /Screenshots/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/gif.gif -------------------------------------------------------------------------------- /Screenshots/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s1.png -------------------------------------------------------------------------------- /Screenshots/s2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s2.png -------------------------------------------------------------------------------- /Screenshots/s3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s3.png -------------------------------------------------------------------------------- /Screenshots/s4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s4.png -------------------------------------------------------------------------------- /Screenshots/s5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/onmyway133/IconGenerator/dd1ed6da60e68e01578ed2c6222b2e71e45fc585/Screenshots/s5.png -------------------------------------------------------------------------------- /afterSignHook.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; } 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var electron_notarize = require('electron-notarize'); 8 | 9 | module.exports = function () { 10 | var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee(params) { 11 | var appId, appPath; 12 | return regeneratorRuntime.wrap(function _callee$(_context) { 13 | while (1) { 14 | switch (_context.prev = _context.next) { 15 | case 0: 16 | if (!(process.platform !== 'darwin')) { 17 | _context.next = 2; 18 | break; 19 | } 20 | 21 | return _context.abrupt('return'); 22 | 23 | case 2: 24 | console.log('afterSign hook triggered', params); 25 | 26 | // Same appId in electron-builder. 27 | appId = 'com.onmyway133.IconGenerator'; 28 | appPath = path.join(params.appOutDir, params.packager.appInfo.productFilename + '.app'); 29 | 30 | if (fs.existsSync(appPath)) { 31 | _context.next = 7; 32 | break; 33 | } 34 | 35 | throw new Error('Cannot find application at: ' + appPath); 36 | 37 | case 7: 38 | 39 | console.log('Notarizing ' + appId + ' found at ' + appPath); 40 | 41 | _context.prev = 8; 42 | _context.next = 11; 43 | return electron_notarize.notarize({ 44 | appBundleId: appId, 45 | appPath: appPath, 46 | appleId: process.env.appleId, 47 | appleIdPassword: process.env.appleIdPassword 48 | }); 49 | 50 | case 11: 51 | _context.next = 16; 52 | break; 53 | 54 | case 13: 55 | _context.prev = 13; 56 | _context.t0 = _context['catch'](8); 57 | 58 | console.error(_context.t0); 59 | 60 | case 16: 61 | 62 | console.log('Done notarizing ' + appId); 63 | 64 | case 17: 65 | case 'end': 66 | return _context.stop(); 67 | } 68 | } 69 | }, _callee, this, [[8, 13]]); 70 | })); 71 | 72 | return function (_x) { 73 | return _ref.apply(this, arguments); 74 | }; 75 | }(); -------------------------------------------------------------------------------- /appstore.sh: -------------------------------------------------------------------------------- 1 | npx electron-packager . "IconGenerator" --app-bundle-id=com.onmyway133.IconGenerator --helper-bundle-id=com.onmyway133.IconGenerator.helper --app-version=1.4.0 --build-version=8 --platform=mas --arch=x64 --icon=Icon/Icon.icns --overwrite 2 | npx electron-osx-sign "IconGenerator-mas-x64/IconGenerator.app" --verbose 3 | npx electron-osx-flat "IconGenerator-mas-x64/IconGenerator.app" --verbose 4 | -------------------------------------------------------------------------------- /babel/afterSignHook.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | var electron_notarize = require('electron-notarize'); 4 | 5 | module.exports = async function (params) { 6 | // Only notarize the app on Mac OS only. 7 | if (process.platform !== 'darwin') { 8 | return; 9 | } 10 | console.log('afterSign hook triggered', params); 11 | 12 | // Same appId in electron-builder. 13 | let appId = 'com.onmyway133.IconGenerator'; 14 | 15 | let appPath = path.join(params.appOutDir, `${params.packager.appInfo.productFilename}.app`); 16 | if (!fs.existsSync(appPath)) { 17 | throw new Error(`Cannot find application at: ${appPath}`); 18 | } 19 | 20 | console.log(`Notarizing ${appId} found at ${appPath}`); 21 | 22 | try { 23 | await electron_notarize.notarize({ 24 | appBundleId: appId, 25 | appPath: appPath, 26 | appleId: process.env.appleId, 27 | appleIdPassword: process.env.appleIdPassword 28 | }); 29 | } catch (error) { 30 | console.error(error); 31 | } 32 | 33 | console.log(`Done notarizing ${appId}`); 34 | }; -------------------------------------------------------------------------------- /babel/components/Application.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _jsxFileName = 'src/components/Application.js'; 4 | 5 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 6 | 7 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 8 | 9 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 10 | 11 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 12 | 13 | var React = require('react'); 14 | var ReactDOM = require('react-dom'); 15 | var Paper = require('@material-ui/core/Paper'); 16 | var MuiThemeProvider = require('material-ui/styles/').MuiThemeProvider; 17 | var InputComponent = require('./InputComponent.js'); 18 | 19 | // http://www.material-ui.com/#/get-started/installation 20 | // injectTapEventPlugin() 21 | 22 | var Application = function (_React$Component) { 23 | _inherits(Application, _React$Component); 24 | 25 | function Application(props) { 26 | _classCallCheck(this, Application); 27 | 28 | return _possibleConstructorReturn(this, (Application.__proto__ || Object.getPrototypeOf(Application)).call(this, props)); 29 | } 30 | 31 | _createClass(Application, [{ 32 | key: 'render', 33 | value: function render() { 34 | var styles = { 35 | div: { 36 | width: '100%', 37 | alignSelf: 'stretch', 38 | display: 'flex', 39 | backgroundcolor: '#F8F8F0' 40 | } 41 | }; 42 | 43 | return React.createElement( 44 | MuiThemeProvider, 45 | { 46 | __source: { 47 | fileName: _jsxFileName, 48 | lineNumber: 26 49 | } 50 | }, 51 | React.createElement( 52 | 'div', 53 | { style: styles.div, __source: { 54 | fileName: _jsxFileName, 55 | lineNumber: 27 56 | } 57 | }, 58 | React.createElement(InputComponent, { file: this.props.file, error: this.props.error, __source: { 59 | fileName: _jsxFileName, 60 | lineNumber: 28 61 | } 62 | }) 63 | ) 64 | ); 65 | } 66 | }]); 67 | 68 | return Application; 69 | }(React.Component); 70 | 71 | module.exports = Application; -------------------------------------------------------------------------------- /babel/components/InputComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _jsxFileName = 'src/components/InputComponent.js'; 4 | 5 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 6 | 7 | var _react = require('react'); 8 | 9 | var _react2 = _interopRequireDefault(_react); 10 | 11 | var _reactDom = require('react-dom'); 12 | 13 | var _reactDom2 = _interopRequireDefault(_reactDom); 14 | 15 | var _Paper = require('@material-ui/core/Paper'); 16 | 17 | var _Paper2 = _interopRequireDefault(_Paper); 18 | 19 | var _RadioGroup = require('@material-ui/core/RadioGroup'); 20 | 21 | var _RadioGroup2 = _interopRequireDefault(_RadioGroup); 22 | 23 | var _Radio = require('@material-ui/core/Radio'); 24 | 25 | var _Radio2 = _interopRequireDefault(_Radio); 26 | 27 | var _Button = require('@material-ui/core/Button'); 28 | 29 | var _Button2 = _interopRequireDefault(_Button); 30 | 31 | var _FormControlLabel = require('@material-ui/core/FormControlLabel'); 32 | 33 | var _FormControlLabel2 = _interopRequireDefault(_FormControlLabel); 34 | 35 | var _DialogContentText = require('@material-ui/core/DialogContentText'); 36 | 37 | var _DialogContentText2 = _interopRequireDefault(_DialogContentText); 38 | 39 | var _imageSize = require('image-size'); 40 | 41 | var _imageSize2 = _interopRequireDefault(_imageSize); 42 | 43 | var _Generator = require('../library/Generator'); 44 | 45 | var _Generator2 = _interopRequireDefault(_Generator); 46 | 47 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 48 | 49 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 50 | 51 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 52 | 53 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 54 | 55 | var InputComponent = function (_React$Component) { 56 | _inherits(InputComponent, _React$Component); 57 | 58 | function InputComponent(props) { 59 | _classCallCheck(this, InputComponent); 60 | 61 | var _this = _possibleConstructorReturn(this, (InputComponent.__proto__ || Object.getPrototypeOf(InputComponent)).call(this, props)); 62 | 63 | _this.state = { 64 | choice: 'iOS (iPhone)' 65 | }; 66 | 67 | _this.handleChoiceChange = _this.handleChoiceChange.bind(_this); 68 | _this.handleGenerate = _this.handleGenerate.bind(_this); 69 | return _this; 70 | } 71 | 72 | _createClass(InputComponent, [{ 73 | key: 'render', 74 | value: function render() { 75 | var styles = { 76 | div: { 77 | display: 'flex', 78 | alignSelf: 'stretch', 79 | width: '100%' 80 | } 81 | }; 82 | 83 | return _react2.default.createElement( 84 | 'div', 85 | { style: styles.div, __source: { 86 | fileName: _jsxFileName, 87 | lineNumber: 34 88 | } 89 | }, 90 | this.makeImage(), 91 | this.makeChoices() 92 | ); 93 | } 94 | 95 | // action 96 | 97 | }, { 98 | key: 'handleChoiceChange', 99 | value: function handleChoiceChange(event, value) { 100 | this.setState({ 101 | choice: value 102 | }); 103 | } 104 | }, { 105 | key: 'handleGenerate', 106 | value: function handleGenerate() { 107 | var generator = new _Generator2.default(); 108 | generator.generate(this.props.file.path, this.state.choice); 109 | } 110 | 111 | // make 112 | 113 | }, { 114 | key: 'makeImage', 115 | value: function makeImage() { 116 | var divOptions = { 117 | style: { 118 | flex: 1.5, 119 | padding: '10px' 120 | } 121 | }; 122 | 123 | var paperOptions = { 124 | style: { 125 | width: '100%', 126 | height: '100%' 127 | } 128 | }; 129 | 130 | return _react2.default.createElement( 131 | 'div', 132 | { style: divOptions.style, __source: { 133 | fileName: _jsxFileName, 134 | lineNumber: 72 135 | } 136 | }, 137 | _react2.default.createElement( 138 | _Paper2.default, 139 | { style: paperOptions.style, __source: { 140 | fileName: _jsxFileName, 141 | lineNumber: 73 142 | } 143 | }, 144 | this.makeImageElement(), 145 | this.makeImageDescriptionElement() 146 | ) 147 | ); 148 | } 149 | }, { 150 | key: 'makeImageElement', 151 | value: function makeImageElement() { 152 | var path = void 0; 153 | if (this.props.file !== null) { 154 | path = this.props.file.path; 155 | } else { 156 | path = ''; 157 | } 158 | 159 | var styles = { 160 | div: { 161 | display: 'flex', 162 | justifyContent: 'center', 163 | paddingTop: '20px' 164 | }, 165 | image: { 166 | width: '300px', 167 | height: '300px', 168 | border: '1px solid black' 169 | } 170 | }; 171 | 172 | return _react2.default.createElement( 173 | 'div', 174 | { style: styles.div, __source: { 175 | fileName: _jsxFileName, 176 | lineNumber: 103 177 | } 178 | }, 179 | _react2.default.createElement('img', { style: styles.image, src: path, __source: { 180 | fileName: _jsxFileName, 181 | lineNumber: 104 182 | } 183 | }) 184 | ); 185 | } 186 | }, { 187 | key: 'makeImageDescriptionElement', 188 | value: function makeImageDescriptionElement() { 189 | var text = void 0; 190 | if (this.props.file !== null) { 191 | var size = (0, _imageSize2.default)(this.props.file.path); 192 | var sizeDescription = size.width + 'x' + size.height; 193 | text = this.props.file.name + ' (' + sizeDescription + ')'; 194 | } else if (this.props.error !== null) { 195 | text = this.props.error; 196 | } else { 197 | text = 'Drag image onto the above box. Prefer 1024x1024 or larger'; 198 | } 199 | 200 | var styles = { 201 | div: { 202 | display: 'flex', 203 | justifyContent: 'center', 204 | marginTop: '10px' 205 | }, 206 | text: { 207 | textAlign: 'center' 208 | } 209 | }; 210 | 211 | return _react2.default.createElement( 212 | 'div', 213 | { style: styles.div, __source: { 214 | fileName: _jsxFileName, 215 | lineNumber: 133 216 | } 217 | }, 218 | _react2.default.createElement( 219 | _DialogContentText2.default, 220 | { style: styles.text, __source: { 221 | fileName: _jsxFileName, 222 | lineNumber: 134 223 | } 224 | }, 225 | text 226 | ) 227 | ); 228 | } 229 | }, { 230 | key: 'makeChoices', 231 | value: function makeChoices() { 232 | var styles = { 233 | div: { 234 | flex: 1, 235 | padding: '10px' 236 | }, 237 | paper: { 238 | paddingTop: '10px', 239 | paddingBottom: '10px' 240 | }, 241 | group: { 242 | paddingLeft: '10px' 243 | } 244 | }; 245 | 246 | var choices = ["iOS (iPhone)", "iOS (iPad)", "iOS (Universal)", "macOS", "macOS (Icns)", "watchOS"]; 247 | 248 | var choiceElements = choices.map(function (name) { 249 | return _react2.default.createElement(_FormControlLabel2.default, { value: name, control: _react2.default.createElement(_Radio2.default, { 250 | __source: { 251 | fileName: _jsxFileName, 252 | lineNumber: 160 253 | } 254 | }), label: name, key: name, __source: { 255 | fileName: _jsxFileName, 256 | lineNumber: 160 257 | } 258 | }); 259 | }); 260 | 261 | return _react2.default.createElement( 262 | 'div', 263 | { style: styles.div, __source: { 264 | fileName: _jsxFileName, 265 | lineNumber: 165 266 | } 267 | }, 268 | _react2.default.createElement( 269 | _Paper2.default, 270 | { style: styles.paper, __source: { 271 | fileName: _jsxFileName, 272 | lineNumber: 166 273 | } 274 | }, 275 | _react2.default.createElement(_RadioGroup2.default, { 276 | style: styles.group, 277 | defaultselected: this.state.choice, 278 | onChange: this.handleChoiceChange, 279 | children: choiceElements, __source: { 280 | fileName: _jsxFileName, 281 | lineNumber: 167 282 | } 283 | }), 284 | this.makeGenerateButton() 285 | ) 286 | ); 287 | } 288 | }, { 289 | key: 'makeGenerateButton', 290 | value: function makeGenerateButton() { 291 | var styles = { 292 | div: { 293 | display: 'flex', 294 | justifyContent: 'center', 295 | marginTop: '10px' 296 | }, 297 | button: { 298 | width: '80%' 299 | } 300 | }; 301 | 302 | return _react2.default.createElement( 303 | 'div', 304 | { style: styles.div, __source: { 305 | fileName: _jsxFileName, 306 | lineNumber: 191 307 | } 308 | }, 309 | _react2.default.createElement( 310 | _Button2.default, 311 | { 312 | style: styles.button, 313 | color: 'secondary', 314 | onClick: this.handleGenerate, 315 | disabled: this.props.file === undefined, 316 | variant: 'contained', __source: { 317 | fileName: _jsxFileName, 318 | lineNumber: 192 319 | } 320 | }, 321 | 'Generate' 322 | ) 323 | ); 324 | } 325 | }]); 326 | 327 | return InputComponent; 328 | }(_react2.default.Component); 329 | 330 | module.exports = InputComponent; -------------------------------------------------------------------------------- /babel/entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | -------------------------------------------------------------------------------- /babel/library/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "idiom" : "car", 95 | "size" : "60x60", 96 | "scale" : "2x" 97 | }, 98 | { 99 | "idiom" : "car", 100 | "size" : "60x60", 101 | "scale" : "3x" 102 | }, 103 | { 104 | "idiom" : "mac", 105 | "size" : "16x16", 106 | "scale" : "1x" 107 | }, 108 | { 109 | "idiom" : "mac", 110 | "size" : "16x16", 111 | "scale" : "2x" 112 | }, 113 | { 114 | "idiom" : "mac", 115 | "size" : "32x32", 116 | "scale" : "1x" 117 | }, 118 | { 119 | "idiom" : "mac", 120 | "size" : "32x32", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "idiom" : "mac", 125 | "size" : "128x128", 126 | "scale" : "1x" 127 | }, 128 | { 129 | "idiom" : "mac", 130 | "size" : "128x128", 131 | "scale" : "2x" 132 | }, 133 | { 134 | "idiom" : "mac", 135 | "size" : "256x256", 136 | "scale" : "1x" 137 | }, 138 | { 139 | "idiom" : "mac", 140 | "size" : "256x256", 141 | "scale" : "2x" 142 | }, 143 | { 144 | "idiom" : "mac", 145 | "size" : "512x512", 146 | "scale" : "1x" 147 | }, 148 | { 149 | "idiom" : "mac", 150 | "size" : "512x512", 151 | "scale" : "2x" 152 | }, 153 | { 154 | "size" : "24x24", 155 | "idiom" : "watch", 156 | "scale" : "2x", 157 | "role" : "notificationCenter", 158 | "subtype" : "38mm" 159 | }, 160 | { 161 | "size" : "27.5x27.5", 162 | "idiom" : "watch", 163 | "scale" : "2x", 164 | "role" : "notificationCenter", 165 | "subtype" : "42mm" 166 | }, 167 | { 168 | "size" : "29x29", 169 | "idiom" : "watch", 170 | "role" : "companionSettings", 171 | "scale" : "2x" 172 | }, 173 | { 174 | "size" : "29x29", 175 | "idiom" : "watch", 176 | "role" : "companionSettings", 177 | "scale" : "3x" 178 | }, 179 | { 180 | "size" : "40x40", 181 | "idiom" : "watch", 182 | "scale" : "2x", 183 | "role" : "appLauncher", 184 | "subtype" : "38mm" 185 | }, 186 | { 187 | "size" : "44x44", 188 | "idiom" : "watch", 189 | "scale" : "2x", 190 | "role" : "appLauncher", 191 | "subtype" : "40mm" 192 | }, 193 | { 194 | "size" : "50x50", 195 | "idiom" : "watch", 196 | "scale" : "2x", 197 | "role" : "appLauncher", 198 | "subtype" : "44mm" 199 | }, 200 | { 201 | "size" : "86x86", 202 | "idiom" : "watch", 203 | "scale" : "2x", 204 | "role" : "quickLook", 205 | "subtype" : "38mm" 206 | }, 207 | { 208 | "size" : "98x98", 209 | "idiom" : "watch", 210 | "scale" : "2x", 211 | "role" : "quickLook", 212 | "subtype" : "42mm" 213 | }, 214 | { 215 | "size" : "108x108", 216 | "idiom" : "watch", 217 | "scale" : "2x", 218 | "role" : "quickLook", 219 | "subtype" : "44mm" 220 | }, 221 | { 222 | "size" : "1024x1024", 223 | "idiom" : "watch-marketing", 224 | "filename" : "Artboard.png", 225 | "scale" : "1x" 226 | } 227 | ], 228 | "info" : { 229 | "version" : 1, 230 | "author" : "xcode" 231 | } 232 | } -------------------------------------------------------------------------------- /babel/library/Generator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 6 | 7 | var Sharp = require('sharp'); 8 | var Fs = require('fs'); 9 | var Os = require('os'); 10 | var rimraf = require('rimraf'); 11 | var Shell = require('electron').shell; 12 | var execSync = require('child_process').execSync; 13 | 14 | var Generator = function () { 15 | function Generator() { 16 | _classCallCheck(this, Generator); 17 | } 18 | 19 | _createClass(Generator, [{ 20 | key: 'generate', 21 | value: function generate(originalImagePath, choice) { 22 | var _this = this; 23 | 24 | var contentsJson = this.makeContentsJson(choice); 25 | var downloadPath = Os.homedir().concat('/Downloads'); 26 | var folderPath = downloadPath.concat('/AppIcon.appiconset'); 27 | 28 | this.writeFolder(folderPath); 29 | this.writeContentsJson(contentsJson, folderPath); 30 | this.writeImages(originalImagePath, contentsJson, folderPath).then(function () { 31 | console.log(originalImagePath, folderPath, choice); 32 | if (choice === 'macOS (Icns)') { 33 | var iconsetPath = downloadPath.concat('/AppIcon.iconset'); 34 | Fs.renameSync(folderPath, iconsetPath); 35 | Fs.unlinkSync(iconsetPath + '/Contents.json'); 36 | execSync('iconutil -c icns ' + iconsetPath); 37 | _this.showFinder(iconsetPath); 38 | if (Fs.existsSync(iconsetPath)) { 39 | rimraf.sync(iconsetPath); 40 | } 41 | } else { 42 | _this.showFinder(folderPath); 43 | } 44 | }); 45 | } 46 | 47 | // Helper 48 | 49 | }, { 50 | key: 'showFinder', 51 | value: function showFinder(path) { 52 | Shell.showItemInFolder(path); 53 | } 54 | }, { 55 | key: 'writeFolder', 56 | value: function writeFolder(folderPath) { 57 | if (Fs.existsSync(folderPath)) { 58 | rimraf.sync(folderPath); 59 | } 60 | 61 | Fs.mkdirSync(folderPath); 62 | } 63 | }, { 64 | key: 'writeContentsJson', 65 | value: function writeContentsJson(json, folderPath) { 66 | var path = folderPath.concat('/Contents.json'); 67 | Fs.writeFileSync(path, JSON.stringify(json, null, 2).replace(new RegExp(": ", "g"), " : ")); 68 | } 69 | }, { 70 | key: 'writeImages', 71 | value: function writeImages(originalImagePath, contentsJson, folderPath) { 72 | return Promise.all(contentsJson.images.map(function (object) { 73 | var output = folderPath.concat('/' + object.filename); 74 | var size = object.size.split('x')[0]; 75 | var scale = object.scale.replace('x', ''); 76 | var finalSize = size * scale; 77 | 78 | return new Promise(function (resolve) { 79 | Sharp(originalImagePath).resize(finalSize, finalSize).toFile(output, function (error, info) { 80 | resolve(); 81 | }); 82 | }); 83 | })); 84 | } 85 | }, { 86 | key: 'makeContentsJson', 87 | value: function makeContentsJson(choice) { 88 | var idioms = this.idioms(choice); 89 | var content = Fs.readFileSync(__dirname + '/Contents.json', 'utf8'); 90 | var json = JSON.parse(content); 91 | 92 | json.images = json.images.filter(function (object) { 93 | return idioms.includes(object.idiom); 94 | }).map(function (object) { 95 | var size = object.size.split('x')[0]; 96 | object.filename = 'icon_' + size + '@' + object.scale + '.png'; 97 | 98 | return object; 99 | }); 100 | 101 | return json; 102 | } 103 | }, { 104 | key: 'idioms', 105 | value: function idioms(choice) { 106 | switch (choice) { 107 | case 'iOS (iPhone)': 108 | return ['iphone', 'ios-marketing']; 109 | case 'iOS (iPad)': 110 | return ['ipad', 'ios-marketing']; 111 | case 'iOS (Universal)': 112 | return ['iphone', 'ipad', 'ios-marketing']; 113 | case 'macOS': 114 | return ['mac']; 115 | case 'macOS (Icns)': 116 | return ['mac']; 117 | case 'watchOS': 118 | return ['watch', 'watch-marketing']; 119 | default: 120 | return []; 121 | } 122 | } 123 | }]); 124 | 125 | return Generator; 126 | }(); 127 | 128 | module.exports = Generator; -------------------------------------------------------------------------------- /babel/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _require = require('electron'), 4 | app = _require.app, 5 | BrowserWindow = _require.BrowserWindow; 6 | 7 | var path = require('path'); 8 | var url = require('url'); 9 | var Menu = require('electron').Menu; 10 | 11 | // global 12 | var win = void 0; 13 | 14 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; 15 | 16 | function createWindow() { 17 | win = new BrowserWindow({ 18 | title: 'Icon Generator', 19 | width: 600, 20 | height: 500, 21 | resizable: false, 22 | icon: __dirname + '/Icon/Icon.icns', 23 | webPreferences: { 24 | nodeIntegration: true 25 | } 26 | }); 27 | 28 | win.loadURL(url.format({ 29 | pathname: path.join(__dirname, '../index.html'), 30 | protocol: 'file:', 31 | slashes: true 32 | })); 33 | 34 | win.on('closed', function () { 35 | win = null; 36 | }); 37 | 38 | createMenu(); 39 | } 40 | 41 | app.on('ready', createWindow); 42 | 43 | app.on('window-all-closed', function () { 44 | if (process.platform !== 'darwin') { 45 | app.quit(); 46 | } 47 | }); 48 | 49 | app.on('activate', function () { 50 | if (win === null) { 51 | createWindow(); 52 | } 53 | }); 54 | 55 | function createMenu() { 56 | var application = { 57 | label: "Icon Generator", 58 | submenu: [{ 59 | label: "New", 60 | accelerator: "Command+N", 61 | click: function click() { 62 | if (win === null) { 63 | createWindow(); 64 | } 65 | } 66 | }, { 67 | type: "separator" 68 | }, { 69 | label: "Quit", 70 | accelerator: "Command+Q", 71 | click: function click() { 72 | app.quit(); 73 | } 74 | }] 75 | }; 76 | 77 | var template = [application]; 78 | 79 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)); 80 | } -------------------------------------------------------------------------------- /babel/renderer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var ReactDOM = require('react-dom'); 5 | var Application = require('./components/Application.js'); 6 | var Path = require('path'); 7 | 8 | // Reload 9 | function reload() { 10 | var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { file: null, error: null }; 11 | 12 | ReactDOM.render(React.createElement(Application, state), document.getElementById('root')); 13 | } 14 | 15 | // Drag and Drop 16 | function handleDragDrop() { 17 | document.addEventListener('drop', function (e) { 18 | e.preventDefault(); 19 | e.stopPropagation(); 20 | 21 | if (e.dataTransfer.files.length <= 0) { 22 | return; 23 | } 24 | 25 | var file = e.dataTransfer.files[0]; 26 | var extension = Path.extname(file.path).replace('.', '').toLowerCase(); 27 | var support = ['png', 'jpeg', 'jpg', 'webp', 'tiff', 'gif', 'svg']; 28 | 29 | if (support.includes(extension)) { 30 | reload({ 31 | file: file, 32 | error: null 33 | }); 34 | } else { 35 | reload({ 36 | file: null, 37 | error: 'This file is not yet supported!' 38 | }); 39 | } 40 | }); 41 | 42 | document.addEventListener('dragover', function (e) { 43 | e.preventDefault(); 44 | e.stopPropagation(); 45 | }); 46 | } 47 | 48 | // Initially 49 | handleDragDrop(); 50 | reload(); -------------------------------------------------------------------------------- /entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 21 | 22 |
23 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icon_generator", 3 | "version": "1.4.0", 4 | "description": "A macOS app to generate app icons", 5 | "main": "babel/main.js", 6 | "repository": "https://github.com/onmyway133/IconGenerator", 7 | "author": "Khoa Pham", 8 | "license": "MIT", 9 | "scripts": { 10 | "start": "npm run babel && electron .", 11 | "babel": "babel ./src --out-dir ./babel --copy-files", 12 | "dist": "npm run babel && electron-builder" 13 | }, 14 | "build": { 15 | "appId": "com.onmyway133.IconGenerator", 16 | "buildVersion": "21", 17 | "productName": "Icon Generator", 18 | "icon": "./Icon/Icon.icns", 19 | "mac": { 20 | "category": "public.app-category.productivity", 21 | "hardenedRuntime": true, 22 | "gatekeeperAssess": false, 23 | "entitlements": "./entitlements.plist", 24 | "entitlementsInherit": "./entitlements.plist" 25 | }, 26 | "win": { 27 | "target": "msi" 28 | }, 29 | "linux": { 30 | "target": [ 31 | "AppImage", 32 | "deb" 33 | ] 34 | }, 35 | "afterSign": "./afterSignHook.js" 36 | }, 37 | "dependencies": { 38 | "@babel/plugin-transform-runtime": "^7.6.2", 39 | "@material-ui/core": "^4.4.2", 40 | "babel-polyfill": "^6.26.0", 41 | "image-size": "^0.6.1", 42 | "material-ui": "^0.20.2", 43 | "react": "^16.9.0", 44 | "react-dom": "^16.9.0", 45 | "rimraf": "^2.7.1", 46 | "sharp": "^0.23.0" 47 | }, 48 | "devDependencies": { 49 | "babel": "^6.23.0", 50 | "babel-cli": "^6.26.0", 51 | "babel-core": "^6.26.3", 52 | "babel-loader": "^7.1.5", 53 | "babel-plugin-transform-runtime": "^6.23.0", 54 | "babel-preset-env": "^1.7.0", 55 | "babel-preset-react": "^6.24.1", 56 | "electron": "^5.0.10", 57 | "electron-builder": "^21.2.0", 58 | "electron-packager": "^14.0.6", 59 | "electron-rebuild": "^1.8.6" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Application.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const ReactDOM = require('react-dom') 3 | const Paper = require('@material-ui/core/Paper') 4 | const MuiThemeProvider = require('material-ui/styles/').MuiThemeProvider 5 | const InputComponent = require('./InputComponent.js') 6 | 7 | // http://www.material-ui.com/#/get-started/installation 8 | // injectTapEventPlugin() 9 | 10 | class Application extends React.Component { 11 | constructor(props) { 12 | super(props) 13 | } 14 | 15 | render() { 16 | const styles = { 17 | div: { 18 | width: '100%', 19 | alignSelf: 'stretch', 20 | display: 'flex', 21 | backgroundcolor: '#F8F8F0' 22 | } 23 | } 24 | 25 | return ( 26 | 27 |
28 | 29 |
30 |
31 | ) 32 | } 33 | } 34 | 35 | module.exports = Application -------------------------------------------------------------------------------- /src/components/InputComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import Paper from '@material-ui/core/Paper' 4 | import RadioGroup from '@material-ui/core/RadioGroup' 5 | import Radio from '@material-ui/core/Radio' 6 | import Button from '@material-ui/core/Button' 7 | import FormControlLabel from '@material-ui/core/FormControlLabel'; 8 | import DialogContentText from '@material-ui/core/DialogContentText' 9 | import sizeOf from 'image-size' 10 | import Generator from '../library/Generator' 11 | 12 | class InputComponent extends React.Component { 13 | constructor(props) { 14 | super(props) 15 | 16 | this.state = { 17 | choice: 'iOS (iPhone)' 18 | } 19 | 20 | this.handleChoiceChange = this.handleChoiceChange.bind(this) 21 | this.handleGenerate = this.handleGenerate.bind(this) 22 | } 23 | 24 | render() { 25 | const styles = { 26 | div: { 27 | display: 'flex', 28 | alignSelf: 'stretch', 29 | width: '100%' 30 | } 31 | } 32 | 33 | return ( 34 |
35 | {this.makeImage()} 36 | {this.makeChoices()} 37 |
38 | ) 39 | } 40 | 41 | // action 42 | 43 | handleChoiceChange(event, value) { 44 | this.setState({ 45 | choice: value 46 | }) 47 | } 48 | 49 | handleGenerate() { 50 | const generator = new Generator() 51 | generator.generate(this.props.file.path, this.state.choice) 52 | } 53 | 54 | // make 55 | 56 | makeImage() { 57 | const divOptions = { 58 | style: { 59 | flex: 1.5, 60 | padding: '10px' 61 | } 62 | } 63 | 64 | const paperOptions = { 65 | style: { 66 | width: '100%', 67 | height: '100%' 68 | } 69 | } 70 | 71 | return ( 72 |
73 | 74 | {this.makeImageElement()} 75 | {this.makeImageDescriptionElement()} 76 | 77 |
78 | ) 79 | } 80 | 81 | makeImageElement() { 82 | let path 83 | if (this.props.file !== null) { 84 | path = this.props.file.path 85 | } else { 86 | path = '' 87 | } 88 | 89 | const styles = { 90 | div: { 91 | display: 'flex', 92 | justifyContent: 'center', 93 | paddingTop: '20px' 94 | }, 95 | image: { 96 | width: '300px', 97 | height: '300px', 98 | border: '1px solid black' 99 | } 100 | } 101 | 102 | return ( 103 |
104 | 105 |
106 | ) 107 | } 108 | 109 | makeImageDescriptionElement() { 110 | let text 111 | if (this.props.file !== null) { 112 | const size = sizeOf(this.props.file.path) 113 | const sizeDescription = size.width + 'x' + size.height 114 | text = this.props.file.name + ' (' + sizeDescription + ')' 115 | } else if (this.props.error !== null) { 116 | text = this.props.error 117 | } else { 118 | text = 'Drag image onto the above box. Prefer 1024x1024 or larger' 119 | } 120 | 121 | const styles = { 122 | div: { 123 | display: 'flex', 124 | justifyContent: 'center', 125 | marginTop: '10px' 126 | }, 127 | text: { 128 | textAlign: 'center' 129 | } 130 | } 131 | 132 | return ( 133 |
134 | {text} 135 |
136 | ) 137 | } 138 | 139 | makeChoices() { 140 | const styles = { 141 | div: { 142 | flex: 1, 143 | padding: '10px' 144 | }, 145 | paper: { 146 | paddingTop: '10px', 147 | paddingBottom: '10px' 148 | }, 149 | group: { 150 | paddingLeft: '10px' 151 | } 152 | } 153 | 154 | const choices = [ 155 | "iOS (iPhone)", "iOS (iPad)", "iOS (Universal)", "macOS", "macOS (Icns)", "watchOS" 156 | ] 157 | 158 | const choiceElements = choices.map((name) => { 159 | return ( 160 | } label={name} key={name} /> 161 | ) 162 | }) 163 | 164 | return ( 165 |
166 | 167 | 172 | {this.makeGenerateButton()} 173 | 174 |
175 | ) 176 | } 177 | 178 | makeGenerateButton() { 179 | const styles = { 180 | div: { 181 | display: 'flex', 182 | justifyContent: 'center', 183 | marginTop: '10px' 184 | }, 185 | button: { 186 | width: '80%' 187 | } 188 | } 189 | 190 | return ( 191 |
192 | 200 |
201 | ) 202 | } 203 | } 204 | 205 | module.exports = InputComponent -------------------------------------------------------------------------------- /src/library/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "idiom" : "car", 95 | "size" : "60x60", 96 | "scale" : "2x" 97 | }, 98 | { 99 | "idiom" : "car", 100 | "size" : "60x60", 101 | "scale" : "3x" 102 | }, 103 | { 104 | "idiom" : "mac", 105 | "size" : "16x16", 106 | "scale" : "1x" 107 | }, 108 | { 109 | "idiom" : "mac", 110 | "size" : "16x16", 111 | "scale" : "2x" 112 | }, 113 | { 114 | "idiom" : "mac", 115 | "size" : "32x32", 116 | "scale" : "1x" 117 | }, 118 | { 119 | "idiom" : "mac", 120 | "size" : "32x32", 121 | "scale" : "2x" 122 | }, 123 | { 124 | "idiom" : "mac", 125 | "size" : "128x128", 126 | "scale" : "1x" 127 | }, 128 | { 129 | "idiom" : "mac", 130 | "size" : "128x128", 131 | "scale" : "2x" 132 | }, 133 | { 134 | "idiom" : "mac", 135 | "size" : "256x256", 136 | "scale" : "1x" 137 | }, 138 | { 139 | "idiom" : "mac", 140 | "size" : "256x256", 141 | "scale" : "2x" 142 | }, 143 | { 144 | "idiom" : "mac", 145 | "size" : "512x512", 146 | "scale" : "1x" 147 | }, 148 | { 149 | "idiom" : "mac", 150 | "size" : "512x512", 151 | "scale" : "2x" 152 | }, 153 | { 154 | "size" : "24x24", 155 | "idiom" : "watch", 156 | "scale" : "2x", 157 | "role" : "notificationCenter", 158 | "subtype" : "38mm" 159 | }, 160 | { 161 | "size" : "27.5x27.5", 162 | "idiom" : "watch", 163 | "scale" : "2x", 164 | "role" : "notificationCenter", 165 | "subtype" : "42mm" 166 | }, 167 | { 168 | "size" : "29x29", 169 | "idiom" : "watch", 170 | "role" : "companionSettings", 171 | "scale" : "2x" 172 | }, 173 | { 174 | "size" : "29x29", 175 | "idiom" : "watch", 176 | "role" : "companionSettings", 177 | "scale" : "3x" 178 | }, 179 | { 180 | "size" : "40x40", 181 | "idiom" : "watch", 182 | "scale" : "2x", 183 | "role" : "appLauncher", 184 | "subtype" : "38mm" 185 | }, 186 | { 187 | "size" : "44x44", 188 | "idiom" : "watch", 189 | "scale" : "2x", 190 | "role" : "appLauncher", 191 | "subtype" : "40mm" 192 | }, 193 | { 194 | "size" : "50x50", 195 | "idiom" : "watch", 196 | "scale" : "2x", 197 | "role" : "appLauncher", 198 | "subtype" : "44mm" 199 | }, 200 | { 201 | "size" : "86x86", 202 | "idiom" : "watch", 203 | "scale" : "2x", 204 | "role" : "quickLook", 205 | "subtype" : "38mm" 206 | }, 207 | { 208 | "size" : "98x98", 209 | "idiom" : "watch", 210 | "scale" : "2x", 211 | "role" : "quickLook", 212 | "subtype" : "42mm" 213 | }, 214 | { 215 | "size" : "108x108", 216 | "idiom" : "watch", 217 | "scale" : "2x", 218 | "role" : "quickLook", 219 | "subtype" : "44mm" 220 | }, 221 | { 222 | "size" : "1024x1024", 223 | "idiom" : "watch-marketing", 224 | "filename" : "Artboard.png", 225 | "scale" : "1x" 226 | } 227 | ], 228 | "info" : { 229 | "version" : 1, 230 | "author" : "xcode" 231 | } 232 | } -------------------------------------------------------------------------------- /src/library/Generator.js: -------------------------------------------------------------------------------- 1 | const Sharp = require('sharp') 2 | const Fs = require('fs') 3 | const Os = require('os') 4 | const rimraf = require('rimraf') 5 | const Shell = require('electron').shell 6 | const execSync = require('child_process').execSync 7 | 8 | class Generator { 9 | 10 | generate(originalImagePath, choice) { 11 | const contentsJson = this.makeContentsJson(choice) 12 | const downloadPath = Os.homedir().concat('/Downloads') 13 | const folderPath = downloadPath.concat('/AppIcon.appiconset') 14 | 15 | this.writeFolder(folderPath) 16 | this.writeContentsJson(contentsJson, folderPath) 17 | this.writeImages(originalImagePath, contentsJson, folderPath).then(() => { 18 | console.log(originalImagePath, folderPath, choice) 19 | if (choice === 'macOS (Icns)') { 20 | const iconsetPath = downloadPath.concat('/AppIcon.iconset') 21 | Fs.renameSync(folderPath, iconsetPath) 22 | Fs.unlinkSync(iconsetPath + '/Contents.json') 23 | execSync('iconutil -c icns ' + iconsetPath) 24 | this.showFinder(iconsetPath) 25 | if (Fs.existsSync(iconsetPath)) { 26 | rimraf.sync(iconsetPath) 27 | } 28 | } else { 29 | this.showFinder(folderPath) 30 | } 31 | }) 32 | } 33 | 34 | // Helper 35 | 36 | showFinder(path) { 37 | Shell.showItemInFolder(path) 38 | } 39 | 40 | writeFolder(folderPath) { 41 | if (Fs.existsSync(folderPath)) { 42 | rimraf.sync(folderPath) 43 | } 44 | 45 | Fs.mkdirSync(folderPath) 46 | } 47 | 48 | writeContentsJson(json, folderPath) { 49 | const path = folderPath.concat('/Contents.json') 50 | Fs.writeFileSync(path, JSON.stringify(json, null, 2).replace(new RegExp(": ", "g"), " : ")) 51 | } 52 | 53 | writeImages(originalImagePath, contentsJson, folderPath) { 54 | return Promise.all( 55 | contentsJson.images.map((object) => { 56 | const output = folderPath.concat(`/${object.filename}`) 57 | const size = object.size.split('x')[0] 58 | const scale = object.scale.replace('x', '') 59 | const finalSize = size * scale 60 | 61 | return new Promise((resolve) => { 62 | Sharp(originalImagePath) 63 | .resize(finalSize, finalSize) 64 | .toFile(output, (error, info) => { 65 | resolve() 66 | }) 67 | }) 68 | }) 69 | ) 70 | } 71 | 72 | makeContentsJson(choice) { 73 | const idioms = this.idioms(choice) 74 | const content = Fs.readFileSync(__dirname + '/Contents.json', 'utf8') 75 | const json = JSON.parse(content) 76 | 77 | json.images = json.images 78 | .filter((object) => { 79 | return idioms.includes(object.idiom) 80 | }).map((object) => { 81 | const size = object.size.split('x')[0] 82 | object.filename = `icon_${size}@${object.scale}.png` 83 | 84 | return object 85 | }) 86 | 87 | return json 88 | } 89 | 90 | idioms(choice) { 91 | switch (choice) { 92 | case 'iOS (iPhone)': 93 | return ['iphone', 'ios-marketing'] 94 | case 'iOS (iPad)': 95 | return ['ipad', 'ios-marketing'] 96 | case 'iOS (Universal)': 97 | return ['iphone', 'ipad', 'ios-marketing'] 98 | case 'macOS': 99 | return ['mac'] 100 | case 'macOS (Icns)': 101 | return ['mac'] 102 | case 'watchOS': 103 | return ['watch','watch-marketing'] 104 | default: 105 | return [] 106 | } 107 | } 108 | } 109 | 110 | 111 | module.exports = Generator 112 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron') 2 | const path = require('path') 3 | const url = require('url') 4 | const Menu = require('electron').Menu 5 | 6 | // global 7 | let win 8 | 9 | process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; 10 | 11 | function createWindow () { 12 | win = new BrowserWindow({ 13 | title: 'Icon Generator', 14 | width: 600, 15 | height: 500, 16 | resizable: false, 17 | icon: __dirname + '/Icon/Icon.icns', 18 | webPreferences: { 19 | nodeIntegration: true 20 | } 21 | }) 22 | 23 | win.loadURL(url.format({ 24 | pathname: path.join(__dirname, '../index.html'), 25 | protocol: 'file:', 26 | slashes: true 27 | })) 28 | 29 | win.on('closed', () => { 30 | win = null 31 | }) 32 | 33 | createMenu() 34 | } 35 | 36 | app.on('ready', createWindow) 37 | 38 | app.on('window-all-closed', () => { 39 | if (process.platform !== 'darwin') { 40 | app.quit() 41 | } 42 | }) 43 | 44 | app.on('activate', () => { 45 | if (win === null) { 46 | createWindow() 47 | } 48 | }) 49 | 50 | function createMenu() { 51 | const application = { 52 | label: "Icon Generator", 53 | submenu: [ 54 | { 55 | label: "New", 56 | accelerator: "Command+N", 57 | click: () => { 58 | if (win === null) { 59 | createWindow() 60 | } 61 | } 62 | }, 63 | { 64 | type: "separator" 65 | }, 66 | { 67 | label: "Quit", 68 | accelerator: "Command+Q", 69 | click: () => { 70 | app.quit() 71 | } 72 | } 73 | ] 74 | } 75 | 76 | const template = [ 77 | application 78 | ] 79 | 80 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 81 | } -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | const React = require('react') 2 | const ReactDOM = require('react-dom') 3 | const Application = require('./components/Application.js') 4 | const Path = require('path') 5 | 6 | // Reload 7 | function reload(state = {file: null, error: null}) { 8 | ReactDOM.render( 9 | React.createElement(Application, state), 10 | document.getElementById('root') 11 | ) 12 | } 13 | 14 | // Drag and Drop 15 | function handleDragDrop() { 16 | document.addEventListener('drop', (e) => { 17 | e.preventDefault() 18 | e.stopPropagation() 19 | 20 | if (e.dataTransfer.files.length <= 0) { 21 | return 22 | } 23 | 24 | const file = e.dataTransfer.files[0] 25 | const extension = Path.extname(file.path).replace('.', '').toLowerCase() 26 | const support = ['png', 'jpeg', 'jpg', 'webp', 'tiff', 'gif', 'svg'] 27 | 28 | if (support.includes(extension)) { 29 | reload({ 30 | file, 31 | error: null 32 | }) 33 | } else { 34 | reload({ 35 | file: null, 36 | error: 'This file is not yet supported!' 37 | }) 38 | } 39 | }) 40 | 41 | document.addEventListener('dragover', (e) => { 42 | e.preventDefault() 43 | e.stopPropagation() 44 | }) 45 | } 46 | 47 | // Initially 48 | handleDragDrop() 49 | reload() --------------------------------------------------------------------------------