initial commit

This commit is contained in:
2025-05-09 06:44:42 +03:00
commit 3f4abaa97d
7 changed files with 1473 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/target
.DS_Store
.idea/
Generated
+1241
View File
File diff suppressed because it is too large Load Diff
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "colorgram"
version = "0.1.0"
edition = "2024"
authors = ["arabian"]
description = "Rust library that extracts colors from image. Port of colorgram.py"
readme = "README.md"
license = "MIT"
homepage = "https://github.com/arabianq/colorgram-rust"
repository = "https://github.com/arabianq/colorgram-rust"
keywords = ["colors", "image", "extract", "colorgram"]
[dependencies]
image = { version = "0.25.6", features = ["default-formats"] }
clap = { version = "4.5.37", features = ["derive"] }
[lib]
name = "colorgram"
path = "src/lib.rs"
[[bin]]
name = "colorgram-cli"
path = "src/main.rs"
[profile.release]
strip = true
lto = true
codegen-units = 1
opt-level = "z"
panic = "abort"
+38
View File
@@ -0,0 +1,38 @@
# colorgram-rust
---
colorgram-rust is a rust program that lets you extract colors from image.
It is a port of [colorgram.py](https://github.com/obskyr/colorgram.py) (which is itself a port
of [colorgram.js](https://github.com/darosh/colorgram-js), so it's basically a port of a port =D)
**Why?** Well, it's ~25 times faster than colorgram.py (9 ms vs 225 ms for test.png on my laptop)
### Installation as CLI utility
```bash
cargo install colorgram
```
### Adding to your rust project
```toml
[dependencies]
colorgram = "0.1.0"
```
### Usage Example (code from main.rs)
```rust
use colorgram::extract;
fn main() {
let img_path = "test.png";
let colors_amount = 10;
let colors = extract(img_path, colors_amount).unwrap();
for color in colors {
println!("RGB: {} {} {} HSL: {} {} {} Proportion: {:.2}", color.rgb.r, color.rgb.g, color.rgb.b, color.hsl.h, color.hsl.s, color.hsl.l, color.proportion);
}
}
```
+121
View File
@@ -0,0 +1,121 @@
use image;
pub struct Hsl {
pub h: u8,
pub s: u8,
pub l: u8,
}
pub struct Rgb {
pub r: u8,
pub g: u8,
pub b: u8,
}
pub struct Color {
pub rgb: Rgb,
pub hsl: Hsl,
pub proportion: f32,
}
impl Color {
fn new(rgb: Rgb, proportion: f32) -> Color {
let hsl = rgb_to_hsl(&rgb);
Color {
rgb,
hsl,
proportion,
}
}
}
fn rgb_to_hsl(rgb: &Rgb) -> Hsl {
let r = rgb.r as i32;
let g = rgb.g as i32;
let b = rgb.b as i32;
let most = r.max(g).max(b);
let least = r.min(g).min(b);
let l = (most + least) >> 1;
let (h, s) = if most == least {
(0, 0)
} else {
let diff = most - least;
let s = if l > 127 {
diff * 255 / (510 - most - least)
} else {
diff * 255 / (most + least)
};
let h = if most == r {
((g - b) * 255 / diff + if g < b { 1530 } else { 0 }) / 6
} else if most == g {
((b - r) * 255 / diff + 510) / 6
} else {
((r - g) * 255 / diff + 1020) / 6
};
(h as u8, s as u8)
};
Hsl { h, s, l: l as u8 }
}
pub fn extract<P: AsRef<std::path::Path>>(
path: P,
number_of_color: usize,
) -> Result<Vec<Color>, Box<dyn std::error::Error>> {
let img = image::open(path)?;
let img = img.to_rgb8();
let mut samples = vec![0u32; 4 * 4096];
for pixel in img.pixels() {
let rgb = Rgb {
r: pixel[0],
g: pixel[1],
b: pixel[2],
};
let hsl = rgb_to_hsl(&rgb);
let y_val = ((rgb.r as f32 * 0.2126 + rgb.g as f32 * 0.7152 + rgb.b as f32 * 0.0722) as u8)
& 0b1100_0000;
let h = hsl.h & 0b1100_0000;
let l = hsl.l & 0b1100_0000;
let packed = ((y_val as usize) << 4) | ((h as usize) << 2) | (l as usize);
let idx = packed * 4;
samples[idx] += rgb.r as u32;
samples[idx + 1] += rgb.g as u32;
samples[idx + 2] += rgb.b as u32;
samples[idx + 3] += 1;
}
let mut used = Vec::new();
for (_i, chunk) in samples.chunks(4).enumerate() {
let count = chunk[3];
if count > 0 {
let avg_r = (chunk[0] / count) as u8;
let avg_g = (chunk[1] / count) as u8;
let avg_b = (chunk[2] / count) as u8;
let avg_rgb = Rgb {
r: avg_r,
g: avg_g,
b: avg_b,
};
used.push((count, avg_rgb));
}
}
used.sort_unstable_by(|a, b| b.0.cmp(&a.0));
let top_used = &used[..number_of_color];
let sum_counts: u32 = top_used.iter().map(|&(count, _)| count).sum();
let mut colors = Vec::with_capacity(number_of_color);
for (count, rgb) in used.into_iter().take(number_of_color) {
colors.push(Color::new(rgb, count as f32 / sum_counts as f32));
}
Ok(colors)
}
+39
View File
@@ -0,0 +1,39 @@
use clap::Parser;
use colorgram::extract;
use std::path::{PathBuf, absolute};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short = 'i', long = "input")]
input_file: PathBuf,
#[arg(short = 'c', long = "colors", default_value_t = 10)]
colors_amount: usize,
}
fn main() {
let args = Args::parse();
let input_path = absolute(&args.input_file).unwrap();
let colors_amount = args.colors_amount;
assert!(input_path.exists(), "Input file does not exist");
assert!(input_path.is_file(), "Input path is not a file");
assert!(colors_amount > 0, "Colors amount must be greater than zero");
match extract(input_path, colors_amount) {
Ok(colors) => {
for color in colors {
println!(
"RGB: ({}, {}, {}), Proportion: {:.2}%",
color.rgb.r,
color.rgb.g,
color.rgb.b,
color.proportion * 100.0
);
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB