Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 65 additions & 31 deletions docs/docs/reference/wasm/modules/image/cielab/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,56 @@ sidebar_position: 2

## Overview

The CIELAB module provides bidirectional conversion between 8-bit sRGB and CIELAB (CIE L\*a\*b\*) color spaces. CIELAB is a perceptually uniform color space designed to approximate human vision, where equal Euclidean distances correspond to roughly equal perceived color differences.
The CIELAB module provides bidirectional conversion between sRGB and CIELAB (CIE L\*a\*b\*) color spaces. CIELAB is a perceptually uniform color space designed to approximate human vision, where equal Euclidean distances correspond to roughly equal perceived color differences.

Functions are templated to accept arbitrary input and output datatypes:

```cpp
template <typename Tin, typename Tout>
```

NOTE: Data types for CIELAB color space should support signed values. Suggest using `float` or `double`.

## Functions

### `rgb_to_lab`

Convert 8-bit sRGB to CIELAB color space.
Convert sRGB to CIELAB color space.

```cpp
template <typename Tin, typename Tout>
void rgb_to_lab(
const uint8_t r_u8,
const uint8_t g_u8,
const uint8_t b_u8,
double& out_l,
double& out_a,
double& out_b
const Tin r_u8,
const Tin g_u8,
const Tin b_u8,
Tout& out_l,
Tout& out_a,
Tout& out_b
);

template <typename Tin, typename Tout>
void rgb_to_lab(
const ImageLib::RGBPixel<Tin> &rgb,
ImageLib::LABPixel<Tout> &lab
);

template <typename Tin, typename Tout>
void rgb_to_lab(
const ImageLib::RGBAPixel<Tin> &rgba,
ImageLib::LABAPixel<Tout> &laba
);
```

#### Parameters

| Parameter | Type | Range | Description |
| :-------- | :-------- | :----------- | :------------------------------ |
| `r_u8` | `uint8_t` | [0, 255] | Red channel (input) |
| `g_u8` | `uint8_t` | [0, 255] | Green channel (input) |
| `b_u8` | `uint8_t` | [0, 255] | Blue channel (input) |
| `out_l` | `double&` | [0, 100] | L\* lightness (output, clamped) |
| `out_a` | `double&` | ~[-128, 127] | a\* green-red axis (output) |
| `out_b` | `double&` | ~[-128, 127] | b\* blue-yellow axis (output) |
| Parameter | Type | Range | Description |
| :-------- | :------ | :----------- | :------------------------------ |
| `r_u8` | `Tin` | [0, 255] | Red channel (input) |
| `g_u8` | `Tin` | [0, 255] | Green channel (input) |
| `b_u8` | `Tin` | [0, 255] | Blue channel (input) |
| `out_l` | `Tout&` | [0, 100] | L\* lightness (output, clamped) |
| `out_a` | `Tout&` | ~[-128, 127] | a\* green-red axis (output) |
| `out_b` | `Tout&` | ~[-128, 127] | b\* blue-yellow axis (output) |

#### Transformation Pipeline

Expand Down Expand Up @@ -69,29 +90,42 @@ rgb_to_lab(r, g, b, L, a, b_lab);

### `lab_to_rgb`

Convert CIELAB to 8-bit sRGB color space.
Convert CIELAB to sRGB color space.

```cpp
template <typename Tin, typename Tout>
void lab_to_rgb(
const Tin L,
const Tin A,
const Tin B,
Tout& r_u8,
Tout& g_u8,
Tout& b_u8
);

template <typename Tin, typename Tout>
void lab_to_rgb(
const ImageLib::LABPixel<Tin> &lab,
ImageLib::RGBPixel<Tout> &rgb
);

template <typename Tin, typename Tout>
void lab_to_rgb(
const double L,
const double A,
const double B,
uint8_t& r_u8,
uint8_t& g_u8,
uint8_t& b_u8
const ImageLib::LABAPixel<Tin> &laba,
ImageLib::RGBAPixel<Tout> &rgba
);
```

#### Parameters

| Parameter | Type | Range | Description |
| :-------- | :--------- | :----------- | :------------------------------ |
| `L` | `double` | [0, 100] | L\* lightness (input) |
| `A` | `double` | ~[-128, 127] | a\* green-red axis (input) |
| `B` | `double` | ~[-128, 127] | b\* blue-yellow axis (input) |
| `r_u8` | `uint8_t&` | [0, 255] | Red channel (output, clamped) |
| `g_u8` | `uint8_t&` | [0, 255] | Green channel (output, clamped) |
| `b_u8` | `uint8_t&` | [0, 255] | Blue channel (output, clamped) |
| Parameter | Type | Range | Description |
| :-------- | :------ | :----------- | :------------------------------ |
| `L` | `Tin` | [0, 100] | L\* lightness (input) |
| `A` | `Tin` | ~[-128, 127] | a\* green-red axis (input) |
| `B` | `Tin` | ~[-128, 127] | b\* blue-yellow axis (input) |
| `r_u8` | `Tout&` | [0, 255] | Red channel (output, clamped) |
| `g_u8` | `Tout&` | [0, 255] | Green channel (output, clamped) |
| `b_u8` | `Tout&` | [0, 255] | Blue channel (output, clamped) |

