beeware / briefcase

Tools to support converting a Python project into a standalone native application.
https://briefcase.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
2.67k stars 371 forks source link

Backfill icons from provided images #386

Open freakboy3742 opened 4 years ago

freakboy3742 commented 4 years ago

Briefcase currently requires that the user provide icons of in various formats. However, if you have a high-res PNG format, any other format can be generated by image conversion.

Briefcase should try to use a correctly named file in the right size and format if it's available; but if it isn't, it should try to backfill by converting the high-res PNG to the needed size and format. This should be clearly logged so the end user knows what has happened.

Pillow can be used for most of these operations, but Pillow doesn't currently support ICNS; however, macOS ships with a tool to generate ICNS files from PNGs.

Creating an ICNS file:

Into a directory named myicon.iconset, put files with the following names and sizes:

A 16x16@2x icon is 32x32 pixels; so yes - you need to have 2 copies of the same 32x32 icon (if you're paying close attention to the details you can specify a different icon; however, if you're autoscaling from other sizes, you're not paying that sort of attention).

Then, invoke: iconutil -c icns myicon.iconset. This will produce myicon.icns.

Andrei-Pozolotin commented 3 years ago

pillow now knows about iconutil

https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#icns

https://github.com/python-pillow/Pillow/blob/master/src/PIL/IcnsImagePlugin.py#L306

def _save(im, fp, filename):
    """
    Saves the image as a series of PNG files,
    that are then converted to a .icns file
    using the macOS command line utility 'iconutil'.
    macOS only.
    """
Andrei-Pozolotin commented 3 years ago

and linux has png2icns / icns2png

https://icns.sourceforge.io/

https://archlinux.org/packages/community/x86_64/libicns/ ``` usr/ usr/bin/ usr/bin/icns2png usr/bin/icontainer2icns usr/bin/png2icns usr/include/ usr/include/icns.h usr/lib/ usr/lib/libicns.so usr/lib/libicns.so.1 usr/lib/libicns.so.1.2.0 usr/lib/pkgconfig/ usr/lib/pkgconfig/libicns.pc usr/share/ usr/share/man/ usr/share/man/man1/ usr/share/man/man1/icns2png.1.gz usr/share/man/man1/icontainer2icns.1.gz usr/share/man/man1/png2icns.1.gz ```
Andrei-Pozolotin commented 3 years ago

an easy solution could be to include into the default app template a linux script which produces images for all platforms, for example:

```sh #!/usr/bin/env bash set -e -u image_source=${1:-arkon.png} image_target=${2:-tasker} image_folder=${3:-.} echo "### master image: $image_source" echo "### image prefix: $image_target" echo "### image folder: $image_folder" # hash convert || { echo "missing imagemagick/convert" ; exit 1 ; } hash png2icns || { echo "missing libicns/png2icns" ; exit 1 ; } [ -f ${image_source} ] || { echo "missing image_source: ${image_source}" ; exit 1 ; } [ -d ${image_folder} ] || mkdir -p ${image_folder} # proper_spec_list=( "24x1" "48x1" "64x1" "96x1" "128x1" "20x1" "20x2" "20x3" "29x1" "29x2" "29x3" "40x1" "40x2" "40x3" "60x2" "60x3" "76x1" "76x2" "83.5x2" "1024x1" ) convert_proper() { for image_spec in ${proper_spec_list[*]} ; do size=${image_spec%x*} scale=${image_spec##*x} resize=$(bc <<< ${size}*${scale} ) echo "### apply ${image_source} spec: ${size}x${size}@${scale}" convert ${image_source} \ -resize ${resize}x${resize} \ -unsharp '1.5x1+0.7+0.02' \ ${image_folder}/${image_target}-${size}x${size}@${scale}x.png done } android_spec_list=( 48 72 96 144 192 ) convert_android() { convert_android_round convert_android_square } convert_android_round() { file_type="round" for image_spec in ${android_spec_list[*]} ; do size=${image_spec} scale=1 resize=$(bc <<< ${size}*${scale} ) corner=$(bc <<< ${size}*0.5 ) # 50% file_name="${image_target}-${file_type}-${size}.png" file_path="${image_folder}/${file_name}" mask_path="${image_folder}/round-mask.png" echo "### apply/android spec: ${size}x${size}@${scale} file: $file_name" # produce mask convert -size ${resize}x${resize} xc:none \ -draw "roundrectangle 0,0,${resize},${resize},${corner},${corner}" \ ${mask_path} # produce resize convert ${image_source} \ -resize ${resize}x${resize} \ -unsharp '1.5x1+0.7+0.02' \ ${file_path} # produce composite convert ${file_path} \ -matte ${mask_path} \ -compose DstIn -composite \ ${file_path} # destroy mask rm ${mask_path} done } convert_android_square() { file_type="square" for image_spec in ${android_spec_list[*]} ; do size=${image_spec} scale=1 resize=$(bc <<< ${size}*${scale} ) file_name="${image_target}-${file_type}-${size}.png" file_path="${image_folder}/${file_name}" echo "### apply/android spec: ${size}x${size}@${scale} file: $file_name" convert ${image_source} \ -resize ${resize}x${resize} \ -unsharp '1.5x1+0.7+0.02' \ ${file_path} done } ios_spec_list=( 20 29 40 58 60 76 80 87 120 152 167 180 1024 ) convert_ios() { for image_spec in ${ios_spec_list[*]} ; do size=${image_spec} scale=1 resize=$(bc <<< ${size}*${scale} ) file_name="${image_target}-${size}.png" file_path="${image_folder}/${file_name}" echo "### apply/ios spec: ${size}x${size}@${scale} file: $file_name" convert ${image_source} \ -resize ${resize}x${resize} \ -unsharp '1.5x1+0.7+0.02' \ ${file_path} done } macos_spec_list=( 16 32 48 128 256 512 ) convert_macos() { file_list="" for image_spec in ${macos_spec_list[*]} ; do size=${image_spec} scale=1 resize=$(bc <<< ${size}*${scale} ) file_name="${image_target}-${size}.png" file_path="${image_folder}/${file_name}" echo "### apply/macos spec: ${size}x${size}@${scale} file: $file_name" convert ${image_source} \ -resize ${resize}x${resize} \ -unsharp '1.5x1+0.7+0.02' \ ${file_path} file_list="${file_list} ${file_path}" done png2icns "${image_target}.icns" ${file_list[@]} } windows_spec_list="16,24,32,48,64,72,96,128,256" convert_windows() { echo "### apply/windows spec: ${windows_spec_list}" convert ${image_source} \ -background transparent \ -define icon:auto-resize=${windows_spec_list} \ "${image_folder}/${image_target}.ico" } convert_default() { size="256" echo "### apply/default size: ${size}" src="${image_target}-256.png" dst="${image_target}.png" cp ${src} ${dst} } convert_android convert_ios convert_macos convert_windows convert_default ```
eltoro0815 commented 5 months ago

The actual beeware version expects different files. I added some some lines of code. Feel free to remove stuff which is no longer needed ;)

