Skip to main content

uiua/algorithm/
media.rs

1//! En/decode Uiua arrays to/from media formats
2
3use std::{
4    borrow::Cow,
5    f64::consts::PI,
6    hash::{Hash, Hasher},
7};
8
9use ecow::{EcoVec, eco_vec};
10use enum_iterator::{Sequence, all};
11#[cfg(feature = "audio_encode")]
12use hound::{SampleFormat, WavReader, WavSpec, WavWriter};
13#[cfg(feature = "image")]
14use image::{DynamicImage, ImageFormat};
15use rapidhash::quality::RapidHasher;
16use serde::*;
17
18#[allow(unused_imports)]
19use crate::{Array, Uiua, UiuaResult, Value};
20#[cfg(feature = "gif")]
21use crate::{ArrayValue, RealArrayValue};
22use crate::{Complex, OptionalArg, Shape, SigNode, SysBackend};
23
24use super::monadic::hsv_to_rgb;
25
26/// Conversion of a value to some media format based on the value's shape
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[allow(missing_docs)]
29pub enum SmartOutput {
30    Normal(String),
31    Png(Vec<u8>, Option<String>),
32    Gif(Vec<u8>, Option<String>),
33    Apng(Vec<u8>, Option<String>),
34    Wav(Vec<u8>, Option<String>),
35    Svg { svg: String, original: Value },
36}
37
38const MIN_AUTO_IMAGE_DIM: usize = 30;
39
40impl SmartOutput {
41    /// Convert a value to a SmartOutput
42    ///
43    /// Animations default to GIF
44    pub fn from_value(value: Value, frame_rate: f64, backend: &dyn SysBackend) -> Self {
45        Self::from_value_impl(value, frame_rate, false, backend)
46    }
47    /// Convert a value to a SmartOutput
48    ///
49    /// Animations default to APNG
50    pub fn from_value_prefer_apng(value: Value, frame_rate: f64, backend: &dyn SysBackend) -> Self {
51        Self::from_value_impl(value, frame_rate, true, backend)
52    }
53    fn from_value_impl(
54        value: Value,
55        frame_rate: f64,
56        prefer_apng: bool,
57        backend: &dyn SysBackend,
58    ) -> Self {
59        // Try to convert the value to audio
60        #[cfg(feature = "audio_encode")]
61        if value.row_count() >= 44100 / 4
62            && matches!(&value, Value::Num(arr) if arr.elements().all(|x| x.abs() <= 5.0))
63            && let Ok(bytes) = value_to_wav_bytes(&value, backend.audio_sample_rate())
64        {
65            let label = value.meta.label.as_ref().map(Into::into);
66            return Self::Wav(bytes, label);
67        }
68        // Try to convert the value to an image
69        #[cfg(feature = "image")]
70        if let Ok(image) = value_to_image(&value)
71            && image.width() >= MIN_AUTO_IMAGE_DIM as u32
72            && image.height() >= MIN_AUTO_IMAGE_DIM as u32
73            && let Ok(bytes) = image_to_bytes(&image, ImageFormat::Png)
74        {
75            let label = value.meta.label.as_ref().map(Into::into);
76            return Self::Png(bytes, label);
77        }
78        // Try to convert the value to a gif or apng
79        let animation = if prefer_apng {
80            Self::try_apng(&value, frame_rate).or_else(|| Self::try_gif(&value, frame_rate))
81        } else {
82            Self::try_gif(&value, frame_rate).or_else(|| Self::try_apng(&value, frame_rate))
83        };
84        if let Some(anim) = animation {
85            return anim;
86        }
87        // Try to convert the value to an svg
88        if let Some(str) = value.as_string_opt() {
89            let mut str = str.trim().to_string();
90            if str.starts_with("<svg") && str.ends_with("</svg>") {
91                if !str.contains("xmlns") {
92                    str = str.replacen("<svg", "<svg xmlns=\"http://www.w3.org/2000/svg\"", 1);
93                }
94                return Self::Svg {
95                    svg: str,
96                    original: value,
97                };
98            }
99        }
100        // Otherwise, just show the value
101        Self::Normal(value.show())
102    }
103    #[cfg(not(feature = "gif"))]
104    fn try_gif(_value: &Value, _frame_rate: f64) -> Option<Self> {
105        None
106    }
107    #[cfg(feature = "gif")]
108    fn try_gif(value: &Value, frame_rate: f64) -> Option<Self> {
109        match &*value.shape {
110            &[_] => {
111                // Already encoded
112                let bytes = value_to_gif_bytes(value, frame_rate).ok()?;
113                let label = value.meta.label.as_ref().map(Into::into);
114                Some(Self::Gif(bytes, label))
115            }
116            &[f, h, w] | &[f, h, w, _]
117                if h >= MIN_AUTO_IMAGE_DIM && w >= MIN_AUTO_IMAGE_DIM && f >= 5 =>
118            {
119                let bytes = value_to_gif_bytes(value, frame_rate).ok()?;
120                let label = value.meta.label.as_ref().map(Into::into);
121                Some(Self::Gif(bytes, label))
122            }
123            _ => None,
124        }
125    }
126    #[cfg(not(feature = "apng"))]
127    fn try_apng(_value: &Value, _frame_rate: f64) -> Option<Self> {
128        None
129    }
130    #[cfg(feature = "apng")]
131    fn try_apng(value: &Value, frame_rate: f64) -> Option<Self> {
132        match &*value.shape {
133            &[f, h, w] | &[f, h, w, _]
134                if h >= MIN_AUTO_IMAGE_DIM && w >= MIN_AUTO_IMAGE_DIM && f >= 5 =>
135            {
136                let bytes = value_to_apng_bytes(value, frame_rate).ok()?;
137                let label = value.meta.label.as_ref().map(Into::into);
138                Some(Self::Apng(bytes.into_iter().collect(), label))
139            }
140            _ => None,
141        }
142    }
143}
144
145pub(crate) fn image_encode(env: &mut Uiua) -> UiuaResult {
146    #[cfg(feature = "image")]
147    {
148        let format = env
149            .pop(1)?
150            .as_string(env, "Image format must be a string")?;
151        let value = env.pop(2)?;
152        let output_format = match format.as_str() {
153            "jpg" | "jpeg" => ImageFormat::Jpeg,
154            "png" => ImageFormat::Png,
155            "bmp" => ImageFormat::Bmp,
156            "gif" => ImageFormat::Gif,
157            "ico" => ImageFormat::Ico,
158            "qoi" => ImageFormat::Qoi,
159            "webp" => ImageFormat::WebP,
160            format => return Err(env.error(format!("Invalid image format: {format}"))),
161        };
162        let bytes =
163            crate::media::value_to_image_bytes(&value, output_format).map_err(|e| env.error(e))?;
164        env.push(Array::<u8>::from(bytes.as_slice()));
165        Ok(())
166    }
167    #[cfg(not(feature = "image"))]
168    Err(env.error("Image encoding is not supported in this environment"))
169}
170
171pub(crate) fn image_decode(env: &mut Uiua) -> UiuaResult {
172    #[cfg(feature = "image")]
173    {
174        let bytes: crate::cowslice::CowSlice<u8> = match env.pop(1)? {
175            Value::Byte(arr) => {
176                if arr.rank() != 1 {
177                    return Err(env.error(format!(
178                        "Image bytes array must be rank 1, but is rank {}",
179                        arr.rank()
180                    )));
181                }
182                arr.data
183            }
184            Value::Num(arr) => {
185                if arr.rank() != 1 {
186                    return Err(env.error(format!(
187                        "Image bytes array must be rank 1, but is rank {}",
188                        arr.rank()
189                    )));
190                }
191                arr.data.iter().map(|&x| x as u8).collect()
192            }
193            _ => return Err(env.error("Image bytes must be a numeric array")),
194        };
195        let format = image::guess_format(&bytes)
196            .map_err(|e| env.error(format!("Failed to read image: {e}")))?;
197        let array =
198            crate::media::image_bytes_to_array(&bytes, false, true).map_err(|e| env.error(e))?;
199        env.push(array);
200        env.push(match format {
201            image::ImageFormat::Jpeg => "jpeg".into(),
202            fmt => format!("{fmt:?}").to_lowercase(),
203        });
204        Ok(())
205    }
206    #[cfg(not(feature = "image"))]
207    Err(env.error("Image decoding is not supported in this environment"))
208}
209
210pub(crate) fn gif_encode(env: &mut Uiua) -> UiuaResult {
211    #[cfg(feature = "gif")]
212    {
213        let frame_rate = env.pop(1)?.as_num(env, "Framerate must be a number")?;
214        let value = env.pop(2)?;
215        let bytes =
216            crate::media::value_to_gif_bytes(&value, frame_rate).map_err(|e| env.error(e))?;
217        env.push(Array::<u8>::from(bytes.as_slice()));
218        Ok(())
219    }
220    #[cfg(not(feature = "gif"))]
221    Err(env.error("GIF encoding is not supported in this environment"))
222}
223
224pub(crate) fn gif_decode(env: &mut Uiua) -> UiuaResult {
225    let bytes = env.pop(1)?;
226    let bytes = bytes.as_bytes(env, "Gif bytes must be a byte array")?;
227    let (frame_rate, value) = crate::media::gif_bytes_to_value(&bytes).map_err(|e| env.error(e))?;
228    env.push(value);
229    env.push(frame_rate);
230    Ok(())
231}
232
233pub(crate) fn apng_encode(env: &mut Uiua) -> UiuaResult {
234    #[cfg(feature = "apng")]
235    {
236        let frame_rate = env.pop(1)?.as_num(env, "Framerate must be a number")?;
237        let value = env.pop(2)?;
238        let bytes =
239            crate::media::value_to_apng_bytes(&value, frame_rate).map_err(|e| env.error(e))?;
240        env.push(Array::<u8>::from(bytes));
241        Ok(())
242    }
243    #[cfg(not(feature = "apng"))]
244    Err(env.error("APNG encoding is not supported in this environment"))
245}
246
247pub(crate) fn audio_encode(env: &mut Uiua) -> UiuaResult {
248    #[cfg(feature = "audio_encode")]
249    {
250        let format = env
251            .pop(1)?
252            .as_string(env, "Audio format must be a string")?;
253
254        const SAMPLE_RATE_REQUIREMENT: &str = "Audio sample rate must be a positive integer";
255        let sample_rate = u32::try_from(env.pop(2)?.as_nat(env, SAMPLE_RATE_REQUIREMENT)?)
256            .map_err(|_| env.error("Audio sample rate is too high"))?;
257        if sample_rate == 0 {
258            return Err(env.error(format!("{SAMPLE_RATE_REQUIREMENT}, but it is zero")));
259        }
260
261        let value = env.pop(3)?;
262        let bytes = match format.as_str() {
263            "wav" => value_to_wav_bytes(&value, sample_rate).map_err(|e| env.error(e))?,
264            "ogg" => value_to_ogg_bytes(&value, sample_rate).map_err(|e| env.error(e))?,
265            format => {
266                return Err(env.error(format!("Invalid or unsupported audio format: {format}")));
267            }
268        };
269        env.push(Array::<u8>::from(bytes.as_slice()));
270        Ok(())
271    }
272    #[cfg(not(feature = "audio_encode"))]
273    Err(env.error("Audio encoding is not supported in this environment"))
274}
275
276pub(crate) fn audio_decode(env: &mut Uiua) -> UiuaResult {
277    #[cfg(feature = "audio_encode")]
278    {
279        let bytes: crate::cowslice::CowSlice<u8> = match env.pop(1)? {
280            Value::Byte(arr) => {
281                if arr.rank() != 1 {
282                    return Err(env.error(format!(
283                        "Audio bytes array must be rank 1, but is rank {}",
284                        arr.rank()
285                    )));
286                }
287                arr.data
288            }
289            Value::Num(arr) => {
290                if arr.rank() != 1 {
291                    return Err(env.error(format!(
292                        "Audio bytes array must be rank 1, but is rank {}",
293                        arr.rank()
294                    )));
295                }
296                arr.data.iter().map(|&x| x as u8).collect()
297            }
298            _ => return Err(env.error("Audio bytes must be a numeric array")),
299        };
300        if let Ok(((array, sample_rate), format)) = array_from_wav_bytes(&bytes)
301            .map(|a| (a, "wav"))
302            .or_else(|_| array_from_ogg_bytes(&bytes).map(|a| (a, "ogg")))
303        {
304            env.push(array);
305            env.push(sample_rate as usize);
306            env.push(format);
307            Ok(())
308        } else {
309            Err(env.error("Invalid or unsupported audio bytes"))
310        }
311    }
312    #[cfg(not(feature = "audio_encode"))]
313    Err(env.error("Audio decoding is not supported in this environment"))
314}
315
316#[doc(hidden)]
317#[cfg(feature = "image")]
318pub fn value_to_image_bytes(value: &Value, format: ImageFormat) -> Result<Vec<u8>, String> {
319    image_to_bytes(&value_to_image(value)?, format)
320}
321
322#[doc(hidden)]
323#[cfg(feature = "image")]
324pub fn rgb_image_to_array(image: image::RgbImage) -> Array<f64> {
325    let shape = crate::Shape::from([image.height() as usize, image.width() as usize, 3]);
326    Array::new(
327        shape,
328        (image.into_raw().into_iter())
329            .map(|b| b as f64 / 255.0)
330            .collect::<crate::cowslice::CowSlice<_>>(),
331    )
332}
333
334#[doc(hidden)]
335#[cfg(feature = "image")]
336pub fn rgba_image_to_array(image: image::RgbaImage) -> Array<f64> {
337    let shape = crate::Shape::from([image.height() as usize, image.width() as usize, 4]);
338    Array::new(
339        shape,
340        (image.into_raw().into_iter())
341            .map(|b| b as f64 / 255.0)
342            .collect::<crate::cowslice::CowSlice<_>>(),
343    )
344}
345
346#[doc(hidden)]
347#[cfg(not(feature = "image"))]
348pub fn image_bytes_to_array(
349    _bytes: &[u8],
350    _gray: bool,
351    _alpha: bool,
352) -> Result<Array<f64>, String> {
353    Err("Decoding images is not supported in this environment".into())
354}
355
356#[doc(hidden)]
357#[cfg(feature = "image")]
358pub fn image_bytes_to_array(bytes: &[u8], gray: bool, alpha: bool) -> Result<Array<f64>, String> {
359    let image = image::load_from_memory(bytes).map_err(|e| format!("Failed to read image: {e}"))?;
360    Ok(match (gray, alpha) {
361        (false, false) => rgb_image_to_array(image.into_rgb8()),
362        (false, true) => {
363            let image = image.into_rgba8();
364            let shape = crate::Shape::from([image.height() as usize, image.width() as usize, 4]);
365            Array::new(
366                shape,
367                (image.into_raw().into_iter())
368                    .map(|b| b as f64 / 255.0)
369                    .collect::<crate::cowslice::CowSlice<_>>(),
370            )
371        }
372        (true, false) => {
373            let image = image.into_luma16();
374            let shape = crate::Shape::from([image.height() as usize, image.width() as usize]);
375            Array::new(
376                shape,
377                (image.into_raw().into_iter())
378                    .map(|l| l as f64 / u16::MAX as f64)
379                    .collect::<crate::cowslice::CowSlice<_>>(),
380            )
381        }
382        (true, true) => {
383            let image = image.into_luma_alpha16();
384            let shape = crate::Shape::from([image.height() as usize, image.width() as usize, 2]);
385            Array::new(
386                shape,
387                (image.into_raw().into_iter())
388                    .map(|l| l as f64 / u16::MAX as f64)
389                    .collect::<crate::cowslice::CowSlice<_>>(),
390            )
391        }
392    })
393}
394
395#[doc(hidden)]
396#[cfg(feature = "image")]
397pub fn image_to_bytes(image: &DynamicImage, format: ImageFormat) -> Result<Vec<u8>, String> {
398    let mut bytes = std::io::Cursor::new(Vec::new());
399    image
400        .write_to(&mut bytes, format)
401        .map_err(|e| format!("Failed to write image: {e}"))?;
402    Ok(bytes.into_inner())
403}
404
405#[doc(hidden)]
406#[cfg(feature = "image")]
407pub fn value_to_image(value: &Value) -> Result<DynamicImage, String> {
408    let is_complex = matches!(value, Value::Complex(_));
409    if !is_complex && ![2, 3].contains(&value.rank()) || is_complex && value.rank() != 2 {
410        return Err(format!(
411            "Image must be a rank 2 or 3 numeric array, but it is a rank-{} {} array",
412            value.rank(),
413            value.type_name()
414        ));
415    }
416    let bytes = match value {
417        Value::Num(nums) => nums.data.iter().map(|f| (*f * 255.0) as u8).collect(),
418        Value::Byte(bytes) => bytes.data.iter().map(|&b| (b > 0) as u8 * 255).collect(),
419        Value::Complex(comp) => (comp.data.iter())
420            .flat_map(|&c| complex_color(c).map(|c| (c * 255.0) as u8))
421            .collect(),
422        _ => return Err("Image must be a numeric or complex array".into()),
423    };
424    #[allow(clippy::match_ref_pats)]
425    let [height, width, px_size] = match &*value.shape {
426        &[a, b] if is_complex => [a, b, 3],
427        &[a, b] if !is_complex => [a, b, 1],
428        &[a, b, c] => [a, b, c],
429        _ => unreachable!("Shape checked above"),
430    };
431    Ok(match px_size {
432        1 => image::GrayImage::from_raw(width as u32, height as u32, bytes)
433            .ok_or("Failed to create image")?
434            .into(),
435        2 => image::GrayAlphaImage::from_raw(width as u32, height as u32, bytes)
436            .ok_or("Failed to create image")?
437            .into(),
438        3 => image::RgbImage::from_raw(width as u32, height as u32, bytes)
439            .ok_or("Failed to create image")?
440            .into(),
441        4 => image::RgbaImage::from_raw(width as u32, height as u32, bytes)
442            .ok_or("Failed to create image")?
443            .into(),
444        n => {
445            return Err(format!(
446                "For a color image, the last dimension of the image array must be between 1 and 4 but it is {n}"
447            ));
448        }
449    })
450}
451
452fn complex_color(c: Complex) -> [f64; 3] {
453    match (c.re.is_nan(), c.im.is_nan()) {
454        (true, true) => return [0.0; 3],
455        (true, false) | (false, true) => return [0.5; 3],
456        (false, false) => {}
457    }
458    let h = c.arg();
459    let mag = c.abs();
460    let s = (0.3 + 0.7 * (-mag / 10.0).exp()) / (0.3 + 0.7 * (-0.1_f64).exp());
461    let v = (1.0 - (-PI * mag).exp()) / (1.0 - (-PI).exp());
462    hsv_to_rgb(h, s.min(1.0), v.min(1.0))
463}
464
465#[doc(hidden)]
466pub fn value_to_audio_channels(audio: &Value) -> Result<Vec<Vec<f32>>, String> {
467    let orig = audio;
468    let mut audio = audio;
469    let mut transposed;
470    if audio.rank() == 2 && audio.shape[1] > 5 {
471        transposed = audio.clone();
472        transposed.transpose();
473        audio = &transposed;
474    }
475    let interleaved: Vec<f32> = match audio {
476        Value::Num(nums) => nums.data.iter().map(|&n| n as f32).collect(),
477        Value::Byte(byte) => byte.data.iter().map(|&b| b as f32).collect(),
478        _ => return Err("Audio must be a numeric array".into()),
479    };
480    let (length, mut channels) = match &*audio.shape {
481        [_] => (interleaved.len(), vec![interleaved]),
482        &[len, ch] => (
483            len,
484            (0..ch)
485                .map(|c| (0..len).map(|i| interleaved[i * ch + c]).collect())
486                .collect(),
487        ),
488        _ => {
489            return Err(format!(
490                "Audio must be a rank 1 or 2 numeric array, but it is rank {}",
491                orig.rank()
492            ));
493        }
494    };
495    if channels.len() > 5 {
496        return Err(format!(
497            "Audio can have at most 5 channels, but its shape is {}",
498            orig.shape
499        ));
500    }
501
502    if channels.is_empty() {
503        channels.push(vec![0.0; length]);
504    }
505    Ok(channels)
506}
507
508#[doc(hidden)]
509#[cfg(feature = "audio_encode")]
510pub fn value_to_wav_bytes(audio: &Value, sample_rate: u32) -> Result<Vec<u8>, String> {
511    if sample_rate == 0 {
512        return Err("Sample rate must not be 0".to_string());
513    }
514    let channels = value_to_audio_channels(audio)?;
515    #[cfg(not(feature = "audio"))]
516    {
517        channels_to_wav_bytes_impl(
518            channels,
519            |f| (f * i16::MAX as f32) as i16,
520            16,
521            SampleFormat::Int,
522            sample_rate,
523        )
524    }
525    #[cfg(feature = "audio")]
526    {
527        channels_to_wav_bytes_impl(channels, |f| f, 32, SampleFormat::Float, sample_rate)
528    }
529}
530
531#[doc(hidden)]
532#[cfg(feature = "audio_encode")]
533pub fn value_to_ogg_bytes(_audio: &Value, _sample_rate: u32) -> Result<Vec<u8>, String> {
534    #[cfg(feature = "ogg")]
535    {
536        use vorbis_rs::*;
537        if _sample_rate == 0 {
538            return Err("Sample rate must not be 0".to_string());
539        }
540        let channels = value_to_audio_channels(_audio)?;
541        let mut bytes = Vec::new();
542        let mut encoder = VorbisEncoderBuilder::new(
543            _sample_rate.try_into().unwrap(),
544            (channels.len() as u8).try_into().unwrap(),
545            &mut bytes,
546        )
547        .map_err(|e| e.to_string())?
548        .build()
549        .map_err(|e| e.to_string())?;
550        let mut start = 0;
551        let mut buffers = vec![[].as_slice(); channels.len()];
552        const WINDOW_SIZE: usize = 1000;
553        while start < channels[0].len() {
554            let end = channels[0].len().min(start + WINDOW_SIZE);
555            for (i, ch) in channels.iter().enumerate() {
556                buffers[i] = &ch[start..end];
557            }
558            encoder
559                .encode_audio_block(&buffers)
560                .map_err(|e| e.to_string())?;
561            start += WINDOW_SIZE;
562        }
563        drop(encoder);
564        Ok(bytes)
565    }
566    #[cfg(not(feature = "ogg"))]
567    Err("ogg encoding is not supported in this environment".into())
568}
569
570#[cfg(feature = "audio_encode")]
571fn channels_to_wav_bytes_impl<T: hound::Sample + Copy>(
572    channels: Vec<Vec<f32>>,
573    convert_samples: impl Fn(f32) -> T + Copy,
574    bits_per_sample: u16,
575    sample_format: SampleFormat,
576    sample_rate: u32,
577) -> Result<Vec<u8>, String> {
578    // We use i16 samples for compatibility with Firefox (if I remember correctly)
579    let channels: Vec<Vec<T>> = channels
580        .into_iter()
581        .map(|c| c.into_iter().map(convert_samples).collect())
582        .collect();
583    let spec = WavSpec {
584        channels: channels.len() as u16,
585        sample_rate,
586        bits_per_sample,
587        sample_format,
588    };
589    let mut bytes = std::io::Cursor::new(Vec::new());
590    let mut writer = WavWriter::new(&mut bytes, spec).map_err(|e| e.to_string())?;
591    for i in 0..channels[0].len() {
592        for channel in &channels {
593            writer
594                .write_sample(channel[i])
595                .map_err(|e| format!("Failed to write audio: {e}"))?;
596        }
597    }
598    writer
599        .finalize()
600        .map_err(|e| format!("Failed to finalize audio: {e}"))?;
601    Ok(bytes.into_inner())
602}
603
604#[doc(hidden)]
605#[cfg(feature = "audio_encode")]
606pub fn stereo_to_wave_bytes<T: hound::Sample + Copy>(
607    samples: &[[f64; 2]],
608    convert_samples: impl Fn(f64) -> T + Copy,
609    bits_per_sample: u16,
610    sample_format: SampleFormat,
611    sample_rate: u32,
612) -> Result<Vec<u8>, String> {
613    let spec = WavSpec {
614        channels: 2,
615        sample_rate,
616        bits_per_sample,
617        sample_format,
618    };
619    let mut bytes = std::io::Cursor::new(Vec::new());
620    let mut writer = WavWriter::new(&mut bytes, spec).map_err(|e| e.to_string())?;
621    for frame in samples {
622        for sample in frame {
623            writer
624                .write_sample(convert_samples(*sample))
625                .map_err(|e| format!("Failed to write audio: {e}"))?;
626        }
627    }
628    writer
629        .finalize()
630        .map_err(|e| format!("Failed to finalize audio: {e}"))?;
631    Ok(bytes.into_inner())
632}
633
634#[cfg(not(feature = "audio_encode"))]
635#[doc(hidden)]
636pub fn array_from_wav_bytes(_bytes: &[u8]) -> Result<(Array<f64>, u32), String> {
637    Err("Audio decoding is not supported in this environment".into())
638}
639
640#[cfg(feature = "audio_encode")]
641#[doc(hidden)]
642pub fn array_from_wav_bytes(bytes: &[u8]) -> Result<(Array<f64>, u32), String> {
643    let mut reader: WavReader<std::io::Cursor<&[u8]>> =
644        WavReader::new(std::io::Cursor::new(bytes)).map_err(|e| e.to_string())?;
645    let spec = reader.spec();
646    match (spec.sample_format, spec.bits_per_sample) {
647        (SampleFormat::Int, 8) => {
648            array_from_wav_bytes_impl::<i8>(&mut reader, |i| i as f64 / i8::MAX as f64)
649        }
650        (SampleFormat::Int, 16) => {
651            array_from_wav_bytes_impl::<i16>(&mut reader, |i| i as f64 / i16::MAX as f64)
652        }
653        (SampleFormat::Int, 32) => {
654            array_from_wav_bytes_impl::<i32>(&mut reader, |i| i as f64 / i32::MAX as f64)
655        }
656        (SampleFormat::Float, 32) => array_from_wav_bytes_impl::<f32>(&mut reader, |f| f as f64),
657        (sample_format, bits_per_sample) => Err(format!(
658            "Unsupported sample format: {sample_format:?} {bits_per_sample} bits per sample"
659        )),
660    }
661}
662
663#[cfg(not(feature = "audio_encode"))]
664#[doc(hidden)]
665pub fn array_from_ogg_bytes(_bytes: &[u8]) -> Result<(Array<f64>, u32), String> {
666    Err("Audio decoding is not supported in this environment".into())
667}
668
669#[cfg(feature = "audio_encode")]
670#[doc(hidden)]
671pub fn array_from_ogg_bytes(_bytes: &[u8]) -> Result<(Array<f64>, u32), String> {
672    #[cfg(feature = "ogg")]
673    {
674        use vorbis_rs::*;
675        let mut decoder = VorbisDecoder::<&[u8]>::new(_bytes).map_err(|e| e.to_string())?;
676        let sample_rate: u32 = decoder.sampling_frequency().into();
677        let channel_count = u8::from(decoder.channels()) as usize;
678        let mut channels = vec![Vec::new(); channel_count];
679        while let Some(block) = decoder.decode_audio_block().map_err(|e| e.to_string())? {
680            for (i, ch) in block.samples().iter().enumerate() {
681                channels[i].extend(ch.iter().map(|&s| s as f64));
682            }
683        }
684        let shape = if channel_count == 1 {
685            Shape::from(channels[0].len())
686        } else {
687            Shape::from([channel_count, channels[0].len()])
688        };
689        let mut channels = channels.into_iter();
690        let mut data = EcoVec::from(channels.next().unwrap());
691        for ch in channels {
692            data.extend_from_slice(&ch);
693        }
694        Ok((Array::new(shape, data), sample_rate))
695    }
696    #[cfg(not(feature = "ogg"))]
697    Err("ogg decoding is not supported in this environment".into())
698}
699
700#[cfg(feature = "audio_encode")]
701fn array_from_wav_bytes_impl<T: hound::Sample>(
702    reader: &mut WavReader<std::io::Cursor<&[u8]>>,
703    sample_to_f64: impl Fn(T) -> f64,
704) -> Result<(Array<f64>, u32), String> {
705    use ecow::EcoVec;
706
707    let channel_count = reader.spec().channels as usize;
708    let mut samples = EcoVec::new();
709    let mut curr_channel = 0;
710    for sample in reader.samples::<T>() {
711        let sample = sample.map_err(|e| e.to_string())?;
712        samples.push(sample_to_f64(sample));
713        curr_channel = (curr_channel + 1) % channel_count;
714    }
715
716    let sample_rate = reader.spec().sample_rate;
717    let arr = if channel_count == 1 {
718        samples.into()
719    } else {
720        Array::new([samples.len() / channel_count, channel_count], samples)
721    };
722    Ok((arr, sample_rate))
723}
724
725#[doc(hidden)]
726#[cfg(feature = "gif")]
727pub fn value_to_gif_bytes(value: &Value, frame_rate: f64) -> Result<Vec<u8>, String> {
728    // Maybe the array is the already-encoded GIF
729    if let Value::Byte(arr) = value
730        && gif::Decoder::new(arr.data.as_slice()).is_ok()
731    {
732        return Ok(arr.data.as_slice().into());
733    }
734    // Faster and higher-quality for grayscale GIFs
735    if value.rank() == 3 && value.type_id() == f64::TYPE_ID {
736        let (width, height) = (value.shape[2], value.shape[1]);
737        return match value {
738            Value::Num(arr) => encode_grayscale_gif(frame_rate, width, height, &arr.data),
739            Value::Byte(arr) => encode_grayscale_gif(frame_rate, width, height, &arr.data),
740            _ => unreachable!(),
741        };
742    }
743
744    // Encode frames from rows
745    let mut rows = value.rows();
746    encode_gif_impl(frame_rate, &mut (), |_| Ok(rows.next()), |_, e| e)
747}
748
749#[cfg(feature = "gif")]
750static GIF_PALETTE: std::sync::LazyLock<Vec<u8>> = std::sync::LazyLock::new(|| {
751    let mut palette = vec![0; 256 * 3];
752    for r in 0u8..8 {
753        for g in 0u8..8 {
754            for b in 0u8..4 {
755                let q = (((r << 5) | (g << 2) | b) as usize + 1).min(255);
756                // println!("{:?} -> {q}", [r, g, b]);
757                palette[q * 3] = (r as f32 / 7.0 * 255.0).round() as u8;
758                palette[q * 3 + 1] = (g as f32 / 7.0 * 255.0).round() as u8;
759                palette[q * 3 + 2] = (b as f32 / 3.0 * 255.0).round() as u8;
760            }
761        }
762    }
763    palette
764});
765
766#[cfg(feature = "gif")]
767fn nearest_color(image::Rgba([r, g, b, a]): image::Rgba<u8>) -> (u8, [f32; 3]) {
768    if a == 0 {
769        return (0, [0.0; 3]);
770    }
771    macro_rules! comp {
772        ($name:ident, $n:literal) => {
773            static $name: [(u8, f32); 256] = {
774                let mut arr = [(0u8, 0.0); 256];
775                let mut i = 0;
776                while i < 256 {
777                    const K: f32 = $n / 255.0;
778                    let mut qf = i as f32 * K;
779                    let floor = qf as u8 as f32;
780                    let fract = qf - floor;
781                    qf = floor;
782                    if fract >= 0.5 {
783                        qf += 1.0;
784                    }
785                    let q = qf as u8;
786                    let e = i as f32 - qf / K;
787                    arr[i] = (q, e);
788                    i += 1;
789                }
790                arr
791            };
792        };
793    }
794    comp!(RS, 7.0);
795    comp!(GS, 7.0);
796    comp!(BS, 3.0);
797    let (rq, re) = RS[r as usize];
798    let (gq, ge) = GS[g as usize];
799    let (bq, be) = BS[b as usize];
800    let q = ((rq << 5) | (gq << 2) | bq).saturating_add(1);
801    (q, [re, ge, be])
802}
803
804/// Returns flat dithered color indices and whether there are any transparent pixels
805#[cfg(feature = "gif")]
806fn dither(mut img: image::RgbaImage, width: u32, height: u32) -> (Vec<u8>, bool) {
807    let (width, height) = (width, height);
808    let mut buffer = vec![0; (width * height) as usize];
809    let mut has_transparent = false;
810    for y in 0..height {
811        // TODO: Maybe use a scanline buffer for the adjusted colors on this line
812        for x in 0..width {
813            let (index, err) = nearest_color(img[(x, y)]);
814            has_transparent |= index == 0;
815            buffer[(y * width + x) as usize] = index;
816            if err == [0.0; 3] {
817                continue;
818            }
819            let [er, eg, eb] = err;
820            for (f, dx, dy) in [
821                (7f32 / 16.0, 1i32, 0i32),
822                (3f32 / 16.0, -1, 1),
823                (5f32 / 16.0, 0, 1),
824                (1f32 / 16.0, 1, 1),
825            ] {
826                let Some(image::Rgba([r, g, b, a])) =
827                    img.get_pixel_mut_checked((x as i32 + dx) as u32, (y as i32 + dy) as u32)
828                else {
829                    continue;
830                };
831                if *a == 0 {
832                    continue;
833                }
834                *r = (*r as f32 + er * f).round() as u8;
835                *g = (*g as f32 + eg * f).round() as u8;
836                *b = (*b as f32 + eb * f).round() as u8;
837            }
838        }
839    }
840    (buffer, has_transparent)
841}
842
843#[cfg(not(feature = "gif"))]
844pub(crate) fn fold_to_gif(_f: SigNode, env: &mut Uiua) -> UiuaResult<Vec<u8>> {
845    Err(env.error("GIF encoding is not supported in this environment"))
846}
847
848#[cfg(feature = "gif")]
849pub(crate) fn fold_to_gif(f: SigNode, env: &mut Uiua) -> UiuaResult<Vec<u8>> {
850    use crate::algorithm::{FixedRowsData, fixed_rows};
851
852    let acc_count = f.sig.outputs().saturating_sub(1);
853    let iter_count = f.sig.args().saturating_sub(acc_count);
854
855    if f.sig.outputs() < 1 {
856        return Err(env.error(format!(
857            "gif!'s function must have at least 1 output, \
858            but its signature is {}",
859            f.sig
860        )));
861    }
862
863    let frame_rate = env
864        .pop("frame rate")?
865        .as_num(env, "Framerate must be a number")?;
866
867    let mut args = Vec::with_capacity(iter_count);
868    for i in 0..iter_count {
869        args.push(env.pop(i + 1)?);
870    }
871    let FixedRowsData {
872        mut rows,
873        row_count: frame_count,
874        ..
875    } = fixed_rows("gif", 1, args, env)?;
876
877    let mut i = 0;
878    encode_gif_impl(
879        frame_rate,
880        env,
881        |env| -> UiuaResult<_> {
882            if i == frame_count {
883                return Ok(None);
884            }
885            i += 1;
886            for arg in rows.iter_mut().rev() {
887                match arg {
888                    Ok(rows) => env.push(rows.next().unwrap()),
889                    Err(row) => env.push(row.clone()),
890                }
891            }
892            env.exec(f.clone())?;
893            Ok(Some(env.pop("frame")?))
894        },
895        |env, e| env.error(e),
896    )
897    .map_err(|e| env.error(e))
898}
899
900#[cfg(feature = "gif")]
901fn encode_gif_impl<C, E>(
902    mut frame_rate: f64,
903    ctx: &mut C,
904    mut next_frame: impl FnMut(&mut C) -> Result<Option<Value>, E>,
905    error: impl Fn(&C, String) -> E,
906) -> Result<Vec<u8>, E> {
907    use gif::{DisposalMethod, Encoder, Frame};
908    use image::GrayImage;
909
910    let first_frame =
911        next_frame(ctx)?.ok_or_else(|| error(ctx, "Cannot encode empty GIF".into()))?;
912    let (width, height) = (
913        first_frame.shape.get(1).copied().unwrap_or(0) as u32,
914        first_frame.shape.first().copied().unwrap_or(0) as u32,
915    );
916    if width > u16::MAX as u32 || height > u16::MAX as u32 {
917        let message = format!(
918            "GIF dimensions must be at most {}x{}, but the frames are {width}x{height}",
919            u16::MAX,
920            u16::MAX,
921        );
922        return Err(error(ctx, message));
923    }
924
925    let mut bytes = std::io::Cursor::new(Vec::new());
926    const MIN_FRAME_RATE: f64 = 1.0 / 60.0;
927    frame_rate = frame_rate.max(MIN_FRAME_RATE).abs();
928    let mut t = 0;
929
930    let mut encoder = if first_frame.rank() == 2 && first_frame.type_id() == f64::TYPE_ID {
931        let first_frame = value_to_image(&first_frame)
932            .map_err(|e| error(ctx, e))?
933            .to_luma8();
934        let pallete: Vec<u8> = (0..=255).flat_map(|c| [c, c, c]).collect();
935        let mut encoder = Encoder::new(&mut bytes, width as u16, height as u16, &pallete)
936            .map_err(|e| error(ctx, e.to_string()))?;
937        let mut write_frame = |i: usize, frame: GrayImage, ctx: &mut C| -> Result<(), E> {
938            let mut frame =
939                Frame::from_indexed_pixels(width as u16, height as u16, frame.into_raw(), None);
940            frame.delay = ((i + 1) as f64 * 100.0 / frame_rate).round() as u16 - t;
941            t += frame.delay;
942            (encoder.write_frame(&frame)).map_err(|e| error(ctx, e.to_string()))?;
943            Ok(())
944        };
945        write_frame(0, first_frame, ctx)?;
946        let mut i = 1;
947        while let Some(frame) = next_frame(ctx)? {
948            let frame = value_to_image(&frame)
949                .map_err(|e| error(ctx, e))?
950                .to_luma8();
951            let (this_width, this_height) = frame.dimensions();
952            if this_width != width || this_height != height {
953                return Err(error(
954                    ctx,
955                    format!(
956                        "First frame was [{width} × {height}], \
957                        but frame {i} is [{this_width} × {this_height}]"
958                    ),
959                ));
960            }
961            write_frame(i, frame, ctx)?;
962            i += 1;
963        }
964        encoder
965    } else {
966        let first_frame = value_to_image(&first_frame)
967            .map_err(|e| error(ctx, e))?
968            .to_rgba8();
969        let mut encoder = Encoder::new(&mut bytes, width as u16, height as u16, &GIF_PALETTE)
970            .map_err(|e| error(ctx, e.to_string()))?;
971        let mut write_frame = |i: usize, frame, ctx: &mut C| -> Result<(), E> {
972            let (indices, has_transparent) = dither(frame, width, height);
973            let mut frame =
974                Frame::from_indexed_pixels(width as u16, height as u16, indices, Some(0));
975            frame.delay = ((i + 1) as f64 * 100.0 / frame_rate).round() as u16 - t;
976            frame.dispose = if has_transparent {
977                DisposalMethod::Background
978            } else {
979                DisposalMethod::Any
980            };
981            t += frame.delay;
982            (encoder.write_frame(&frame)).map_err(|e| error(ctx, e.to_string()))?;
983            Ok(())
984        };
985        write_frame(0, first_frame, ctx)?;
986        let mut i = 1;
987        while let Some(frame) = next_frame(ctx)? {
988            let frame = value_to_image(&frame)
989                .map_err(|e| error(ctx, e))?
990                .to_rgba8();
991            let (this_width, this_height) = frame.dimensions();
992            if this_width != width || this_height != height {
993                return Err(error(
994                    ctx,
995                    format!(
996                        "First frame was [{width} × {height}], \
997                        but frame {i} is [{this_width} × {this_height}]"
998                    ),
999                ));
1000            }
1001            write_frame(i, frame, ctx)?;
1002            i += 1;
1003        }
1004        encoder
1005    };
1006    (encoder.set_repeat(gif::Repeat::Infinite)).map_err(|e| error(ctx, e.to_string()))?;
1007    encoder
1008        .into_inner()
1009        .map_err(|e| error(ctx, e.to_string()))?;
1010    Ok(bytes.into_inner())
1011}
1012
1013#[cfg(feature = "gif")]
1014fn encode_grayscale_gif<T>(
1015    mut frame_rate: f64,
1016    width: usize,
1017    height: usize,
1018    data: &[T],
1019) -> Result<Vec<u8>, String>
1020where
1021    T: RealArrayValue,
1022{
1023    use gif::{Encoder, Frame};
1024    if width > u16::MAX as usize || height > u16::MAX as usize {
1025        return Err(format!(
1026            "GIF dimensions must be at most {}x{}, but the frames are {width}x{height}",
1027            u16::MAX,
1028            u16::MAX
1029        ));
1030    }
1031    if width == 0 || height == 0 {
1032        return Err(format!(
1033            "GIF dimensions cannot be 0, but the frames are {width}x{height}"
1034        ));
1035    }
1036    let mut bytes = std::io::Cursor::new(Vec::new());
1037    let pallete: Vec<u8> = (0..=255).flat_map(|c| [c, c, c]).collect();
1038    let mut encoder = Encoder::new(&mut bytes, width as u16, height as u16, &pallete)
1039        .map_err(|e| e.to_string())?;
1040    (encoder.set_repeat(gif::Repeat::Infinite)).map_err(|e| e.to_string())?;
1041    const MIN_FRAME_RATE: f64 = 1.0 / 60.0;
1042    frame_rate = frame_rate.max(MIN_FRAME_RATE).abs();
1043    let mut t = 0;
1044    if width > 0 && height > 0 {
1045        for (i, frame) in data.chunks_exact(width * height).enumerate() {
1046            let frame: Vec<u8> = frame.iter().map(|&x| (x.to_f64() * 255.0) as u8).collect();
1047            let mut frame = Frame::from_indexed_pixels(width as u16, height as u16, frame, None);
1048            frame.delay = ((i + 1) as f64 * 100.0 / frame_rate).round() as u16 - t;
1049            t += frame.delay;
1050            encoder.write_frame(&frame).map_err(|e| e.to_string())?;
1051        }
1052    }
1053    encoder.into_inner().map_err(|e| e.to_string())?;
1054    Ok(bytes.into_inner())
1055}
1056
1057#[doc(hidden)]
1058#[cfg(not(feature = "gif"))]
1059pub fn gif_bytes_to_value(_bytes: &[u8]) -> Result<(f64, Value), String> {
1060    Err("GIF decoding is not supported in this environment".into())
1061}
1062
1063#[doc(hidden)]
1064#[cfg(feature = "gif")]
1065pub fn gif_bytes_to_value(bytes: &[u8]) -> Result<(f64, Value), String> {
1066    gif_bytes_to_value_impl(bytes, gif::ColorOutput::RGBA).map_err(|e| e.to_string())
1067}
1068
1069#[cfg(not(feature = "gif"))]
1070pub(crate) fn gif_bytes_to_value_gray(_bytes: &[u8]) -> Result<(f64, Value), String> {
1071    Err("GIF decoding is not supported in this environment".into())
1072}
1073
1074#[cfg(feature = "gif")]
1075pub(crate) fn gif_bytes_to_value_gray(bytes: &[u8]) -> Result<(f64, Value), String> {
1076    gif_bytes_to_value_impl(bytes, gif::ColorOutput::Indexed).map_err(|e| e.to_string())
1077}
1078
1079#[doc(hidden)]
1080#[cfg(feature = "gif")]
1081pub fn gif_bytes_to_value_impl(
1082    bytes: &[u8],
1083    mode: gif::ColorOutput,
1084) -> Result<(f64, Value), gif::DecodingError> {
1085    let mut decoder = gif::DecodeOptions::new();
1086    decoder.set_color_output(mode);
1087    let mut decoder = decoder.read_info(bytes)?;
1088    let first_frame = decoder.read_next_frame()?.unwrap();
1089    let gif_width = first_frame.width as usize;
1090    let gif_height = first_frame.height as usize;
1091    let mut data: crate::cowslice::CowSlice<f64> = Default::default();
1092    let mut frame_count = 1;
1093    let mut delay_sum = first_frame.delay as f64 / 100.0;
1094    // Init frame data with the first frame
1095    let mut frame_data = first_frame.buffer.to_vec();
1096    match mode {
1097        gif::ColorOutput::RGBA => data.extend(frame_data.iter().map(|b| *b as f64 / 255.0)),
1098        gif::ColorOutput::Indexed => data.extend(frame_data.iter().map(|b| *b as f64)),
1099    }
1100    // Loop through the rest of the frames
1101    while let Some(frame) = decoder.read_next_frame()? {
1102        let frame_width = frame.width as usize;
1103        let frame_height = frame.height as usize;
1104        // Some frames may have different dimensions than the GIF
1105        if frame_width == gif_width && frame_height == gif_height {
1106            frame_data.copy_from_slice(&frame.buffer);
1107        } else {
1108            // Copy the frame into the correct position in the GIF
1109            let frame_left = frame.left as usize;
1110            let frame_top = frame.top as usize;
1111            for dy in 0..frame_height {
1112                let y = frame_top + dy;
1113                for dx in 0..frame_width {
1114                    let x = frame_left + dx;
1115                    let outer_i = (y * gif_width + x) * 4;
1116                    let inner_i = (dy * frame_width + dx) * 4;
1117                    frame_data[outer_i..][..4].copy_from_slice(&frame.buffer[inner_i..][..4]);
1118                }
1119            }
1120        }
1121        match mode {
1122            gif::ColorOutput::RGBA => data.extend(frame_data.iter().map(|b| *b as f64 / 255.0)),
1123            gif::ColorOutput::Indexed => data.extend(frame_data.iter().map(|b| *b as f64)),
1124        }
1125        frame_count += 1;
1126        delay_sum += frame.delay as f64 / 100.0;
1127    }
1128    let avg_delay = delay_sum / frame_count as f64;
1129    let frame_rate = 1.0 / avg_delay;
1130    let mut shape = crate::Shape::from_iter([frame_count, gif_height, gif_width]);
1131    if let gif::ColorOutput::RGBA = mode {
1132        shape.push(4)
1133    }
1134    let mut num = Value::Num(Array::new(shape, data));
1135    num.try_shrink();
1136    Ok((frame_rate, num))
1137}
1138
1139#[doc(hidden)]
1140#[cfg(feature = "apng")]
1141pub(crate) fn value_to_apng_bytes(value: &Value, frame_rate: f64) -> Result<EcoVec<u8>, String> {
1142    use png::{ColorType, Encoder};
1143    fn err(s: &'static str) -> impl Fn(png::EncodingError) -> String {
1144        move |e| format!("Error {s}: {e}")
1145    }
1146
1147    if value.row_count() == 0 {
1148        return Err("Cannot convert empty array into APNG".into());
1149    }
1150    if value.rank() < 3 {
1151        return Err("APNG array must be at least rank 3".into());
1152    }
1153    let frame_count = value.shape[0] as u32;
1154    let height = value.shape[1] as u32;
1155    let width = value.shape[2] as u32;
1156    let mut buffer = EcoVec::new();
1157    let mut encoder = Encoder::new(&mut buffer, width, height);
1158    (encoder.set_animated(frame_count, 0)).map_err(err("marking as animated"))?;
1159    (encoder.set_frame_delay(1, (frame_rate.round() as u16).max(1)))
1160        .map_err(err("setting frame delay"))?;
1161    encoder.set_color(ColorType::Rgba);
1162    let mut writer = encoder.write_header().map_err(err("writing header"))?;
1163    for row in value.rows() {
1164        let image = value_to_image(&row)?.into_rgba8();
1165        (writer.write_image_data(&image.into_raw())).map_err(err("writing frame"))?;
1166    }
1167    writer.finish().map_err(err("finishing encoding"))?;
1168    Ok(buffer)
1169}
1170
1171macro_rules! builtin_params {
1172    ($name:ident, $(($param:ident, $comment:literal, $default:expr)),* $(,)?) => {
1173        #[derive(Sequence)]
1174        pub(crate) enum $name {
1175            $($param,)*
1176        }
1177        impl $name {
1178            pub fn args() -> Vec<OptionalArg> {
1179                all::<Self>()
1180                    .map(|param| {
1181                        let (name, comment, default) = match param {
1182                            $($name::$param => (stringify!($param), $comment, $default.into()),)*
1183                        };
1184                        OptionalArg { name, comment, default }
1185                    })
1186                    .collect()
1187            }
1188        }
1189    }
1190}
1191
1192builtin_params!(
1193    VoxelsParam,
1194    (Fog, "Color for depth fog", Value::default()),
1195    (Scale, "Number of pixels per voxel", 1),
1196    (Camera, "The position of the camera", [1, 1, 1]),
1197);
1198
1199pub(crate) fn voxels(val: Value, args: Option<Value>, env: &mut Uiua) -> UiuaResult<Value> {
1200    if ![3, 4].contains(&val.rank()) {
1201        return Err(env.error(format!(
1202            "Voxel array must be rank 3 or 4, but its shape is {}",
1203            val.shape
1204        )));
1205    }
1206    if val.rank() == 4 && ![2, 3, 4].contains(&val.shape[3]) {
1207        return Err(env.error(format!(
1208            "Rank 4 voxel array must have a last \
1209            dimension of 2, 3, or 4, but its shape is {}",
1210            val.shape
1211        )));
1212    }
1213    let arr = match val {
1214        Value::Num(arr) => arr,
1215        Value::Byte(arr) => arr.convert(),
1216        Value::Complex(arr) => {
1217            let mut shape = arr.shape.clone();
1218            let data: EcoVec<_> = if shape.last() == Some(&2) {
1219                shape.pop();
1220                shape.push(4);
1221                let mut data = eco_vec![0.0; shape.elements()];
1222                let slice = data.make_mut();
1223                for (i, &c) in arr.data.iter().enumerate() {
1224                    if i % 2 == 0 {
1225                        let rgb = complex_color(c);
1226                        for j in 0..3 {
1227                            slice[i / 2 * 4 + j] = rgb[j];
1228                        }
1229                    } else {
1230                        slice[i / 2 * 4 + 3] = c.abs();
1231                    }
1232                }
1233                data
1234            } else {
1235                shape.push(3);
1236                arr.data.iter().flat_map(|&c| complex_color(c)).collect()
1237            };
1238            Array::new(shape, data)
1239        }
1240        val => {
1241            return Err(env.error(format!(
1242                "Voxel array must be numeric, but it is {}",
1243                val.type_name_plural()
1244            )));
1245        }
1246    };
1247    let mut pos: Option<[f64; 3]> = None;
1248    let mut scale = None;
1249    let mut fog = None;
1250    for (i, arg) in args
1251        .into_iter()
1252        .flat_map(Value::into_rows)
1253        .map(Value::unboxed)
1254        .enumerate()
1255    {
1256        match all::<VoxelsParam>().nth(i) {
1257            Some(VoxelsParam::Fog) => {
1258                if arg.shape == 0 {
1259                    continue;
1260                }
1261                let nums = arg.as_nums(env, "Fog must be a scalar number or 3 numbers")?;
1262                match *nums {
1263                    [gray] if arg.shape.is_empty() => fog = Some([gray; 3]),
1264                    [r, g, b] => fog = Some([r, g, b]),
1265                    _ => {
1266                        return Err(env.error(format!(
1267                            "Fog must be a scalar or list of 3 numbers, but its shape is {}",
1268                            arg.shape
1269                        )));
1270                    }
1271                }
1272            }
1273            Some(VoxelsParam::Scale) => scale = Some(arg.as_num(env, "Scale must be a number")?),
1274            Some(VoxelsParam::Camera) => {
1275                let nums = arg.as_nums(env, "Camera position must be 3 numbers")?;
1276                if let [x, y, z] = *nums {
1277                    pos = Some([x, y, z]);
1278                } else {
1279                    return Err(env.error(format!(
1280                        "Camera position must be 3 numbers, but its shape is {}",
1281                        arg.shape
1282                    )));
1283                }
1284            }
1285            None => return Err(env.error(format!("Invalid voxels params index {i}"))),
1286        }
1287    }
1288
1289    let mut pos_arg = pos.unwrap_or([1.0, 1.0, 1.0]);
1290    let scale = scale.unwrap_or(1.0);
1291
1292    fn map<A: Copy, B: Copy, C, const N: usize>(
1293        a: [A; N],
1294        b: [B; N],
1295        f: impl Fn(A, B) -> C,
1296    ) -> [C; N] {
1297        std::array::from_fn(|i| f(a[i], b[i]))
1298    }
1299    fn mul(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
1300        map(a, b, |a, b| a * b)
1301    }
1302    fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
1303        map(a, b, |a, b| a + b)
1304    }
1305    fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
1306        map(a, b, |a, b| a - b)
1307    }
1308    fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
1309        mul(a, b).iter().sum()
1310    }
1311    fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
1312        [
1313            a[1] * b[2] - a[2] * b[1],
1314            a[2] * b[0] - a[0] * b[2],
1315            a[0] * b[1] - a[1] * b[0],
1316        ]
1317    }
1318    fn plane_point(normal: [f64; 3], d: f64, point: [f64; 3]) -> [f64; 3] {
1319        let mag = (dot(normal, point) - d) / dot(normal, normal);
1320        let offset = normal.map(|n| n * mag);
1321        sub(point, offset)
1322    }
1323    fn mag(v: [f64; 3]) -> f64 {
1324        dot(v, v).sqrt()
1325    }
1326    fn norm(v: [f64; 3]) -> [f64; 3] {
1327        let mag = mag(v);
1328        v.map(|x| x / mag)
1329    }
1330
1331    #[derive(Clone, Copy, PartialEq)]
1332    enum Mode {
1333        Gray,
1334        GrayA,
1335        Rgb,
1336        Rgba,
1337    }
1338    let mode = if arr.rank() == 3 {
1339        Mode::Gray
1340    } else if arr.shape[3] == 2 {
1341        Mode::GrayA
1342    } else if arr.shape[3] == 3 {
1343        Mode::Rgb
1344    } else {
1345        Mode::Rgba
1346    };
1347    let vox_size = match mode {
1348        Mode::Gray => 1,
1349        Mode::GrayA => 2,
1350        Mode::Rgb => 3,
1351        Mode::Rgba => 4,
1352    };
1353    let fog_has_hue = fog.is_some_and(|fog| fog.windows(2).any(|w| w[0] != w[1]));
1354    let pix_size = match mode {
1355        Mode::Gray if fog_has_hue => 3,
1356        Mode::GrayA if fog_has_hue => 4,
1357        Mode::Gray => 1,
1358        Mode::GrayA => 2,
1359        Mode::Rgb => 3,
1360        Mode::Rgba => 4,
1361    };
1362    let color_size = (pix_size - 1) / 2 * 2 + 1;
1363
1364    let max_dim = arr.shape.iter().take(3).copied().max().unwrap_or(0);
1365    let scene_radius = max_dim as f64 / 2.0;
1366    let shell_radius = (arr.shape.iter())
1367        .fold(0.0, |acc, &x| acc + (x as f64).powi(2))
1368        .sqrt()
1369        / 2.0;
1370    let res_dim = (shell_radius * 2.0 * scale).round() as usize;
1371    let mut res_shape = Shape::from([res_dim; 2]);
1372    let mut idxs = vec![0; res_shape.elements()];
1373    let mut depth_buf = vec![f64::INFINITY; res_shape.elements()];
1374    let mut translucents: Vec<(usize, usize, f64)> = Vec::new();
1375
1376    let target = [scene_radius; 3];
1377    if pos_arg == [0.0; 3] {
1378        pos_arg = [1.1; 3];
1379    }
1380    pos_arg = norm(pos_arg);
1381    let cam_pos = [
1382        target[0] + shell_radius * pos_arg[0],
1383        target[1] + shell_radius * pos_arg[1],
1384        target[2] + shell_radius * pos_arg[2],
1385    ];
1386    let shell_dist = mag(sub(target, cam_pos)) - shell_radius;
1387    let normal = norm(sub(target, cam_pos));
1388    let d = dot(normal, cam_pos);
1389    let cam_center = plane_point(normal, d, target);
1390    let up_hint = if normal[0].abs() < 0.999 {
1391        [1.0, 0.0, 0.0]
1392    } else {
1393        [0.0, 1.0, 0.0]
1394    };
1395    let u = norm(cross(up_hint, normal));
1396    let v = cross(normal, u);
1397
1398    // println!("im radius: {shell_radius:.3}");
1399    // println!("scene radius: {scene_radius:.3}");
1400    // println!("shell dist: {shell_dist:.3}");
1401    // println!("res_dim: {res_dim}");
1402    // println!("pos: {pos:.3?}");
1403    // println!("cam_center: {cam_center:.3?}");
1404    // println!("target: {target:.3?}");
1405    // println!("normal: {normal:.3?}");
1406    // println!("u: {u:.3?}, v: {v:.3?}");
1407
1408    let x_stride = arr.shape[1] * arr.shape[2];
1409    let y_stride = arr.shape[2];
1410    let scale_start = 0.5 / scale;
1411    let scale_step = 1.0 / scale;
1412    let scale_steps = (scale.round() as usize).max(1);
1413    let offset = [
1414        (max_dim - arr.shape[0]) as f64 / 2.0,
1415        (max_dim - arr.shape[1]) as f64 / 2.0,
1416        (max_dim - arr.shape[2]) as f64 / 2.0,
1417    ];
1418    // Precompute scaling offsets
1419    let mut voxel_surface_offsets = Vec::with_capacity(scale_steps * scale_steps * 6);
1420    for i in 0..scale_steps {
1421        let di = scale_start + i as f64 * scale_step;
1422        for j in 0..scale_steps {
1423            let dj = scale_start + j as f64 * scale_step;
1424            for k in 0..scale_steps {
1425                if ![i, j, k].iter().any(|&x| x == 0 || x == scale_steps - 1) {
1426                    continue;
1427                }
1428                let dk = scale_start + k as f64 * scale_step;
1429                let offset = [di, dj, dk];
1430                voxel_surface_offsets.push(offset);
1431            }
1432        }
1433    }
1434    // Fill indices and depth buffer
1435    for i in 0..arr.shape[0] {
1436        for j in 0..arr.shape[1] {
1437            env.respect_execution_limit()?;
1438            for k in 0..arr.shape[2] {
1439                let arr_index = i * x_stride + j * y_stride + k;
1440                match mode {
1441                    Mode::Gray if arr.data[arr_index] == 0.0 => continue,
1442                    Mode::GrayA if arr.data[arr_index * 2 + 1] == 0.0 => continue,
1443                    Mode::Rgb if arr.data[arr_index * 3..][..3] == [0.0; 3] => continue,
1444                    Mode::Rgba if arr.data[arr_index * 4 + 3] == 0.0 => continue,
1445                    _ => {}
1446                }
1447                let corner = add([i, j, k].map(|d| d as f64), offset);
1448                for &offset in &voxel_surface_offsets {
1449                    let center = add(corner, offset);
1450                    let proj = plane_point(normal, d, center);
1451                    let delta = sub(center, proj);
1452                    let cam_delta = sub(proj, cam_center);
1453                    let x = (shell_radius - dot(cam_delta, u)) * scale;
1454                    let y = (shell_radius - dot(cam_delta, v)) * scale;
1455                    if x < 0.0 || y < 0.0 {
1456                        continue;
1457                    }
1458                    let x = x.floor() as usize;
1459                    let y = y.floor() as usize;
1460                    if x >= res_dim || y >= res_dim {
1461                        continue;
1462                    }
1463                    let dist = mag(delta);
1464                    let im_index = y * res_dim + x;
1465                    if dist < depth_buf[im_index] {
1466                        match mode {
1467                            Mode::GrayA if arr.data[arr_index * 2 + 1] != 1.0 => {
1468                                translucents.push((im_index, arr_index, dist))
1469                            }
1470                            Mode::Rgba if arr.data[arr_index * 4 + 3] != 1.0 => {
1471                                translucents.push((im_index, arr_index, dist))
1472                            }
1473                            _ => {
1474                                depth_buf[im_index] = dist;
1475                                idxs[im_index] = arr_index;
1476                            }
1477                        }
1478                    }
1479                }
1480            }
1481        }
1482    }
1483    // Render opaques
1484    let fog_mul =
1485        |depth: f64, alpha: f64| 1.0 - alpha * (depth - shell_dist) / (shell_radius * 2.0);
1486    if pix_size != 1 {
1487        res_shape.push(pix_size);
1488    }
1489    let mut res_data = if let Some(fog) = fog {
1490        if !fog_has_hue && matches!(mode, Mode::Gray | Mode::GrayA) {
1491            // Grayscale image with grayscale fog
1492            let fog = fog[0];
1493            let mut res_data = eco_vec![0f64; res_shape.elements()];
1494            for ((index, px), &depth) in idxs
1495                .into_iter()
1496                .zip(res_data.make_mut().chunks_exact_mut(pix_size))
1497                .zip(&depth_buf)
1498            {
1499                if depth == f64::INFINITY {
1500                    continue;
1501                }
1502                let factor = fog_mul(depth, 1.0);
1503                px[0] = arr.data[index * vox_size] * factor + fog * (1.0 - factor);
1504                if mode == Mode::GrayA {
1505                    px[1] = 1.0;
1506                }
1507            }
1508            res_data
1509        } else {
1510            // Image with colored fog
1511            let mut res_data = eco_vec![0f64; res_shape.elements()];
1512            for ((index, px), &depth) in idxs
1513                .into_iter()
1514                .zip(res_data.make_mut().chunks_exact_mut(pix_size))
1515                .zip(&depth_buf)
1516            {
1517                if depth == f64::INFINITY {
1518                    continue;
1519                }
1520                let factor = fog_mul(depth, 1.0);
1521                match mode {
1522                    Mode::Gray | Mode::GrayA => {
1523                        for i in 0..3 {
1524                            px[i] = arr.data[index * vox_size] * factor + fog[i] * (1.0 - factor);
1525                        }
1526                    }
1527                    Mode::Rgb | Mode::Rgba => {
1528                        for i in 0..3 {
1529                            px[i] =
1530                                arr.data[index * vox_size + i] * factor + fog[i] * (1.0 - factor);
1531                        }
1532                    }
1533                }
1534                if matches!(mode, Mode::GrayA | Mode::Rgba) {
1535                    px[pix_size - 1] = 1.0;
1536                }
1537            }
1538            res_data
1539        }
1540    } else {
1541        match mode {
1542            Mode::Gray | Mode::GrayA => {
1543                // Grayscale image without fog
1544                let mut res_data = eco_vec![0f64; res_shape.elements()];
1545                for ((index, px), &depth) in idxs
1546                    .into_iter()
1547                    .zip(res_data.make_mut().chunks_exact_mut(pix_size))
1548                    .zip(&depth_buf)
1549                {
1550                    if depth == f64::INFINITY {
1551                        continue;
1552                    }
1553                    px[0] = arr.data[index * vox_size];
1554                    if mode == Mode::GrayA {
1555                        px[1] = 1.0;
1556                    }
1557                }
1558                res_data
1559            }
1560            Mode::Rgb | Mode::Rgba => {
1561                // Colored image without fog
1562                let mut res_data = eco_vec![0f64; res_shape.elements()];
1563                for ((index, px), &depth) in idxs
1564                    .into_iter()
1565                    .zip(res_data.make_mut().chunks_exact_mut(pix_size))
1566                    .zip(&depth_buf)
1567                {
1568                    if depth == f64::INFINITY {
1569                        continue;
1570                    }
1571                    for i in 0..color_size {
1572                        px[i] = arr.data[index * vox_size + i];
1573                    }
1574                    if matches!(mode, Mode::Rgba) {
1575                        px[3] = 1.0;
1576                    }
1577                }
1578                res_data
1579            }
1580        }
1581    };
1582    // Render translucents
1583    translucents.sort_by(|(ai, aa, ad), (bi, ba, bd)| {
1584        ((ai, aa).cmp(&(bi, ba))).then_with(|| ad.partial_cmp(bd).unwrap())
1585    });
1586    translucents.dedup_by_key(|(i, a, _)| (*i, *a));
1587    translucents.sort_by(|(_, _, a), (_, _, b)| a.partial_cmp(b).unwrap());
1588    let image = res_data.make_mut();
1589    for (im_index, arr_index, dist) in translucents.into_iter().rev() {
1590        if depth_buf[im_index] < dist {
1591            continue;
1592        }
1593        let vox_alpha = arr.data[arr_index * vox_size + color_size];
1594        for i in 0..color_size {
1595            let bg = image[im_index * pix_size + i];
1596            let fg = arr.data[arr_index * vox_size + i];
1597            let new = (1.0 - vox_alpha) * bg + vox_alpha * fg;
1598            image[im_index * pix_size + i] = new;
1599        }
1600        image[im_index * pix_size + color_size] =
1601            (image[im_index * pix_size + color_size] + vox_alpha).min(1.0);
1602        if let Some(fog) = fog {
1603            let factor = fog_mul(dist, vox_alpha);
1604            for i in 0..color_size {
1605                image[im_index * pix_size + i] =
1606                    image[im_index * pix_size + i] * factor + fog[i] * (1.0 - factor);
1607            }
1608        }
1609    }
1610    Ok(Array::new(res_shape, res_data).into())
1611}
1612
1613builtin_params!(
1614    LayoutParam,
1615    (LineHeight, "The height of a line", 1),
1616    (Size, "Size of the rendering area", Value::default()),
1617    (Color, "Text color", Value::default()),
1618    (Bg, "Background color", Value::default()),
1619);
1620
1621pub(crate) fn layout_text(
1622    size: Value,
1623    text: Value,
1624    args: Option<Value>,
1625    env: &mut Uiua,
1626) -> UiuaResult<Value> {
1627    #[cfg(feature = "font_shaping")]
1628    {
1629        layout_text_impl(size, text, args, env)
1630    }
1631    #[cfg(not(feature = "font_shaping"))]
1632    Err(env.error("Text layout is not supported in this environment"))
1633}
1634
1635#[cfg(feature = "font_shaping")]
1636fn layout_text_impl(
1637    size: Value,
1638    text: Value,
1639    args: Option<Value>,
1640    env: &mut Uiua,
1641) -> UiuaResult<Value> {
1642    use std::{cell::RefCell, iter::repeat_n};
1643
1644    use cosmic_text::*;
1645    use ecow::eco_vec;
1646
1647    use crate::{Boxed, Shape, algorithm::validate_size, grid_fmt::GridFmt};
1648    struct FontStuff {
1649        system: FontSystem,
1650        swash_cache: SwashCache,
1651    }
1652    thread_local! {
1653        static FONT_STUFF: RefCell<Option<FontStuff>> = const { RefCell::new(None) };
1654    }
1655
1656    let mut string = String::new();
1657    match text {
1658        Value::Char(arr) if arr.rank() <= 1 => string = arr.data.iter().copied().collect(),
1659        Value::Char(arr) if arr.rank() == 2 => {
1660            for (i, row) in arr.row_slices().enumerate() {
1661                if i > 0 {
1662                    string.push('\n');
1663                }
1664                string.extend(row.iter().copied());
1665            }
1666        }
1667        Value::Box(arr) if arr.rank() == 1 => {
1668            for (i, Boxed(val)) in arr.data.iter().enumerate() {
1669                if i > 0 {
1670                    string.push('\n');
1671                }
1672                match val {
1673                    Value::Char(arr) if arr.rank() <= 1 => string.extend(arr.data.iter().copied()),
1674                    Value::Char(arr) if arr.rank() == 2 => {
1675                        for (j, row) in arr.row_slices().enumerate() {
1676                            if j > 0 {
1677                                string.push(' ');
1678                            }
1679                            string.extend(row.iter().copied());
1680                        }
1681                    }
1682                    Value::Box(arr) if arr.rank() == 1 => {
1683                        for (j, Boxed(val)) in arr.data.iter().enumerate() {
1684                            if j > 0 {
1685                                string.push(' ');
1686                            }
1687                            string.push_str(&val.as_string(env, "Text word must be a string")?);
1688                        }
1689                    }
1690                    _ => string.push_str(&val.as_string(env, "Text line must be a string")?),
1691                }
1692            }
1693        }
1694        Value::Box(arr) if arr.rank() == 2 => {
1695            for (i, row) in arr.row_slices().enumerate() {
1696                if i > 0 {
1697                    string.push('\n');
1698                }
1699                for (j, Boxed(val)) in row.iter().enumerate() {
1700                    if j > 0 {
1701                        string.push(' ');
1702                    }
1703                    string.push_str(&val.as_string(env, "Text word must be a string")?);
1704                }
1705            }
1706        }
1707        val => {
1708            string = val.as_string(env, "Text must be a rank 0, 1, or 2 character or box array")?
1709        }
1710    }
1711
1712    // Default options
1713    let size = size.as_num(env, "Size must be a number")? as f32;
1714    if size <= 0.0 {
1715        return Err(env.error("Text size must be positive"));
1716    }
1717    let mut line_height = 1.0;
1718    let mut width = None;
1719    let mut height = None;
1720    let mut color: Option<Color> = None;
1721    let mut bg = None;
1722
1723    // Parse options
1724    for (i, arg) in args
1725        .into_iter()
1726        .flat_map(Value::into_rows)
1727        .map(Value::unboxed)
1728        .enumerate()
1729    {
1730        match all::<LayoutParam>().nth(i) {
1731            Some(LayoutParam::LineHeight) => {
1732                line_height = arg.as_num(env, "Line height must be a scalar number")? as f32
1733            }
1734            Some(LayoutParam::Size) => {
1735                if arg.shape == 0 {
1736                    continue;
1737                }
1738                let nums = arg.as_nums(env, "Size must be a scalar number or 2 numbers")?;
1739                let [h, w] = match *nums {
1740                    [s] if arg.shape.is_empty() => [s; 2],
1741                    [h, w] => [h, w],
1742                    _ => {
1743                        return Err(env.error(format!(
1744                            "Size must be a scalar or list of 2 numbers, but its shape is {}",
1745                            arg.shape
1746                        )));
1747                    }
1748                };
1749                if w < 0.0 || w.is_nan() {
1750                    return Err(env.error(format!(
1751                        "Canvas width must be a non-negative number, but it is {}",
1752                        w.grid_string(false)
1753                    )));
1754                }
1755                if h < 0.0 || h.is_nan() {
1756                    return Err(env.error(format!(
1757                        "Canvas height must be a non-negative number, but it is {}",
1758                        h.grid_string(false)
1759                    )));
1760                }
1761                if !w.is_infinite() {
1762                    width = Some(w as f32);
1763                }
1764                if !h.is_infinite() {
1765                    height = Some(h as f32);
1766                }
1767            }
1768            Some(LayoutParam::Color) => {
1769                if arg.shape == 0 {
1770                    continue;
1771                }
1772                let nums = arg.as_nums(
1773                    env,
1774                    "Color must be a scalar number or list of 3 or 4 numbers",
1775                )?;
1776                let ([r, g, b], a) =
1777                    match *nums {
1778                        [gray] if arg.shape.is_empty() => ([gray; 3], None),
1779                        [r, g, b] => ([r, g, b], None),
1780                        [r, g, b, a] => ([r, g, b], Some(a)),
1781                        _ => return Err(env.error(format!(
1782                            "Color must be a scalar or list of 3 or 4 numbers, but its shape is {}",
1783                            arg.shape
1784                        ))),
1785                    };
1786                color = Some(if let Some(a) = a {
1787                    Color::rgba(
1788                        (r * 255.0) as u8,
1789                        (g * 255.0) as u8,
1790                        (b * 255.0) as u8,
1791                        (a * 255.0) as u8,
1792                    )
1793                } else {
1794                    Color::rgb((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8)
1795                });
1796            }
1797            Some(LayoutParam::Bg) => {
1798                if arg.shape == 0 {
1799                    continue;
1800                }
1801                bg = Some(arg.as_number_array::<f64>(env, "Background color must be numbers")?)
1802            }
1803            None => return Err(env.error(format!("Invalid layout params index {i}"))),
1804        }
1805    }
1806
1807    line_height *= size;
1808    let metrics = Metrics::new(size, line_height);
1809
1810    FONT_STUFF.with(|stuff| -> UiuaResult<Value> {
1811        let mut stuff = stuff.borrow_mut();
1812        if stuff.is_none() {
1813            let mut db = fontdb::Database::new();
1814            db.load_system_fonts();
1815            db.set_monospace_family("Uiua386");
1816            db.set_sans_serif_family("Uiua386");
1817            db.load_font_data(
1818                (env.rt.backend)
1819                    .big_constant(crate::BigConstant::Uiua386)
1820                    .map_err(|e| env.error(e))?
1821                    .into_owned(),
1822            );
1823            let locale = sys_locale::get_locale().unwrap_or_else(|| "en-US".into());
1824            let system = FontSystem::new_with_locale_and_db(locale, db);
1825            *stuff = Some(FontStuff {
1826                system,
1827                swash_cache: SwashCache::new(),
1828            });
1829        }
1830        let FontStuff {
1831            system,
1832            swash_cache,
1833        } = stuff.as_mut().unwrap();
1834        // Init buffer
1835        let mut buffer = Buffer::new(system, metrics);
1836        let mut buffer = buffer.borrow_with(system);
1837        buffer.set_size(width, height);
1838        let attrs = Attrs::new();
1839        buffer.set_text(&string, attrs, Shaping::Advanced);
1840        buffer.shape_until_scroll(true);
1841
1842        // Get canvas size
1843        let canvas_width = width.unwrap_or_else(|| {
1844            buffer
1845                .layout_runs()
1846                .map(|run| run.line_w)
1847                .max_by(|a, b| a.partial_cmp(b).unwrap())
1848                .unwrap_or(0.0)
1849        });
1850        let canvas_height =
1851            height.unwrap_or_else(|| buffer.layout_runs().map(|run| run.line_height).sum::<f32>());
1852
1853        // Init array shape/data
1854        let colored = color.is_some() || bg.is_some();
1855        let pixel_shape: &[usize] = if colored { &[4] } else { &[] };
1856        let mut canvas_shape = Shape::from_iter([canvas_height as usize, canvas_width as usize]);
1857        canvas_shape.extend(pixel_shape.iter().copied());
1858        let elem_count = validate_size::<f64>(canvas_shape.iter().copied(), env)?;
1859        let mut canvas_data = if let Some(bg) = bg {
1860            let color = match &*bg.shape {
1861                [] | [1] => [bg.data[0], bg.data[0], bg.data[0], 1.0],
1862                [3] | [4] => {
1863                    let alpha = bg.data.get(3).copied().unwrap_or(1.0);
1864                    [bg.data[0], bg.data[1], bg.data[2], alpha]
1865                }
1866                _ => return Err(env.error("Background color must be a list of 3 or 4 numbers")),
1867            };
1868            repeat_n(color, elem_count / 4).flatten().collect()
1869        } else {
1870            eco_vec![0.0; elem_count]
1871        };
1872        let slice = canvas_data.make_mut();
1873        // Render
1874        let color = color.unwrap_or(Color::rgb(0xFF, 0xFF, 0xFF));
1875        if color.a() == 0 {
1876            return Ok(Array::new(canvas_shape, canvas_data).into());
1877        }
1878        let a = color.a() as f64 / 255.0;
1879        buffer.draw(swash_cache, color, |x, y, _, _, color| {
1880            let alpha = color.a();
1881            if alpha == 0
1882                || (x < 0 || x >= canvas_width as i32)
1883                || (y < 0 || y >= canvas_height as i32)
1884            {
1885                return;
1886            }
1887            let i = (y * canvas_width as i32 + x) as usize;
1888            if colored {
1889                let a = a * alpha as f64 / 255.0;
1890                if a == 1.0 {
1891                    slice[i * 4] = color.r() as f64 / 255.0;
1892                    slice[i * 4 + 1] = color.g() as f64 / 255.0;
1893                    slice[i * 4 + 2] = color.b() as f64 / 255.0;
1894                    slice[i * 4 + 3] = 1.0;
1895                } else {
1896                    let [tr, tg, tb, ta, ..] = &mut slice[i * 4..] else {
1897                        unreachable!()
1898                    };
1899                    *tr = *tr * *ta * (1.0 - a) + color.r() as f64 / 255.0 * a;
1900                    *tg = *tg * *ta * (1.0 - a) + color.g() as f64 / 255.0 * a;
1901                    *tb = *tb * *ta * (1.0 - a) + color.b() as f64 / 255.0 * a;
1902                    *ta = 1.0 - ((1.0 - *ta) * (1.0 - a));
1903                }
1904            } else {
1905                slice[i] = color.a() as f64 / 255.0;
1906            }
1907        });
1908        Ok(Array::new(canvas_shape, canvas_data).into())
1909    })
1910}
1911
1912impl Value {
1913    /// Generate `noise`
1914    pub fn noise(&self, seed: &Self, octaves: &Self, env: &Uiua) -> UiuaResult<Array<f64>> {
1915        #[inline]
1916        fn smoothstep(x: f64) -> f64 {
1917            3.0 * x.powi(2) - 2.0 * x.powi(3)
1918        }
1919        #[inline]
1920        /// Produces [-1, 1) from a hasher
1921        fn hasher_uniform(hasher: impl Hasher) -> f64 {
1922            hasher.finish() as f64 / u64::MAX as f64 * 2.0 - 1.0
1923        }
1924
1925        // Seed chosen as a pleasant-looking default for range 0-1
1926        let mut hasher = RapidHasher::new(1);
1927        seed.hash(&mut hasher);
1928        let mut shape = self.shape.clone();
1929
1930        // Get coords
1931        let (n, coords) = match self {
1932            Value::Num(arr) => {
1933                if arr.rank() == 0 {
1934                    arr.data[0].to_bits().hash(&mut hasher);
1935                    return Ok(hasher_uniform(hasher).into());
1936                }
1937                let n = shape.pop().unwrap();
1938                (n, Cow::Borrowed(arr.data.as_slice()))
1939            }
1940            Value::Byte(arr) => {
1941                if arr.rank() == 0 {
1942                    (arr.data[0] as f64).to_bits().hash(&mut hasher);
1943                    return Ok(hasher_uniform(hasher).into());
1944                }
1945                let n = shape.pop().unwrap();
1946                let data = Cow::Owned(arr.data.iter().map(|&x| x as f64).collect());
1947                (n, data)
1948            }
1949            Value::Complex(arr) => (
1950                2,
1951                Cow::Owned(arr.data.iter().flat_map(|&c| [c.re, c.im]).collect()),
1952            ),
1953            value => {
1954                return Err(env.error(format!(
1955                    "Cannot generate noise from {} array",
1956                    value.type_name()
1957                )));
1958            }
1959        };
1960
1961        // Get octaves
1962        let octaves: Array<f64> =
1963            octaves.as_number_array(env, "Octaves must be a rank 0, 1, or 2 array of numbers")?;
1964        enum Octaves<'a> {
1965            Pow2(usize),
1966            Specified(&'a [f64]),
1967            PerDim(usize, &'a [f64]),
1968        }
1969        impl Octaves<'_> {
1970            fn count(&self) -> usize {
1971                match self {
1972                    Octaves::Pow2(n) => *n,
1973                    Octaves::Specified(os) => os.len(),
1974                    Octaves::PerDim(n, os) => os.len() / *n,
1975                }
1976            }
1977            fn get(&self, o: usize, i: usize) -> f64 {
1978                match self {
1979                    Octaves::Pow2(_) => 2f64.powi(o as i32),
1980                    Octaves::Specified(os) => os[o],
1981                    Octaves::PerDim(n, os) => os[o * n + i],
1982                }
1983            }
1984            fn avg(&self, o: usize) -> f64 {
1985                match self {
1986                    Octaves::Pow2(_) => 2f64.powi(o as i32),
1987                    Octaves::Specified(os) => os[o],
1988                    Octaves::PerDim(n, os) => os[o * n..][..*n].iter().sum::<f64>() / *n as f64,
1989                }
1990            }
1991        }
1992        let octaves = match octaves.rank() {
1993            0 => Octaves::Pow2(octaves.data[0].round() as usize),
1994            1 => Octaves::Specified(&octaves.data),
1995            2 => {
1996                if *octaves.shape.last().unwrap() != n {
1997                    return Err(env.error(format!(
1998                        "Octaves array's last axis must be the same as \
1999                        the number of noise dimensions, but {} ≠ {n}",
2000                        octaves.shape.last().unwrap(),
2001                    )));
2002                }
2003                Octaves::PerDim(n, &octaves.data)
2004            }
2005            rank => {
2006                return Err(env.error(format!(
2007                    "Noise octaves must be rank 0, 1, or 2, \
2008                    but the array is rank {rank}"
2009                )));
2010            }
2011        };
2012
2013        let mut data = eco_vec![0f64; shape.elements()];
2014        if n == 0 {
2015            return Ok(Array::new(shape, data));
2016        }
2017        let slice = data.make_mut();
2018
2019        // Setup
2020        let (corner_count, overflowed) = 2usize.overflowing_pow(n as u32);
2021        if overflowed {
2022            return Err(env.error(format!(
2023                "The coordinate array has shape {}, \
2024                which implies {n} dimensions, \
2025                which is too many for noise",
2026                self.shape
2027            )));
2028        }
2029        let sqrt_n = (n as f64).sqrt();
2030
2031        if n == 2 {
2032            // Fast case for 2D
2033            for o in 0..octaves.count() {
2034                let oct_avg_sqrt_n = octaves.avg(o) * sqrt_n;
2035                let (o0, o1) = (octaves.get(o, 0), octaves.get(o, 1));
2036                for (noise, coord) in slice.iter_mut().zip(coords.chunks_exact(n)) {
2037                    let (x, y) = (coord[0] * o0, coord[1] * o1);
2038                    let (xfract, yfract) = (x.rem_euclid(1.0).fract(), y.rem_euclid(1.0).fract());
2039                    let (xl, xr) = (smoothstep(1.0 - xfract), smoothstep(xfract));
2040                    let (yl, yr) = (smoothstep(1.0 - yfract), smoothstep(yfract));
2041                    let (x1, y1) = (x.floor(), y.floor());
2042                    let (x2, y2) = (x1 + 1.0, y1 + 1.0);
2043                    for [cx, kx] in [[x1, xl], [x2, xr]] {
2044                        let mut hasher = hasher;
2045                        cx.to_bits().hash(&mut hasher);
2046                        let dx = x - cx;
2047                        let kx_over_oct_avg_sqrt_n = kx / oct_avg_sqrt_n;
2048                        for [cy, ky] in [[y1, yl], [y2, yr]] {
2049                            let mut hasher = hasher;
2050                            cy.to_bits().hash(&mut hasher);
2051                            let (hx, mut hy) = (hasher, hasher);
2052                            1.hash(&mut hy);
2053                            let (gradx, grady) = (hasher_uniform(hx), hasher_uniform(hy));
2054                            let dy = y - cy;
2055                            *noise += kx_over_oct_avg_sqrt_n * ky * (gradx * dx + grady * dy)
2056                                / (gradx * gradx + grady * grady).sqrt();
2057                        }
2058                    }
2059                }
2060            }
2061        } else {
2062            // General nD case
2063            let mut top_left = vec![0f64; n];
2064            let mut corner = vec![0f64; n];
2065            let mut scaled = vec![0f64; n];
2066            let mut coefs = vec![0f64; n * 2];
2067            let mut prods = vec![0f64; corner_count];
2068
2069            // Main loop
2070            for o in 0..octaves.count() {
2071                let oct_avg_sqrt_n = octaves.avg(o) * sqrt_n;
2072                for (noise, coord) in slice.iter_mut().zip(coords.chunks_exact(n)) {
2073                    // Scale coord to octave and fine top-left corner
2074                    for (i, ((t, x), s)) in
2075                        top_left.iter_mut().zip(coord).zip(&mut scaled).enumerate()
2076                    {
2077                        *s = *x * octaves.get(o, i);
2078                        *t = s.floor();
2079                    }
2080                    prods.fill(0.0);
2081                    for (offset, prod) in prods.iter_mut().enumerate() {
2082                        // Calculate corner position
2083                        for (i, (cor, tl)) in corner.iter_mut().zip(&top_left).enumerate() {
2084                            *cor = *tl + ((offset >> i) & 1) as f64;
2085                        }
2086                        // Hash corner for gradient
2087                        let mut hasher = hasher;
2088                        corner.iter().for_each(|c| c.to_bits().hash(&mut hasher));
2089                        // Dot product with delta from point to corner and normalize
2090                        let mut grad_sqr_sum = 0.0;
2091                        for (i, (x, c)) in scaled.iter().zip(&corner).enumerate() {
2092                            let mut hasher = hasher;
2093                            i.hash(&mut hasher);
2094                            let grad = hasher_uniform(hasher);
2095                            grad_sqr_sum += grad * grad;
2096                            *prod += grad * (*x - *c);
2097                        }
2098                        *prod /= grad_sqr_sum.sqrt();
2099                    }
2100                    // Apply coefficients to product
2101                    for (x, cs) in scaled.iter().zip(coefs.chunks_exact_mut(2)) {
2102                        let fract = x.rem_euclid(1.0).fract();
2103                        cs[0] = smoothstep(1.0 - fract);
2104                        cs[1] = smoothstep(fract);
2105                    }
2106                    for (j, prod) in prods.iter_mut().enumerate() {
2107                        for i in 0..n {
2108                            *prod *= coefs[i * 2 + (j >> i & 1)];
2109                        }
2110                    }
2111                    // Add product
2112                    let prod: f64 = prods.iter().sum::<f64>();
2113                    *noise += prod / oct_avg_sqrt_n;
2114                }
2115            }
2116        }
2117
2118        // Map to ~[0, 1]
2119        for noise in slice {
2120            *noise += 0.5;
2121        }
2122        Ok(Array::new(shape, data))
2123    }
2124}