Skip to content

Commit

Permalink
Merge branch 'main' into 14-implement-change-shadows
Browse files Browse the repository at this point in the history
  • Loading branch information
DPende authored Mar 18, 2024
2 parents 0456d37 + 4bee58b commit 7835856
Show file tree
Hide file tree
Showing 15 changed files with 522 additions and 44 deletions.
Binary file added Assets/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# EditPix
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![NPM Test on Main](https://github.com/studio-YOLO/editpix/actions/workflows/main.yml/badge.svg)](https://github.com/studio-YOLO/editpix/actions/workflows/main.yml)

A powerful and versatile image editor for [insert your target audience/users].

## Features
![key1](Assets/logo.png)

### Basic Editing:

Expand Down
1 change: 1 addition & 0 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<script type="module" src="scripts/resize_image_demo.js"></script>
<script type="module" src="scripts/change_opacity_demo.js"></script>-->
<script type="module" src="scripts/change_shadow_demo.js"></script>

</body>

</html>
11 changes: 4 additions & 7 deletions demo/scripts/color_palette_wasm_demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,13 @@ container.classList.add("container")

//waiting image load
image.onload = () => {
//calculate color palette
editpix.getColorPaletteWasm(image, 13, 1)
console.log("Image loaded.");

editpix.getColorPaletteWasm(image, 15, 2, "median cut")
.then(colorPalette => {
console.log(colorPalette)
displayPalette(colorPalette);
})




});
};


Expand Down
23 changes: 23 additions & 0 deletions demo/scripts/higher_color_contrast_demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import EditPix from "../../src/editpix.js";

const editpix = new EditPix();

//color
const hexColor = "#78828c";

//convert color from hex to rgb
const rgbColor = editpix.convertToRgb(hexColor);

//get the higher contrast color
const higherColorContrastRgb = editpix.getHigherContrast(rgbColor);

//convert higher contrast color from rgb to hex
const higherColorContrastHex = editpix.convertToHex(higherColorContrastRgb);


//display results
document.body.style.backgroundColor = hexColor;
const contrastText = document.createElement("h1");
contrastText.textContent = "Hello World!"
contrastText.style.color = higherColorContrastHex;
document.body.appendChild(contrastText)
12 changes: 3 additions & 9 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,11 @@ edition = "2021"

[dependencies]
wasm-bindgen = "0.2"
getrandom = { version = "0.2", features = ["js"] }
rand = "*"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies.web-sys]
version = "0.3.68"
features = [
'Document',
'Element',
'HtmlElement',
'Node',
'Window',
]


3 changes: 0 additions & 3 deletions lib/src/.gitignore

This file was deleted.

197 changes: 192 additions & 5 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
extern crate web_sys;
use rand::prelude::*;

#[inline]
fn euclidean_distance(color1: &[u8; 3], color2: &[u8; 3]) -> f64 {
f64::sqrt(
((color1[0] as i16 - color2[0] as i16) as f64).powi(2) +
((color1[1] as i16 - color2[1] as i16) as f64).powi(2) +
((color1[2] as i16 - color2[2] as i16) as f64).powi(2)
(color1[0] as f64 - color2[0] as f64).powi(2) +
(color1[1] as f64 - color2[1] as f64).powi(2) +
(color1[2] as f64 - color2[2] as f64).powi(2)
)
}

Expand Down Expand Up @@ -105,4 +105,191 @@ pub fn k_means(colors_r: Vec<u8>, color_number: usize, max_iterations: usize) ->
.collect();

serialized_vector
}
}

fn square_distance(color1: &[u8; 3], color2: &[u8; 3]) -> f64 {
(color1[0] as f64 - color2[0] as f64).powi(2) +
(color1[1] as f64 - color2[1] as f64).powi(2) +
(color1[2] as f64 - color2[2] as f64).powi(2)
}

fn initialize_centroids_pp(colors: &Vec<[u8; 3]>, color_number: usize) -> Vec<[u8; 3]> {
let mut rng = thread_rng();
let mut centroids: Vec<[u8; 3]> = Vec::new();
let first_centroid = colors[rng.gen_range(0..colors.len())];
centroids.push(first_centroid);

for _i in 1..color_number {
let distances = colors.iter().map(|x| {
let partial_distances = centroids.iter().map(|y| square_distance(x, y));
partial_distances.fold(f64::INFINITY, |a, b| a.min(b))
});
let total_weight = distances.clone().fold(0.0, |a, b| a + b);
let distances: Vec<_> = distances.collect();
let target = rng.gen::<f64>() * total_weight;
let mut cumulative = 0.0;
for i in 0..colors.len() {
cumulative += distances[i];
if cumulative >= target {
centroids.push(colors[i]);
break;
}
}
}

centroids
}

fn assign_to_centroids_pp(colors: &[[u8; 3]], centroids: Vec<[u8; 3]>) -> Vec<usize> {
let mut assignments: Vec<usize> = Vec::new();
for i in 0..colors.len() {
let mut min_distance = f64::INFINITY;
let mut closest_centroid = 0;
for j in 0..centroids.len() {
let distance = square_distance(colors.get(i).unwrap(), centroids.get(j).unwrap());
if distance < min_distance {
min_distance = distance;
closest_centroid = j;
}
}
assignments.push(closest_centroid);
}

assignments
}

#[wasm_bindgen]
pub fn k_means_pp(colors_r: Vec<u8>, color_number: usize, max_iterations: usize) -> Vec<u8> {
let colors: Vec<[u8; 3]> = colors_r
.chunks_exact(3) // Get chunks of 3 elements
.map(|chunk| {
let mut array: [u8; 3] = [0; 3];
array.copy_from_slice(chunk);
array
})
.collect();


let mut centroids = initialize_centroids_pp(&colors, color_number);

let mut iterations: usize = 0;
let mut previous_assignments;
let mut assignments: Vec<usize> = Vec::new();

loop {
previous_assignments = assignments;
assignments = assign_to_centroids_pp(&colors, centroids);
centroids = calculate_new_centroids(&colors, &assignments, color_number);
iterations += 1;
if iterations > max_iterations || assignments == previous_assignments {
break;
}
}

let serialized_vector: Vec<u8> = centroids
.into_iter()
.flat_map(|array| array.into_iter())
.collect();

serialized_vector
}




// Sort an RGB pixel array by its channel with the highest variance
fn sort_pixels(pixels: &Vec<[u8; 3]>, channel: usize) -> Vec<[u8; 3]> {
let mut px = pixels.to_owned();
px.sort_by(|a, b| a[channel].cmp(&b[channel]));
px
}

// Find the color channel with the highest variance
fn find_max_channel(pixels: &Vec<[u8; 3]>) -> usize {
let mut min = [255, 255, 255];
let mut max = [0, 0, 0];
for (_i, pixel) in pixels.iter().enumerate() {
for j in 0..3 {
if pixel[j] < min[j] {
min[j] = pixel[j];
}
if pixel[j] > max[j] {
max[j] = pixel[j];
}
}
}
let mut range = [0, 0, 0];
for j in 0..3 {
range[j] = max[j] - min[j];
}
let max_channel: usize = (0..3).into_iter().max().unwrap();
max_channel
}

// Find the average color of an RGB pixel array
fn find_average_color(pixels: Vec<[u8; 3]>) -> [u8; 3] {
let mut sum: [f32; 3] = [0.0, 0.0, 0.0];
for pixel in &pixels {
for j in 0..3 {
sum[j] += pixel[j] as f32;
}
}
let avg: [u8; 3] = [(sum[0] / pixels.len() as f32) as u8, (sum[1] / pixels.len() as f32) as u8, (sum[2] / pixels.len() as f32) as u8];
avg
}

