From 82a580ec4585e96ea31fe0f36f3238e34d46e599 Mon Sep 17 00:00:00 2001 From: nothingismagick Date: Mon, 3 May 2021 00:02:58 +0200 Subject: [PATCH] feat(tauricon): fix transparency (#1678) Co-authored-by: Lucas Nogueira --- .changes/tauri-icon-fix.md | 10 +++++ tooling/cli.js/bin/tauri-icon.js | 3 +- tooling/cli.js/package.json | 1 - tooling/cli.js/src/api/tauricon.ts | 37 +++++++++++++----- tooling/cli.js/src/helpers/tauricon.config.ts | 14 +------ .../test/jest/__tests__/tauricon.spec.js | 28 ++++++------- .../cli.js/test/jest/fixtures/no-alpha.png | Bin 0 -> 863 bytes tooling/cli.js/yarn.lock | 33 ---------------- 8 files changed, 54 insertions(+), 72 deletions(-) create mode 100644 .changes/tauri-icon-fix.md create mode 100644 tooling/cli.js/test/jest/fixtures/no-alpha.png diff --git a/.changes/tauri-icon-fix.md b/.changes/tauri-icon-fix.md new file mode 100644 index 000000000..799322e24 --- /dev/null +++ b/.changes/tauri-icon-fix.md @@ -0,0 +1,10 @@ +--- +"cli.js": patch +--- + +Updates to `tauri icon` +- detect if icon is NOT transparent via metadata +- detect again on pixel level +- remove pngquant as a waste of space +- make optipng default +- relax optipng settings diff --git a/tooling/cli.js/bin/tauri-icon.js b/tooling/cli.js/bin/tauri-icon.js index dadcfc3a3..e533f1f03 100644 --- a/tooling/cli.js/bin/tauri-icon.js +++ b/tooling/cli.js/bin/tauri-icon.js @@ -43,7 +43,8 @@ if (argv.help) { --help, -h Displays this message --log, l Logging [boolean] --target, t Target folder (default: 'src-tauri/icons') - --compression, c Compression type [pngquant|optipng|zopfli] + --compression, c Compression type [optipng|zopfli] + --ci Runs the script in CI mode `) process.exit(0) } diff --git a/tooling/cli.js/package.json b/tooling/cli.js/package.json index ffdb831b6..f2fcd07fb 100644 --- a/tooling/cli.js/package.json +++ b/tooling/cli.js/package.json @@ -58,7 +58,6 @@ "got": "11.8.2", "imagemin": "8.0.0", "imagemin-optipng": "8.0.0", - "imagemin-pngquant": "9.0.2", "imagemin-zopfli": "7.0.0", "inquirer": "8.0.0", "is-png": "3.0.0", diff --git a/tooling/cli.js/src/api/tauricon.ts b/tooling/cli.js/src/api/tauricon.ts index bac5dc6a5..d8e54e176 100644 --- a/tooling/cli.js/src/api/tauricon.ts +++ b/tooling/cli.js/src/api/tauricon.ts @@ -21,7 +21,6 @@ import { access, ensureDir, ensureFileSync, writeFileSync } from 'fs-extra' import imagemin, { Plugin } from 'imagemin' import optipng from 'imagemin-optipng' -import pngquant, { Options as PngQuantOptions } from 'imagemin-pngquant' import zopfli from 'imagemin-zopfli' import isPng from 'is-png' import path from 'path' @@ -38,7 +37,7 @@ const log = logger('app:spawn') const warn = logger('app:spawn', chalk.red) let image: boolean | sharp.Sharp = false -const spinnerInterval = false +let spinnerInterval: NodeJS.Timeout | null = null const exists = async function (file: string | Buffer): Promise { try { @@ -70,7 +69,26 @@ const checkSrc = async (src: string): Promise => { } else { const buffer = await readChunk(src, 0, 8) if (isPng(buffer)) { - return (image = sharp(src)) + image = sharp(src) + const meta = await image.metadata() + if (!meta.hasAlpha || meta.channels !== 4) { + if (spinnerInterval) clearInterval(spinnerInterval) + warn('[ERROR] Source png for tauricon is not transparent') + process.exit(1) + } + + // just because PNG is sneaky, lets look at the + // individual pixels for something weird + const stats = await image.stats() + if (stats.isOpaque) { + if (spinnerInterval) clearInterval(spinnerInterval) + warn( + '[ERROR] Source png for tauricon could not be detected as transparent' + ) + process.exit(1) + } + + return image } else { image = false if (spinnerInterval) clearInterval(spinnerInterval) @@ -164,7 +182,10 @@ const progress = (msg: string): void => { * // later * clearInterval(spinnerInterval) */ -const spinner = (): NodeJS.Timeout => { +const spinner = (): NodeJS.Timeout | null => { + if ('CI' in process.env || process.argv.some((arg) => arg === '--ci')) { + return null + } return setInterval(() => { process.stdout.write('/ \r') setTimeout(() => { @@ -197,7 +218,7 @@ const tauricon = (exports.tauricon = { if (!src) { src = path.resolve(appDir, 'app-icon.png') } - const spinnerInterval = spinner() + spinnerInterval = spinner() options = options || settings.options.tauri progress(`Building Tauri icns and ico from "${src}"`) await this.validate(src, target) @@ -211,7 +232,7 @@ const tauricon = (exports.tauricon = { log('no minify strategy') } progress('Tauricon Finished') - clearInterval(spinnerInterval) + if (spinnerInterval) clearInterval(spinnerInterval) return true }, @@ -403,10 +424,6 @@ const tauricon = (exports.tauricon = { strategy = minify.type } switch (strategy) { - case 'pngquant': - // TODO: is minify.pngquantOptions the proper format? - cmd = pngquant((minify.pngquantOptions as any) as PngQuantOptions) - break case 'optipng': cmd = optipng(minify.optipngOptions) break diff --git a/tooling/cli.js/src/helpers/tauricon.config.ts b/tooling/cli.js/src/helpers/tauricon.config.ts index 4af0f2d95..5bd0fa23b 100644 --- a/tooling/cli.js/src/helpers/tauricon.config.ts +++ b/tooling/cli.js/src/helpers/tauricon.config.ts @@ -14,20 +14,10 @@ export const options = { minify: { batch: false, overwrite: true, - available: ['pngquant', 'optipng', 'zopfli'], - type: 'pngquant', - pngcrushOptions: { - reduce: true - }, - pngquantOptions: { - quality: [0.6, 0.8], - floyd: 0.1, // 0.1 - 1 - speed: 10 // 1 - 10 - }, + available: ['optipng', 'zopfli'], + type: 'optipng', optipngOptions: { optimizationLevel: 4, - bitDepthReduction: true, - colorTypeReduction: true, paletteReduction: true }, zopfliOptions: { diff --git a/tooling/cli.js/test/jest/__tests__/tauricon.spec.js b/tooling/cli.js/test/jest/__tests__/tauricon.spec.js index 5fe77fea3..3216b4520 100644 --- a/tooling/cli.js/test/jest/__tests__/tauricon.spec.js +++ b/tooling/cli.js/test/jest/__tests__/tauricon.spec.js @@ -27,6 +27,17 @@ describe('[CLI] tauri-icon internals', () => { expect(process.exit.mock.calls[0][0]).toBe(1) jest.clearAllMocks() }) + + it('should fail if PNG does not have transparency', async () => { + jest.spyOn(process, 'exit').mockImplementation(() => true) + await tauricon.validate( + 'test/jest/fixtures/no-alpha.png', + 'test/jest/fixtures/' + ) + expect(process.exit.mock.calls[0][0]).toBe(1) + jest.clearAllMocks() + }) + it('can validate an image as PNG', async () => { const valid = await tauricon.validate( 'test/jest/fixtures/tauri-logo.png', @@ -45,31 +56,18 @@ describe('[CLI] tauri-icon builder', () => { ) expect(valid).toBe(true) }) -}) -describe('[CLI] tauri-icon builder', () => { it('will not validate a non-file', async () => { try { await tauricon.make( 'test/jest/fixtures/tauri-foo-not-found.png', - 'test/jest/tmp/pngquant', - 'pngquant' + 'test/jest/tmp/optipng', + 'optipng' ) } catch (e) { expect(e.message).toBe('Input file is missing') } }) -}) - -describe('[CLI] tauri-icon builder', () => { - it('makes a set of icons with pngquant', async () => { - const valid = await tauricon.make( - 'test/jest/fixtures/tauri-logo.png', - 'test/jest/tmp/pngquant', - 'pngquant' - ) - expect(valid).toBe(true) - }) it('makes a set of icons with optipng', async () => { const valid = await tauricon.make( diff --git a/tooling/cli.js/test/jest/fixtures/no-alpha.png b/tooling/cli.js/test/jest/fixtures/no-alpha.png new file mode 100644 index 0000000000000000000000000000000000000000..99089dce58d72654fd6e57e371b234524880e6da GIT binary patch literal 863 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(AjMhW5n0T@pr;JNj1^1m%NQ7# z1wCCHLn`LHJ=h3T!EkuN&*1on455r5&L|iSfe{x12lAP=Ffc}n_`U~u-P6_2Wt~$( F69Dv>7qS2V literal 0 HcmV?d00001 diff --git a/tooling/cli.js/yarn.lock b/tooling/cli.js/yarn.lock index 52f829293..d7eec0666 100644 --- a/tooling/cli.js/yarn.lock +++ b/tooling/cli.js/yarn.lock @@ -4368,17 +4368,6 @@ imagemin-optipng@8.0.0: is-png "^2.0.0" optipng-bin "^7.0.0" -imagemin-pngquant@9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/imagemin-pngquant/-/imagemin-pngquant-9.0.2.tgz#38155702b0cc4f60f671ba7c2b086ea3805d9567" - integrity sha512-cj//bKo8+Frd/DM8l6Pg9pws1pnDUjgb7ae++sUX1kUVdv2nrngPykhiUOgFeE0LGY/LmUbCf4egCHC4YUcZSg== - dependencies: - execa "^4.0.0" - is-png "^2.0.0" - is-stream "^2.0.0" - ow "^0.17.0" - pngquant-bin "^6.0.0" - imagemin-zopfli@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/imagemin-zopfli/-/imagemin-zopfli-7.0.0.tgz#a44daa3bb80e2620cd1dc883d823b20b4d3788d6" @@ -6071,13 +6060,6 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= -ow@^0.17.0: - version "0.17.0" - resolved "https://registry.yarnpkg.com/ow/-/ow-0.17.0.tgz#4f938999fed6264c9048cd6254356e0f1e7f688c" - integrity sha512-i3keDzDQP5lWIe4oODyDFey1qVrq2hXKTuTH2VpqwpYtzPiKZt2ziRI4NBQmgW40AnV5Euz17OyWweCb+bNEQA== - dependencies: - type-fest "^0.11.0" - p-cancelable@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" @@ -6378,16 +6360,6 @@ png2icons@2.0.1: resolved "https://registry.yarnpkg.com/png2icons/-/png2icons-2.0.1.tgz#09d8f10b71302e98ca178d3324bc4deff9b90124" integrity sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA== -pngquant-bin@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/pngquant-bin/-/pngquant-bin-6.0.0.tgz#aff0d7e61095feb96ced379ad8c7294ad3dd1712" - integrity sha512-oXWAS9MQ9iiDAJRdAZ9KO1mC5UwhzKkJsmetiu0iqIjJuW7JsuLhmc4JdRm7uJkIWRzIAou/Vq2VcjfJwz30Ow== - dependencies: - bin-build "^3.0.0" - bin-wrapper "^4.0.1" - execa "^4.0.0" - logalot "^2.0.0" - posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" @@ -7827,11 +7799,6 @@ type-detect@4.0.8: resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== -type-fest@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" - integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"