%% options copyright owner = Dirk Krause copyright year = 2017-xxxx SPDX-License-Identifier: BSD-3-Clause %% header /** @file dk4cs.h Color spaces, especially for bitmap images. This module provides color space conversion functions. Note: You should not use this module for conversion from other color spaces to CMYK or vice versa. Conversion to CMYK should be done by professional programs, as the conversion depends on the production process, the used inks, and the used paper. Professional conversion programs use color profiles and let you choose a number of settings. This module uses just simple formulas. The following symbols are used in formulas below: Symbol | Meaning :----: | :-----: d | Bit depth (integer), number of bits per component (sample) M | Maximum component value (integer), 2^d-1 r | Red component for a pixel (integer), 0<=r<=M g | Green component for a pixel (integer), 0<=r<=M b | Blue component for a pixel (integer), 0<=b<=M e | Gray value for a pixel (integer), 0<=e<=M c | Cyan component for a pixel (integer), 0<=c<=M m | Magenta component for a pixel (integer), 0<=m<=M y | Yellow component for pixel (integer), 0<=y<=M k | Black or key component for a pixel (integer), 0<=k<=M maxrgb | Maximum value of r, g, and b (integer), 0<=maxrgb<=M mincmy | Minimum value of c, m, and y (integer), 0<=mincmy<=M r' | Normalized red component (floating point), 0.0<=r'<=1.0 g' | Normalized green component (floating point), 0.0<=g'<=1.0 b' | Normalized blue component (floating point), 0.0<=b'<=1.0 e' | Normalized gray component (floating point), 0.0<=e'<=1.0 c' | Normalized cyan component (floating point), 0.0<=c'<=1.0 m' | Normalized magenta component (floating point), 0.0<=m'<=1.0 y' | Normalized yellow component (floating point), 0.0<=y'<=1.0 k' | Normalized black component (floating point), 0.0<=k'<=1.0 maxrgb' | Normalized red component (floating point), 0.0<=maxrgb'<=1.0 mincmy' | Normalized red component (floating point), 0.0<=mincmy'<=1.0 ucr(x) | Undercolour removal function, maps [0,1] to [0,1] bg(x) | Black generation function, maps [0,1] to [0,1] To switch from default algorithms to other algorithms, open a conversion context, set up the conversion context, pass the context to the conversion functions and close the context when no longer needed. */ #ifndef DK4CONF_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4conf.h" #else #include #endif #endif #ifndef DK4TYPES_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4types.h" #else #include #endif #endif #ifndef DK4ERROR_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4error.h" #else #include #endif #endif #ifndef DK4PX_H_INCLUDED #if DK4_BUILDING_DKTOOLS4 #include "dk4px.h" #else #include #endif #endif /** Color spaces used by the images we want to process. */ enum { DK4_CS_GRAY = 0 , /**< Gray. */ DK4_CS_GRAY_ALPHA , /**< Gray, opacity. */ DK4_CS_RGB , /**< Red, green, blue. */ DK4_CS_RGB_ALPHA , /**< Red, green, blue, opacity. */ DK4_CS_CMYK , /**< Cyan, magentam, yellow, black (key). */ DK4_CS_MIN = DK4_CS_GRAY , /**< Minimum value. */ DK4_CS_MAX = DK4_CS_CMYK /**< Maximum value. */ }; /** Choose algorithm to convert RGB to CMYK. */ typedef enum { DK4_CS_CONV_RGB_TO_CMYK_NOT_PS_PDF = 0 , /**< Algorithm differing from PS/PDF standard. Most web pages about color conversion recommend the algorithm like this (default): @code c' = 1 - r' m' = 1 - g' y' = 1 - b' mincmy' = min{c',m',y'} if 1 = mincmy' then k' = 1 c' = m' = y' = 0 else k' = mincmy' c' = (c' - k') / (1 - k') m' = (m' - k') / (1 - k') y' = (y' - k') / (1 - k') endif @endcode Substitution results in: @code maxrgb' = max{r',g',b'} if 0 = maxrgb' then k' = 1 c' = m' = y' = 0 else c' = (maxrgb' - r') / maxrgb' m' = (maxrgb' - g') / maxrgb' y' = (maxrgb' - b') / maxrgb' k' = 1 - maxrgb' endif @endcode The result for pixel components is: @code maxrgb = max{r,g,b} if 0 = maxrgb then k = M c = m = y = 0 else c = (M * (maxrgb - r)) / maxrgb m = (M * (maxrgb - g)) / maxrgb y = (M * (maxrgb - b)) / maxrgb k = M - maxrgb endif @endcode */ DK4_CS_CONV_RGB_TO_CMYK_PS_PDF /**< Algorithm as described in the PS and PDF standards. @code c' = 1 - r' m' = 1 - g' y' = 1 - b' mincmy' = min{c',m',y'} c' = c' - ucr(mincmy') m' = m' - ucr(mincmy') y' = y' - ucr(mincmy') k' = bg(mincmy') @endcode Substitution and use of simplified functions ucr(x)=x and bg(x)=x results in: @code maxrgb' = max{r',g',b'} c' = maxrgb' - r' m' = maxrgb' - g' y' = maxrgb' - b' k' = 1 - maxrgb' @endcode The result for components values is: @code maxrgb = max{r,g,b} c = maxrgb - r m = maxrgb - g y = maxrgb - b k = M - maxrgb @endcode */ } dk4_cs_conv_rgb_to_cmyk_t; /** How to convert RGB to gray. */ typedef enum { /** Reproduce luminance (default). @code e' = 0.299 * r' + 0.587 * g' + 0.114 * b' e = (299 * r + 587 * g + 114 * b) / 1000 @endcode */ DK4_CS_CONV_RGB_TO_GRAY_ITU_BT601 = 0, /** Reproduce luminance. Same as above but reduced number of digits in the coefficients. @code e' = 0.3 * r' + 0.59 * g' + 0.11 * b' e = (30 * r + 59 * g + 11 * b) / 100 @endcode */ DK4_CS_CONV_RGB_TO_GRAY_LUMA , /** Reproduce luminance, use alternative coefficients. @code e' = 0.2126 * r' + 0.7152 * g' + 0.0722 * b' e = (2126 * r + 7152 * g + 722 * b) / 10000 @endcode */ DK4_CS_CONV_RGB_TO_GRAY_ITU_BT709 , /** Faster approximation for pixel values. @code e = (77 * r + 151 * g + 28 * b) >> 8 @endcode For normalized colors use @code e' = 0.299 * r' + 0.587 * g' + 0.114 * b' @endcode */ DK4_CS_CONV_RGB_TO_GRAY_FAST , /** Still faster - and more inaccurate - approximation for pixel values. @code e = ((r << 1) + ((g << 2) + g) + b) >> 3 @endcode is a fast implementation of @code e = (2 * r + 5 * g + b) / 8 @endcode For normalized colors use @code e' = 0.299 * r' + 0.587 * g' + 0.114 * b' @endcode */ DK4_CS_CONV_RGB_TO_GRAY_VERY_FAST , /** Use average. @code e' = (r' + g' + b') / 3 e = (r + g + b) / 3 @endcode */ DK4_CS_CONV_RGB_TO_GRAY_AVERAGE , /** Desaturation. Flatter than luminance, less contrast. @code e' = (max{r',g',b'} + min{r',g',b'}) / 2 e = (max{r,b,b} + min{r,g,b}) / 2 @endcode */ DK4_CS_CONV_RGB_TO_GRAY_DESATURATION , /** Minimum decomposition. Produces dark images. @code e' = min{r',g',b'} e = min{r,g,b} @endcode */ DK4_CS_CONV_RGB_TO_GRAY_MIN_DECOMPOSITION , /** Medium value decomposition (not minimum, not maximum). */ DK4_CS_CONV_RGB_TO_GRAY_MEDIUM_DECOMPOSITION , /** Maximum decomposition. Produces bright images. @code e' = max{r',g',b'} e = max{r,g,b} @endcode */ DK4_CS_CONV_RGB_TO_GRAY_MAX_DECOMPOSITION , /** Use just red channel. @code e' = r' e = r @endcode */ DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_RED , /** Use just green channel. @code e' = g' e = g @endcode */ DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_GREEN , /** Use just blue channel. @code e' = b' e = b @endcode */ DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_BLUE , /** Minimum value. */ DK4_CS_CONV_RGB_TO_GRAY_MIN = DK4_CS_CONV_RGB_TO_GRAY_ITU_BT601 , /** Maximum value. */ DK4_CS_CONV_RGB_TO_GRAY_MAX = DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_BLUE } dk4_cs_conv_rgb_to_gray_t; /** How to handle alpha channel. Only used when converting from a color space containing alpha data to a color space not containing alpha data. */ typedef enum { /** Mix against specified background. */ DK4_CS_CONV_ALPHA_MIX = 0, /** Ignore alpha value, just return foreground. */ DK4_CS_CONV_ALPHA_FOREGROUND } dk4_cs_conv_alpha_t; /** Conversion context containing setup details for conversions. */ typedef struct { dk4_cs_conv_rgb_to_cmyk_t rgb_to_cmyk; /**< RGB to CMYK. */ dk4_cs_conv_rgb_to_gray_t rgb_to_gray; /**< RGB to gray. */ dk4_cs_conv_alpha_t alpha; /**< Alpha channel handling. */ } dk4_cs_conv_ctx_t; #ifdef __cplusplus extern "C" { #endif /** Create new conversion context and initialize it. @param erp Error report, may be NULL. @return Valid pointer on success, NULL on error. Error codes: - DK4_E_MEMORY_ALLOCATION_FAILED
with mem.elsize and mem.nelem set if there is not enough memory available. */ dk4_cs_conv_ctx_t * dk4cs_context_open( dk4_er_t *erp ); /** Initialize a conversion context. @param pctx Context to set up. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx is NULL. */ int dk4cs_context_init( dk4_cs_conv_ctx_t *pctx, dk4_er_t *erp ); /** Set RGB to CMYK handling in a conversion context. @param pctx Context to set up. @param rgb_to_cmyk Details for RGB to CMYK conversion. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx is NULL. */ int dk4cs_context_set_rgb_to_cmyk( dk4_cs_conv_ctx_t *pctx, dk4_cs_conv_rgb_to_cmyk_t rgb_to_cmyk, dk4_er_t *erp ); /** Set RGB to gray handling in a conversion context. @param pctx Context to set up. @param rgb_to_gray Details or RGB to gray conversion. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx is NULL. */ int dk4cs_context_set_rgb_to_gray( dk4_cs_conv_ctx_t *pctx, dk4_cs_conv_rgb_to_gray_t rgb_to_gray, dk4_er_t *erp ); /** Set alpha channel handling. @param pctx Context to set up. @param alpha Alpha channel handling method. @param erp Error report, may be NULL. @return 1 on success, 0 on error. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx is NULL. */ int dk4cs_context_set_alpha( dk4_cs_conv_ctx_t *pctx, dk4_cs_conv_alpha_t alpha, dk4_er_t *erp ); /** Destroy conversion context, release resources. @param pctx Conversion context to close. */ void dk4cs_context_close( dk4_cs_conv_ctx_t *pctx ); /** Convert pixel data from one color space into another, samples are dk4_px_t. @param dptr Address of destination array. @param dcs Destination color space. @param dsz Size of destination array (number of elements). @param sptr Address of source array. @param scs Source color space. @param ssz Source array size (number of elements). @param bptr Address of background array, may be NULL. @param bsz Size of background array (number of elements), may be 0. @param max Maximum value for a component. @param pctx Conversion context, may be NULL. @param erp Error report, may be NULL. Error codes: - DK4_E_INVALID_ARGUMENTS
if anything is wrong with arguments, i.e.: * dptr or sptr is NULL, * dcs or scs is not a valid color space, * dsz or ssz is too small for the color space, * max is 0, * one of the sptr components is larger than max * a background is required but bptr is NULL, bsz is too small for the colro space or one of the background components is larger than max. */ void dk4cs_px_convert( dk4_px_t *dptr, int dcs, size_t dsz, dk4_px_t const *sptr, int scs, size_t ssz, dk4_px_t const *bptr, size_t bsz, dk4_px_t max, dk4_cs_conv_ctx_t const *pctx, dk4_er_t *erp ); /** Convert pixel data from one color space into another, samples are double. @param dptr Address of destination array, values in interval [0;1]. @param dcs Destination color space. @param dsz Size of destination array (number of elements). @param sptr Address of source array, values in interval [0;1]. @param scs Source color space. @param ssz Source array size (number of elements). @param bptr Address of background array, values in [0;1], may be NULL. @param bsz Size of background array (number of elements), may be 0. @param pctx Conversion context, may be NULL. @param erp Error report, may be NULL. Error codes: - DK4_E_INVALID_ARGUMENTS
if anything is wrong with arguments, i.e.: * dptr or sptr is NULL, * dcs or scs is not a valid color space, * dsz or ssz is too small for the color space, * one of the sptr components is out of interval [0;1], * a background is required but bptr is NULL, bsz is too small for the colro space or one of the background components is out of interval [0;1]. */ void dk4cs_color_convert( double *dptr, int dcs, size_t dsz, double const *sptr, int scs, size_t ssz, double const *bptr, size_t bsz, dk4_cs_conv_ctx_t const *pctx, dk4_er_t *erp ); /** Mix pixel data foreground against background value. @param value Foreground value. @param alpha Foreground opacity. @param bg Background value. @param max Maximum for foreground, background, and alpha. @return Mixing result. */ dk4_px_t dk4cs_mix_alpha( dk4_px_t value, dk4_px_t alpha, dk4_px_t bg, dk4_px_t max ); /** Mix pixel data RGB to gray. @param r Red. @param g Green. @param b Blue. @param pctx Conversion context, may be NULL. @return Gray value for RGB pixel. */ dk4_px_t dk4cs_mix_rgb_to_gray( unsigned long r, unsigned long g, unsigned long b, dk4_cs_conv_ctx_t const *pctx ); /** Mix normalized color foreground against background using alpha value. @param v Foreground value, normalized. @param a Alpha value, normalized. @param b Background value, normalized. */ double dk4cs_color_mix_alpha( double v, double a, double b ); /** Mix normalized color RGB to gray @param r Normalized red component. @param g Normalized green component. @param b Normalized blue component. @param pctx Conversion context, may be NULL. @return Gray value. */ double dk4cs_color_mix_rgb_to_gray( double r, double g, double b, dk4_cs_conv_ctx_t const *pctx ); /** Retrieve number of components for a color space. @param cs Color space. @return Number of components in color space cs. */ size_t dk4cs_color_space_size(int cs); /** Check whether an integer number represents a valid color space. @param cs Number to check. @return 1 on success (cs is a color space), 0 otherwise. */ int dk4cs_check(int cs); /** Set RGB to CMYK conversion method from text. @param pctx Context to set up. @param txt Text containing the method name. @param erp Error report, may be NULL. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx or txt is NULL, - DK4_E_SYNTAX
if txt does not cotain a valid method name. */ int dk4cs_context_text_rgb_to_cmyk( dk4_cs_conv_ctx_t *pctx, dkChar const *txt, dk4_er_t *erp ); /** Set RGB to GRAY conversion method from text. @param pctx Context to set up. @param txt Text containing the method name. @param erp Error report, may be NULL. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx or txt is NULL, - DK4_E_SYNTAX
if txt does not cotain a valid method name. */ int dk4cs_context_text_rgb_to_gray( dk4_cs_conv_ctx_t *pctx, dkChar const *txt, dk4_er_t *erp ); /** Set alpha channel handling method. @param pctx Context to set up. @param txt Text containing the method name. @param erp Error report, may be NULL. Error codes: - DK4_E_INVALID_ARGUMENTS
if pctx or txt is NULL, - DK4_E_SYNTAX
if txt does not cotain a valid method name. */ int dk4cs_context_text_alpha( dk4_cs_conv_ctx_t *pctx, dkChar const *txt, dk4_er_t *erp ); #ifdef __cplusplus } #endif /* vim: set ai sw=4 ts=4 : */ %% module #ifndef DK4CONF_H_INCLUDED #include "dk4conf.h" #endif #ifndef DK4BIFTY_H_INCLUDED #include "dk4bifty.h" #endif #ifndef DK4CS_H_INCLUDED #include "dk4cs.h" #endif #ifndef DK4MEM_H_INCLUDED #include "dk4mem.h" #endif #ifndef DK4MATH_H_INCLUDED #include "dk4math.h" #endif #ifndef DK4STRD_H_INCLUDED #include "dk4strd.h" #endif #ifndef BIF_H_INCLUDED #include "bif.h" #endif #if DK4_HAVE_ASSERT_H #ifndef ASSERT_H_INCLUDED #include #define ASSERT_H_INCLUDED 1 #endif #endif $!trace-include /** Names for the conversion methods from RGB to CMYK. */ static const dkChar * const dk4cs_rgb_to_cmyk_names[] = { $!string-table macro=dkT # # Most web sites explain a method not matching the PS and PDF standard. # n$on-standard # # Do as in the PS and PDF standard. # s$tandard $!end }; /** Names for the conversion methods from RGB to GRAY. */ static const dkChar * const dk4cs_rgb_to_gray_names[] = { $!string-table macro=dkT # # 0 Reproduce luminance # l$uminance # # 1 Reproduce luminance, just 2 digits # in$accurate # # 2 Faster method # fast # # 3 Faster but more inaccurate # fastest # # 4 ITU-BT709 # it$u-bt709 # # 5 Average (stupid method) # a$verage # # 6 Desaturation # d$esaturation # # 7 Minimum decomposition # mi$n-decomposition # # 8 Medium decomposition # me$dium-decomposition # # 9 Maximum decomposition # ma$x-decomposition # # 10 Just use red channel # r$ed # # 11 Just use green channel # g$reen # # 12 Just use blue channel # b$lue $!end }; /** Names of alpha channel handling mechanisms. */ static const dkChar * const dk4cs_alpha_names[] = { $!string-table macro=dkT m$ix i$gnore $!end }; int dk4cs_context_init( dk4_cs_conv_ctx_t *pctx, dk4_er_t *erp ) { int back = 0; $? "+ dk4cs_context_init" #if DK4_USE_ASSERT assert(NULL != pctx); #endif if (NULL != pctx) { /* Initialize all components */ pctx->rgb_to_cmyk = DK4_CS_CONV_RGB_TO_CMYK_NOT_PS_PDF; pctx->rgb_to_gray = DK4_CS_CONV_RGB_TO_GRAY_ITU_BT601; pctx->alpha = DK4_CS_CONV_ALPHA_MIX; /* Indicate success */ back = 1; } else { $? "! pctx" dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); } $? "- dk4cs_context_init %d", back return back; } dk4_cs_conv_ctx_t * dk4cs_context_open( dk4_er_t *erp ) { dk4_cs_conv_ctx_t *back; $? "+ dk4_cs_conv_ctx_t" back = dk4mem_new(dk4_cs_conv_ctx_t,1,erp); if (NULL != back) { (void)dk4cs_context_init(back, NULL); } $? "- dk4_cs_conv_ctx_t %d", TR_IPTR(back) return back; } int dk4cs_context_set_rgb_to_cmyk( dk4_cs_conv_ctx_t *pctx, dk4_cs_conv_rgb_to_cmyk_t rgb_to_cmyk, dk4_er_t *erp ) { int back = 0; $? "+ dk4cs_context_set_rgb_to_cmyk" #if DK4_USE_ASSERT assert(NULL != pctx); #endif if (NULL != pctx) { pctx->rgb_to_cmyk = rgb_to_cmyk; back = 1; } else { $? "! pctx" dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); } $? "- dk4cs_context_set_rgb_to_cmyk %d", back return back; } int dk4cs_context_set_rgb_to_gray( dk4_cs_conv_ctx_t *pctx, dk4_cs_conv_rgb_to_gray_t rgb_to_gray, dk4_er_t *erp ) { int back = 0; $? "+ dk4cs_context_set_rgb_to_gray" #if DK4_USE_ASSERT assert(NULL != pctx); #endif if (NULL != pctx) { pctx->rgb_to_gray = rgb_to_gray; back = 1; } else { $? "! pctx" dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); } $? "- dk4cs_context_set_rgb_to_gray %d", back return back; } int dk4cs_context_set_alpha( dk4_cs_conv_ctx_t *pctx, dk4_cs_conv_alpha_t alpha, dk4_er_t *erp ) { int back = 0; $? "+ dk4cs_context_set_alpha" #if DK4_USE_ASSERT assert(NULL != pctx); #endif if (NULL != pctx) { pctx->alpha = alpha; back = 1; } else { dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); } $? "- dk4cs_context_set_alpha %d", back return back; } int dk4cs_context_text_rgb_to_cmyk( dk4_cs_conv_ctx_t *pctx, dkChar const *txt, dk4_er_t *erp ) { int back = 0; int ind = 0; /* String index in array */ #if DK4_USE_ASSERT assert(NULL != pctx); assert(NULL != txt); #endif if ((NULL == pctx) || (NULL == txt)) { dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); goto finished; } ind = dk4str_abbr_index(dk4cs_rgb_to_cmyk_names, dkT('$'), txt, 0); if (0 > ind) { dk4error_set_simple_error_code(erp,DK4_E_SYNTAX); goto finished; } pctx->rgb_to_cmyk = (dk4_cs_conv_rgb_to_cmyk_t)ind; back = 1; finished: return back; } int dk4cs_context_text_rgb_to_gray( dk4_cs_conv_ctx_t *pctx, dkChar const *txt, dk4_er_t *erp ) { int back = 0; int ind = 0; /* String index in array */ #if DK4_USE_ASSERT assert(NULL != pctx); assert(NULL != txt); #endif if ((NULL == pctx) || (NULL == txt)) { dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); goto finished; } ind = dk4str_abbr_index(dk4cs_rgb_to_gray_names, dkT('$'), txt, 0); if (0 > ind) { dk4error_set_simple_error_code(erp,DK4_E_SYNTAX); goto finished; } pctx->rgb_to_gray = (dk4_cs_conv_rgb_to_gray_t)ind; back = 1; finished: return back; } int dk4cs_context_text_alpha( dk4_cs_conv_ctx_t *pctx, dkChar const *txt, dk4_er_t *erp ) { int back = 0; int ind = 0; /* String index in array */ #if DK4_USE_ASSERT assert(NULL != pctx); assert(NULL != txt); #endif if ((NULL == pctx) || (NULL == txt)) { dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); goto finished; } ind = dk4str_abbr_index(dk4cs_alpha_names, dkT('$'), txt, 0); if (0 > ind) { dk4error_set_simple_error_code(erp,DK4_E_SYNTAX); goto finished; } pctx->alpha = (dk4_cs_conv_alpha_t)ind; back = 1; finished: return back; } void dk4cs_context_close( dk4_cs_conv_ctx_t *pctx ) { $? "+ dk4cs_context_close" #if DK4_USE_ASSERT assert(NULL != pctx); #endif if (NULL != pctx) { dk4mem_free(pctx); } $? "- dk4cs_context_close" } dk4_px_t dk4cs_mix_alpha(dk4_px_t value, dk4_px_t alpha, dk4_px_t bg, dk4_px_t max) { $? "= dk4cs_mix_alpha value=%lu bg=%lu alpha=%lu max=%lu", (unsigned long)value, (unsigned long)bg, (unsigned long)alpha, (unsigned long)max return ( (dk4_px_t)( ( ((unsigned long)value * (unsigned long)alpha) + ( (unsigned long)bg * ((unsigned long)max - (unsigned long)alpha) ) ) / ((unsigned long)max) ) ); } double dk4cs_color_mix_alpha( double v, double a, double b ) { $? "= dk4cs_color_mix_alpha" return ((v * a) + ((1.0 - a) * b)); } dk4_px_t dk4cs_mix_rgb_to_gray( unsigned long r, unsigned long g, unsigned long b, dk4_cs_conv_ctx_t const *pctx ) { unsigned long min; /* Minimum of r g b */ unsigned long max; /* Maximum of r g b */ unsigned long back = 0UL; dk4_cs_conv_rgb_to_gray_t alg = DK4_CS_CONV_RGB_TO_GRAY_ITU_BT601; $? "+ dk4cs_mix_rgb_to_gray r=%lu g=%lu b=%lu", r, g, b if (NULL != pctx) { alg = pctx->rgb_to_gray; } switch (alg) { case DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_BLUE : { back = b; } break; case DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_GREEN : { back = g; } break; case DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_RED : { back = r; } break; case DK4_CS_CONV_RGB_TO_GRAY_MAX_DECOMPOSITION : { back = r; if (g > back) back = g; if (b > back) back = b; } break; case DK4_CS_CONV_RGB_TO_GRAY_MEDIUM_DECOMPOSITION : { if (r > g) { if (g > b) { back = g; } else { if (r > b) { back = b; } else { back = r; } } } else { if (g < b) { back = g; } else { if (r < b) { back = b; } else { back = r; } } } } break; case DK4_CS_CONV_RGB_TO_GRAY_MIN_DECOMPOSITION : { back = r; if (g < back) back = g; if (b < back) back = b; } break; case DK4_CS_CONV_RGB_TO_GRAY_DESATURATION : { min = max = r; if (g < min) min = g; if (g > max) max = g; if (b < min) min = b; if (b > max) max = b; back = (max + min) / 2UL; } break; case DK4_CS_CONV_RGB_TO_GRAY_ITU_BT709 : { back = (2126UL * r + 7152UL * g + 722UL * b) / 10000UL; } break; case DK4_CS_CONV_RGB_TO_GRAY_AVERAGE : { back = (r + g + b) / 3UL; } break; case DK4_CS_CONV_RGB_TO_GRAY_VERY_FAST : { back = ((r << 1) + ((g << 2) + g) + b) >> 3; } break; case DK4_CS_CONV_RGB_TO_GRAY_FAST : { back = (77UL * r + 151UL * g + 28UL * b) >> 8; } break; case DK4_CS_CONV_RGB_TO_GRAY_LUMA : { back = (30UL * r + 59UL * g + 11UL * b) / 100UL; } break; default : { back = (299UL * r + 587UL * g + 114UL * b) / 1000UL; } break; } $? "- dk4cs_mix_rgb_to_gray %u", (unsigned)back return ((dk4_px_t)back); } /** Convert pixel data RGB to CMYK @param dptr Destination array address. @param sptr Source array address. @param max Maximum value for R, G, B, C, M, Y, and K. @param pctx Conversion context, may be NULL. */ static void dk4cs_mix_rgb_to_cmyk( dk4_px_t *dptr, dk4_px_t const *sptr, dk4_px_t max, dk4_cs_conv_ctx_t const *pctx ) { dk4_px_t maxrgb; /* Maximum of r g b */ dk4_px_t c; /* Cyan */ dk4_px_t m; /* Magenta */ dk4_px_t y; /* Yellow */ dk4_px_t k; /* Black */ dk4_cs_conv_rgb_to_cmyk_t rgb_to_cmyk = DK4_CS_CONV_RGB_TO_CMYK_NOT_PS_PDF; $? "+ dk4cs_mix_rgb_to_cmyk" #if DK4_USE_ASSERT assert(NULL != dptr); assert(NULL != sptr); #endif $? ". r=%u", (unsigned)(sptr[0]) $? ". g=%u", (unsigned)(sptr[1]) $? ". b=%u", (unsigned)(sptr[2]) if (NULL != pctx) { rgb_to_cmyk = pctx->rgb_to_cmyk; } maxrgb = sptr[0]; if (sptr[1] > maxrgb) { maxrgb = sptr[1]; } if (sptr[2] > maxrgb) { maxrgb = sptr[2]; } k = max - maxrgb; switch (rgb_to_cmyk) { case DK4_CS_CONV_RGB_TO_CMYK_PS_PDF : { c = maxrgb - sptr[0]; m = maxrgb - sptr[1]; y = maxrgb - sptr[2]; } break; default : { if ((dk4_px_t)0U == maxrgb) { c = m = y = (dk4_px_t)0U; } else { c = (dk4_px_t)( ( ((unsigned long)max) * (((unsigned long)maxrgb) - ((unsigned long)(sptr[0]))) ) / ((unsigned long)maxrgb) ); m = (dk4_px_t)( ( ((unsigned long)max) * (((unsigned long)maxrgb) - ((unsigned long)(sptr[1]))) ) / ((unsigned long)maxrgb) ); y = (dk4_px_t)( ( ((unsigned long)max) * (((unsigned long)maxrgb) - ((unsigned long)(sptr[2]))) ) / ((unsigned long)maxrgb) ); } } break; } dptr[0] = c; dptr[1] = m; dptr[2] = y; dptr[3] = k; $? ". c=%u", (unsigned)(dptr[0]) $? ". m=%u", (unsigned)(dptr[1]) $? ". y=%u", (unsigned)(dptr[2]) $? ". k=%u", (unsigned)(dptr[3]) $? "- dk4cs_mix_rgb_to_cmyk" } /** Convert pixel data CMYK to RGB. @param dptr Destination array address. @param sptr Source array address. @param max Maximum value for R, G, B, C, M, Y, and K. @param pctx Conversion context, may be NULL. */ static void dk4cs_mix_cmyk_to_rgb( dk4_px_t *dptr, dk4_px_t const *sptr, dk4_px_t max, dk4_cs_conv_ctx_t const *pctx ) { dk4_cs_conv_rgb_to_cmyk_t alg = DK4_CS_CONV_RGB_TO_CMYK_NOT_PS_PDF; dk4_px_t r; /* Red */ dk4_px_t g; /* Green */ dk4_px_t b; /* Blue */ dk4_px_t tr; /* Additional red component */ dk4_px_t tg; /* Additional green component */ dk4_px_t tb; /* Additional blue component */ $? "+ dk4cs_mix_cmyk_to_rgb" #if DK4_USE_ASSERT assert(NULL != dptr); assert(NULL != sptr); #endif $? ". c=%u", (unsigned)(sptr[0]) $? ". m=%u", (unsigned)(sptr[1]) $? ". y=%u", (unsigned)(sptr[2]) $? ". k=%u", (unsigned)(sptr[3]) if (NULL != pctx) { alg = pctx->rgb_to_cmyk; } r = g = b = max - sptr[3]; switch (alg) { case DK4_CS_CONV_RGB_TO_CMYK_PS_PDF : { /* Do nothing */ } break; default : { tr = (dk4_px_t)( (((unsigned long)(sptr[0])) * ((unsigned long)(sptr[3]))) / ((unsigned long)max) ); tg = (dk4_px_t)( (((unsigned long)(sptr[1])) * ((unsigned long)(sptr[3]))) / ((unsigned long)max) ); tb = (dk4_px_t)( (((unsigned long)(sptr[2])) * ((unsigned long)(sptr[3]))) / ((unsigned long)max) ); if ((max - tr) >= r) { r += tr; } else { r = max; } if ((max - tg) >= g) { g += tg; } else { g = max; } if ((max - tb) >= b) { b += tb; } else { b = max; } } break; } if (r >= sptr[0]) { r -= sptr[0]; } else { r = 0; } if (g >= sptr[1]) { g -= sptr[1]; } else { g = 0; } if (b >= sptr[2]) { g -= sptr[2]; } else { g = 0; } dptr[0] = r; $? ". r=%u", (unsigned)(dptr[0]) dptr[1] = g; $? ". g=%u", (unsigned)(dptr[1]) dptr[2] = b; $? ". b=%u", (unsigned)(dptr[2]) $? "- dk4cs_mix_cmyk_to_rgb" } size_t dk4cs_color_space_size(int cs) { size_t back = 0; $? "+ dk4cs_color_space_size cs=%d", cs switch (cs) { case DK4_CS_GRAY : { back = 1; } break; case DK4_CS_GRAY_ALPHA : { back = 2; } break; case DK4_CS_RGB : { back = 3; } break; case DK4_CS_RGB_ALPHA : case DK4_CS_CMYK : { back = 4; } break; } $? "- dk4cs_color_space_size %u", (unsigned)back return back; } void dk4cs_nc_px_convert( dk4_px_t *dptr, int dcs, dk4_px_t const *sptr, int scs, dk4_px_t const *bptr, dk4_px_t max, dk4_cs_conv_ctx_t const *pctx ) { dk4_px_t myrgb[4]; /* Temporary pixel value */ size_t i; /* Traverse components when mixing */ dk4_cs_conv_alpha_t alpha; /* Alpha handling choice */ $? "+ dk4cs_nc_px_convert" #if DK4_USE_ASSERT assert(NULL != dptr); assert(NULL != sptr); #endif switch (scs) { case DK4_CS_GRAY : { $? ". e = %u", (unsigned)(sptr[0]) switch (dcs) { case DK4_CS_GRAY : { $? ". gray to gray" dptr[0] = sptr[0]; $? ". e = %u", (unsigned)(dptr[0]) } break; case DK4_CS_GRAY_ALPHA : { $? ". gray to gray+alpha" dptr[0] = sptr[0]; dptr[1] = max; $? ". e = %u", (unsigned)(dptr[0]) $? ". a = %u", (unsigned)(dptr[1]) } break; case DK4_CS_RGB : { $? ". gray to rgb" dptr[0] = dptr[1] = dptr[2] = sptr[0]; $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) } break; case DK4_CS_RGB_ALPHA : { $? ". gray to rgb+alpha" dptr[0] = dptr[1] = dptr[2] = sptr[0]; dptr[3] = max; $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) $? ". a = %u", (unsigned)(dptr[3]) } break; case DK4_CS_CMYK : { $? ". gray to cmyk" dptr[3] = max - sptr[0]; dptr[0] = dptr[1] = dptr[2] = 0; $? ". c = %u", (unsigned)(dptr[0]) $? ". m = %u", (unsigned)(dptr[1]) $? ". y = %u", (unsigned)(dptr[2]) $? ". k = %u", (unsigned)(dptr[3]) } break; default : { } break; } } break; case DK4_CS_GRAY_ALPHA : { $? ". e = %u", (unsigned)(sptr[0]) $? ". a = %u", (unsigned)(sptr[1]) alpha = DK4_CS_CONV_ALPHA_MIX; if (NULL != pctx) { alpha = pctx->alpha; } switch (dcs) { case DK4_CS_GRAY : { $? ". gray+alpha to gray" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[0] = sptr[0]; } break; default : { dptr[0] = dk4cs_mix_alpha( sptr[0], sptr[1], bptr[0], max ); } break; } $? ". e = %u", (unsigned)(dptr[0]) } break; case DK4_CS_GRAY_ALPHA : { $? ". gray+alpha to gray+alpha" dptr[0] = sptr[0]; dptr[1] = sptr[1]; $? ". e = %u", (unsigned)(dptr[0]) $? ". a = %u", (unsigned)(dptr[1]) } break; case DK4_CS_RGB : { $? ". gray+alpha to rgb" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[0] = dptr[1] = dptr[2] = sptr[0]; } break; default : { dptr[0] = dptr[1] = dptr[2] = dk4cs_mix_alpha( sptr[0], sptr[1], bptr[0], max ); } break; } $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) } break; case DK4_CS_RGB_ALPHA : { $? ". gray+alpha to rgb+alpha" dptr[3] = sptr[1]; dptr[0] = dptr[1] = dptr[2] = sptr[0]; $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) $? ". a = %u", (unsigned)(dptr[3]) } break; case DK4_CS_CMYK : { $? ". gray+alpha to cmyk" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[3] = max - sptr[0]; } break; default : { dptr[3] = max - dk4cs_mix_alpha( sptr[0], sptr[1], bptr[0], max ); } break; } dptr[0] = dptr[1] = dptr[2] = 0; $? ". c = %u", (unsigned)(dptr[0]) $? ". m = %u", (unsigned)(dptr[1]) $? ". y = %u", (unsigned)(dptr[2]) $? ". k = %u", (unsigned)(dptr[3]) } break; default : { } break; } } break; case DK4_CS_RGB : { $? ". r = %u", (unsigned)(sptr[0]) $? ". g = %u", (unsigned)(sptr[1]) $? ". b = %u", (unsigned)(sptr[2]) switch (dcs) { case DK4_CS_GRAY : { $? ". rgb to gray" dptr[0] = dk4cs_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); $? ". e = %u", (unsigned)(dptr[0]) } break; case DK4_CS_GRAY_ALPHA : { $? ". rgb to gray+alpha" dptr[0] = dk4cs_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); dptr[1] = max; $? ". e = %u", (unsigned)(dptr[0]) $? ". a = %u", (unsigned)(dptr[1]) } break; case DK4_CS_RGB : { $? ". rgb to rgb" dptr[0] = sptr[0]; dptr[1] = sptr[1]; dptr[2] = sptr[2]; $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) } break; case DK4_CS_RGB_ALPHA : { $? ". rgb to rgb+alpha" dptr[0] = sptr[0]; dptr[1] = sptr[1]; dptr[2] = sptr[2]; dptr[3] = max; $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) $? ". a = %u", (unsigned)(dptr[3]) } break; case DK4_CS_CMYK : { $? ". rgb to cmyk" dk4cs_mix_rgb_to_cmyk(dptr, sptr, max, pctx); $? ". c = %u", (unsigned)(dptr[0]) $? ". m = %u", (unsigned)(dptr[1]) $? ". y = %u", (unsigned)(dptr[2]) $? ". k = %u", (unsigned)(dptr[3]) } break; default : { } break; } } break; case DK4_CS_RGB_ALPHA : { $? ". r = %u", (unsigned)(sptr[0]) $? ". g = %u", (unsigned)(sptr[1]) $? ". b = %u", (unsigned)(sptr[2]) $? ". a = %u", (unsigned)(sptr[3]) alpha = DK4_CS_CONV_ALPHA_MIX; if (NULL != pctx) { alpha = pctx->alpha; } switch (dcs) { case DK4_CS_GRAY : { $? ". rgb+alpha to gray" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[0] = dk4cs_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); } break; default : { for (i = 0; i < 3; i++) { myrgb[i] = dk4cs_mix_alpha( sptr[i], sptr[3], bptr[i], max ); } dptr[0] = dk4cs_mix_rgb_to_gray( myrgb[0], myrgb[1], myrgb[2], pctx ); } break; } $? ". e = %u", (unsigned)(dptr[0]) } break; case DK4_CS_GRAY_ALPHA : { $? ". rgb+alpha to gray+alpha" dptr[0] = dk4cs_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); dptr[1] = sptr[3]; $? ". e = %u", (unsigned)(dptr[0]) $? ". a = %u", (unsigned)(dptr[1]) } break; case DK4_CS_RGB : { $? ". rgb+alpha to rgb" for (i = 0; i < 3; i++) { switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[i] = sptr[i]; } break; default : { dptr[i] = dk4cs_mix_alpha( sptr[i], sptr[3], bptr[i], max ); } break; } } $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) } break; case DK4_CS_RGB_ALPHA : { $? ". rgb+alpha to rgb+alpha" for (i = 0; i < 4; i++) { dptr[i] = sptr[i]; } $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) $? ". a = %u", (unsigned)(dptr[3]) } break; case DK4_CS_CMYK : { $? ". rgb+alpha to cmyk" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dk4cs_mix_rgb_to_cmyk(dptr, sptr, max, pctx); } break; default : { for (i = 0; i < 3; i++) { myrgb[i] = dk4cs_mix_alpha( sptr[i], sptr[3], bptr[i], max ); } dk4cs_mix_rgb_to_cmyk(dptr, myrgb, max, pctx); } break; } $? ". c = %u", (unsigned)(dptr[0]) $? ". m = %u", (unsigned)(dptr[1]) $? ". y = %u", (unsigned)(dptr[2]) $? ". k = %u", (unsigned)(dptr[3]) } break; default : { } break; } } break; case DK4_CS_CMYK : { $? ". c = %u", (unsigned)(sptr[0]) $? ". m = %u", (unsigned)(sptr[1]) $? ". y = %u", (unsigned)(sptr[2]) $? ". k = %u", (unsigned)(sptr[3]) switch (dcs) { case DK4_CS_GRAY : { $? ". cmyk to gray" dk4cs_mix_cmyk_to_rgb(myrgb, sptr, max, pctx); dptr[0] = dk4cs_mix_rgb_to_gray( myrgb[0], myrgb[1], myrgb[2], pctx ); $? ". e = %u", (unsigned)(dptr[0]) } break; case DK4_CS_GRAY_ALPHA : { $? ". cmyk to gray+alpha" dk4cs_mix_cmyk_to_rgb(myrgb, sptr, max, pctx); dptr[0] = dk4cs_mix_rgb_to_gray( myrgb[0], myrgb[1], myrgb[2], pctx ); dptr[1] = max; $? ". e = %u", (unsigned)(dptr[0]) $? ". a = %u", (unsigned)(dptr[1]) } break; case DK4_CS_RGB : { $? ". cmyk to rgb" dk4cs_mix_cmyk_to_rgb(dptr, sptr, max, pctx); $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) } break; case DK4_CS_RGB_ALPHA : { $? ". cmyk to rgb+alpha" dk4cs_mix_cmyk_to_rgb(dptr, sptr, max, pctx); dptr[3] = max; $? ". r = %u", (unsigned)(dptr[0]) $? ". g = %u", (unsigned)(dptr[1]) $? ". b = %u", (unsigned)(dptr[2]) $? ". a = %u", (unsigned)(dptr[3]) } break; case DK4_CS_CMYK : { $? ". cmyk to cmyk" for (i = 0; i < 4; i++) { dptr[i] = sptr[i]; } $? ". c = %u", (unsigned)(dptr[0]) $? ". m = %u", (unsigned)(dptr[1]) $? ". y = %u", (unsigned)(dptr[2]) $? ". k = %u", (unsigned)(dptr[3]) } break; default : { } break; } } break; default : { } break; } $? "- dk4cs_nc_px_convert" } void dk4cs_px_convert( dk4_px_t *dptr, int dcs, size_t dsz, dk4_px_t const *sptr, int scs, size_t ssz, dk4_px_t const *bptr, size_t bsz, dk4_px_t max, dk4_cs_conv_ctx_t const *pctx, dk4_er_t *erp ) { size_t i; $? "+ dk4cs_px_convert" #if DK4_USE_ASSERT assert(NULL != dptr); assert(0 < dsz); assert(NULL != sptr); assert(0 < ssz); #endif /* Check arguments */ if (NULL == dptr) { $? "! dptr" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if ((DK4_CS_MIN > dcs) || (DK4_CS_MAX < dcs)) { $? "! dcs" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (dsz < dk4cs_color_space_size(dcs)) { $? "! dsz" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (NULL == sptr) { $? "! sptr" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if ((DK4_CS_MIN > scs) || (DK4_CS_MAX < scs)) { $? "! scs" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (ssz < dk4cs_color_space_size(scs)) { $? "! ssz" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if ((dk4_px_t)0U == max) { $? "! max" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } for (i = 0; i < ssz; i++) { if (max < sptr[i]) { $? "! sptr[]>max" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } } switch (scs) { case DK4_CS_GRAY_ALPHA : case DK4_CS_RGB_ALPHA : { if (NULL == bptr) { $? "! bptr" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (bsz < (dk4cs_color_space_size(scs) - 1)) { $? "! bsz" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } for (i = 0; i < bsz; i++) { if (max < bptr[i]) { $? "! bptr[]>max" dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); goto finished; } } } break; } /* Do conversion */ dk4cs_nc_px_convert(dptr, dcs, sptr, scs, bptr, max, pctx); finished: $? "- dk4cs_px_convert" return; } double dk4cs_color_mix_rgb_to_gray( double r, double g, double b, dk4_cs_conv_ctx_t const *pctx ) { double min; /* Minimum of r g b */ double max; /* Maximum of r g b */ double back = 0.0; dk4_cs_conv_rgb_to_gray_t alg = DK4_CS_CONV_RGB_TO_GRAY_ITU_BT601; $? "+ dk4cs_color_mix_rgb_to_gray r=%g g=%g b=%g", r, g, b if (NULL != pctx) { alg = pctx->rgb_to_gray; } switch (alg) { case DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_BLUE : { back = b; } break; case DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_GREEN : { back = g; } break; case DK4_CS_CONV_RGB_TO_GRAY_CHANNEL_RED : { back = r; } break; case DK4_CS_CONV_RGB_TO_GRAY_MAX_DECOMPOSITION : { back = r; if (g > back) back = g; if (b > back) back = b; } break; case DK4_CS_CONV_RGB_TO_GRAY_MEDIUM_DECOMPOSITION : { if (r > g) { if (g > b) { back = g; } else { if (r > b) { back = b; } else { back = r; } } } else { if (g < b) { back = g; } else { if (r < b) { back = b; } else { back = r; } } } } break; case DK4_CS_CONV_RGB_TO_GRAY_MIN_DECOMPOSITION : { back = r; if (g < back) back = g; if (b < back) back = b; } break; case DK4_CS_CONV_RGB_TO_GRAY_DESATURATION : { min = max = r; if (g < min) min = g; if (g > max) max = g; if (b < min) min = b; if (b > max) max = b; back = (max + min) / 2.0; } break; case DK4_CS_CONV_RGB_TO_GRAY_ITU_BT709 : { back = 0.2126*r + 0.7152*g + 0.0722*b; } break; case DK4_CS_CONV_RGB_TO_GRAY_AVERAGE : { back = (r + g + b) / 3.0; } break; case DK4_CS_CONV_RGB_TO_GRAY_LUMA : { back = 0.3*r + 0.59*g + 0.11*b; } break; default : { back = 0.299*r + 0.587*g + 0.114*b; } break; } $? "- dk4cs_color_mix_rgb_to_gray %g", back return back; } /** Convert normalized color RGB to CMYK @param dptr Address of result array (CMYK). @param sptr Source values (RGB). @param pctx Conversion context, may be NULL. */ static void dk4cs_color_mix_rgb_to_cmyk( double *dptr, const double *sptr, dk4_cs_conv_ctx_t const *pctx ) { double c; /* Cyan */ double m; /* Magenta */ double y; /* Yellow */ double k; /* Black */ double maxrgb; /* Maximum of r g b */ int me; /* Flag: Math error */ dk4_cs_conv_rgb_to_cmyk_t alg = DK4_CS_CONV_RGB_TO_CMYK_NOT_PS_PDF; $? "+ dk4cs_color_mix_rgb_to_cmyk" #if DK4_USE_ASSERT assert(NULL != dptr); assert(NULL != sptr); #endif $? ". r=%g", sptr[0] $? ". g=%g", sptr[1] $? ". b=%g", sptr[2] if (NULL != pctx) { alg = pctx->rgb_to_cmyk; } maxrgb = sptr[0]; if (sptr[1] > maxrgb) { maxrgb = sptr[1]; } if (sptr[2] > maxrgb) { maxrgb = sptr[2]; } c = maxrgb - sptr[0]; m = maxrgb - sptr[1]; y = maxrgb - sptr[2]; k = 1.0 - maxrgb; switch (alg) { case DK4_CS_CONV_RGB_TO_CMYK_PS_PDF : { /* Nothing to do */ } break; default : { me = 0; c = c / maxrgb; m = m / maxrgb ; y = y / maxrgb; if (!(dk4ma_is_finite(c))) { me = 1; } if (!(dk4ma_is_finite(m))) { me = 1; } if (!(dk4ma_is_finite(y))) { me = 1; } if ((0.0 > c) || (1.0 < c)) { me = 1; } if ((0.0 > m) || (1.0 < m)) { me = 1; } if ((0.0 > y) || (1.0 < y)) { me = 1; } if (0 != me) { c = m = y = 0.0; k = 1.0; } } break; } dptr[0] = c; dptr[1] = m; dptr[2] = y; dptr[3] = k; $? ". c=%g", dptr[0] $? ". m=%g", dptr[1] $? ". y=%g", dptr[2] $? ". k=%g", dptr[3] $? "- dk4cs_color_mix_rgb_to_cmyk" } /** Convert normalized color CMYK to RGB. @param dptr Address of result array. @param sptr Address of source array. @param pctx Conversion context, may be NULL. */ static void dk4cs_color_mix_cmyk_to_rgb( double *dptr, double const *sptr, dk4_cs_conv_ctx_t const *pctx ) { double r; /* Red */ double g; /* Green */ double b; /* Blue */ dk4_cs_conv_rgb_to_cmyk_t alg = DK4_CS_CONV_RGB_TO_CMYK_NOT_PS_PDF; $? "+ dk4cs_color_mix_cmyk_to_rgb" #if DK4_USE_ASSERT assert(NULL != dptr); assert(NULL != sptr); #endif $? ". c=%g", sptr[0] $? ". m=%g", sptr[1] $? ". y=%g", sptr[2] $? ". k=%g", sptr[3] if (NULL != pctx) { alg = pctx->rgb_to_cmyk; } r = g = b = 1.0 - sptr[3]; r -= sptr[0]; g -= sptr[1]; b -= sptr[2]; switch (alg) { case DK4_CS_CONV_RGB_TO_CMYK_PS_PDF : { /* Nothing to do */ } break; default : { r += (sptr[0] * sptr[3]); g += (sptr[1] * sptr[3]); b += (sptr[2] * sptr[3]); } break; } if (0.0 > r) { r = 0.0; } if (1.0 < r) { r = 1.0; } if (0.0 > g) { g = 0.0; } if (1.0 < g) { g = 1.0; } if (0.0 > b) { b = 0.0; } if (1.0 < b) { b = 1.0; } dptr[0] = r; dptr[1] = g; dptr[2] = b; $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] $? "- dk4cs_color_mix_cmyk_to_rgb" } void dk4cs_nc_color_convert( double *dptr, int dcs, double const *sptr, int scs, double const *bptr, dk4_cs_conv_ctx_t const *pctx ) { double myrgb[4]; /* Temporary pixel data for mixing */ size_t i; /* Traverse all pixel components */ dk4_cs_conv_alpha_t alpha; $? "+ dk4cs_nc_color_convert" #if DK4_USE_ASSERT assert(NULL != dptr); assert(NULL != sptr); #endif switch (scs) { case DK4_CS_GRAY : { $? ". e=%g", sptr[0] switch (dcs) { case DK4_CS_GRAY : { $? ". gray to gray" dptr[0] = sptr[0]; $? ". e=%g", dptr[0] } break; case DK4_CS_GRAY_ALPHA : { $? ". gray to gray+alpha" dptr[0] = sptr[0]; dptr[1] = 1.0; $? ". e=%g", dptr[0] $? ". a=%g", dptr[1] } break; case DK4_CS_RGB : { $? ". gray to rgb" dptr[0] = dptr[1] = dptr[2] = sptr[0]; $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] } break; case DK4_CS_RGB_ALPHA : { $? ". gray to rgb+alpha" dptr[0] = dptr[1] = dptr[2] = sptr[0]; dptr[3] = 1.0; $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] $? ". a=%g", dptr[3] } break; case DK4_CS_CMYK : { $? ". gray to cmyk" dptr[3] = 1.0 - sptr[0]; dptr[0] = dptr[1] = dptr[2] = 0.0; $? ". c=%g", dptr[0] $? ". m=%g", dptr[1] $? ". y=%g", dptr[2] $? ". k=%g", dptr[3] } break; default : { } break; } } break; case DK4_CS_GRAY_ALPHA : { $? ". e=%g", sptr[0] $? ". a=%g", sptr[1] alpha = DK4_CS_CONV_ALPHA_MIX; if (NULL != pctx) { alpha = pctx->alpha; } switch (dcs) { case DK4_CS_GRAY : { $? ". gray+alpha to gray" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[0] = sptr[0]; } break; default : { dptr[0] = dk4cs_color_mix_alpha( sptr[0], sptr[1], bptr[0] ); } break; } $? ". e=%g", dptr[0] } break; case DK4_CS_GRAY_ALPHA : { $? ". gray+alpha to gray+alpha" dptr[0] = sptr[0]; dptr[1] = sptr[1]; $? ". e=%g", dptr[0] $? ". a=%g", dptr[1] } break; case DK4_CS_RGB : { $? ". gray+alpha to rgb" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[0] = dptr[1] = dptr[2] = sptr[0]; } break; default : { dptr[0] = dptr[1] = dptr[2] = dk4cs_color_mix_alpha(sptr[0], sptr[1], bptr[0]); } } $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] } break; case DK4_CS_RGB_ALPHA : { $? ". gray+alpha to rgb+alpha" dptr[3] = sptr[1]; dptr[0] = dptr[1] = dptr[2] = sptr[0]; $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] $? ". a=%g", dptr[3] } break; case DK4_CS_CMYK : { $? ". gray+alpha to cmyk" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[3] = 1.0 - sptr[0]; } break; default : { dptr[3] = 1.0-dk4cs_color_mix_alpha(sptr[0],sptr[1],bptr[0]); } } dptr[0] = dptr[1] = dptr[2] = 0.0; $? ". c=%g", dptr[0] $? ". m=%g", dptr[1] $? ". y=%g", dptr[2] $? ". k=%g", dptr[3] } break; default : { } break; } } break; case DK4_CS_RGB : { $? ". r=%g", sptr[0] $? ". g=%g", sptr[1] $? ". b=%g", sptr[2] switch (dcs) { case DK4_CS_GRAY : { $? ". rgb to gray" dptr[0] = dk4cs_color_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); $? ". e=%g", dptr[0] } break; case DK4_CS_GRAY_ALPHA : { $? ". rgb to gray+alpha" dptr[0] = dk4cs_color_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); dptr[1] = 1.0; $? ". e=%g", dptr[0] $? ". a=%g", dptr[1] } break; case DK4_CS_RGB : { $? ". rgb to rgb" for (i = 0; i < 3; i++) { dptr[i] = sptr[i]; } $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] } break; case DK4_CS_RGB_ALPHA : { $? ". rgb to rgb+alpha" for (i = 0; i < 3; i++) { dptr[i] = sptr[i]; } dptr[3] = 1.0; $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] $? ". a=%g", dptr[3] } break; case DK4_CS_CMYK : { $? ". rgb to cmyk" dk4cs_color_mix_rgb_to_cmyk(dptr, sptr, pctx); $? ". c=%g", dptr[0] $? ". m=%g", dptr[1] $? ". y=%g", dptr[2] $? ". k=%g", dptr[3] } break; default : { } break; } } break; case DK4_CS_RGB_ALPHA : { $? ". r=%g", sptr[0] $? ". g=%g", sptr[1] $? ". b=%g", sptr[2] $? ". a=%g", sptr[3] alpha = DK4_CS_CONV_ALPHA_MIX; if (NULL != pctx) { alpha = pctx->alpha; } switch (dcs) { case DK4_CS_GRAY : { $? ". rgb+alpha to gray" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[0] = dk4cs_color_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); } break; default : { for (i = 0; i < 3; i++) { myrgb[i] = dk4cs_color_mix_alpha( sptr[i], sptr[3], bptr[i] ); } dptr[0] = dk4cs_color_mix_rgb_to_gray( myrgb[0], myrgb[1], myrgb[2], pctx ); } } $? ". e=%g", dptr[0] } break; case DK4_CS_GRAY_ALPHA : { $? ". rgb+alpha to gray+alpha" dptr[0] = dk4cs_color_mix_rgb_to_gray( sptr[0], sptr[1], sptr[2], pctx ); dptr[1] = sptr[3]; $? ". e=%g", dptr[0] $? ". a=%g", dptr[1] } break; case DK4_CS_RGB : { $? ". rgb+alpha to rgb" for (i = 0; i < 3; i++) { switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dptr[i] = sptr[i]; } break; default : { dptr[i] = dk4cs_color_mix_alpha( sptr[i], sptr[3], bptr[i] ); } break; } } $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] } break; case DK4_CS_RGB_ALPHA : { $? ". rgb+alpha to rgb+alpha" for (i = 0; i < 4; i++) { dptr[i] = sptr[i]; } $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] $? ". a=%g", dptr[3] } break; case DK4_CS_CMYK : { $? ". rgb+alpha to cmyk" switch (alpha) { case DK4_CS_CONV_ALPHA_FOREGROUND : { dk4cs_color_mix_rgb_to_cmyk(dptr, sptr, pctx); } break; default : { for (i = 0; i < 3; i++) { myrgb[i] = dk4cs_color_mix_alpha( sptr[i], sptr[3], bptr[i] ); } dk4cs_color_mix_rgb_to_cmyk(dptr, myrgb, pctx); } break; } $? ". c=%g", dptr[0] $? ". m=%g", dptr[1] $? ". y=%g", dptr[2] $? ". k=%g", dptr[3] } break; default : { } break; } } break; case DK4_CS_CMYK : { $? ". c=%g", sptr[0] $? ". m=%g", sptr[1] $? ". y=%g", sptr[2] $? ". k=%g", sptr[3] switch (dcs) { case DK4_CS_GRAY : { $? ". cmyk to gray" dk4cs_color_mix_cmyk_to_rgb(myrgb, sptr, pctx); dptr[0] = dk4cs_color_mix_rgb_to_gray( myrgb[0], myrgb[1], myrgb[2], pctx ); $? ". e=%g", dptr[0] } break; case DK4_CS_GRAY_ALPHA : { $? ". cmyk to gray+alpha" dk4cs_color_mix_cmyk_to_rgb(myrgb, sptr, pctx); dptr[0] = dk4cs_color_mix_rgb_to_gray( myrgb[0], myrgb[1], myrgb[2], pctx ); dptr[1] = 1.0; $? ". e=%g", dptr[0] $? ". a=%g", dptr[1] } break; case DK4_CS_RGB : { $? ". cmyk to rgb" dk4cs_color_mix_cmyk_to_rgb(dptr, sptr, pctx); $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] } break; case DK4_CS_RGB_ALPHA : { $? ". cmyk to rgb+alpha" dk4cs_color_mix_cmyk_to_rgb(dptr, sptr, pctx); dptr[3] = 1.0; $? ". r=%g", dptr[0] $? ". g=%g", dptr[1] $? ". b=%g", dptr[2] $? ". a=%g", dptr[3] } break; case DK4_CS_CMYK : { $? ". cmyk to cmyk" for (i = 0; i < 4; i++) { dptr[i] = sptr[i]; } $? ". c=%g", dptr[0] $? ". m=%g", dptr[1] $? ". y=%g", dptr[2] $? ". k=%g", dptr[3] } break; default : { } break; } } break; default : { } break; } $? "- dk4cs_nc_color_convert" } void dk4cs_color_convert( double *dptr, int dcs, size_t dsz, double const *sptr, int scs, size_t ssz, double const *bptr, size_t bsz, dk4_cs_conv_ctx_t const *pctx, dk4_er_t *erp ) { size_t i; $? "+ dk4cs_color_convert" #if DK4_USE_ASSERT assert(NULL != dptr); assert(0 < dsz); assert(NULL != sptr); assert(0 < ssz); #endif /* Check arguments */ if (NULL == dptr) { $? "! dptr" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if ((DK4_CS_MIN > dcs) || (DK4_CS_MAX < dcs)) { $? "! dcs" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (dsz < dk4cs_color_space_size(dcs)) { $? "! dsz" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (NULL == sptr) { $? "! sptr" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if ((DK4_CS_MIN > scs) || (DK4_CS_MAX < scs)) { $? "! scs" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (ssz < dk4cs_color_space_size(scs)) { $? "! ssz" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } for (i = 0; i < ssz; i++) { if ((0.0 > sptr[i]) || (1.0 < sptr[i])) { $? "! sptr[]" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } } switch (scs) { case DK4_CS_GRAY_ALPHA : case DK4_CS_RGB_ALPHA : { if (NULL == bptr) { $? "! bptr" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } if (bsz < (dk4cs_color_space_size(scs) - 1)) { $? "! bsz" dk4error_set_simple_error_code(erp, DK4_E_INVALID_ARGUMENTS); goto finished; } for (i = 0; i < bsz; i++) { if ((0.0 > bptr[i]) || (1.0 < bptr[i])) { $? "! bptr[]" dk4error_set_simple_error_code(erp,DK4_E_INVALID_ARGUMENTS); goto finished; } } } break; } /* Do conversion */ dk4cs_nc_color_convert(dptr, dcs, sptr, scs, bptr, pctx); finished: $? "- dk4cs_color_convert" return; } int dk4cs_check(int cs) { int back = 0; if ((DK4_CS_MIN <= cs) && (DK4_CS_MAX >= cs)) { back = 1; } return back; } /* vim: set ai sw=4 ts=4 : */