Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add options for generating only/no rounded icons #129

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Icon management for Mobile Apps. Create icons, generate all required sizes, labe
* [Generating Icons](#generating-icons)
* [Labelling Icons](#labelling-icons)
* [Adaptive Icons](#adaptive-icons)
* [Rounded Icons on Android](#rounded-icons-on-android)
* [Developer Guide](#developer-guide)
* [Initial Setup](#initial-setup)
* [Running Tests](#running-tests)
Expand Down Expand Up @@ -180,6 +181,23 @@ To test how adaptive icons will look when animated, swiped, etc, the [Adaptive I

Note that Adaptive Icons of *all* supported sizes are generated. However, we also generate the `res/mipmap-anydpi-v26/` adaptive icon. This is a large size icon which Android from v26 onwards will automatically rescale as needed to all other sizes. This technically makes the density specific icons redundant. The reason we generate both is to ensure that after `generate` is run, *all* icons in the project will be consistent.

### Rounded Icons on Android

The default behaviour when generating icons is that both normal and rounded icons are generated from a single image. If you want to use different image files for normal and rounded icons, you can set the `--rounded` option to specify behaviour.

Setting `--rounded none` means that the tool will not generate any `ic_launcher_round.png` icons for Android. While setting `--rounded only` means that it will generate **only** `ic_launcher_round.png` icons.

In this way, you can generate "normal" and "rounded" icons from different image files.

Sample usage:
```bash
# generates all iOS icons and "normal" icons on Android:
app-icon generate -i icon.png --platforms=android,ios --rounded none

# generates just rounded icons on Android:
app-icon generate -i icon.round.png --platforms=android --rounded only
```

## Developer Guide

The only dependencies are Node 8 (or above) and Yarn.
Expand Down
3 changes: 3 additions & 0 deletions bin/app-icon.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ program
.option('--background-icon [optional]', "The background icon path. Defaults to 'icon.background.png'")
.option('--foreground-icon [optional]', "The foregroud icon path. Defaults to 'icon.foregroud.png'")
.option('--adaptive-icons [optional]', "Additionally, generate Android Adaptive Icon templates. Defaults to 'false'")
.option('--rounded [optional]', 'Filter options for rounded icons on Android, Possible values: [none, only]. No default, which means both normal and rounded icons are generated.')
.action(async (parameters) => {
const {
icon,
Expand All @@ -54,6 +55,7 @@ program
search,
platforms,
adaptiveIcons,
rounded,
} = parameters;

await imageMagickCheck();
Expand All @@ -75,6 +77,7 @@ program
searchRoot: search,
platforms,
adaptiveIcons,
rounded,
});
} catch (err) {
console.error(chalk.red(`An error occurred generating the icons: ${err.message}`));
Expand Down
17 changes: 15 additions & 2 deletions src/android/generate-manifest-icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const androidManifestIcons = require('./AndroidManifest.icons.json');
const resizeImage = require('../resize/resize-image');

// Generate Android Manifest icons given a manifest file.
module.exports = async function generateManifestIcons(sourceIcon, manifest) {
module.exports = async function generateManifestIcons(sourceIcon, manifest, rounded) {
// Create the object we will return.
const results = {
icons: [],
Expand All @@ -17,8 +17,21 @@ module.exports = async function generateManifestIcons(sourceIcon, manifest) {
// We've got the manifest file, get the parent folder.
const manifestFolder = path.dirname(manifest);

function filterRounded(icon) {
if (!rounded) return true;

switch (rounded) {
case 'only':
return icon.path.indexOf('ic_launcher_round.png') !== -1;
case 'none':
return icon.path.indexOf('ic_launcher_round.png') === -1;
default:
return true;
}
}

// Generate each image in the full icon set, updating the contents.
await Promise.all(androidManifestIcons.icons.map(async (icon) => {
await Promise.all(androidManifestIcons.icons.filter(filterRounded).map(async (icon) => {
const targetPath = path.join(manifestFolder, icon.path);

// Each icon lives in its own folder, so we'd better make sure that folder
Expand Down
36 changes: 36 additions & 0 deletions src/android/generate-manifest-icons.specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,42 @@ const testManifests = [{
describe('generate-manifest-icons', () => {
// Run each test.
testManifests.forEach(({ projectName, manifestPath }) => {
it(`should be able to specify only round icons for the ${projectName} manifest`, async () => {
// Get the manifest folder, create an array of every icon we expect to see.
const manifestFolder = path.dirname(manifestPath);
const resourceFolders = expectedFolders.map((f) => path.join(manifestFolder, f));
const resourceFoldersFiles = resourceFolders.reduce((allFiles, folder) => {
expectedFiles.forEach((ef) => allFiles.push(path.join(folder, ef)));
return allFiles;
}, []);

// Delete all of the folders we're expecting to create, then generate the icons.
await Promise.all(resourceFolders.map(deleteFolderIfExists));
await (generateManifestIcons(sourceIcon, manifestPath, 'only'));
const filesDoExist = await Promise.all(resourceFoldersFiles.map(fileExists));
filesDoExist.forEach((exists, index) => {
expect(exists, `${resourceFoldersFiles[index]} should be generated`).to.equal(index % 2 !== 0);
});
});

it(`should be able to specify none round icons for the ${projectName} manifest`, async () => {
// Get the manifest folder, create an array of every icon we expect to see.
const manifestFolder = path.dirname(manifestPath);
const resourceFolders = expectedFolders.map((f) => path.join(manifestFolder, f));
const resourceFoldersFiles = resourceFolders.reduce((allFiles, folder) => {
expectedFiles.forEach((ef) => allFiles.push(path.join(folder, ef)));
return allFiles;
}, []);

// Delete all of the folders we're expecting to create, then generate the icons.
await Promise.all(resourceFolders.map(deleteFolderIfExists));
await (generateManifestIcons(sourceIcon, manifestPath, 'none'));
const filesDoExist = await Promise.all(resourceFoldersFiles.map(fileExists));
filesDoExist.forEach((exists, index) => {
expect(exists, `${resourceFoldersFiles[index]} should be generated`).to.equal(index % 2 === 0);
});
});

it(`should be able to generate icons and round icons for the ${projectName} manifest`, async () => {
// Get the manifest folder, create an array of every icon we expect to see.
const manifestFolder = path.dirname(manifestPath);
Expand Down
3 changes: 2 additions & 1 deletion src/generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = async function generate(parameters) {
searchRoot,
platforms,
adaptiveIcons,
rounded,
} = validateParameters(parameters || {});

// Set up the results object.
Expand Down Expand Up @@ -43,7 +44,7 @@ module.exports = async function generate(parameters) {
if (!platforms.includes('android')) return null;
console.log(`Found Android Manifest: ${manifest}...`);

const manResult = await generateManifestIcons(sourceIcon, manifest);
const manResult = await generateManifestIcons(sourceIcon, manifest, rounded);
results.manifests.push({ manifest, icons: manResult.icons });
manResult.icons.forEach((icon) => {
console.log(` ${chalk.green('✓')} Generated icon ${icon}`);
Expand Down
6 changes: 6 additions & 0 deletions src/validate-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,18 @@ module.exports = function validateParameters(parameters) {
const backgroundIcon = parameters.backgroundIcon || 'icon.background.png';
const foregroundIcon = parameters.foregroundIcon || 'icon.foreground.png';

const { rounded } = parameters;
if (rounded && (rounded !== 'none' && rounded !== 'only')) {
throw new Error(`--rounded option '${rounded}' unrecognized, expected 'none' or 'only'.`);
}

return {
sourceIcon,
backgroundIcon,
foregroundIcon,
searchRoot,
platforms,
adaptiveIcons,
rounded,
};
};