Skip to content

Commit e2c52c9

Browse files
committed
Implement AI gallery example 🎉
It displays the most popular daily images of Civitai!
1 parent 890d852 commit e2c52c9

File tree

8 files changed

+714
-121
lines changed

8 files changed

+714
-121
lines changed

Cargo.lock

+231-114
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ window_clipboard = "0.4.1"
193193
winit = { git = "https://github.com/iced-rs/winit.git", rev = "11414b6aa45699f038114e61b4ddf5102b2d3b4b" }
194194

195195
[workspace.lints.rust]
196-
rust_2018_idioms = { level = "forbid", priority = -1 }
196+
rust_2018_idioms = { level = "deny", priority = -1 }
197197
missing_debug_implementations = "deny"
198198
missing_docs = "deny"
199199
unsafe_code = "deny"

examples/changelog/Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,4 @@ tracing-subscriber = "0.3"
2323

2424
[dependencies.reqwest]
2525
version = "0.12"
26-
default-features = false
27-
features = ["json", "rustls-tls"]
26+
features = ["json"]

examples/download_progress/Cargo.toml

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ iced.features = ["tokio"]
1111

1212
[dependencies.reqwest]
1313
version = "0.12"
14-
default-features = false
15-
features = ["stream", "rustls-tls"]
14+
features = ["stream"]

examples/gallery/Cargo.toml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "gallery"
3+
version = "0.1.0"
4+
authors = ["Héctor Ramón Jiménez <[email protected]>"]
5+
edition = "2021"
6+
publish = false
7+
8+
[dependencies]
9+
iced.workspace = true
10+
iced.features = ["tokio", "image", "web-colors", "debug"]
11+
12+
reqwest.version = "0.12"
13+
reqwest.features = ["json"]
14+
15+
serde.version = "1.0"
16+
serde.features = ["derive"]
17+
18+
bytes.workspace = true
19+
image.workspace = true
20+
tokio.workspace = true
21+
22+
[lints]
23+
workspace = true

examples/gallery/src/civitai.rs

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use bytes::Bytes;
2+
use serde::Deserialize;
3+
use tokio::task;
4+
5+
use std::fmt;
6+
use std::io;
7+
use std::sync::Arc;
8+
9+
#[derive(Debug, Clone, Deserialize)]
10+
pub struct Image {
11+
pub id: Id,
12+
url: String,
13+
}
14+
15+
impl Image {
16+
pub async fn list() -> Result<Vec<Self>, Error> {
17+
let client = reqwest::Client::new();
18+
19+
#[derive(Deserialize)]
20+
struct Response {
21+
items: Vec<Image>,
22+
}
23+
24+
let response: Response = client
25+
.get(endpoint("/images"))
26+
.query(&[
27+
("sort", "Most Reactions"),
28+
("period", "Day"),
29+
("nsfw", "None"),
30+
("limit", "99"),
31+
])
32+
.send()
33+
.await?
34+
.error_for_status()?
35+
.json()
36+
.await?;
37+
38+
Ok(response.items)
39+
}
40+
41+
pub async fn download(self, size: Size) -> Result<Rgba, Error> {
42+
let client = reqwest::Client::new();
43+
44+
let bytes = client
45+
.get(match size {
46+
Size::Original => self.url,
47+
Size::Thumbnail => self
48+
.url
49+
.split("/")
50+
.map(|part| {
51+
if part.starts_with("width=") {
52+
"width=640"
53+
} else {
54+
part
55+
}
56+
})
57+
.collect::<Vec<_>>()
58+
.join("/"),
59+
})
60+
.send()
61+
.await?
62+
.error_for_status()?
63+
.bytes()
64+
.await?;
65+
66+
let image = task::spawn_blocking(move || {
67+
Ok::<_, Error>(
68+
image::ImageReader::new(io::Cursor::new(bytes))
69+
.with_guessed_format()?
70+
.decode()?
71+
.to_rgba8(),
72+
)
73+
})
74+
.await??;
75+
76+
Ok(Rgba {
77+
width: image.width(),
78+
height: image.height(),
79+
pixels: Bytes::from(image.into_raw()),
80+
})
81+
}
82+
}
83+
84+
#[derive(
85+
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize,
86+
)]
87+
pub struct Id(u32);
88+
89+
#[derive(Clone)]
90+
pub struct Rgba {
91+
pub width: u32,
92+
pub height: u32,
93+
pub pixels: Bytes,
94+
}
95+
96+
impl fmt::Debug for Rgba {
97+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98+
f.debug_struct("Rgba")
99+
.field("width", &self.width)
100+
.field("height", &self.height)
101+
.finish()
102+
}
103+
}
104+
105+
#[derive(Debug, Clone, Copy)]
106+
pub enum Size {
107+
Original,
108+
Thumbnail,
109+
}
110+
111+
fn endpoint(path: &str) -> String {
112+
const API_URL: &str = "https://civitai.com/api/v1";
113+
114+
format!("{API_URL}{path}")
115+
}
116+
117+
#[derive(Debug, Clone)]
118+
#[allow(dead_code)]
119+
pub enum Error {
120+
RequestFailed(Arc<reqwest::Error>),
121+
IOFailed(Arc<io::Error>),
122+
JoinFailed(Arc<task::JoinError>),
123+
ImageDecodingFailed(Arc<image::ImageError>),
124+
}
125+
126+
impl From<reqwest::Error> for Error {
127+
fn from(error: reqwest::Error) -> Self {
128+
Self::RequestFailed(Arc::new(error))
129+
}
130+
}
131+
132+
impl From<io::Error> for Error {
133+
fn from(error: io::Error) -> Self {
134+
Self::IOFailed(Arc::new(error))
135+
}
136+
}
137+
138+
impl From<task::JoinError> for Error {
139+
fn from(error: task::JoinError) -> Self {
140+
Self::JoinFailed(Arc::new(error))
141+
}
142+
}
143+
144+
impl From<image::ImageError> for Error {
145+
fn from(error: image::ImageError) -> Self {
146+
Self::ImageDecodingFailed(Arc::new(error))
147+
}
148+
}

0 commit comments

Comments
 (0)