Skip to content

Commit

Permalink
some window stuff work nicely
Browse files Browse the repository at this point in the history
  • Loading branch information
kaikalii committed Nov 20, 2024
1 parent 4298a74 commit 430e701
Show file tree
Hide file tree
Showing 7 changed files with 768 additions and 421 deletions.
23 changes: 23 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ wasm-bindgen = {version = "0.2.92", optional = true}
web-sys = {version = "0.3.60", optional = true}

# Window dependencies
eframe = {version = "0.29.1", features = ["persistence"], optional = true}
eframe = {version = "0.29.1", optional = true, features = ["persistence"]}
rmp-serde = {version = "1.3.0", optional = true}

[features]
audio = ["hodaun", "lockfree", "audio_encode"]
Expand Down Expand Up @@ -152,7 +153,7 @@ terminal_image = ["viuer", "image", "icy_sixel"]
tls = ["httparse", "rustls", "webpki-roots", "rustls-pemfile"]
web = ["wasm-bindgen", "js-sys", "web-sys"]
webcam = ["image", "uiua-nokhwa"]
window = ["eframe"]
window = ["eframe", "rmp-serde"]
xlsx = ["calamine", "simple_excel_writer"]
# Use system static libraries instead of building them
system = ["libffi?/system"]
Expand Down
56 changes: 12 additions & 44 deletions pad/editor/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@ use std::{
};

use base64::engine::{general_purpose::URL_SAFE, Engine};
use image::ImageOutputFormat;
use leptos::*;

