/* * macaw: * Efficient color types and math for CHICKEN Scheme. * * Copyright © 2020 John Croisant. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #define MACAW_CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : (x) > (hi) ? (hi) : (x)) #define MACAW_BYTE(x) MACAW_CLAMP(x, 0, 255) #define MACAW_CLAMPF(x, lo, hi) fmaxf(lo, fminf(hi, x)) #define MACAW_BYTEF(x) MACAW_CLAMPF(x, 0.0, 255.0) /* COLOR SPACE CONVERSION */ float macaw_gamma_expand(float n) { if (n <= 0.04045 / 12.92) { return n * 12.92; } else { return 1.055 * powf(n, 1 / 2.4) - 0.055; } } float macaw_gamma_compress(float n) { if (n <= 0.04045) { return n / 12.92; } else { return powf((n + 0.055) / 1.055, 2.4); } } void macaw_rgb_to_rgb8(float* src, unsigned char* dst) { dst[0] = MACAW_BYTEF(macaw_gamma_compress(src[0]) * 255.0); dst[1] = MACAW_BYTEF(macaw_gamma_compress(src[1]) * 255.0); dst[2] = MACAW_BYTEF(macaw_gamma_compress(src[2]) * 255.0); dst[3] = MACAW_BYTEF(src[3] * 255.0); } void macaw_rgb8_to_rgb(unsigned char* src, float* dst) { dst[0] = macaw_gamma_expand(src[0] / 255.0); dst[1] = macaw_gamma_expand(src[1] / 255.0); dst[2] = macaw_gamma_expand(src[2] / 255.0); dst[3] = src[3] / 255.0; } void macaw_srgb_to_hsl(float* src, float* dst) { float r = MACAW_CLAMPF(src[0], 0.0, 1.0); float g = MACAW_CLAMPF(src[1], 0.0, 1.0); float b = MACAW_CLAMPF(src[2], 0.0, 1.0); float lo = fminf(r, fminf(g, b)); float hi = fmaxf(r, fmaxf(g, b)); float hue = 0.0; float sat = 0.0; float lum = (lo + hi) / 2.0; if (hi != lo) { if (hi == r) { hue = (g < b ? 360.0 : 0.0) + 60.0 * (g - b) / (hi - lo); } else if (hi == g) { hue = 120.0 + 60.0 * (b - r) / (hi - lo); } else { hue = 240.0 + 60.0 * (r - g) / (hi - lo); } if (0.0 < lum && lum <= 0.5) { sat = (hi - lo) / (hi + lo); } else { sat = (hi - lo) / (2.0 - hi - lo); } } dst[0] = hue; dst[1] = sat; dst[2] = lum; dst[3] = src[3]; } float macaw_hsl_to_srgb_helper(float n, float z, float h, float l) { float k = n + h / 30.0; while (k > 12.0) { k -= 12.0; } return l - z * MACAW_CLAMPF(fminf(k - 3.0, 9.0 - k), -1.0, 1.0); } void macaw_hsl_to_srgb(float* src, float* dst) { float h = src[0]; while (h < 0) { h += 360.0; } while (h >= 360.0) { h -= 360.0; } float s = MACAW_CLAMPF(src[1], 0.0, 1.0); float l = MACAW_CLAMPF(src[2], 0.0, 1.0); float z = s * fminf(l, 1.0 - l); dst[0] = macaw_hsl_to_srgb_helper(0, z, h, l); dst[1] = macaw_hsl_to_srgb_helper(8, z, h, l); dst[2] = macaw_hsl_to_srgb_helper(4, z, h, l); dst[3] = src[3]; } void macaw_rgb_to_hsl(float* src, float* dst) { float temp[4] = { macaw_gamma_compress(src[0]), macaw_gamma_compress(src[1]), macaw_gamma_compress(src[2]), src[3] }; macaw_srgb_to_hsl(temp, dst); } void macaw_hsl_to_rgb(float* src, float* dst) { float temp[4]; macaw_hsl_to_srgb(src, temp); dst[0] = macaw_gamma_expand(temp[0]); dst[1] = macaw_gamma_expand(temp[1]); dst[2] = macaw_gamma_expand(temp[2]); dst[3] = temp[3]; } void macaw_rgb8_to_hsl(unsigned char* src, float* dst) { float temp[4] = { src[0] / 255.0f, src[1] / 255.0f, src[2] / 255.0f, src[3] / 255.0f }; macaw_srgb_to_hsl(temp, dst); } void macaw_hsl_to_rgb8(float* src, unsigned char* dst) { float temp[4]; macaw_hsl_to_srgb(src, temp); dst[0] = MACAW_BYTE(temp[0] * 255); dst[1] = MACAW_BYTE(temp[1] * 255); dst[2] = MACAW_BYTE(temp[2] * 255); dst[3] = MACAW_BYTE(temp[3] * 255); } /* ADDITION */ void macaw_add_f32color(float* c1, float* c2, float* out) { out[0] = c1[0] + c2[0] * c2[3]; out[1] = c1[1] + c2[1] * c2[3]; out[2] = c1[2] + c2[2] * c2[3]; out[3] = c1[3]; } void macaw_add_u8color(unsigned char* c1, unsigned char* c2, unsigned char* out) { out[0] = MACAW_BYTE(c1[0] + c2[0] * c2[3] / 255); out[1] = MACAW_BYTE(c1[1] + c2[1] * c2[3] / 255); out[2] = MACAW_BYTE(c1[2] + c2[2] * c2[3] / 255); out[3] = c1[3]; } /* SUBTRACTION */ void macaw_sub_f32color(float* c1, float* c2, float* out) { out[0] = c1[0] - c2[0] * c2[3]; out[1] = c1[1] - c2[1] * c2[3]; out[2] = c1[2] - c2[2] * c2[3]; out[3] = c1[3]; } void macaw_sub_u8color(unsigned char* c1, unsigned char* c2, unsigned char* out) { out[0] = MACAW_BYTE(c1[0] - c2[0] * c2[3] / 255); out[1] = MACAW_BYTE(c1[1] - c2[1] * c2[3] / 255); out[2] = MACAW_BYTE(c1[2] - c2[2] * c2[3] / 255); out[3] = c1[3]; } /* MULTIPLICATION */ void macaw_mul_f32color(float* c1, float* c2, float* out) { /* lerp c1 with c1 * c2, using c2 alpha as blend factor */ out[0] = c1[0] + (c1[0] * c2[0] - c1[0]) * c2[3]; out[1] = c1[1] + (c1[1] * c2[1] - c1[1]) * c2[3]; out[2] = c1[2] + (c1[2] * c2[2] - c1[2]) * c2[3]; out[3] = c1[3]; } void macaw_mul_u8color(unsigned char* c1, unsigned char* c2, unsigned char* out) { /* lerp c1 with (c1 * c2 / 255), using c2 alpha as blend factor */ out[0] = c1[0] + (c1[0] * c2[0] / 255 - c1[0]) * c2[3] / 255; out[1] = c1[1] + (c1[1] * c2[1] / 255 - c1[1]) * c2[3] / 255; out[2] = c1[2] + (c1[2] * c2[2] / 255 - c1[2]) * c2[3] / 255; out[3] = c1[3]; } /* SCALE */ void macaw_scale_f32color(float* c1, float n, float* out) { out[0] = c1[0] * n; out[1] = c1[1] * n; out[2] = c1[2] * n; out[3] = c1[3]; } void macaw_scale_u8color(unsigned char* c1, float n, unsigned char* out) { out[0] = MACAW_BYTEF(c1[0] * n); out[1] = MACAW_BYTEF(c1[1] * n); out[2] = MACAW_BYTEF(c1[2] * n); out[3] = c1[3]; } /* LINEAR INTERPOLATION */ void macaw_lerp_f32color(float* c1, float* c2, float t, float* out) { out[0] = c1[0] + (c2[0] - c1[0]) * t; out[1] = c1[1] + (c2[1] - c1[1]) * t; out[2] = c1[2] + (c2[2] - c1[2]) * t; out[3] = c1[3] + (c2[3] - c1[3]) * t; } void macaw_lerp_u8color(unsigned char* c1, unsigned char* c2, float t, unsigned char* out) { out[0] = MACAW_BYTE(c1[0] + (c2[0] - c1[0]) * t); out[1] = MACAW_BYTE(c1[1] + (c2[1] - c1[1]) * t); out[2] = MACAW_BYTE(c1[2] + (c2[2] - c1[2]) * t); out[3] = MACAW_BYTE(c1[3] + (c2[3] - c1[3]) * t); } /* MIX */ void macaw_mix_f32color(float* accum, float* color, float weight, float* out) { out[0] = accum[0] + color[0] * weight; out[1] = accum[1] + color[1] * weight; out[2] = accum[2] + color[2] * weight; out[3] = accum[3] + color[3] * weight; } void macaw_mix_u8color(unsigned char* accum, unsigned char* color, float weight, unsigned char* out) { out[0] = MACAW_BYTE(accum[0] + color[0] * weight); out[1] = MACAW_BYTE(accum[1] + color[1] * weight); out[2] = MACAW_BYTE(accum[2] + color[2] * weight); out[3] = MACAW_BYTE(accum[3] + color[3] * weight); } /* OVER / UNDER */ void macaw_over_f32color(float* top, float* bottom, float* out) { if (1.0 == top[3]) { out[0] = top[0]; out[1] = top[1]; out[2] = top[2]; out[3] = top[3]; return; } else if (0.0 == top[3]) { out[0] = bottom[0]; out[1] = bottom[1]; out[2] = bottom[2]; out[3] = bottom[3]; return; } float a = top[3] + bottom[3] * (1.0 - top[3]); out[0] = (top[0] * top[3] + bottom[0] * bottom[3] * (1.0 - top[3])) / a; out[1] = (top[1] * top[3] + bottom[1] * bottom[3] * (1.0 - top[3])) / a; out[2] = (top[2] * top[3] + bottom[2] * bottom[3] * (1.0 - top[3])) / a; out[3] = a; } void macaw_over_u8color(unsigned char* top, unsigned char* bottom, unsigned char* out) { if (255 == top[3]) { out[0] = top[0]; out[1] = top[1]; out[2] = top[2]; out[3] = top[3]; return; } else if (0 == top[3]) { out[0] = bottom[0]; out[1] = bottom[1]; out[2] = bottom[2]; out[3] = bottom[3]; return; } /* The code below comes from refactoring these equations: * a = top[3]/255 + bottom[3]/255 * (1 - top[3]/255) * out[i] = 255 * ( top[i]/255 * top[3]/255 * + bottom[i]/255 * bottom[3]/255 * (1 - top[3]/255) * ) / a; * out[3] = 255 * a; */ unsigned short f1 = 255 * top[3]; unsigned short f2 = bottom[3] * (255 - top[3]); unsigned short a = f1 + f2; out[0] = (top[0] * f1 + bottom[0] * f2) / a; out[1] = (top[1] * f1 + bottom[1] * f2) / a; out[2] = (top[2] * f1 + bottom[2] * f2) / a; out[3] = a / 255; }