#### Transformation Pipeline

Expand Down
15 changes: 8 additions & 7 deletions src/components/WasmImageProcessor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const WasmImageProcessor = () => {
const inputId = useId();
const inputRef = useRef(null);

const { bilateralFilter, blackThreshold, kmeans, findContours } = useWasmWorker();
const { bilateralFilter, kmeans, findContours } = useWasmWorker();

const [originalSrc, setOriginalSrc] = useState(null);
const [fileData, setFileData] = useState(null);
Expand Down Expand Up @@ -85,18 +85,19 @@ const WasmImageProcessor = () => {
height,
});

step(45);
const thresholded = await blackThreshold({
// step(45);
// is this needed? num_colors is incorrect should be num_threshold
/*const thresholded = await blackThreshold({
...fileData,
pixels: imgBilateralFiltered,
num_colors: 8,
});
});*/

step(70);
const { pixels: kmeansed, labels } = await kmeans({
...fileData,
pixels: thresholded,
num_colors: 8,
pixels: imgBilateralFiltered,
num_colors: 16,
});

const contours = await findContours({
Expand Down Expand Up @@ -126,7 +127,7 @@ const WasmImageProcessor = () => {
step(0);
}, 800);
}
}, [fileData, bilateralFilter, blackThreshold, kmeans, findContours, navigate, step]);
}, [fileData, bilateralFilter, kmeans, findContours, navigate, step]);

