@@ -4,6 +4,7 @@ use iced::widget::{
44use iced:: { Alignment , Center , Element , Fill , Task } ;
55use plume_utils:: { Package , PlistInfoTrait , SignerInstallMode , SignerMode , SignerOptions } ;
66use std:: path:: PathBuf ;
7+ use tiny_skia:: { FillRule , Mask , Path , PathBuilder , Transform } ;
78
89use crate :: appearance;
910
@@ -48,12 +49,12 @@ impl PackageScreen {
4849 let package_icon_handle = package
4950 . as_ref ( )
5051 . and_then ( |p| p. app_icon_data . as_ref ( ) )
51- . map ( |data| image :: Handle :: from_bytes ( data. clone ( ) ) ) ;
52+ . and_then ( |data| icon_handle_from_bytes ( data) ) ;
5253
5354 let custom_icon_path = options. custom_icon . clone ( ) ;
5455 let custom_icon_handle = custom_icon_path
5556 . as_ref ( )
56- . map ( |path| image :: Handle :: from_path ( path . clone ( ) ) ) ;
57+ . and_then ( icon_handle_from_path ) ;
5758
5859 Self {
5960 selected_package : package,
@@ -202,7 +203,7 @@ impl PackageScreen {
202203 if let Some ( path) = path {
203204 self . options . custom_icon = Some ( path. clone ( ) ) ;
204205 self . custom_icon_path = Some ( path. clone ( ) ) ;
205- self . custom_icon_handle = Some ( image :: Handle :: from_path ( path) ) ;
206+ self . custom_icon_handle = icon_handle_from_path ( & path) ;
206207 }
207208
208209 Task :: none ( )
@@ -449,22 +450,24 @@ impl PackageScreen {
449450 . align_y ( Center ) ;
450451
451452 let preview: Element < ' _ , Message > = if let Some ( handle) = & self . custom_icon_handle {
452- stack ! [
453+ container ( stack ! [
453454 loading_indicator,
454- image( handle. clone( ) )
455- . width( ICON_SIZE )
456- . height( ICON_SIZE )
457- . border_radius( appearance:: THEME_CORNER_RADIUS )
458- ]
455+ image( handle. clone( ) ) . width( ICON_SIZE ) . height( ICON_SIZE )
456+ ] )
457+ . width ( ICON_SIZE )
458+ . height ( ICON_SIZE )
459+ . align_x ( Center )
460+ . align_y ( Center )
459461 . into ( )
460462 } else if let Some ( handle) = & self . package_icon_handle {
461- stack ! [
463+ container ( stack ! [
462464 loading_indicator,
463- image( handle. clone( ) )
464- . width( ICON_SIZE )
465- . height( ICON_SIZE )
466- . border_radius( appearance:: THEME_CORNER_RADIUS )
467- ]
465+ image( handle. clone( ) ) . width( ICON_SIZE ) . height( ICON_SIZE )
466+ ] )
467+ . width ( ICON_SIZE )
468+ . height ( ICON_SIZE )
469+ . align_x ( Center )
470+ . align_y ( Center )
468471 . into ( )
469472 } else {
470473 container ( text ( "No icon" ) . size ( 11 ) )
@@ -520,3 +523,113 @@ impl PackageScreen {
520523 }
521524 }
522525}
526+
527+ const IOS_ICON_CORNER_RADIUS_FACTOR : f32 = 0.225 ;
528+ const IOS_ICON_EDGE : f32 = 1.528_665 ;
529+ const IOS_ICON_SHOULDER : f32 = 0.631_493_8 ;
530+ const IOS_ICON_KNEE : f32 = 0.074_911_39 ;
531+ const IOS_ICON_CTRL_EDGE : f32 = 1.088_493 ;
532+ const IOS_ICON_CTRL_SHOULDER : f32 = 0.868_406_95 ;
533+ const IOS_ICON_CTRL_CURVE_OUTER : f32 = 0.372_823_83 ;
534+ const IOS_ICON_CTRL_CURVE_INNER : f32 = 0.169_059_56 ;
535+
536+ fn icon_handle_from_bytes ( data : & [ u8 ] ) -> Option < image:: Handle > {
537+ icon_handle_from_image ( :: image:: load_from_memory ( data) . ok ( ) ?)
538+ }
539+
540+ fn icon_handle_from_path ( path : & PathBuf ) -> Option < image:: Handle > {
541+ icon_handle_from_image ( :: image:: open ( path) . ok ( ) ?)
542+ }
543+
544+ fn icon_handle_from_image ( image : :: image:: DynamicImage ) -> Option < image:: Handle > {
545+ let mut pixels = image. to_rgba8 ( ) ;
546+ let ( width, height) = pixels. dimensions ( ) ;
547+ let mut mask = Mask :: new ( width, height) ?;
548+ mask. fill_path (
549+ & ios_icon_mask ( width as f32 , height as f32 ) ?,
550+ FillRule :: Winding ,
551+ true ,
552+ Transform :: identity ( ) ,
553+ ) ;
554+
555+ for ( pixel, coverage) in pixels. chunks_exact_mut ( 4 ) . zip ( mask. data ( ) ) {
556+ pixel[ 3 ] = ( ( u16:: from ( pixel[ 3 ] ) * u16:: from ( * coverage) + 127 ) / 255 ) as u8 ;
557+ }
558+
559+ Some ( image:: Handle :: from_rgba ( width, height, pixels. into_raw ( ) ) )
560+ }
561+
562+ fn ios_icon_mask ( width : f32 , height : f32 ) -> Option < Path > {
563+ let radius = width. min ( height) * IOS_ICON_CORNER_RADIUS_FACTOR ;
564+ let mut path = PathBuilder :: new ( ) ;
565+ let tl = |x : f32 , y : f32 | ( x * radius, y * radius) ;
566+ let tr = |x : f32 , y : f32 | ( width - x * radius, y * radius) ;
567+ let br = |x : f32 , y : f32 | ( width - x * radius, height - y * radius) ;
568+ let bl = |x : f32 , y : f32 | ( x * radius, height - y * radius) ;
569+
570+ let ( x, y) = tl ( IOS_ICON_EDGE , 0.0 ) ;
571+ path. move_to ( x, y) ;
572+
573+ let ( x, y) = tr ( IOS_ICON_EDGE , 0.0 ) ;
574+ path. line_to ( x, y) ;
575+ let ( x1, y1) = tr ( IOS_ICON_CTRL_EDGE , 0.0 ) ;
576+ let ( x2, y2) = tr ( IOS_ICON_CTRL_SHOULDER , 0.0 ) ;
577+ let ( x, y) = tr ( IOS_ICON_SHOULDER , IOS_ICON_KNEE ) ;
578+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
579+ let ( x1, y1) = tr ( IOS_ICON_CTRL_CURVE_OUTER , IOS_ICON_CTRL_CURVE_INNER ) ;
580+ let ( x2, y2) = tr ( IOS_ICON_CTRL_CURVE_INNER , IOS_ICON_CTRL_CURVE_OUTER ) ;
581+ let ( x, y) = tr ( IOS_ICON_KNEE , IOS_ICON_SHOULDER ) ;
582+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
583+ let ( x1, y1) = tr ( 0.0 , IOS_ICON_CTRL_SHOULDER ) ;
584+ let ( x2, y2) = tr ( 0.0 , IOS_ICON_CTRL_EDGE ) ;
585+ let ( x, y) = tr ( 0.0 , IOS_ICON_EDGE ) ;
586+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
587+
588+ let ( x, y) = br ( 0.0 , IOS_ICON_EDGE ) ;
589+ path. line_to ( x, y) ;
590+ let ( x1, y1) = br ( 0.0 , IOS_ICON_CTRL_EDGE ) ;
591+ let ( x2, y2) = br ( 0.0 , IOS_ICON_CTRL_SHOULDER ) ;
592+ let ( x, y) = br ( IOS_ICON_KNEE , IOS_ICON_SHOULDER ) ;
593+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
594+ let ( x1, y1) = br ( IOS_ICON_CTRL_CURVE_INNER , IOS_ICON_CTRL_CURVE_OUTER ) ;
595+ let ( x2, y2) = br ( IOS_ICON_CTRL_CURVE_OUTER , IOS_ICON_CTRL_CURVE_INNER ) ;
596+ let ( x, y) = br ( IOS_ICON_SHOULDER , IOS_ICON_KNEE ) ;
597+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
598+ let ( x1, y1) = br ( IOS_ICON_CTRL_SHOULDER , 0.0 ) ;
599+ let ( x2, y2) = br ( IOS_ICON_CTRL_EDGE , 0.0 ) ;
600+ let ( x, y) = br ( IOS_ICON_EDGE , 0.0 ) ;
601+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
602+
603+ let ( x, y) = bl ( IOS_ICON_EDGE , 0.0 ) ;
604+ path. line_to ( x, y) ;
605+ let ( x1, y1) = bl ( IOS_ICON_CTRL_EDGE , 0.0 ) ;
606+ let ( x2, y2) = bl ( IOS_ICON_CTRL_SHOULDER , 0.0 ) ;
607+ let ( x, y) = bl ( IOS_ICON_SHOULDER , IOS_ICON_KNEE ) ;
608+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
609+ let ( x1, y1) = bl ( IOS_ICON_CTRL_CURVE_OUTER , IOS_ICON_CTRL_CURVE_INNER ) ;
610+ let ( x2, y2) = bl ( IOS_ICON_CTRL_CURVE_INNER , IOS_ICON_CTRL_CURVE_OUTER ) ;
611+ let ( x, y) = bl ( IOS_ICON_KNEE , IOS_ICON_SHOULDER ) ;
612+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
613+ let ( x1, y1) = bl ( 0.0 , IOS_ICON_CTRL_SHOULDER ) ;
614+ let ( x2, y2) = bl ( 0.0 , IOS_ICON_CTRL_EDGE ) ;
615+ let ( x, y) = bl ( 0.0 , IOS_ICON_EDGE ) ;
616+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
617+
618+ let ( x, y) = tl ( 0.0 , IOS_ICON_EDGE ) ;
619+ path. line_to ( x, y) ;
620+ let ( x1, y1) = tl ( 0.0 , IOS_ICON_CTRL_EDGE ) ;
621+ let ( x2, y2) = tl ( 0.0 , IOS_ICON_CTRL_SHOULDER ) ;
622+ let ( x, y) = tl ( IOS_ICON_KNEE , IOS_ICON_SHOULDER ) ;
623+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
624+ let ( x1, y1) = tl ( IOS_ICON_CTRL_CURVE_INNER , IOS_ICON_CTRL_CURVE_OUTER ) ;
625+ let ( x2, y2) = tl ( IOS_ICON_CTRL_CURVE_OUTER , IOS_ICON_CTRL_CURVE_INNER ) ;
626+ let ( x, y) = tl ( IOS_ICON_SHOULDER , IOS_ICON_KNEE ) ;
627+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
628+ let ( x1, y1) = tl ( IOS_ICON_CTRL_SHOULDER , 0.0 ) ;
629+ let ( x2, y2) = tl ( IOS_ICON_CTRL_EDGE , 0.0 ) ;
630+ let ( x, y) = tl ( IOS_ICON_EDGE , 0.0 ) ;
631+ path. cubic_to ( x1, y1, x2, y2, x, y) ;
632+
633+ path. close ( ) ;
634+ path. finish ( )
635+ }
0 commit comments