use uiua::{
ast::Item,
encode::{image_to_bytes, value_to_gif_bytes, value_to_image, value_to_wav_bytes},
encode::SmartOutput,
lsp::{BindingDocsKind, ImportSrc},
Compiler, DiagnosticKind, Inputs, Primitive, Report, ReportFragment, ReportKind, SpanKind,
Spans, SysBackend, Uiua, UiuaError, UiuaResult, Value,
Spans, Uiua, UiuaError, UiuaResult, Value,
};
use unicode_segmentation::UnicodeSegmentation;
use wasm_bindgen::JsCast;
Expand Down Expand Up @@ -1005,52 +1004,21 @@ fn run_code_single(code: &str) -> (Vec<OutputItem>, Option<UiuaError>) {
let mut stack = Vec::new();
let value_count = values.len();
for (i, value) in values.into_iter().enumerate() {
// Try to convert the value to audio
if value.shape().last().is_some_and(|&n| n >= 44100 / 4)
&& matches!(&value, Value::Num(arr) if arr.elements().all(|x| x.abs() <= 5.0))
{
if let Ok(bytes) = value_to_wav_bytes(&value, io.audio_sample_rate()) {
let label = value.meta().label.as_ref().map(Into::into);
stack.push(OutputItem::Audio(bytes, label));
let value = match SmartOutput::from_value(value, io) {
SmartOutput::Png(bytes, label) => {
stack.push(OutputItem::Image(bytes, label));
continue;
}
}
// Try to convert the value to an image
const MIN_AUTO_IMAGE_DIM: usize = 30;
if let Ok(image) = value_to_image(&value) {
if image.width() >= MIN_AUTO_IMAGE_DIM as u32
&& image.height() >= MIN_AUTO_IMAGE_DIM as u32
{
if let Ok(bytes) = image_to_bytes(&image, ImageOutputFormat::Png) {
let label = value.meta().label.as_ref().map(Into::into);
stack.push(OutputItem::Image(bytes, label));
continue;
}
}
}
// Try to convert the value to a gif
if let Ok(bytes) = value_to_gif_bytes(&value, 16.0) {
match value.shape().dims() {
&[f, h, w] | &[f, h, w, _]
if h >= MIN_AUTO_IMAGE_DIM && w >= MIN_AUTO_IMAGE_DIM && f >= 5 =>
{
let label = value.meta().label.as_ref().map(Into::into);
stack.push(OutputItem::Gif(bytes, label));
continue;
}
_ => {}
SmartOutput::Gif(bytes, label) => {
stack.push(OutputItem::Gif(bytes, label));
continue;
}
}
// Try to convert the value to SVG
if let Ok(mut str) = value.as_string(&rt, "") {
if str.starts_with("<svg") && str.ends_with("</svg>") {
if !str.contains("xmlns") {
str = str.replacen("<svg", "<svg xmlns=\"http://www.w3.org/2000/svg\"", 1);
}
stack.push(OutputItem::Svg(str));
SmartOutput::Wav(bytes, label) => {
stack.push(OutputItem::Audio(bytes, label));
continue;
}
}
SmartOutput::Normal(value) => value,
};
// Otherwise, just show the value
let class = if value_count == 1 {
""
Expand Down
81 changes: 74 additions & 7 deletions src/algorithm/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,66 @@
use hound::{SampleFormat, WavReader, WavSpec, WavWriter};
#[cfg(feature = "image")]
use image::{DynamicImage, ImageOutputFormat};
use serde::*;

use crate::SysBackend;
#[allow(unused_imports)]
use crate::{Array, Uiua, UiuaResult, Value};

/// Conversion of a value to some media format based on the value's shape
#[derive(Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub enum SmartOutput {
Normal(Value),
Png(Vec<u8>, Option<String>),
Gif(Vec<u8>, Option<String>),
Wav(Vec<u8>, Option<String>),
}

impl SmartOutput {
/// Convert a value to a SmartOutput
pub fn from_value(value: Value, backend: &dyn SysBackend) -> Self {
// Try to convert the value to audio
#[cfg(feature = "audio_encode")]
if value.shape().last().is_some_and(|&n| n >= 44100 / 4)
&& matches!(&value, Value::Num(arr) if arr.elements().all(|x| x.abs() <= 5.0))
{
if let Ok(bytes) = value_to_wav_bytes(&value, backend.audio_sample_rate()) {
let label = value.meta().label.as_ref().map(Into::into);
return Self::Wav(bytes, label);
}
}
// Try to convert the value to an image
#[cfg(feature = "image")]
const MIN_AUTO_IMAGE_DIM: usize = 30;
if let Ok(image) = value_to_image(&value) {
if image.width() >= MIN_AUTO_IMAGE_DIM as u32
&& image.height() >= MIN_AUTO_IMAGE_DIM as u32
{
if let Ok(bytes) = image_to_bytes(&image, ImageOutputFormat::Png) {
let label = value.meta().label.as_ref().map(Into::into);
return Self::Png(bytes, label);
}
}
}
// Try to convert the value to a gif
#[cfg(feature = "gif")]
if let Ok(bytes) = value_to_gif_bytes(&value, 16.0) {
match value.shape().dims() {
&[f, h, w] | &[f, h, w, _]
if h >= MIN_AUTO_IMAGE_DIM && w >= MIN_AUTO_IMAGE_DIM && f >= 5 =>
{
let label = value.meta().label.as_ref().map(Into::into);
return Self::Gif(bytes, label);
}
_ => {}
}
}
// Otherwise, just show the value
Self::Normal(value)
}
}

pub(crate) fn image_encode(env: &mut Uiua) -> UiuaResult {
#[cfg(feature = "image")]
{
Expand Down Expand Up @@ -184,6 +240,18 @@ pub fn rgb_image_to_array(image: image::RgbImage) -> Array<f64> {
)
}

#[doc(hidden)]
#[cfg(feature = "image")]
pub fn rgba_image_to_array(image: image::RgbaImage) -> Array<f64> {
let shape = crate::Shape::from([image.height() as usize, image.width() as usize, 4]);
Array::new(
shape,
(image.into_raw().into_iter())
.map(|b| b as f64 / 255.0)
.collect::<crate::cowslice::CowSlice<_>>(),
)
}

#[doc(hidden)]
#[cfg(feature = "image")]
pub fn image_bytes_to_array(bytes: &[u8], alpha: bool) -> Result<Array<f64>, String> {
Expand Down Expand Up @@ -339,11 +407,11 @@ pub fn value_to_wav_bytes(audio: &Value, sample_rate: u32) -> Result<Vec<u8>, St
if sample_rate == 0 {
return Err("Sample rate must not be 0".to_string());
}

let channels = value_to_audio_channels(audio)?;
#[cfg(not(feature = "audio"))]
{
value_to_wav_bytes_impl(
audio,
channels_to_wav_bytes_impl(
channels,
|f| (f * i16::MAX as f64) as i16,
16,
SampleFormat::Int,
Expand All @@ -352,20 +420,19 @@ pub fn value_to_wav_bytes(audio: &Value, sample_rate: u32) -> Result<Vec<u8>, St
}
#[cfg(feature = "audio")]
{
value_to_wav_bytes_impl(audio, |f| f as f32, 32, SampleFormat::Float, sample_rate)
channels_to_wav_bytes_impl(channels, |f| f as f32, 32, SampleFormat::Float, sample_rate)
}
}

#[cfg(feature = "audio_encode")]
fn value_to_wav_bytes_impl<T: hound::Sample + Copy>(
audio: &Value,
fn channels_to_wav_bytes_impl<T: hound::Sample + Copy>(
channels: Vec<Vec<f64>>,
convert_samples: impl Fn(f64) -> T + Copy,
bits_per_sample: u16,
sample_format: SampleFormat,
sample_rate: u32,
) -> Result<Vec<u8>, String> {
// We use i16 samples for compatibility with Firefox (if I remember correctly)
let channels = value_to_audio_channels(audio)?;
let channels: Vec<Vec<T>> = channels
.into_iter()
.map(|c| c.into_iter().map(convert_samples).collect())
Expand Down
Loading

0 comments on commit 430e701

Please sign in to comment.