// Apply the median cut algorithm to an RGB pixel array and return a downsized color palette
#[wasm_bindgen]
pub fn median_cut(pixels_r: Vec<u8>, palette_size: usize) -> Vec<u8> {
// Turn the linear array into an array of RGB arrays
let pixels: Vec<[u8; 3]> = pixels_r
.chunks_exact(3) // Get chunks of 3 elements
.map(|chunk| {
let mut array: [u8; 3] = [0; 3];
array.copy_from_slice(chunk);
array
})
.collect();

// Initialize a queue of regions with all pixels
let mut queue = vec![pixels];
// Repeat the following loop until the queue reaches the correct size
while queue.len() < palette_size {
// Extract the region with the most pixels from the queue
let mut max_index = 0;
let mut max_size = 0;
for (i, region) in queue.iter().enumerate() {
if region.len() > max_size {
max_size = region.len();
max_index = i;
}
}
let region = queue.remove(max_index);
// Find the channel with the highest variance within the region
let channel = find_max_channel(&region);
// Sort the pixels in the region by that channel
let sorted: Vec<[u8; 3]> = sort_pixels(&region, channel).iter().cloned().collect();
// Find the average and bisect the region
let median = sorted.len() / 2;
let left = &sorted[..median];
let right = &sorted[median..];
// Add the two regions to the queue
queue.push(left.to_vec());
queue.push(right.to_vec());
}
// Compute the average color of each region and return the palette
let mut palette: Vec<[u8; 3]> = Vec::new();
for region in queue {
let color = find_average_color(region);
palette.push(color);
}

// Serialize the array of arrays to get a linear array
let serialized_vector: Vec<u8> = palette
.into_iter()
.flat_map(|array| array.into_iter())
.collect();

serialized_vector
}

23 changes: 23 additions & 0 deletions src/core/change_exposure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Function to change the exposure of an image.
* @param {number[]} pixelArray: Image pixel array in the format [R, G, B, alpha,..., R, G, B, alpha].
* @param {number} factor: Factor to adjust the exposure (-100 to 100).
* @returns {number[]} Pixel array of the image with adjusted exposure.
*/
function changeExposure(pixelArray, factor) {
for (let i = 0; i < pixelArray.length; i += 4) {
const r = pixelArray[i];
const g = pixelArray[i + 1];
const b = pixelArray[i + 2];

// Apply exposure adjustment to each channel
const newR = Math.max(0, Math.min(255, r + factor * 2.55)); // Factor scaled to range 0-255
const newG = Math.max(0, Math.min(255, g + factor * 2.55));
const newB = Math.max(0, Math.min(255, b + factor * 2.55));

pixelArray[i] = newR;
pixelArray[i + 1] = newG;
pixelArray[i + 2] = newB;
}
return pixelArray;
}
23 changes: 23 additions & 0 deletions src/core/change_tint.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Function to change the tint of an image.
* @param {number[]} pixelArray: Image pixel array in the format [R, G, B, alpha,..., R, G, B, alpha].
* @param {number} tint: Tint to apply (-100 to 100).
* @returns {number[]} Pixel array of the image with adjusted tint.
*/
function changeTint(pixelArray, tint) {
for (let i = 0; i < pixelArray.length; i += 4) {
const r = pixelArray[i];
const g = pixelArray[i + 1];
const b = pixelArray[i + 2];

// Apply tint adjustment to each channel
const newR = Math.max(0, Math.min(255, r + (255 - r) * (tint / 100)));
const newG = Math.max(0, Math.min(255, g + (255 - g) * (tint / 100)));
const newB = Math.max(0, Math.min(255, b + (255 - b) * (tint / 100)));

pixelArray[i] = newR;
pixelArray[i + 1] = newG;
pixelArray[i + 2] = newB;
}
return pixelArray;
}
Loading

0 comments on commit 7835856

Please sign in to comment.