To update the icons for your android app, you have to:

briefcase create android
briefcase build android
briefcase run android
#!/usr/bin/env bash

set -e -u

image_source=${1:-thor_edit.png}
image_target=${2:-daciaspringstatus}
image_folder=${3:-.}

echo "### master image: $image_source"
echo "### image prefix: $image_target"
echo "### image folder: $image_folder"

#
hash convert ||  { echo "missing imagemagick/convert" ; exit 1 ; }
hash png2icns ||  { echo "missing libicns/png2icns" ; exit 1 ; }
[ -f ${image_source} ] ||  { echo "missing image_source: ${image_source}" ; exit 1 ; }

[ -d ${image_folder} ] || mkdir -p ${image_folder}

#
proper_spec_list=(
   "24x1" "48x1" "64x1" "96x1" "128x1"
   "20x1" "20x2" "20x3"
   "29x1" "29x2" "29x3"
   "40x1" "40x2" "40x3"
   "60x2" "60x3"
   "76x1" "76x2"
   "83.5x2" "1024x1"
)

convert_proper() {
    for image_spec in ${proper_spec_list[*]} ; do
       size=${image_spec%x*}
       scale=${image_spec##*x}
       resize=$(bc <<< ${size}*${scale} )
       echo "### apply ${image_source} spec: ${size}x${size}@${scale}"
       convert ${image_source} \
          -resize ${resize}x${resize} \
          -unsharp '1.5x1+0.7+0.02' \
          ${image_folder}/${image_target}-${size}x${size}@${scale}x.png
    done
}

android_spec_list=(
    48 72 96 144 192 320 480 640 960 1280
)

convert_android() {
    convert_android_round
    convert_android_square
}

convert_android_round() {
    file_type="round"
    for image_spec in ${android_spec_list[*]} ; do
       size=${image_spec}
       scale=1
       resize=$(bc <<< ${size}*${scale} )
       corner=$(bc <<< ${size}*0.5 ) # 50%
       file_name="${image_target}-${file_type}-${size}.png"
       file_path="${image_folder}/${file_name}"
       mask_path="${image_folder}/round-mask.png"
       echo "### apply/android spec: ${size}x${size}@${scale} file: $file_name"
       # produce mask
       convert -size ${resize}x${resize} xc:none \
          -draw "roundrectangle 0,0,${resize},${resize},${corner},${corner}" \
          ${mask_path}
       # produce resize
       convert ${image_source} \
          -resize ${resize}x${resize} \
          -unsharp '1.5x1+0.7+0.02' \
          ${file_path}
       # produce composite
       convert ${file_path} \
          -matte ${mask_path} \
          -compose DstIn -composite \
          ${file_path}
       # destroy mask
       rm ${mask_path}
    done
}

convert_android_square() {
    file_type="square"
    for image_spec in ${android_spec_list[*]} ; do
       size=${image_spec}
       scale=1
       resize=$(bc <<< ${size}*${scale} )
       file_name="${image_target}-${file_type}-${size}.png"
       file_path="${image_folder}/${file_name}"
       echo "### apply/android spec: ${size}x${size}@${scale} file: $file_name"
       convert ${image_source} \
          -resize ${resize}x${resize} \
          -unsharp '1.5x1+0.7+0.02' \
          ${file_path}
    done
}

ios_spec_list=(
    20 29 40 58 60 76 80 87 120 152 167 180 1024
)

convert_ios() {
    for image_spec in ${ios_spec_list[*]} ; do
       size=${image_spec}
       scale=1
       resize=$(bc <<< ${size}*${scale} )
       file_name="${image_target}-${size}.png"
       file_path="${image_folder}/${file_name}"
       echo "### apply/ios spec: ${size}x${size}@${scale} file: $file_name"
       convert ${image_source} \
          -resize ${resize}x${resize} \
          -unsharp '1.5x1+0.7+0.02' \
          ${file_path}
    done
}

macos_spec_list=(
    16 32 48 128 256 512
)

convert_macos() {
    file_list=""
    for image_spec in ${macos_spec_list[*]} ; do
       size=${image_spec}
       scale=1
       resize=$(bc <<< ${size}*${scale} )
       file_name="${image_target}-${size}.png"
       file_path="${image_folder}/${file_name}"
       echo "### apply/macos spec: ${size}x${size}@${scale} file: $file_name"
       convert ${image_source} \
          -resize ${resize}x${resize} \
          -unsharp '1.5x1+0.7+0.02' \
          ${file_path}
       file_list="${file_list} ${file_path}"
    done
    png2icns "${image_target}.icns" ${file_list[@]}
}

windows_spec_list="16,24,32,48,64,72,96,128,256"

convert_windows() {
    echo "### apply/windows spec: ${windows_spec_list}"
    convert ${image_source} \
        -background transparent \
        -define icon:auto-resize=${windows_spec_list} \
        "${image_folder}/${image_target}.ico"
}

convert_default() {
    size="256"
    echo "### apply/default size: ${size}"
    src="${image_target}-256.png"
    dst="${image_target}.png"
    cp ${src} ${dst}
}

adaptive_spec_list=(
    108 162 216 324 432
)

convert_adaptive() {
    file_type="adaptive"
    for image_spec in ${adaptive_spec_list[*]} ; do
       size=${image_spec}
       scale=1
       resize=$(bc <<< ${size}*${scale} )
       file_name="${image_target}-${file_type}-${size}.png"
       file_path="${image_folder}/${file_name}"
       echo "### apply/adaptive spec: ${size}x${size}@${scale} file: $file_name"
       convert ${image_source} \
          -resize ${resize}x${resize} \
          -unsharp '1.5x1+0.7+0.02' \
          ${file_path}
    done
}

convert_android

convert_ios

convert_macos

convert_windows

convert_default

convert_adaptive