1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]

use crate::image::ToRGB;
use crate::image::RGBAPLU;
use crate::image::RGBLU;
use imgref::*;
use rayon::prelude::*;

const D65x: f64 = 0.9505;
const D65y: f64 = 1.0;
const D65z: f64 = 1.089;

pub type GBitmap = ImgVec<f32>;
pub(crate) trait ToLAB {
    fn to_lab(&self) -> (f32, f32, f32);
}

impl ToLAB for RGBLU {
    fn to_lab(&self) -> (f32, f32, f32) {
        let fx = (self.r as f64 * 0.4124 + self.g as f64 * 0.3576 + self.b as f64 * 0.1805) / D65x;
        let fy = (self.r as f64 * 0.2126 + self.g as f64 * 0.7152 + self.b as f64 * 0.0722) / D65y;
        let fz = (self.r as f64 * 0.0193 + self.g as f64 * 0.1192 + self.b as f64 * 0.9505) / D65z;

        let epsilon: f64 = 216. / 24389.;
        let k = 24389. / (27. * 116.); // http://www.brucelindbloom.com/LContinuity.html
        let X = if fx > epsilon { fx.powf(1. / 3.) - 16. / 116. } else { k * fx };
        let Y = if fy > epsilon { fy.powf(1. / 3.) - 16. / 116. } else { k * fy };
        let Z = if fz > epsilon { fz.powf(1. / 3.) - 16. / 116. } else { k * fz };

        let lab = (
            (Y * 1.05) as f32, // 1.05 instead of 1.16 to boost color importance without pushing colors outside of 1.0 range
            (86.2 / 220.0 + 500.0 / 220.0 * (X - Y)) as f32,  /* 86 is a fudge to make the value positive */
            (107.9 / 220.0 + 200.0 / 220.0 * (Y - Z)) as f32, /* 107 is a fudge to make the value positive */
        );
        debug_assert!(lab.0 <= 1.0 && lab.1 <= 1.0 && lab.2 <= 1.0);
        lab
    }
}

/// Convert image to L\*a\*b\* planar
///
/// It should return 1 (gray) or 3 (color) planes.
pub trait ToLABBitmap {
    fn to_lab(&self) -> Vec<GBitmap>;
}

impl ToLABBitmap for ImgVec<RGBAPLU> {
    #[inline(always)]
    fn to_lab(&self) -> Vec<GBitmap> {
        self.as_ref().to_lab()
    }
}

impl ToLABBitmap for ImgVec<RGBLU> {
    #[inline(always)]
    fn to_lab(&self) -> Vec<GBitmap> {
        self.as_ref().to_lab()
    }
}

impl ToLABBitmap for GBitmap {
    fn to_lab(&self) -> Vec<GBitmap> {
        let width = self.width();
        let height = self.height();
        let size = width * height;

        let mut out = Vec::with_capacity(size);
        unsafe { out.set_len(size) };

        // For output width == stride
        out.par_chunks_mut(width).enumerate().for_each(|(y, out_row)| {
            let start = y * self.stride();
            let in_row = &self.buf()[start..start + width];
            let out_row = &mut out_row[0..width];
            let epsilon: f32 = 216. / 24389.;
            for x in 0..width {
                let fy = in_row[x];
                // http://www.brucelindbloom.com/LContinuity.html
                let Y = if fy > epsilon { fy.powf(1. / 3.) - 16. / 116. } else { ((24389. / 27.) / 116.) * fy };
                out_row[x] = Y * 1.16;
            }
        });

        vec![Img::new(out, width, height)]
    }
}

fn rgb_to_lab<T: Copy + Sync + Send + 'static, F>(img: ImgRef<'_, T>, cb: F) -> Vec<GBitmap>
    where F: Fn(T, usize) -> (f32, f32, f32) + Sync + Send + 'static
{
    let width = img.width();
    let height = img.height();
    let stride = img.stride();
    let size = width * height;

    let mut out_l = Vec::with_capacity(size);
    unsafe { out_l.set_len(size) };
    let mut out_a = Vec::with_capacity(size);
    unsafe { out_a.set_len(size) };
    let mut out_b = Vec::with_capacity(size);
    unsafe { out_b.set_len(size) };

    // For output width == stride
    out_l.par_chunks_mut(width).zip(
        out_a.par_chunks_mut(width).zip(out_b.par_chunks_mut(width))
    ).enumerate()
    .for_each(|(y, (l_row, (a_row, b_row)))| {
        let start = y * stride;
        let in_row = &img.buf()[start .. start + width];
        let l_row = &mut l_row[0..width];
        let a_row = &mut a_row[0..width];
        let b_row = &mut b_row[0..width];
        for x in 0..width {
            let n = (x+11) ^ (y+11);
            let (l,a,b) = cb(in_row[x], n);
            l_row[x] = l;
            a_row[x] = a;
            b_row[x] = b;
        }
    });

    return vec![
        Img::new(out_l, width, height),
        Img::new(out_a, width, height),
        Img::new(out_b, width, height),
    ];
}

impl<'a> ToLABBitmap for ImgRef<'a, RGBAPLU> {
    #[inline]
    fn to_lab(&self) -> Vec<GBitmap> {
        rgb_to_lab(*self, |px, n|{
            px.to_rgb(n).to_lab()
        })
    }
}

impl<'a> ToLABBitmap for ImgRef<'a, RGBLU> {
    #[inline]
    fn to_lab(&self) -> Vec<GBitmap> {
        rgb_to_lab(*self, |px, _n|{
            px.to_lab()
        })
    }
}