@@ -171,115 +171,43 @@ function syncControls(config) {
171171}
172172
173173// Interpolate between two hex colors
174- function interpolateColor ( color1 , color2 , factor ) {
175- const c1 = parseInt ( color1 . slice ( 1 ) , 16 ) ;
176- const c2 = parseInt ( color2 . slice ( 1 ) , 16 ) ;
177- const r1 = ( c1 >> 16 ) & 255 , g1 = ( c1 >> 8 ) & 255 , b1 = c1 & 255 ;
178- const r2 = ( c2 >> 16 ) & 255 , g2 = ( c2 >> 8 ) & 255 , b2 = c2 & 255 ;
179- const r = Math . round ( r1 + ( r2 - r1 ) * factor ) ;
180- const g = Math . round ( g1 + ( g2 - g1 ) * factor ) ;
181- const b = Math . round ( b1 + ( b2 - b1 ) * factor ) ;
182- return `#${ ( ( r << 16 ) | ( g << 8 ) | b ) . toString ( 16 ) . padStart ( 6 , '0' ) } ` ;
183- }
184-
185- // Get color from gradient at a given position (0-1)
186- function getGradientColor ( position , colors , positions ) {
187- if ( ! colors || colors . length === 0 ) return '#ffffff' ;
188- if ( colors . length === 1 ) return colors [ 0 ] ;
189- if ( ! positions || positions . length !== colors . length ) return colors [ 0 ] ;
190-
191- // Normalize position to 0-1
192- const t = Math . max ( 0 , Math . min ( 1 , position ) ) ;
193-
194- // Find the two stops this position falls between
195- let startIdx = 0 ;
196- for ( let i = 0 ; i < positions . length - 1 ; i ++ ) {
197- const pos1 = positions [ i ] / 100 ;
198- const pos2 = positions [ i + 1 ] / 100 ;
199- if ( t >= pos1 && t <= pos2 ) {
200- startIdx = i ;
201- break ;
202- }
203- }
204-
205- // Clamp to valid indices
206- startIdx = Math . min ( startIdx , positions . length - 2 ) ;
207- const endIdx = Math . min ( startIdx + 1 , positions . length - 1 ) ;
208-
209- const pos1 = positions [ startIdx ] / 100 ;
210- const pos2 = positions [ endIdx ] / 100 ;
211- const range = pos2 - pos1 ;
212- const factor = range === 0 ? 0 : ( t - pos1 ) / range ;
174+ const DEFAULT_PALETTES = {
175+ 2 : [ "#0066ff" , "#ff6b35" ] ,
176+ 3 : [ "#0066ff" , "#00ff41" , "#ff6b35" ] ,
177+ 4 : [ "#0066ff" , "#00aaff" , "#00ff41" , "#ff6b35" ] ,
178+ 5 : [ "#0066ff" , "#00aaff" , "#00ff41" , "#ffaa00" , "#ff6b35" ] ,
179+ 6 : [ "#0066ff" , "#00aaff" , "#00ffaa" , "#00ff41" , "#ffaa00" , "#ff6b35" ] ,
180+ 8 : [ "#0066ff" , "#00aaff" , "#00ddff" , "#00ff41" , "#ffff00" , "#ffaa00" , "#ff6b35" , "#ff0055" ] ,
181+ } ;
213182
214- return interpolateColor ( colors [ startIdx ] , colors [ endIdx ] , factor ) ;
183+ function getDefaultPalette ( alphabetSize ) {
184+ return DEFAULT_PALETTES [ alphabetSize ] || ( DEFAULT_PALETTES [ alphabetSize ] =
185+ Array . from ( { length : alphabetSize } , ( _ , i ) => {
186+ const hue = ( i / alphabetSize ) * 360 ;
187+ return `hsl(${ Math . round ( hue ) } , 100%, 50%)` ;
188+ } )
189+ ) ;
215190}
216191
217- function colorFor ( value , config , rowIndex , colIndex , channels ) {
192+ function colorFor ( value , config , channels ) {
218193 const visualValue = channels && channels . length > 1 ? channels [ 1 ] : value ;
219194 const alphabet = config . alphabet_sortie || config . alphabet_entree || [ 0 , 1 ] ;
220195 let index = alphabet . findIndex ( ( item ) => String ( item ) === String ( visualValue ) ) ;
221196
222- // Use gradient if available
223- const gradient = config . rendu . gradient ;
224- if ( gradient && gradient . colors && gradient . colors . length > 0 ) {
225- let position = 0 ;
226- if ( index >= 0 ) {
227- position = index / Math . max ( 1 , alphabet . length - 1 ) ;
228- } else {
229- position = ( Math . abs ( Number ( visualValue ) || Number ( value ) || 0 ) % 100 ) / 100 ;
230- }
231-
232- // Apply gradient based on type
233- if ( gradient . type === 'linear' ) {
234- position = ( colIndex / ( config . largeur || 100 ) ) * 0.5 + ( rowIndex / ( config . hauteur || 100 ) ) * 0.5 ;
235- } else if ( gradient . type === 'radial' ) {
236- const centerX = ( config . largeur || 100 ) / 2 ;
237- const centerY = ( config . hauteur || 100 ) / 2 ;
238- const dist = Math . sqrt ( Math . pow ( colIndex - centerX , 2 ) + Math . pow ( rowIndex - centerY , 2 ) ) ;
239- const maxDist = Math . sqrt ( Math . pow ( centerX , 2 ) + Math . pow ( centerY , 2 ) ) ;
240- position = Math . min ( 1 , dist / maxDist ) ;
241- } else if ( gradient . type === 'conic' ) {
242- const centerX = ( config . largeur || 100 ) / 2 ;
243- const centerY = ( config . hauteur || 100 ) / 2 ;
244- const angle = Math . atan2 ( rowIndex - centerY , colIndex - centerX ) ;
245- position = ( ( angle + Math . PI ) / ( 2 * Math . PI ) ) ;
246- }
247-
248- return getGradientColor ( position , gradient . colors , gradient . positions ) ;
249- }
197+ if ( index < 0 ) index = Math . abs ( Number ( visualValue ) || Number ( value ) || 0 ) % alphabet . length ;
250198
251- // Fallback to palette if no gradient
252- const palette = config . rendu . palette || [ "#07121f" , "#ff9d4d" , "#53b0ff" , "#f5f7ff" ] ;
253- if ( index < 0 ) index = Math . abs ( Number ( visualValue ) || Number ( value ) || 0 ) % palette . length ;
254- const drift = ( rowIndex + colIndex ) % Math . max ( 1 , palette . length - 1 ) ;
255- return palette [ ( index + drift ) % palette . length ] || palette [ 0 ] ;
199+ const palette = config . rendu . palette || getDefaultPalette ( alphabet . length ) ;
200+ return palette [ index % palette . length ] || palette [ 0 ] ;
256201}
257202
258- window . applyGradientToPalette = function ( colors , type , positions ) {
203+ window . setPaletteColor = function ( alphabetIndex , color ) {
259204 if ( ! state . config ) return ;
260205 state . config . rendu = state . config . rendu || { } ;
261-
262- const finalColors = colors || [ "#07121f" , "#ff9d4d" , "#53b0ff" , "#f5f7ff" ] ;
263- const finalPositions = positions || [ 0 , 50 , 100 ] ;
264-
265- // Ensure colors and positions arrays have the same length
266- const length = Math . min ( finalColors . length , finalPositions . length ) ;
267- const syncedColors = finalColors . slice ( 0 , length ) ;
268- const syncedPositions = finalPositions . slice ( 0 , length ) ;
269-
270- // If arrays were mismatched, pad with defaults
271- if ( syncedColors . length < 2 ) {
272- syncedColors . push ( "#ff9d4d" , "#f5f7ff" ) ;
273- syncedPositions . push ( 50 , 100 ) ;
206+ const alphabet = state . config . alphabet_sortie || state . config . alphabet_entree || [ 0 , 1 ] ;
207+ if ( ! state . config . rendu . palette ) {
208+ state . config . rendu . palette = getDefaultPalette ( alphabet . length ) ;
274209 }
275-
276- state . config . rendu . gradient = {
277- colors : syncedColors ,
278- type : type || 'linear' ,
279- positions : syncedPositions . map ( Number )
280- } ;
281- // Keep palette as fallback
282- state . config . rendu . palette = syncedColors ;
210+ state . config . rendu . palette [ alphabetIndex ] = color ;
283211 render ( ) ;
284212} ;
285213
@@ -294,7 +222,7 @@ function drawUniverseToCanvas(targetCanvas, universe, maxWidth = 240, maxHeight
294222 lignes . forEach ( ( ligne , y ) => {
295223 ligne . forEach ( ( value , x ) => {
296224 if ( String ( value ) === String ( configuration . alphabet_entree [ 0 ] ) && ! configuration . rendu . afficher_zero ) return ;
297- context . fillStyle = colorFor ( value , configuration , y , x , sorties ?. [ y ] ?. [ x ] ) ;
225+ context . fillStyle = colorFor ( value , configuration , sorties ?. [ y ] ?. [ x ] ) ;
298226 context . fillRect ( x * cell , y * cell , cell , cell ) ;
299227 } ) ;
300228 } ) ;
@@ -316,7 +244,7 @@ function render() {
316244 lignes . forEach ( ( ligne , y ) => {
317245 ligne . forEach ( ( value , x ) => {
318246 if ( String ( value ) === String ( configuration . alphabet_entree [ 0 ] ) && ! configuration . rendu . afficher_zero ) return ;
319- ctx . fillStyle = colorFor ( value , configuration , y , x , sorties ?. [ y ] ?. [ x ] ) ;
247+ ctx . fillStyle = colorFor ( value , configuration , sorties ?. [ y ] ?. [ x ] ) ;
320248 ctx . fillRect ( x * cell , y * cell , cell , cell ) ;
321249 } ) ;
322250 } ) ;
@@ -343,6 +271,9 @@ function applyConfig(config, { updateJson = true, updateControls = true } = {})
343271 if ( updateJson ) controls . json . value = JSON . stringify ( normalized , null , 2 ) ;
344272 describe ( normalized ) ;
345273 render ( ) ;
274+ if ( typeof window . updatePaletteEditor === 'function' ) {
275+ window . updatePaletteEditor ( ) ;
276+ }
346277 return true ;
347278}
348279
@@ -448,6 +379,13 @@ document.querySelector("#apply-json").addEventListener("click", () => {
448379document . querySelector ( "#reset-json" ) . addEventListener ( "click" , ( ) => {
449380 controls . json . value = JSON . stringify ( state . lastValidConfig || state . config , null , 2 ) ;
450381} ) ;
382+
383+ // Expose API for palette editor
384+ window . AutomaginariumApp = {
385+ state,
386+ render,
387+ getDefaultPalette,
388+ } ;
451389controls . importJson . addEventListener ( "change" , async ( ) => {
452390 const file = controls . importJson . files [ 0 ] ;
453391 if ( ! file ) return ;
0 commit comments