/* Memo'd UI fragments */
const EmptyState = useMemo(
Expand Down
17 changes: 11 additions & 6 deletions src/hooks/useWasmWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,12 @@ export function useWasmWorker() {
sigma_spatial = 3.0,
sigma_range = 50.0,
color_space = 0,
n_threads = 8,
}) => {
return (
await call('bilateral_filter', { pixels, width, height, sigma_spatial, sigma_range, color_space }, ['pixels'])
await call('bilateral_filter', { pixels, width, height, sigma_spatial, sigma_range, color_space, n_threads }, [
'pixels',
])
).output.pixels;
};
const blackThreshold = async ({ pixels, width, height, num_colors }) => {
Expand All @@ -58,13 +61,15 @@ export function useWasmWorker() {
out_labels = new Int32Array(width * height),
num_colors,
max_iter = 100,
color_space = 0,
n_threads = 8,
}) => {
const result = (
await call('kmeans', { pixels, out_pixels, out_labels, width, height, num_colors, max_iter }, [
'pixels',
'out_pixels',
'out_labels',
])
await call(
'kmeans',
{ pixels, out_pixels, out_labels, width, height, num_colors, max_iter, color_space, n_threads },
['pixels', 'out_pixels', 'out_labels']
)
).output;
return {
pixels: result.out_pixels,
Expand Down
1 change: 1 addition & 0 deletions src/utils/image-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const uint8ClampedArrayToSVG = async ({ pixels, width, height }) => {
let svgString = ImageTracer.imagedataToSVG(imageData, {
ltres: 0,
rightangleenhance: false,
// numberofcolors: 128,
});

// Inject viewBox
Expand Down
9 changes: 6 additions & 3 deletions src/wasm/modules/image/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,24 @@ set(COMMON_FLAGS
"SHELL:-s MODULARIZE=1"
"SHELL:-s EXPORT_ES6=1"
"SHELL:-s EXIT_RUNTIME=1"
"SHELL:-s ENVIRONMENT=web"
"SHELL:-s ENVIRONMENT=web,worker"
"SHELL:-s EXPORTED_FUNCTIONS=['_malloc','_free']"
"SHELL:-s EXPORTED_RUNTIME_METHODS=['ccall','cwrap','getValue','setValue','HEAPU8','HEAP32']"
"SHELL:-s INITIAL_MEMORY=1024MB"
"SHELL:-s MAXIMUM_MEMORY=2048MB"
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s EXPORT_NAME=create${CAP_MODULE_NAME}Module"
"SHELL:-s USE_PTHREADS=1"
"SHELL:-s PTHREAD_POOL_SIZE=8"
)

# Apply common flags
target_link_options(${MODULE_NAME}_wasm PRIVATE ${COMMON_FLAGS})
target_compile_options(${MODULE_NAME}_wasm PRIVATE -pthread -msimd128)
target_link_options(${MODULE_NAME}_wasm PRIVATE ${COMMON_FLAGS} -pthread -msimd128)

# Build-type specific flags
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_options(${MODULE_NAME}_wasm PRIVATE -O0 -g4)
target_compile_options(${MODULE_NAME}_wasm PRIVATE -O2 -g4)
target_link_options(${MODULE_NAME}_wasm PRIVATE
"SHELL:-s ASSERTIONS=2"
-g4
Expand Down
34 changes: 34 additions & 0 deletions src/wasm/modules/image/include/LABAPixel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef LABAPixel_H
#define LABAPixel_H

#include "LABPixel.h"

namespace ImageLib {
template <typename NumberT>
struct LABAPixel : public ImageLib::LABPixel<NumberT> {
// ----- Members -----
NumberT alpha;

// ----- Constructors -----
constexpr LABAPixel(NumberT l = 0, NumberT a = 0, NumberT b = 0,
NumberT alpha = 255)
: LABPixel<NumberT>(l, a, b), alpha(alpha) {}

// ----- Modifiers -----
[[nodiscard]] inline bool operator==(const LABAPixel &other) const {
return LABPixel<NumberT>::operator==(other) && alpha == other.alpha;
}
[[nodiscard]] inline bool operator!=(const LABAPixel &other) const {
return !(*this == other);
}

// ----- Utilities -----
inline void setGray(NumberT gray, NumberT alpha = 255) {
LABPixel<NumberT>::setGray(gray);
this->alpha = alpha;
}

} __attribute__((packed));
} // namespace ImageLib

#endif // LABAPixel_H
36 changes: 36 additions & 0 deletions src/wasm/modules/image/include/LABPixel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#ifndef LABPIXEL_H
#define LABPIXEL_H

#include "Pixel.h"

/*
can support signed data types
preferrably float or double
*/

namespace ImageLib {
template <typename NumberT> struct LABPixel : public Pixel<NumberT> {
// ----- Members -----
NumberT l, a, b;

constexpr LABPixel(NumberT l = 0, NumberT a = 0, NumberT b = 0)
: l(l), a(a), b(b) {}

// ----- Modifiers -----
[[nodiscard]] inline bool operator==(const LABPixel &other) const {
return l == other.l && a == other.a && b == other.b;
}
[[nodiscard]] inline bool operator!=(const LABPixel &other) const {
return !(*this == other);
}

// ----- Utilities -----
inline void setGray(NumberT new_luma) {
l = new_luma;
a = b = 0;
}

} __attribute__((packed));
} // namespace ImageLib

#endif // LABPIXEL_H
2 changes: 1 addition & 1 deletion src/wasm/modules/image/include/bilateral_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace bilateral {
// (radiometric decay)
void bilateral_filter(uint8_t *image, size_t width, size_t height,
double sigma_spatial, double sigma_range,
uint8_t color_space);
uint8_t color_space, const uint8_t n_threads = 8);

} // namespace bilateral

Expand Down
36 changes: 32 additions & 4 deletions src/wasm/modules/image/include/cielab.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
#ifndef CIELAB_H
#define CIELAB_H

#include "LABAPixel.h"
#include "LABPixel.h"
#include "RGBAPixel.h"
#include "RGBPixel.h"
#include <cstdint>
#include <type_traits>

void rgb_to_lab(const uint8_t r_u8, const uint8_t g_u8, const uint8_t b_u8,
double &out_l, double &out_a, double &out_b);
// templates must be in headers

template <typename Tin, typename Tout>
void rgb_to_lab(const Tin r_u8, const Tin g_u8, const Tin b_u8, Tout &out_l,
Tout &out_a, Tout &out_b);

template <typename Tin, typename Tout>
void lab_to_rgb(const Tin L, const Tin A, const Tin B, Tout &r_u8, Tout &g_u8,
Tout &b_u8);

template <typename Tin, typename Tout>
void rgb_to_lab(const ImageLib::RGBAPixel<Tin> &rgba,
ImageLib::LABAPixel<Tout> &laba);

template <typename Tin, typename Tout>
void rgb_to_lab(const ImageLib::RGBPixel<Tin> &rgb,
ImageLib::LABPixel<Tout> &lab);

template <typename Tin, typename Tout>
void lab_to_rgb(const ImageLib::LABAPixel<Tin> &laba,
ImageLib::RGBAPixel<Tout> &rgba);

template <typename Tin, typename Tout>
void lab_to_rgb(const ImageLib::LABPixel<Tin> &lab,
ImageLib::RGBPixel<Tout> &rgb);

#include "cielab_impl.h"

void lab_to_rgb(const double L, const double A, const double B, uint8_t &r_u8,
uint8_t &g_u8, uint8_t &b_u8);
#endif // CIELAB_H
Loading