1use 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#[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 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 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 #[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 #[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 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 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 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 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 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 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 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 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 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#[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 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 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 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 if frame_width == gif_width && frame_height == gif_height {
1106 frame_data.copy_from_slice(&frame.buffer);
1107 } else {
1108 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 fn hasher_uniform(hasher: impl Hasher) -> f64 {
1922 hasher.finish() as f64 / u64::MAX as f64 * 2.0 - 1.0
1923 }
1924
1925 let mut hasher = RapidHasher::new(1);
1927 seed.hash(&mut hasher);
1928 let mut shape = self.shape.clone();
1929
1930 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 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 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 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 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 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 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 for (i, (cor, tl)) in corner.iter_mut().zip(&top_left).enumerate() {
2084 *cor = *tl + ((offset >> i) & 1) as f64;
2085 }
2086 let mut hasher = hasher;
2088 corner.iter().for_each(|c| c.to_bits().hash(&mut hasher));
2089 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 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 let prod: f64 = prods.iter().sum::<f64>();
2113 *noise += prod / oct_avg_sqrt_n;
2114 }
2115 }
2116 }
2117
2118 for noise in slice {
2120 *noise += 0.5;
2121 }
2122 Ok(Array::new(shape, data))
2123 }
2124}