Posted to tcl by FLORENTIS at Wed Aug 26 00:09:56 GMT 2026view raw
- # It's allways better to not change what you have paste...
- # Adapted from "https://wiki.tcl-lang.org/page/HSV+colorPicker"
- # To be executed with tip-759 branch
-
- catch {namespace delete ::gColorNS}
- namespace eval ::gColorNS {
- # Variables du namespace
- variable canvas_size 300
- variable radius [($canvas_size / 2.1)]
- variable ring_thickness [($radius / 8)]
- variable cx [($canvas_size / 2)]
- variable cy [($canvas_size / 2)]
- variable triangle_coords {}
- variable triangle_id
- variable hue_marker_id
- variable sv_marker_id
- variable cumulative_angle 0
- variable current_saturation 0.5
- variable current_value 0.5
- variable current_hue 0
- variable hue_marker_coords {}
- variable hue_ring_image
- variable active_tag ""
- variable canvas {}
- variable info_frame {}
- variable info_bg {}
- variable last_update_time 0
- variable update_threshold_ms 30
- variable pending_update_after_id 0
- variable rgbvar {}
- variable hexvar {}
-
- # Fonctions de conversion de coordonnées et couleurs
- proc ::tcl::mathfunc::polar_to_cartesian {radius angle cx cy} {(
- radian = $angle * acos(-1) / 180;
- x = $cx + $radius * cos($radian);
- y = $cy - $radius * sin($radian);
- [format "%.5f" $x], [format "%.5f" $y]
- )}
-
- proc hsv_to_rgb {h s v} {
- # "Muted" arithmetic substitution : script to set variables
- [( h = fmod($h, 360.0);
- c = $v*$s;
- x = $c*(1 - abs(fmod($h / 60.0, 2) - 1));
- m = $v-$c )]
-
- # Inlined arithmetic substitution : complex test to assign list of variables
- lassign [(
- $h < 60 ? ($c, $x, 0)
- : $h < 120 ? ($x, $c, 0)
- : $h < 180 ? (0, $c, $x)
- : $h < 240 ? (0, $x, $c)
- : $h < 300 ? ($x, 0, $c)
- : ($c, 0, $x)
- )] r g b
-
- # Inlined arithmetic substitution : to return a list of value
- [( round(($r + $m)*255), round(($g + $m)*255), round(($b + $m)*255) )]
- }
-
- proc rgb_to_hsv {r g b} {(
- r = $r / 255.0;
- g = $g / 255.0;
- b = $b / 255.0;
-
- cmax = max($r, max($g, $b));
- cmin = min($r, min($g, $b));
- diff = $cmax - $cmin;
-
- abs($diff) < 0.00001 ? (h = 0)
- : abs($cmax - $r) < 0.00001 ? (h = 60 * fmod(($g - $b)/$diff, 6))
- : abs($cmax - $g) < 0.00001 ? (h = 60 * (($b - $r)/$diff + 2))
- : (h = 60 * (($r - $g)/$diff + 4));
-
- $h < 0 ? (h = $h + 360):;
-
- $cmax == 0 ? (s = 0) : (s = $diff/$cmax);
-
- ($h,$s,$cmax)
- )}
-
- #geometry functions
- proc point_in_triangle {px py t1 t2 t3} {(
- [lassign $t1 x1 y1
- lassign $t2 x2 y2
- lassign $t3 x3 y3];
-
- (denom = ($y2-$y3)*($x1-$x3) + ($x3-$x2)*($y1-$y3)) == 0 ? [return 0] :;
-
- a = (($y2-$y3)*($px-$x3)+($x3-$x2)*($py-$y3)) / $denom;
- b = (($y3-$y1)*($px-$x3)+($x1-$x3)*($py-$y3)) / $denom;
- c = 1.0 - $a - $b;
-
- $a >= 0 && $b >= 0 && $c >= 0
- )}
-
- proc calculate_edge_distance {px py t1 t2 t3} {
- [( edges = (($t1,$t2),($t2,$t3),($t3,$t1));
- min_distance = 999999999 )]
-
- foreach edge $edges {
- lassign $edge p1 p2
- lassign $p1 x1 y1
- lassign $p2 x2 y2
-
- [( A = $y2-$y1; B = $x1-$x2;
- C = $x2*$y1 - $x1*$y2;
- distance = abs($A * $px + $B * $py + $C) / hypot($A, $B);
- $distance < $min_distance ? (min_distance = $distance) :; )]
- }
-
- [( [point_in_triangle $px $py $t1 $t2 $t3] ? -$min_distance : $min_distance )]
- }
-
- proc project_point_to_edge {px py p1 p2} {
- lassign $p1 x1 y1
- lassign $p2 x2 y2
-
- [( dx = $x2 - $x1;
- dy = $y2 - $y1;
-
- $dx == 0 && $dy == 0 ? [return $p1]:;
-
- t = ((($px - $x1) * $dx) + (($py - $y1) * $dy)) / (($dx * $dx) + ($dy * $dy));
- t = max(0, min(1, $t));
-
- $x1 + $t * $dx, $y1 + $t * $dy )]
- }
-
- proc constrain_to_triangle {px py t1 t2 t3} {
- if {[point_in_triangle $px $py $t1 $t2 $t3]} {
- return [($px,$py)]
- }
-
- [( edges = (($t1,$t2), ($t2,$t3), ($t3, $t1));
- min_dist = 1e9;
- closest_point = {})]
-
- foreach edge $edges {(
- [lassign $edge p1 p2];
- proj = [project_point_to_edge $px $py $p1 $p2];
- dist = hypot([lindex $proj 0] - $px, [lindex $proj 1] - $py);
-
- $dist < $min_dist ? (
- min_dist = $dist;
- closest_point = $proj):;
- )};
-
- return $closest_point
- }
-
- proc calculate_saturation {px py t1 t2 t3} {
- lassign $t1 x1 y1
- lassign $t2 x2 y2
- lassign $t3 x3 y3
- [( denom = ($y2-$y3)*($x1-$x3) + ($x3-$x2)*($y1-$y3);
- $denom == 0 ? [return 0.0]:;
- alpha = (($y2-$y3)*($px-$x3) + ($x3-$x2)*($py-$y3))/$denom ;
- max(0.0, min(1.0, $alpha)) )]
- }
-
-
- proc calculate_value {px py t1 t2 t3} {
- lassign $t1 x1 y1
- lassign $t2 x2 y2
- lassign $t3 x3 y3
- [( denom = ($y2-$y3)*($x1-$x3) + ($x3-$x2)*($y1-$y3);
- $denom == 0 ? [return 0.0]:;
- alpha = (($y2-$y3)*($px-$x3) + ($x3-$x2)*($py-$y3)) / $denom;
- beta = (($y3-$y1)*($px-$x3) + ($x1-$x3)*($py-$y3)) / $denom;
- gamma = 1.0 - $alpha - $beta;
-
- max(0.0, min(1.0, 1.0 - $gamma)) )]
- }
-
- #UI drawings
- proc point_in_ring {x y} {(
- [ variable cx
- variable cy
- variable radius
- variable ring_thickness];
-
- dx = $x - $cx;
- dy = $y - $cy;
- distance = hypot($dx, $dy);
-
- $distance <= $radius && $distance >= $radius - $ring_thickness
- )}
-
- proc create_hue_ring_image {} {
- variable canvas_size
- variable radius
- variable ring_thickness
- variable cx
- variable cy
- variable info_bg
- variable canvas
-
- [( img = [image create photo -width $canvas_size -height $canvas_size];
-
- # Convertir la couleur de fond en valeurs RGB
- [lassign [winfo rgb . $info_bg] bg_r bg_g bg_b];
- bg_r = $bg_r >> 8;
- bg_g = $bg_g >> 8;
- bg_b = $bg_b >> 8;
-
- inner_radius = $radius - $ring_thickness;
- outer_radius = $radius;
-
- bg_color = "#FFFFFF"; )]
-
- if {[package vsatisfies $::tcl_patchLevel 8.7-]} { append bg_color "00" }
-
- foreach y [lseq $canvas_size] {
- set row {}
- foreach x [lseq $canvas_size] {
- [( dx = $x - $cx;
- dy = $y - $cy;
- distance = hypot($dx, $dy) )]
-
- if {$distance <= $outer_radius && $distance >= $inner_radius} {(
-
- angle = atan2(-$dy, $dx) * 180 / acos(-1);
- $angle < 0 ? (angle = $angle + 360):;
-
- [ lassign [hsv_to_rgb $angle 1.0 1.0] r g b ];
-
- # Calculer l'alpha pour le lissage des bords
- alpha = 1.0;
-
- ($distance > $outer_radius - 1.5 || $distance < $inner_radius + 1.5) ? (
- $distance > $outer_radius - 1.5 ? (alpha = 1.0-($distance-($outer_radius-1.5))/1.5)
- : (alpha = ($distance - $inner_radius) / 1.5)):;
-
- # Mélanger avec la couleur de fond
- r = round($alpha * $r + (1.0 - $alpha) * $bg_r);
- g = round($alpha * $g + (1.0 - $alpha) * $bg_g);
- b = round($alpha * $b + (1.0 - $alpha) * $bg_b);
-
- [lappend row [format "#%02x%02x%02x" $r $g $b]];
- )} else {
- lappend row $bg_color
- }
- }
- $img put [list [join $row " "]] -to 0 $y
-
- if {![package vsatisfies $::tcl_patchLevel 8.7-]} {
- foreach {r g b} [winfo rgb . $bg_color] {(
- bg_color = ($r>>8, $g>>8, $b>>8)
- )}
- foreach j [lseq $canvas_size] {
- foreach i [lseq $canvas_size] {
- if {[$img get $i $j] == $bg_color} {
- $img transparency set $i $j 1
- }
- }
- }
- }
- }
- $canvas create image 0 0 -anchor nw -image $img -tags hue_ring
- }
-
- proc fill_triangle {hue} {
- variable triangle_coords
- variable sv_marker_id
- variable current_saturation
- variable current_value
- variable canvas
- variable info_bg
-
- [( # Détecter la plateforme
- is_macos = ([tk windowingsystem] eq "aqua");
- # Ajuster le facteur d'échelle selon la plateforme
- # Pour macOS, on utilise 1/2 (0.5) pour une meilleure qualité
- # Pour Linux, on reste à 1/2 (0.5) pour éviter des problèmes de redimensionnement
- scale_factor = 0.5;
-
- # Nombre de sous-pixels pour l'antialiasing des bords (uniquement pour macOS)
- subpixel_count = $is_macos ? 2 : 1;
-
- # Largeur de la bordure à pleine résolution (en pixels)
- border_width = $is_macos ? 6 : 1;
-
- # Précalculer les valeurs RGB de la couleur de fond (pour optimisation)
- [lassign [winfo rgb $canvas $info_bg] bg_r bg_g bg_b ];
- bg_r = $bg_r >> 8;
- bg_g = $bg_g >> 8;
- bg_b = $bg_b >> 8;
- info_bg = [format "#%02x%02x%02x" $bg_r $bg_g $bg_b];
-
- # Obtenir les coordonnées du triangle
- t1 = [lrange $triangle_coords 0 1];
- t2 = [lrange $triangle_coords 2 3];
- t3 = [lrange $triangle_coords 4 5]; )]; #end SubArithme
-
- # Effacer les éléments existants
- foreach item [$canvas find withtag triangle_fill] { $canvas delete $item }
-
- [( #<SubArithme>
- # Trouver les limites du triangle
- xs = ([lindex $t1 0], [lindex $t2 0], [lindex $t3 0]);
- ys = ([lindex $t1 1], [lindex $t2 1], [lindex $t3 1]);
-
- minX = int(floor([lindex [lsort -real $xs] 0]));
- maxX = int(ceil([lindex [lsort -real $xs] end]));
- minY = int(floor([lindex [lsort -real $ys] 0]));
- maxY = int(ceil([lindex [lsort -real $ys] end]));
-
- width = $maxX - $minX;
- height = $maxY - $minY;
-
- ($width <= 0 || $height <= 0) ? [return]:;
-
- # Dimensions de l'image à résolution réduite
- small_width = int(ceil($width * $scale_factor));
- small_height = int(ceil($height * $scale_factor));
-
- # Créer une image plus petite pour le calcul
- smallImage = [image create photo -width $small_width -height $small_height];
- )]; # </SubArithme>
-
- # Calculer l'image à résolution réduite
- foreach j [lseq $small_height] {
- [( pixelRow = {} )]
- foreach i [lseq $small_width] {
- # Calculer les coordonnées correspondantes dans l'espace d'origine
- [( px = $minX + $i / $scale_factor;
- py = $minY + $j / $scale_factor;
-
- # Calculer la distance au bord
- distance_to_edge = [calculate_edge_distance $px $py $t1 $t2 $t3];
-
- # Vérifier si nous sommes près du bord
- is_border = ($distance_to_edge >= 0 && $distance_to_edge <= $border_width); )]
-
- # Traitement spécial pour les bords sous macOS
- if {$is_border && $is_macos} {
- [( #Pour les bords sur macOS, calculer plusieurs sous-pixels pour une meilleure précision
- subpixels = {};
- # 3×3 sous-pixels par pixel (ajustable)
- subpixel_count = 2 )]
-
- foreach sub_j [lseq $subpixel_count] {
- foreach sub_i [lseq $subpixel_count] {
- [( sub_px = $px + ($sub_i - 0.5) / ($subpixel_count * $scale_factor);
- sub_py = $py + ($sub_j - 0.5) / ($subpixel_count * $scale_factor);
- sub_distance = [calculate_edge_distance $sub_px $sub_py $t1 $t2 $t3] )]
-
- if {$sub_distance <= 1.5} {
- [( sub_alpha = $sub_distance <= 0 ? 1.0 : (1.0 - ($sub_distance / 1.5)) )]
-
- # Utiliser la fonction calculate_saturation_and_value si elle existe
- # Sinon, utiliser les fonctions individuelles
- if {[info procs calculate_saturation_and_value] ne ""} {
- lassign [calculate_saturation_and_value $sub_px $sub_py $t1 $t2 $t3] sub_s sub_v
- } else {(
- sub_s = [calculate_saturation $sub_px $sub_py $t1 $t2 $t3];
- sub_v = [calculate_value $sub_px $sub_py $t1 $t2 $t3];
- )}
-
- lassign [hsv_to_rgb $hue $sub_s $sub_v] sub_r sub_g sub_b
-
- [( sub_r = round($sub_alpha * $sub_r + (1.0 - $sub_alpha) * $bg_r);
- sub_g = round($sub_alpha * $sub_g + (1.0 - $sub_alpha) * $bg_g);
- sub_b = round($sub_alpha * $sub_b + (1.0 - $sub_alpha) * $bg_b);
- )]
-
- lappend subpixels [($sub_r, $sub_g, $sub_b)]
- } else {
- lappend subpixels [($bg_r, $bg_g, $bg_b)]
- }
- }
- }
-
- # Moyenner les sous-pixels pour obtenir la couleur finale
- lassign [(0,0,0)] avg_r avg_g avg_b
-
- foreach subpixel $subpixels {(
- [lassign $subpixel sub_r sub_g sub_b];
- avg_r = $avg_r + $sub_r;
- avg_g = $avg_g + $sub_g;
- avg_b = $avg_b + $sub_b
- )}
-
- set total_subpixels [llength $subpixels]
-
- if {$total_subpixels > 0} {(
- avg_r = int($avg_r / $total_subpixels);
- avg_g = int($avg_g / $total_subpixels);
- avg_b = int($avg_b / $total_subpixels)
- )}
-
- lappend pixelRow [format "#%02x%02x%02x" $avg_r $avg_g $avg_b]
-
- } elseif {$distance_to_edge <= 1.5} {
- # Traitement normal pour les pixels intérieurs ou sur Linux
- [( alpha = $distance_to_edge <= 0 ? 1.0 : (1.0 - ($distance_to_edge / 1.5)) )]
-
- # Utiliser la fonction calculate_saturation_and_value si elle existe
- # Sinon, utiliser les fonctions individuelles
- if {[info procs calculate_saturation_and_value] ne ""} {
- lassign [calculate_saturation_and_value $px $py $t1 $t2 $t3] s v
- } else {(
- s = [calculate_saturation $px $py $t1 $t2 $t3];
- v = [calculate_value $px $py $t1 $t2 $t3];
- )}
-
- lassign [hsv_to_rgb $hue $s $v] r g b
-
- [( r = round($alpha * $r + (1.0 - $alpha) * $bg_r);
- g = round($alpha * $g + (1.0 - $alpha) * $bg_g);
- b = round($alpha * $b + (1.0 - $alpha) * $bg_b); )]
-
- lappend pixelRow [format "#%02x%02x%02x" $r $g $b]
- } else {
- lappend pixelRow $info_bg
- }
- }
-
- $smallImage put [list [join $pixelRow " "]] -to 0 $j
- }
- [( # Créer l'image finale à taille réelle :
- triangleImage = [image create photo -width $width -height $height];
-
- # Agrandir l'image (en utilisant l'opération zoom de Tk)
- # zoom_factor sera 2 avec scale_factor=0.5
- zoom_factor = int(ceil(1.0 / $scale_factor)) )]
- $triangleImage copy $smallImage -zoom $zoom_factor
-
- # S'assurer que l'image finale a exactement les dimensions souhaitées
- if {[image width $triangleImage] != $width || [image height $triangleImage] != $height} {
- set temp [image create photo -width $width -height $height]
- $temp copy $triangleImage -subsample 1 1 -to 0 0 $width $height
- image delete $triangleImage
- set triangleImage $temp
- }
-
- # Nettoyer l'image temporaire
- image delete $smallImage
-
- # Placer l'image finale sur le canvas
- $canvas create image $minX $minY -anchor nw -image $triangleImage -tags triangle_fill
- $canvas raise hue_ring
- $canvas raise hue_marker
-
- if {$sv_marker_id ne ""} {
- $canvas delete $sv_marker_id
- }
-
- [( # Code pour le marqueur :
- marker_x = [lindex $t1 0] * ($current_saturation * $current_value)
- + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 0] * (1 - $current_value);
-
- marker_y = [lindex $t1 1] * ($current_saturation * $current_value)
- + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 1] * (1 - $current_value);
-
- marker_x = int(round($marker_x));
- marker_y = int(round($marker_y));
-
- sv_marker_id = [$canvas create oval [($marker_x - 5, $marker_y - 5, $marker_x + 5, $marker_y + 5)] \
- -fill "black" -outline "white" -tags sv_marker ]
- )]
- }
-
- #color management
- proc set_color_from_hex {hex_color} {
- variable current_hue
- variable current_saturation
- variable current_value
- variable triangle_coords
- variable hue_marker_id
- variable sv_marker_id
- variable radius
- variable ring_thickness
- variable cx
- variable cy
- variable cumulative_angle
- variable canvas
-
- if {[string length $hex_color] == 7 && [string index $hex_color 0] eq "#"} {
- scan [string range $hex_color 1 end] "%2x%2x%2x" r g b
- } else {
- error "Format de couleur invalide. Utilisez le format #RRGGBB"
- }
-
- lassign [rgb_to_hsv $r $g $b] h s v
-
- [( current_hue = $h;
- current_saturation = $s;
- current_value = $v;
- cumulative_angle = $h;
-
- # Calcul de l'angle pour la rotation du triangle
- current_angle = atan2($cy -[lindex $triangle_coords 1], [lindex $triangle_coords 0] - $cx)* 180 / acos(-1);
-
- $current_angle < 0 ? (current_angle = $current_angle + 360):;
-
- angle_diff = $h - $current_angle;
-
- $angle_diff > 180 ? ( angle_diff = $angle_diff - 360)
- : $angle_diff < -180 ? (angle_diff = $angle_diff + 360) :; )]
-
- rotate_triangle $angle_diff 0
-
- [( inner_pt = polar_to_cartesian($radius - $ring_thickness, $h, $cx, $cy);
- outer_pt = polar_to_cartesian($radius, $h, $cx, $cy);
-
- [$canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt];
-
- t1=[lrange $triangle_coords 0 1];
- t2=[lrange $triangle_coords 2 3];
- t3=[lrange $triangle_coords 4 5];
-
- marker_x = [lindex $t1 0] * ($s * $v)
- + [lindex $t2 0] * ($v * (1 - $s))
- + [lindex $t3 0] * (1 - $v);
-
- marker_y = [lindex $t1 1] * ($s * $v)
- + [lindex $t2 1] * ($v * (1 - $s))
- + [lindex $t3 1] * (1 - $v); )]
-
- $canvas coords $sv_marker_id [($marker_x-5, $marker_y-5, $marker_x+5, $marker_y+5 )]
-
- fill_triangle $h
- update_color_display
- }
-
- # Procédures de contrôle HSV
- proc increment_hue {step} {
- variable current_hue
- variable cumulative_angle
- variable radius
- variable ring_thickness
- variable cx
- variable cy
- variable hue_marker_id
- variable canvas
-
- [( new_hue = (h = $current_hue + $step) >= 360 ? ($h - 360) : $h;
- angle_diff = $new_hue - $current_hue;
- cumulative_angle = $new_hue;
-
- # Mise à jour du marqueur de teinte
- inner_pt = polar_to_cartesian($radius - $ring_thickness, $new_hue, $cx, $cy);
- outer_pt = polar_to_cartesian($radius, $new_hue, $cx, $cy); )]
-
- $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt
-
- rotate_triangle $angle_diff 1
- update_color_display
- }
-
- proc incdec_hue {step value} {(
- [variable last_incdechue];
- (step = $step * ($last_incdechue - $value)) < 0 ?
- ( step = abs($step); [::gColorNS::decrement_hue $step];)
- : [::gColorNS::increment_hue $step];
-
- last_incdechue = $value
- )}
-
- proc incdec_saturation {step value} {
- variable last_incdecsaturation
- set step [($step * ($last_incdecsaturation - $value))]
- ::gColorNS::decrement_saturation $step
- set last_incdecsaturation $value
- }
-
- proc incdec_value {step value} {
- variable last_incdecvalue
- set step [($step * ($last_incdecvalue - $value))]
- ::gColorNS::decrement_value $step
- set last_incdecvalue $value
- }
-
- proc update_scale_values {} {
- variable current_hue
- variable current_saturation
- variable current_value
- variable last_incdechue
- variable last_incdecsaturation
- variable last_incdecvalue
- variable info_frame
- foreach {component scale} {hue 1 saturation 100.0 value 100.0} {
- set value [set current_$component]
- set value [($value * $scale)]
- set last_incdec$component $value
- $info_frame.hsv_controls.$component.scale configure -value $value
- }
- }
-
- proc update_all {{inpcolor ""}} {
- if {$inpcolor eq {}} {
- set inpcolor $::gColorNS::hexvar
- }
- catch {
- set_color_from_hex $inpcolor
- update_color_display
- update_scale_values
- }
- return 1
- }
-
- proc decrement_hue {step} {
- variable current_hue
- variable cumulative_angle
- variable radius
- variable ring_thickness
- variable cx
- variable cy
- variable hue_marker_id
- variable canvas
-
- [( new_hue = (h = ($current_hue - $step)) < 0 ? $h+360 : $h;
- angle_diff = $new_hue - $current_hue;
- cumulative_angle = $new_hue;
-
- # Mise à jour du marqueur de teinte
- inner_pt = polar_to_cartesian($radius - $ring_thickness, $new_hue, $cx, $cy);
- outer_pt = polar_to_cartesian($radius, $new_hue, $cx, $cy) )]
-
- $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt
- rotate_triangle $angle_diff 1
- update_color_display
- }
-
- proc increment_saturation {step} {
- variable current_saturation
- variable current_value
- variable current_hue
- variable triangle_coords
- variable sv_marker_id
- variable canvas
-
- [( new_saturation = min(1.0, $current_saturation + $step) )]
-
- if {$new_saturation != $current_saturation} {
- [( current_saturation=$new_saturation;
-
- # Mise à jour de la position du marqueur SV
- t1=[lrange $triangle_coords 0 1];
- t2=[lrange $triangle_coords 2 3];
- t3=[lrange $triangle_coords 4 5];
-
- marker_x = [lindex $t1 0] * ($current_saturation * $current_value)
- + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 0] * (1 - $current_value);
-
- marker_y = [lindex $t1 1] * ($current_saturation * $current_value)
- + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 1] * (1 - $current_value);
- )]
-
- $canvas coords $sv_marker_id \
- [($marker_x - 5, $marker_y - 5, $marker_x + 5, $marker_y + 5)]
-
- fill_triangle $current_hue
- update_color_display
- }
- }
-
- proc decrement_saturation {step} {
- variable current_saturation
- variable current_value
- variable current_hue
- variable triangle_coords
- variable sv_marker_id
- variable canvas
-
- [( new_saturation = max(0.0, $current_saturation - $step) )]
- if {$new_saturation != $current_saturation} {
- [( current_saturation=$new_saturation;
- # Mise à jour de la position du marqueur SV
- t1=[lrange $triangle_coords 0 1];
- t2=[lrange $triangle_coords 2 3];
- t3=[lrange $triangle_coords 4 5];
-
- marker_x = [lindex $t1 0] * ($current_saturation * $current_value)
- + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 0] * (1 - $current_value);
- marker_y = [lindex $t1 1] * ($current_saturation * $current_value)
- + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 1] * (1 - $current_value); )]
-
- $canvas coords $sv_marker_id \
- [( $marker_x - 5, $marker_y - 5, $marker_x + 5, $marker_y + 5 )]
-
- fill_triangle $current_hue
- update_color_display
- }
- }
-
- proc increment_value {step} {
- variable current_value
- variable current_saturation
- variable current_hue
- variable triangle_coords
- variable sv_marker_id
- variable canvas
-
- [(new_value = min(1.0, $current_value + $step) )]
- if {$new_value != $current_value} {
- [( current_value=$new_value;
- # Mise à jour de la position du marqueur SV
- t1=[lrange $triangle_coords 0 1];
- t2=[lrange $triangle_coords 2 3];
- t3=[lrange $triangle_coords 4 5];
-
- marker_x = [lindex $t1 0] * ($current_saturation * $current_value)
- + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 0] * (1 - $current_value);
- marker_y = [lindex $t1 1] * ($current_saturation * $current_value)
- + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 1] * (1 - $current_value);
- )]
-
- $canvas coords $sv_marker_id \
- [( $marker_x - 5, $marker_y - 5, $marker_x + 5, $marker_y + 5 )]
-
- fill_triangle $current_hue
- update_color_display
- }
- }
-
- proc decrement_value {step} {
- variable current_value
- variable current_saturation
- variable current_hue
- variable triangle_coords
- variable sv_marker_id
- variable canvas
-
- [( new_value = max(0.0, $current_value - $step) )]
- if {$new_value != $current_value} {
- [( current_value=$new_value;
- # Mise à jour de la position du marqueur SV
- t1=[lrange $triangle_coords 0 1];
- t2=[lrange $triangle_coords 2 3];
- t3=[lrange $triangle_coords 4 5];
-
- marker_x = [lindex $t1 0] * ($current_saturation * $current_value)
- + [lindex $t2 0] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 0] * (1 - $current_value);
-
- marker_y = [lindex $t1 1] * ($current_saturation * $current_value)
- + [lindex $t2 1] * ($current_value * (1 - $current_saturation))
- + [lindex $t3 1] * (1 - $current_value); )]
-
- $canvas coords $sv_marker_id \
- [( $marker_x - 5, $marker_y - 5, $marker_x + 5, $marker_y + 5)]
-
- fill_triangle $current_hue
- update_color_display
- }
- }
-
- proc rotate_triangle {angle {update_hue 0}} {
- variable triangle_coords
- variable cx
- variable cy
- variable current_hue
- variable sv_marker_id
- variable hue_marker_coords
- variable triangle_id
- variable canvas
-
- set rotated {}
- foreach i [lseq 3 by 2] {
- [( x = [lindex $triangle_coords $i];
- y = [lindex $triangle_coords [($i + 1)]];
- dx = $x - $cx;
- dy = $y - $cy;
- distance = hypot($dx, $dy);
- current_angle = atan2(-$dy, $dx) * 180 / acos(-1);
- new_angle = $current_angle + $angle;
- $new_angle < 0 ? (new_angle = $new_angle + 360)
- : $new_angle > 360 ? (new_angle = $new_angle - 360)
- :; )]
-
- lappend rotated {*}[(polar_to_cartesian($distance,$new_angle,$cx,$cy))]
- }
-
- set triangle_coords $rotated
- $canvas coords $triangle_id {*}$rotated
-
- if {$update_hue} {(
- current_hue = (h = $current_hue + $angle) < 0 ? $h+360 : $h >= 360 ? $h-360 : $h
- )}
-
- set t_h [lrange $triangle_coords 0 1]
- set hue_marker_coords $t_h
-
- if {$sv_marker_id ne ""} {(
- sv_coords = [$canvas coords $sv_marker_id];
- sv_x = ([lindex $sv_coords 0] + [lindex $sv_coords 2]) / 2;
- sv_y = ([lindex $sv_coords 1] + [lindex $sv_coords 3]) / 2;
- dx = $sv_x - $cx;
- dy = $sv_y - $cy;
- distance = hypot($dx, $dy);
- current_angle = atan2(-$dy, $dx) * 180 / acos(-1);
- new_angle = (a = $current_angle + $angle) < 0 ? $a+360 : $a >= 360 ? $a-360 : $a;
-
- new_coords = polar_to_cartesian($distance,$new_angle,$cx,$cy);
- [ $canvas coords $sv_marker_id \
- [( [lindex $new_coords 0]-5, [lindex $new_coords 1]-5,
- [lindex $new_coords 0]+5, [lindex $new_coords 1]+5 )] ];
- )}
-
- fill_triangle $current_hue
- }
-
- #ui update
- proc throttle_update_hue_marker {x y} {
- variable last_update_time
- variable update_threshold_ms
-
- set current_time [clock milliseconds]
- if {![info exists last_update_time] || \
- ($current_time - $last_update_time) >= $update_threshold_ms} {
- update_hue_marker $x $y
- set last_update_time $current_time
- }
- }
-
- proc update_hue_marker {x y} {
- variable cx
- variable cy
- variable radius
- variable ring_thickness
- variable hue_marker_id
- variable current_hue
- variable cumulative_angle
- variable canvas
- variable pending_update_after_id
-
- # Mettre à jour uniquement le marqueur de teinte immédiatement
- [( dx = $x - $cx;
- dy = $y - $cy;
- new_angle = atan2(-$dy, $dx) * 180 / acos(-1);
- $new_angle < 0 ? (new_angle = $new_angle + 360):;
-
- inner_pt = polar_to_cartesian($radius-$ring_thickness, $new_angle, $cx, $cy);
- outer_pt = polar_to_cartesian($radius, $new_angle, $cx, $cy); )]
-
- $canvas coords $hue_marker_id {*}$inner_pt {*}$outer_pt
-
- # Annuler toute mise à jour en attente
- if {[info exists pending_update_after_id]} {
- after cancel $pending_update_after_id
- }
-
- # Programmer la mise à jour complète après un délai
- set pending_update_after_id [after 0 [list ::gColorNS::complete_hue_update $new_angle $cumulative_angle]]
- }
-
- proc complete_hue_update {new_angle old_angle} {
- variable cumulative_angle
-
- [( delta_angle = (a = $new_angle - $old_angle) > 180 ? $a-360 : $a < -180 ? $a+360 : $a;
- cumulative_angle = $new_angle; )]
-
- rotate_triangle $delta_angle 1
- update_color_display
- }
-
- proc update_color_display {} {
- variable current_hue
- variable current_saturation
- variable current_value
- variable info_frame
-
- set rgb [hsv_to_rgb $current_hue $current_saturation $current_value]
- set hex [format "#%02x%02x%02x" {*}$rgb]
-
- $info_frame.color_display configure -bg $hex
- set ::gColorNS::rgbvar [join $rgb {, }]
- set ::gColorNS::hexvar $hex
- }
-
- proc update_sv_marker {x y} {
- variable triangle_coords
- variable sv_marker_id
- variable current_saturation
- variable current_value
- variable canvas
-
- [( t1=[lrange $triangle_coords 0 1];
- t2=[lrange $triangle_coords 2 3];
- t3=[lrange $triangle_coords 4 5];
-
- constrained_point = [constrain_to_triangle $x $y $t1 $t2 $t3];
- px=[lindex $constrained_point 0];
- py=[lindex $constrained_point 1]; )]
-
- $canvas coords $sv_marker_id \
- [($px-5, $py-5, $px+5, $py+5)]
-
- [( current_saturation = [calculate_saturation $px $py $t1 $t2 $t3];
- current_value = [calculate_value $px $py $t1 $t2 $t3]; )]
-
- update_color_display
- }
-
- # Procédure principale de configuration de l'interface utilisateur
- proc setup_ui {parent_frame {inpcolor #ffffff} {okttl OK} {cancelttl Cancel}} {
- variable canvas_size
- variable radius
- variable ring_thickness
- variable cx
- variable cy
- variable triangle_coords
- variable triangle_id
- variable hue_marker_id
- variable sv_marker_id
- variable current_hue
- variable current_value
- variable current_saturation
- variable canvas
- variable info_frame
- variable info_bg
- variable cumulative_angle
-
- [( main=[ttk::frame $parent_frame.m];
- info=[ttk::frame $parent_frame.i];
- info_frame=$info;
- canvas="$main.canvas" )]
-
- catch {set info_bg [. cget -bg]}
- if {![info exists info_bg] || $info_bg eq {}} {
- set info_bg white
- }
-
- canvas $canvas -width $canvas_size -height $canvas_size -bg $info_bg -bd 0 -highlightthickness 0
-
- if {[tk windowingsystem] in {aqua win32}} {
- label $info.color_display -width 10 -height 6 -bg $inpcolor
- } else {
- label $info.color_display -width 12 -height 6 -bg $inpcolor
- }
- ttk::label $info.label_rgb -text "RGB:" -width 5
- ttk::label $info.label_hex -text "Hex:" -width 5
- ttk::entry $info.entry_rgb -textvariable ::gColorNS::rgbvar -width 12
- ttk::separator $info.separ -orient horizontal
- ttk::entry $info.entry_hex -textvariable ::gColorNS::hexvar -width 12 \
- -validate focusout -validatecommand ::gColorNS::update_all
- ttk::frame $info.frbutton
- ttk::button $info.frbutton.button_ok -text $okttl \
- -command {set ::gColorNS::isOK 1}
- ttk::button $info.frbutton.button_cancel -text $cancelttl \
- -command {set ::gColorNS::isOK 0}
-
- # Frame pour les contrôles HSV
- ttk::frame $info.hsv_controls
- # Contrôles pour H, S, V
- foreach {component} {hue saturation value} {
- set label [string totitle [string range $component 0 2]]
- ttk::frame $info.hsv_controls.$component
- ttk::label $info.hsv_controls.$component.label -text $label: -width 5
- pack $info.hsv_controls.$component -side top -fill x
- pack $info.hsv_controls.$component.label -side left
- set scaleto [($component eq "hue" ? 360 : 100.0)]
- ttk::scale $info.hsv_controls.$component.scale \
- -command [list ::gColorNS::incdec_$component \
- [($component eq "hue" ? 1 : 0.01)]] \
- -orient horizontal -from 0 -to $scaleto -takefocus 0
- pack $info.hsv_controls.$component.scale -side left -expand 1 -fill x
- }
-
- set current_hue 0
- set cumulative_angle 0
-
- create_hue_ring_image
-
- [( inner_radius = $radius - $ring_thickness * 1.5;
- t1 = polar_to_cartesian($inner_radius,0,$cx,$cy);
- t2 = polar_to_cartesian($inner_radius,120,$cx,$cy);
- t3 = polar_to_cartesian($inner_radius,-120,$cx,$cy);
-
- triangle_coords = [list {*}$t1 {*}$t2 {*}$t3];
- triangle_id = [$canvas create polygon $triangle_coords -outline $info_bg -tags triangle];
-
- inner_coords = polar_to_cartesian($radius - $ring_thickness, 0, $cx, $cy);
- outer_coords = polar_to_cartesian($radius, 0, $cx, $cy);
- hue_marker_id = [$canvas create line \
- [lindex $inner_coords 0] [lindex $inner_coords 1] \
- [lindex $outer_coords 0] [lindex $outer_coords 1] \
- -fill black -width 2 -tags hue_marker];
-
- sv_x = ([lindex $t1 0] + [lindex $t2 0] + [lindex $t3 0]) / 3;
- sv_y = ([lindex $t1 1] + [lindex $t2 1] + [lindex $t3 1]) / 3;
- marker_size = $ring_thickness / 2;
- sv_marker_id = [$canvas create oval \
- [( $sv_x - $marker_size, $sv_y - $marker_size,
- $sv_x + $marker_size, $sv_y + $marker_size)] \
- -fill black -outline white -tags sv_marker];
- )]
-
- bind $canvas <Button-1> {
- set x %x
- set y %y
-
- if {[::gColorNS::point_in_ring $x $y]} {
- set ::gColorNS::active_tag "hue_ring"
- ::gColorNS::update_hue_marker $x $y
- } elseif {[::gColorNS::point_in_triangle $x $y \
- [lrange $::gColorNS::triangle_coords 0 1] \
- [lrange $::gColorNS::triangle_coords 2 3] \
- [lrange $::gColorNS::triangle_coords 4 5]]} {
- set ::gColorNS::active_tag "sv_area"
- ::gColorNS::update_sv_marker $x $y
- }
- }
-
- bind $canvas <B1-Motion> {
- set x %x
- set y %y
-
- if {$::gColorNS::active_tag eq "hue_ring"} {
- ::gColorNS::throttle_update_hue_marker $x $y
- } elseif {$::gColorNS::active_tag eq "sv_area"} {
- ::gColorNS::update_sv_marker $x $y
- }
- }
-
- bind $canvas <ButtonRelease-1> {
- set ::gColorNS::active_tag ""
- ::gColorNS::update_scale_values
- }
-
- fill_triangle $current_hue
-
- grid $main
- grid $canvas -row 0 -column 0 -rowspan 7 -sticky nswe
- grid $info -row 0 -column 1 -rowspan 7 -sticky nswe
- grid $info.label_rgb -sticky w -pady 0 -padx 2 -row 0 -column 0
- grid $info.entry_rgb -sticky w -pady 0 -row 0 -column 1
- grid $info.label_hex -sticky w -padx 2 -row 1 -column 0
- grid $info.entry_hex -sticky w -row 1 -column 1
- grid $info.hsv_controls -sticky ew -padx 4 -pady 4 -row 2 -column 0 -columnspan 2
- grid $info.color_display -pady 4 -row 3 -column 0 -columnspan 2
- grid $info.separ -sticky nswe -row 4 -column 0 -columnspan 2
- grid $info.frbutton -sticky nswe -pady 4 -row 5 -column 0 -columnspan 2
- grid $info.frbutton.button_ok -sticky es -padx 2 -row 0 -column 0
- grid $info.frbutton.button_cancel -sticky es -padx 2 -row 0 -column 1
- grid rowconfigure $info 3 -weight 111
-
- update_all $inpcolor
-
- }
-
- proc run {args} {
-
- # Runs the picker.
- # args - list of options
- # Options:
- # "-color value" to set HEX color value, e.g. -color #ff4b4b
- # "-title ttl" to set the picker's title, e.g. -title "Choose item color"
- # "-oktitle ttl" to name OK button, e.g. -oktitle "To clipboard"
- # "-canceltitle ttl" to name Cancel button, e.g. -canceltitle "Otmena"
- # "-geometry +X+Y" to position the picker, e.g. -geometry +100+200
- # "-parent win" to set parent window path, e.g. -parent .mywin
- # "-modal bool" to set modal mode (default 1), e.g. -modal 0
- # "-topmost bool" to set topmost mode (default 0), e.g. -topmost 1
-
- # parse options
- foreach {opt def} {geometry - color #ffffff parent - \
- title Color oktitle OK canceltitle Cancel modal 1 topmost 0} {
- set $opt $def
- catch {set $opt [dict get $args -$opt]}
- }
-
- # create picker's window
- set win .gColor
- if {$parent eq {-}} {
- set parent [lindex [winfo children .] end]
- }
- set win [string trimright $parent .].gColor
- toplevel $win
-
- # populate the picker's window
- set wfr $win.f
- if {[catch {set bg [. cget -bg]}]} {set bg #d9d9d9}
- frame $wfr -background $bg
- setup_ui $wfr $color $oktitle $canceltitle
- grid $wfr -sticky news
-
- # wm options
- wm title $win $title
- wm attributes $win -topmost $topmost
- if {[regexp {^\+\d+\+\d+$} $geometry]} {
- wm geometry $win $geometry
- }
- wm protocol $win WM_DELETE_WINDOW {set ::gColorNS::isOK 0}
- wm transient $win $parent
- wm resizable $win 0 0
-
- # wait for the user's choice
- set wgr [grab current]
- catch {grab release $wgr}
- if {$modal} {catch {grab set $win}}
- bind $win <Escape> {set ::gColorNS::isOK 0}
- set ::gColorNS::isOK {}
- after 1 ;# solves an issue with doubleclicking buttons
- if {![winfo viewable $win]} {
- tkwait visibility $win
- }
- tkwait variable ::gColorNS::isOK
- catch {grab release $win}
- catch {grab set $wgr}
- catch {destroy $win}
-
- # get the user's choice and return HEX value or {}
- if {$::gColorNS::isOK > 0} {
- return $::gColorNS::hexvar
- }
- return {}
- }
-
- # ________________________ EONS _________________________ #
-
- }
-
- if {[info exist ::argv0] && [info exist ::argv] && \
- [file normalize $::argv0] eq [file normalize [info script]]} {
- wm withdraw .
- set clr #ffffff
- while 1 {
- set clr [gColorNS::run -color $clr -oktitle {To clipboard} {*}$::argv]
- if {$clr eq {}} break
- clipboard clear
- clipboard append -type STRING $clr
- }
- exit
- }
-
Add a comment