PDL::T...::Color(3pm)
π NAME π
PDL::Transform::Color β the glow-up toolkit for color system conversions in PDL β¨
(also, hi, this page lives in the User Contributed Perl Documentation section, so you'll see it filed as PDL::Transform::Color(3pm) β section 3pm, perl module docs, you know the drill π«Ά)
π SYNOPSIS π
### Shrink an RGB image with proper linear interpolation:
### DEcode the sRGB image values, then interpolate, then ENcode sRGB
$im = rpic("big_colorimage.jpg");
$im2 = $im->invert(t_srgb())->match([500,500],{m=>'g'})->apply(t_srgb());
okay bestie look at this. you decode, you interpolate, you encode. that's it. that's the whole vibe. π₯
π DESCRIPTION π
PDL::Transform::Color is serving a whole buffet of color conversion transformations. What can you even do with them? so many things:
- simple hacks on machine-native color representations (RGB <-> HSV, etc.) π
- simple encoding/decoding of machine-native color representations such as sRGB
- or, if you're feeling β¨extraβ¨, more sophisticated manipulation of absolute color standards β including large-gamut or perceptual systems
The color transforms in this module can also be used for converting between proper color systems, for gamma-converting pixel values, or for generating pseudocolor from one or two input parameters. And on top of all that, several named "color maps" (also called "color tables") are provided. yes, they're giving freebies. we love that here. π
The module uses linearized sRGB (lsRGB) as a fundamental color basis, and honestly? slay choice. sRGB is the standard color system used by most consumer- to mid-grade computer equipment, so casual users can just use this color representation without much regard for gamuts, colorimetric standards, etc. no cap. π
Most of the transform generators convert from lsRGB to various other systems. The notable simple ones are:
- HSV (Hue, Saturation, Value)
- HSL (Hue, Saturation, Lightness)
- CMYK (Cyan, Magenta, Yellow, blacK)
If you aren't familiar with PDL::Transform, bestie, you should read that POD now, because this is a subclass of PDL::Transform. Like, go read it. I'll wait. π
So, quick recap of what transforms even are: they represent and encapsulate vector transformations β one- or two-way vector functions that may be applied, composed, or (if possible) inverted. They're created through constructor methods that often allow parametric adjustment at creation time. iconic behavior, honestly. π
If you just want to "manipulate some RGB images" and not learn about the esoterica of color representations (and honestly valid of you), you can treat all the routines as working "from RGB" on the interval [0,1], and use t_srgb to import/export color images from/to "24-bit color" that your computer probably expects. If you do care about the esoterica, read on, scholar. π§
The output transfer function for sRGB is nonlinear β the luminance of a pixel on-screen varies somewhat faster than the square of the input value β which is inconvenient for blending, merging, and manipulating color. π
Many common operations work best with a linear photometric representation. So PDL::Transform::Color works with an internal model that is a floating-point linear system representing pixels as 3-vectors whose components are proportional to photometric brightness in the sRGB primary colors. This system is called lsRGB within the module. periodt. β¨
Note that, in general, RGB representations are limited to a particular narrow gamut of physically accessible values. π
Here's why, and I'm gonna be real with you: while the human eye has three dominant colorimetric input channels and hence color can be represented as a 3-vector, the human eye does not cleanly separate the spectra responsible for red, green, and blue stimuli. As a result, no trio of physical primary colors (which must have positive-definite spectra and positive-definite overall intensities) can represent every perceivable color β even though they form a basis of color space. the math said no and the math meant it. π
But in digital representation? there's no hard limit on the values of the RGB vectors β they can be negative or arbitrarily large. So this permits representation of out-of-gamut values using negative or over-unity intensities. Which means floating-point lsRGB lets you represent literally any color value that the human eye can perceive, and many that it can't. π¦
This is useful even though many such colors can't be rendered on a monitor. For example, you can change between several color representations and not be limited by the formal gamut of each representation β only by the final export standard. iykyk. π
πΈ The Three Major Output Formats πΈ
Three major output formats are supported, and they each get their own little moment:
- sRGB β standard "24-bit color", with the industry standard transfer function
- bRGB β bytescaled RGB, with a controllable gamma function (default
2.2, matching the average gamma value of most CRTs and calibrated flat monitors) - CMYK β direct linear inversion of the RGB values, with byte scaling
These are created by applying the transforms t_srgb, t_brgb, and t_cmyk, respectively, to an lsRGB color triplet.
The t_srgb export routine will translate represented colors in floating-point lsRGB to byte-encoded sRGB (or, if inverted, vice versa), using the correct nonlinear scaling β which is slightly more complicated than gamma functions, fyi. π
In general, you can use !t_srgb to import existing images you may have found lying around the net; manipulate their hue, etc.; and re-export with t_srgb. very fetch. π
If you prefer to work with direct gamma functions or straight scaling, you can import/export from/to byte values with t_brgb instead. For example, to export a color in the CIE RGB system (different primaries than sRGB), use:
t_brgb() x t_ciergb
π¦ Pseudocolor Transformations π¦
There are also some pseudocolor transformations, which convert a single data value to normalized RGB. They are:
t_pcβ for photometric (typical scientific) values π¬t_pcpβ for perceptual (typical consumer camera) values π·
They are described below, along with a collection of named pseudocolor maps that are supplied with the module. and yes, the named maps are absolutely going to eat. stay tuned. β
β¨ OVERVIEW OF COLOR THEORY β¨
Okay bestie, buckle up, because before we touch a single function we gotta understand why color is Like That. π
Because of the biophysics of the human eye, color is genuinely well represented as a 3-vector of red, green, and blue brightness values β those represent brightness in the long, middle, and short portions of the visible spectrum. But here's the tea π«: the absorption/sensitivity bands overlap significantly, so no physical light (of any wavelength) can form a proper "primary color" (orthonormal basis element) of this space. Any vector in color space can be written as a linear sum of three independent basis vectors ("primary colors")... but there's no such thing as negative intensity, so any tricolor representation of the color space is stuck inside a "gamut" β the set you can actually reach with positive linear combinations of whatever primary colors you picked. π
Some professional color representations (think 5- and 7-color dye processes) expand that gamut so it better matches the overall spectral response of the human eye β but the price is over-determining color values in what is fundamentally a 3-space. It's giving "too many cooks" energy ngl.
RGB color representations require you to specify particular primary colors that represent particular spectral profiles. The choice of primaries depends on the technical solution being used for I/O. The most universal "standard" representation is the CIE RGB standard, developed in 1931 by the Commission Internationale de l'Eclairage (CIE; International Commission on Illumination). The 1931 CIE RGB system is also called simply CIERGB by many sources. It uses primary wavelengths of 700nm (red), 546.1 nm (green), and 435.8 nm (blue). π
The most universal "computer" representation is the sRGB standard defined by Anderson et al. (1996), which uses slightly different primary colors than the 1931 CIE RGB standard does β because sRGB is based on the colorimetric output of color television phosphors in CRTs, while CIE RGB was developed based on easily lab-reproducible spectra. Different origin story, different primaries, slay. π
The PDL::Transform::Color transformations are all relative to the sRGB color basis. Negative values are permitted, allowing representation of all colors β possible or impossible. π
CIE defined several other important color systems too. First up: an XYZ system based on nonphysical primaries X, Y, and Z that correspond to red, green, and blue, respectively. The XYZ system can represent all colors detectable to the human eye with positive-definite intensities of the "primaries" β the necessary negative intensities are just hidden in the formal spectrum of each of the primaries. Sneaky!! π The Y primary of this system corresponds closely to green, and is used by CIE as a proxy for overall luminance.
The CIE also separated "chrominance" and "luminance" signals, in a separate system called "xyY", which represents color as sum-normalized vectors "x=X/(X+Y+Z), "y=Y/(X+Y+Z)", and "z=Z/(X+Y+Z)". By construction, x+y+z=1, so "x" and "y" alone describe the color range of the system, and "Y" stands in for overall luminance. Easy math, iconic result. β¨
A linear RGB system is specified exactly by the chrominance (CIE XYZ or xyY) coordinates of the three primaries, plus a white point chrominance. The white point chrominance sets the relative scaling between the brightnesses of the primaries to achieve a color-free ("white") luminance. Different systems with the same R, G, B primary vectors can have different gains between those colors, which yields a slightly different shade of color at the R=G=B line. This "white" reference chrominance varies across systems, and the most common "white" standard is CIE's D65 spectrum based on a 6500K black body β but CIE, in particular, specifies a large number of white standards, and some systems use none of those but instead specify CIE XYZ values for the white point. So many options, we love a customizable queen. π
Similarly, real RGB systems typically use dynamic range compression via a nonlinear transfer function, which is most typically a "gamma function". A built-in database tracks about 15 standard named systems, so you can convert color values between them. Or you can specify your own system with a standard hash format (see "get_rgb"). DIY era fr fr. π οΈ
Provision exists for converting between different RGB systems with different primaries and different white points, by linearizing and then scaling. The most straightforward way to use this module to convert between two RGB systems (neither of which is lsRGB) is to inverse-transform one to lsRGB, then transform forward to the other. This is accomplished with the "t_shift_rgb" transform. Plot twist: hop through the middle and you're golden. π«
Many other representations than RGB exist to separate chromatic value from brightness. In general, these split into two camps: polar coordinates that represent hue as a single value divorced from the rgb basis, and those that represent it as a combination of two values like the 'x' and 'y' of the CIE xyY space. These are all based on the Munsell and Ostwald color systems, which were worked out at about the same time as the CIE system. Both Ostwald and Munsell worked around the start of the 20th century and pioneered colorimetric classification. History girlies rise π«Ά
Ostwald worked with quasi-linear representations of chromaticity as a 2-vector independent of brightness; these representations relate to CIERGB, CIEXYZ, and related systems via simple geometric projection β the CIE xyY space is an example. The most commonly used variant of xyY is CIELAB, a perceptual color space that separates color into a perceived lightness parameter L, and separate chromaticities 'a' and 'b'. CIELAB is commonly used by graphic artists and related professions, because it's an absolute space like XYZ (so that each LAB value corresponds to a particular perceivable color), and because the Cartesian norm between vectors in LAB space is approximately proportional to perceived difference between the corresponding colors. The system is thus useful for communicating color values precisely across different groups, or for developing perceptually-uniform display maps for generated data. The L, A, and B coordinates are highly nonlinear to approximately match the typical human visual system. β¨
Other related systems include YUV, YPbPr, and YCbCr β which are used for representing color for digital cinema and for video transmission. π¬πΊ
Munsell developed a color system based on separating the "hue" of a color into a single value separate from both its brightness and saturation level. This system is closely related to cylindrical polar coordinates in an RGB space, with the center of the cylinder on top of the line of values corresponding to neutral shades from "black" through "grey" to "white".
Two simple Munsell-like representations that work within the gamut of a particular RGB basis are HSL and HSV. Both of these systems are loose representations that are best defined relative to a particular RGB system. They're both designed specifically to represent an entire RGB gamut with a quasi-polar coordinate system, and are based on hexagonal angle β i.e. they are not exactly polar in nature. Almost-polar, the fakest kind of polar, we stan a hexagon. π·
HSL separates "Hue" and "Saturation" from "Lightness". Hue represents the spectral shade of the color as a direction from the central white reference line through RGB space: the R=G=B line. Saturation is a normalized chromaticity measuring the fraction of the distance from the white locus to the nearest edge of the RGB gamut at a particular hue and lightness. Lightness is an approximately hue-independent measure of total intensity. Deeply objectively "saturated" colors are only accessible at L=0.5; the L=0.5 surface includes all the additive and subtractive primary colors of the RGB system. Darker colors are less-saturated shades, while brighter colors fade to pastels. Pastel era πΈ
HSV is similar to HSL, but tracks only the brightest component among the RGB triplet as "Value" rather than the derived "Lightness". As a result, highly saturated HSV values have lower overall luminance than unsaturated HSV values with the same V, and the V=1 surface includes all the primary and secondary colors of the parent RGB system. This system takes advantage of the "Helmholtz-Kolhrausch effect" β that perceived brightness increases with saturation β so V better approximates perceived brightness at a given hue and saturation than L does. Not me learning science from a manpage π
Modern display devices generally produce physical brightnesses that are proportional not to their input signal, but to a nonlinear function of the input signal. The most common nonlinear function is a simple power law ("gamma function"): output is approximately proportional to the "gamma" power of the input. Raising a signal value to the power "1/gamma" is "gamma-encoding" it, and raising it to the power "gamma" is "gamma-decoding" it. Encode, decode, we stay busy. π
The sRGB 24-bit color standard specifies a slightly more complicated transfer curve, consisting of a linear segment spliced onto a horizontally-offset power law with gamma=2.4. This reduces quantization noise for very dark pxels, but approximates an overall power law with gamma=2.2. Hence, "t_brgb" (which supports general power law transfer functions) defaults to an output gamma of 2.2, but "t_srgb" yields a more accurate export transfer in typical use. The gamma value of 2.2 was selected in the early days of the television era, to approximately match the perceptual response of the human eye, and for nearly 50 years cathode-ray-tube (CRT) displays were specifically designed for a transfer gamma of 2.2 between applied voltage at the electron gun input stage and luminance (luminous energy flux) at the display screen. Throwback tech, honestly kind of iconic. πΊβ¨
Incidentally, some now-obsolete display systems (early MacOS systems and Silcon Graphics displays) operated with a gamma factor of 1.8, slightly less nonlinear than the standard. This derives from early use of checkerboard (and similar) pixelwise dithering to achieve a higher-bit-depth color palette than was otherwise possible, with early equipment. The display gamma of 2.2 interacted with direct dithering of digital values in the nonlinear space, to produce an effective gamma closer to 1.8 than 2.2. Rip to those systems, you did your best πͺ¦
π STANDARD OPTIONS π
gamma
Okay so gamma is the gamma-correction exponent β the lil number that turns your stored RGB values into actual physical luminance values for whatever RGB space you're coming from β¨. Most color math in here happens in linear space, i.e. gamma=1, which means: if you hand a gamma value to a conversion transform, the normalized RGB values get decoded into linear physical values before processing in the forward direction, and encoded back after processing in the reverse direction. That's the whole deal π«Ά.
Example time, bestie: say you wanna square the normalized floating-point lsRGB values before converting to bRGB β you'd use t_brgb(gamma="2)>. That gamma option is basically declaring "hey, the brightness the output device actually wants varies as the square of the pixel value sitting in the stored data" π₯.
And since lsRGB is the default working space for most transforms, you normally don't need to touch gamma at all β the default of 1.0 is already correct. No notes π
Contrariwise (yes I said contrariwise, it's giving scholar π
), the t_brgb export transform has a display_gamma option that sets the gamma function for the output bytes. So t_brgb(display_gamma=>2) square-roots the data before export β that way, squaring those numbers back gives you values proportional to the desired luminance of a display device π.
So the gamma option is kept around for completeness, but ngl β unless you know it's what you really want, you probably don't actually want it π. Instead, work in a linear space and only decode/encode the gamma of your import/export color space as you read values in or write values out. For example: generic images from the internet are typically in the sRGB system, and you can import them to lsRGB via the !t_srgb transform, or export with t_srgb β or handle other gamma-corrected 24-bit color systems directly with t_brgb and its display_gamma option π.
π§ FUNCTIONS π§
t_gamma
$t = t_gamma($gamma);
This is an internal generator that implements the standard gamma parameter for all the color transforms π. It's exported too, because honestly a lot of casual users just wanna slap a gamma curve on some existing data rather than do anything more rigorous β and that's valid π«Ά.
In the forward direction, t_gamma applies/decodes the gamma correction you indicated. So if your $gamma parameter at generation time is 2, the forward direction squares its input, and the inverse direction takes the square root (that's the "encodes the gamma correction" part) β¨.
Gamma correction here is implemented using a sign-tolerant approach: every value has its magnitude scaled with the power law, regardless of whether the value is positive or negative. Sign stays, magnitude obeys the law π .
t_brgb
$t = t_brgb();
Converts lsRGB (normalized to [0,1]) into byte-scaled RGB ([0,255]) π₯. By default t_brgb preps byte values tuned for a display gamma of 2.2, which approximates sRGB β and sRGB is the standard output color coding for most computer displays, so like, most of the time that's the vibe πΈ.
The difference between t_brgb and t_srgb in this usage? t_srgb uses the actual spliced-curve approximation specified in the sRGB standard, while t_brgb just uses a simple gamma law for export. Both valid, different flavors π.
t_brgb accepts these options, all of which may be abbreviated:
gamma(default 1) β if set, this is a gamma-encoding value for the original lsRGB, which gets decoded before the transform β¨.display_gamma(default 2.2) β if set, this is the gamma of the display the output is intended for π. The default compresses the brightness vector before output (taking approximately the square root). This matches the "standard gamma" applied by MacOS and Microsoft Windows displays, and approximates the sRGB standard. See alsot_srgbπ.clip(default 1) β if set, output is clipped to[0,256)in the forward direction and to[0,1]in the reverse direction βοΈ.byte(default 1) β if set, the output is converted to byte type in the forward direction. Heads up: this is a non-reversible operation, because precision is lost in the conversion to bytes π. (The reverse transform always creates a floating point value, since lsRGB lives on the interval[0,1]and an integer type would be useless.)
t_srgb
Converts lsRGB (the internal floating-point base representation) to sRGB β the typical RGB encoding used by most computing devices π. And since most computer terminals use sRGB, the representation's gamut is well matched to most computer monitors. Convenient? Iconic? Both π .
sRGB is a spliced standard, rather than having a direct gamma correction β which means there's genuinely no way to adjust the output gamma here π«. If you want to do that, use t_brgb instead, fr fr.
t_srgb accepts these options, all of which may be abbreviated:
gamma(default 1) β if set, this is a gamma-encoding value for the original lsRGB, which is decoded before the transform β¨.byte(default 1) β if set, the output gets clipped to the range[0,255]and rounded to a byte type PDL (24-bit color) π¨. (The reverse transform always creates a floating point value, since lsRGB exists on the interval[0,1]and an integer type would be useless.)clip(default 0) β if set, output gets clipped to the range[0,255]even if thebyteoption is not set βοΈ.
t_pc and t_pcp
These two implement a general purpose pseudocolor transformation π¦. You feed in a monochromatic value (zero active dims) and you get out an RGB value (one active dim, size 3). And because the most common use case is generating sRGB values, the default output is sRGB β you have to set a flag for lsRGB output, like if you wanna produce output in some other system by composing t_pc with a color transformation π
t_pc generates pseudocolor transforms ("color maps") with a photometric interpretation of the input: the input data are considered proportional to some kind of measured luminance or similar physical parameter π¬. This produces "correct" renderings of scenes captured by scientific cameras and similar instrumentation.
t_pcp generates pseudocolor transforms ("color maps") with a perceptual interpretation of the input: the input data are considered proportional to the perceptual variation desired across the display πΈ. This produces "correct" renderings of many non-luminant types of data β temperature, Doppler shift, frequency plots, etc. Big fan of these.
Both t_pc and t_pcp generate transforms from a collection of named transformations stored in an internal database (the global hash ref $PDL::Transform::Color::pc_tab) ποΈ. The transformations come in two basic sorts: quasi-photometric ones, which use luminance as the dominant varying parameter, and non-photometric ones, which use hue or saturation as the dominant varying parameter β¨. Only the photometric transformations get modified by t_pc vs t_pcp β so for example, t_pcp('rainbow') yields the exact same transform as t_pc('rainbow') π«Ά.
Some of the color transformations are "split" and intended for display of signed data β like the dop transformation, which fades red-to-white-to-blue and is meant for displaying Doppler or similar signals π«.
β οΈ NOTE:t_pcandt_pcpwork BACKWARDS from most of the transformations in this package: they convert FROM a data value TO sRGB or lsRGB. Don't get caught out bestie π.
There are options to adjust input gamma and the domain of the transformation (e.g. if your input data are on [0,1000] instead of [0,1]) π.
And if you feed in no arguments at all, either t_pc or t_pcp will list a collection of named pseudocolor transformations that work, right on the standard output π β free menu, no cap.
Options accepted are:
gamma(default 1) β presumed encoding gamma of the input β the input is decoded from this gamma value.1treats it as linear in luminance β¨.lsRGB(default 0) β produce lsRGB output instead of sRGB β (this may be abbreviatedlfor "linear") π.domainβ domain of the input; it's a synonym forirangeπ«Ά.irange(default[0,1]) β input range of the data β input data are by default clipped to[0,1]before application of the color map βοΈ. Specifying an undefined value causes the color map to be autoscaled to the input data, e.g.ir="[0,undef]>causes the color map to be scaled from 0 to the maximum value of the input. For full autoscaling, useir="[]>π₯.combination(default 0) β recombine r,g,b post facto β this option lets you perturb maps you like by mixing up r, g, and b after all the other calculations are done π. You feed in a number from0to5. If it's nonzero, you get a different combination of the three primaries. You can mock this up more compactly by appending-Cnto the (possibly abbreviated) name of the table. (Replace thenwith a number.)
For example, if you specify the color table sepia or sepia-c0 you'll get the sepiatone color table π¨. If you specify sepia-c5 you'll get almost the exact same color table as grepia π
You can abbreviate color table names with unique abbreviations. Tables currently accepted, and their intended uses, are:
QUASI-PHOTOMETRIC PSEUDOCOLOR MAPS FOR NORMAL USE
grey,gray, ormono(photometric) β simple monochrome. Minimalist queen π€.sepia,blepia,grepia,vepia,rygβ sepiatone and variants β these use color scaling to enhance contrast in a simple luminance transfer β¨.sepiais a black-brown-white curve reminiscent of sepia ink. The others are similar, but emphasize different primary colors π. Therygduplicates sepiatone, but with green highlights to increase contrast in near-saturated parts of an image πΏ.heatβ this black-red-yellow-white situation is reminiscent of blackbody curves (but does not match them rigorously β don't come for me, I'm just the messenger π₯).pm3d,voyβpm3dis the default color table for Gnuplot π. It's a colorblind-friendly, highly saturated table with horrible aesthetics but good contrast throughout π (I said what I said).voyis violet-orange-yellow. It's a more aesthetically pleasing colorblind-friendly map with a ton of contrast throughout the range π.oceanβ deep green through blue to white π.spring,summer,autumn,winterβ these are reminiscent of the "seasonal" colors provided by MatLab π. Thespringis horrendous but may be useful for certain aesthetic presentations π. Summer and Winter are similar to the sepia-like tables, but with different color paths. Autumn is similar to heat, but less garish π.
SPLIT PSEUDOCOLOR MAPS FOR SIGNED QUANTITIES
dop,dop1,dop2,dop3β these are various presentations of signed information, originally intended to display Doppler shift π. They are all quasi-photometric and split π«.vbgβ this is a violet-black-green signed fade useful for non-Doppler signed quantities π. Quasi-photometric and split.
π NON-PHOTOMETRIC PSEUDOCOLOR MAPS π
rainbowβ every color of the rainbow, red all the way through"violet"(which, plot twist, is magenta π)wheelβ the full-on"color wheel", and yes that includes the controversial magenta-to-red segment bestie, we're keeping it π
t_cieXYZ, t_xyz
The t_cieXYZ transform β and t_xyz, which is just a synonym, same energy π« β converts the module-native lsRGB over to the CIE XYZ representation. CIE XYZ is a nonphysical RGB-style system that minimally represents every physical color a human can possibly perceive under steady illumination. It relates to sRGB through a linear transformation (read: matrix multiplication, bestie) and it's the blueprint behind a whole bunch of other color systems (like CIE xyY).
CIE XYZ values are set up so they're positive definite for every human-perceptible color β the tradeoff being that the primaries are nonphysical (they straight up don't correspond to any possible spectral color π).
"t_ciexyz" takes these options:
gamma(default 1) β taken to be a coded gamma value in the original lsRGB, which gets decoded before conversion to the CIE XYZ system.rgb_system(default undef) β if it's there, it's gotta be either the name of an RGB system or an RGB system descriptor hash, exactly as described in"t_shift_rgb". If you don't specify one, the standard linearized sRGB that the rest of the module uses is assumed.use_system_gamma(default 0) β if this flag is set andrgb_systemis set too, then the RGB side of the transform is taken to be gamma-encoded with that RGB system's default value. Heads up: unless you explicitly specify an RGB system (by name or by hash), this flag is just ignored, fr.
t_rgi
Converts RGB to RG chroma with a separate intensity channel.
Real talk though β intensity here is just the average of the R, G, and B values. If what you actually want is perceptible luminance, use t_rgl or t_ycbcr instead π«Ά.
t_xyy and t_xyY
Convert from sRGB to CIE xyY. The "xyY" system is part of the CIE 1931 color spec. Luminance lives in the 2 coordinate, and chrominance x and y live in the 0 and 1 coordinates (zero-indexed, iykyk).
This is the coordinate system where "chromaticity diagrams" get plotted β¨. It can represent every illuminant color the typical human eye can perceive β and honestly a bunch more that it can't β with positive-definite coordinates.
Most of the domain space (which runs over [0-1] in all three dimensions) is inaccessible to most displays, because RGB gamuts are generally smaller than the actual visual gamut, which itself is a subset of the actual xyY data space. It's giving nested boxes π¦.
t_cielab or t_lab
$t = t_cielab();
Convert RGB to CIE Lab colors. "Lab" stands for Lightness, "a", and "b" β representing the overall luminance detection plus two opponent systems in the human eye (a: red/green, and b: yellow/blue). Lab colors are approximately perceptually uniform: they get mapped using a nonlinear transformation involving cube roots. Lab's whole flex is that Euclidean distances of equal size in the space yield approximately equal perceptual shifts in the color it represents. Iconic behavior honestly π
Lightness runs 0-100, and the a and b opponent systems run -100 to +100.
The Lab space includes the entire CIE XYZ gamut and a ton of "impossible colors" that can't be represented directly with physical light. Many of these "impossible colors" (also called "chimeric colors") can actually be experienced firsthand using visual fatigue effects, and they can be classified using Lab. Not me gatekeeping hidden colors π.
Lab is easiest to convert directly from XYZ space, so the "t_lab" constructor hands you back a compound transform of "t_xyz2lab" and "t_xyz".
t_xyz2lab
$t = t_xyz2lab();
Converts CIE XYZ to CIE Lab. Simple as that π .
t_cmyk
converts rgb to cmyk in the most straightforward way (by subtracting RGB values from unity).
CMYK and other process spaces are very complicated; this transform only serves up a relatively simple conversion that does not take ink gamut variation or a bunch of other effects into account. So don't come for it πΈ.
There is a provision for halftone gamma correction: "htgamma", which works exactly like the rgb gamma correction but gets applied to the CMYK output.
Options:
gamma(default 1) β the standard gamma affecting the RGB cubehtgamma(default 1) β a"halftone gamma"that's suitable for non-wash output processes such as halftoning. it acts on the CMYK values themselves.byte(default 0) β if present, the CMYK side gets scaled to 0-255 and converted to a byte type.
t_hsl and t_hsv
$rgb = $hsl->invert($t_hsl());
HSL stands for Hue, Saturation, Lightness. It's not an absolute color space, just derived from each RGB (by default, linearized sRGB). it has the same gamut as the host RGB system. The coordinates are hexagonal on the (RYGCBM) hexagon, following the nearest face of the (diagonally sliced) RGB cube. Geometry core fr π.
HSL is a double-cone system, so iso-L surfaces sit close to the plane perpendicular to the double-diagonal white/illuminant line R=G=B. The effect? It reduces saturation at high lightness levels, but keeps luminosity independent of saturation. Maximum saturation hits when S=1 and L=0.5; at higher L values, colors grow less saturated and more pastel, so that L follows the total luminosity of the output.
HSV is a stacked-cone system: iso-V surfaces are parallel to the bright faces of the RGB cube, so maximal bright saturation happens when S=1 and V=1. This means output luminosity drops with saturation β but thanks to the Helmholtz-Kolrausch effect (which links saturation to apparent brightness) the perceived brightness is less S-dependent: V follows the total apparent brightness of the output, even though output luminosity drops with S. Physics really said "both things are true" π.
You can represent out-of-gamut values in either system, by using S values greater than unity, or "illegal" V or L values. Yes bestie, chaotic, but the option's there π.
Hue, Saturation, and (Lightness or Value) each run from 0 to 1.
By default, the hue value follows a sin**4 scaling along each side of the RYGCBM hexagon. This softens the boundaries near the edges of the RGB cube, giving a better perceptual "color-wheel" transition between hues. There's a flag to switch to the linear behavior described in, e.g., the Wikipedia article on the HSV system.
You can encode the Lightness or Value with a gamma value ("lgamma") if you want. Your call π.
Options:
gamma(default 1) β treat the base RGB as gamma-encoded (default 1 is linear)lgamma(default 1) β treat the L coordinate as gamma-encoded (default 1 is linear).hsv(default 0 if called as"t_hsl", 1 if called as"t_hsv") β sets which of the HSL/HSV transform is to be used.hue_linear(default 0) β this flag determines how the hue ("angle") is calculated. By default, a sin**4 scaling is used along each branch of the RYGCBM hexagon, to soften the perceptual effects at the corners. If you set this flag, then the calculated"hue"is linear along each branch of the hexagon, to match (e.g.) the Wikipedia definition.
t_shift_illuminant
"t_new_illuminant" shifts a color from an old RGB system to a new one with a different white point. It accepts either a PDL containing a CIE xyY representation of the new illuminant, or a name of the new illuminant, plus some options.
Because this is shifting RGB to RGB within the same representation, gamma transformations get re-encoded afterward: if you use, say, "gamma=>2", then the RGB values are squared, then transformed, then square-rooted. Round trip slay π.
Options are:
gamma(default=1) β if present, this is the gamma coefficient for the representation of both the source and destination RGB spaces.from(default="D65") β if present, this is the xyY or name of the OLD illuminant. The default is D65, the illuminant for sRGB (and therefore lsRGB as well).basis(default="sRGB") β if present, this needs to be either"sRGB"or"XYZ"(case insensitive). If it's sRGB, the input and output are treated as standard lsRGB coordinates. If it's XYZ, then the input and output are in CIE XYZ coordinates.method(default="Bradford") β this can be"Bradford","Von Kries","XYZ", or a 3x3 matrix Ma (see"http://www.brucelindbloom.com/index.html?WorkingSpaceInfo.html")
t_shift_rgb
$t = t_shift_rgb("NTSC",{from=>"sRGB"});
Shifts the primary color basis of the lsrgb TO the destination system. Most named RGB systems have an associated preferred gamma, but that gets ignored by default: the RGB values are treated as if they were all linear representations. You can specify EITHER the name of the system OR the specific RGB parameters for that system. Pick your fighter π .
The RGB parameters, if you specify them, need to be in the form of a hash ref. The hash keys should be the same as what "PDL::Transform::Color::get_rgb" would return. All the keys must be present, except for gamma (which is ignored).
Alternatively, you can just use the name of a known system. These are listed in the documentation for "PDL::Transform::Color::get_rgb".
"t_shift_rgb" takes several options.
gamma(default 1) β the input triplets are assumed to be encoded with this gamma function. The default assumes linear representation.ogamma(default gamma) β the output triplets are assumed to need encoding with this gamma function.use_system_gammas(default 0) β this overrides the settings of"gamma"and"ogamma", and encodes/decodes according to the original system.wp_method(default undef) β this is the whitepoint shift method used to change illuminant value between systems with different whitepoints. See"t_shift_illuminant"for an explanation.from(default"sRGB") β this is the RGB system to convert from, in the same format as the system to convert to (names or a hash ref as described).
PDL::Transform::Color::xyy_from_D
$xyy = PDL::Transform::Color::xyy_from_D($D_value)
This utility routine generates CIE xyY system colorimetric values for standard CIE D-class illuminants (e.g., D50 or D65). The illuminants are calculated from a standard formula and correspond to black body temperatures between 4,000K and 250,000K. The D value is the temperature in K divided by 100, e.g. broad daylight is D65, corresponding to 6500 Kelvin. Math girlies rise up π₯.
This is used for calculating standard reference illuminants, to convert RGB values between illuminants.
For example, sRGB uses a D65 illuminant, but many other color standards refer to a D50 illuminant.
The colorimetric values are xy only; the Y coordinate can be specified via an option, or defaults to 0.5.
This routine is mainly used by "xyy_from_illuminant", which handles most of the CIE-recognized standard illuminant sources including the D's.
See "t_xyy" for a description of the CIE xyY absolute colorimetric system.
"xyy_from_D" accepts the following options:
Yβ the Y value of the output xyY coordinate
πΈ PDL::Transform::Color::xyy_from_illuminant πΈ
$xyy = PDL::Transform::Color::xyy_from_illuminant($name)
okay bestie so this lil utility does ONE thing and does it gorgeously: it spits out CIE xyY system colorimetric values for literally every standard CIE illuminant π The illuminants live in a table that gets filled in from the CIE publication Colorimetry, 3rd edition. no cap.
so like, an illuminant of a system? it's the same thing as its white point π It's the spot in xyY absolute colorimetric space that counts as "white". that's it. that's the vibe. β
CIE recognizes a whole bunch of standard illuminants, and (as of 2017, slay) is out here in the process of cooking up a brand new set β the "L" series illuminants β meant to represent LED lighting. β¨
ngl, doing an illuminant properly needs a full spectral representation, and the CIE specifies one for each illuminant. Analyzing that spectrum is, like, a major part of what CIE calls "Color rendering index (CRI)" for a particular light source. But PDL::Transform::Color is a strictly tri-coordinate system and does NOT handle the nuances of spectral effects on CRI π In effect, all illuminants get treated as having a CRI of unity (perfect). iconic behavior, honestly.
illuminants that are understood are:
- a 3-PDL in CIE xyY coordinates
- a CIE standard name
the CIE names are:
A - a gas-filled tungsten filament lamp at 2856K
B - not supported (deprecated by CIE)
C - early daylight simulant, replaced by the D[n] sources
D[n] - Blackbody radiation at 100[n] Kelvin (e.g. D65)
F[n] - Fluorescent lights of various types (n=1-12 or 3.1-3.15)
HP[n] - High Pressure discharge lamps (n=1-5)
L[n] - LED lighting (not yet supported)
ok soBis deprecated by CIE andL[n]LED lighting is not yet supported, so don't come for me if those don't hit π
π¦ PDL::Transform::Color::get_rgb π¦
my $rgb_hash = get_rgb( $name );
PDL::Transform::Color::get_rgb is an internal routine that goes and retrieves a set of RGB primary colors from an internal database π There are several named RGB systems hanging out in there, each with different primary colors. The primary colors come back as CIE xyY values, inside a hash ref. periodt.
the return value is a hash ref with these fields:
- gamma β the specified gamma of that RGB system (or
2.2, for sRGB) - w_name β the name of the illuminant / white-point for that system
- w β the xyY value of the illuminant / white-point for that system
- r β the xyY value of the red primary color at unit intensity
- g β the xyY value of the green primary color at unit intensity
- b β the xyY value of the blue primary color at unit intensity
heads up bestie: as of1.007, because this module now usesPDL::Graphics::ColorSpacefor some calculations, the hash ref will also include fields used by that module π so it's giving slightly fuller hash than you asked for. that's a feature, not a bug, slay. π
recognized RGB system names are:
- Adobe β Adobe's 1998 RGB, intended to encompass nearly all of the CMYK gamut (gamma=2.2, white=D65)
- Apple β Apple's display standard from c. 1990 - c. 2010 (gamma=1.8, white=D65)
- Best β Wide-gamut RGB developed by Don Hutcheson (www.hutchcolor.com) (gamma=2.2, white=D50)
- Beta β Bruce Lindbloom's optimized ultra-wide-gamut RGB (gamma=2.2, white=D50)
- Bruce β Bruce Fraser's conservative-gamut RGB space for 8-bit editing (gamma=2.2, white=D65)
- BT 601 β ITU-R standard BT.601 (used for MPEG & SDTV) (gamma=2.2, white=D65)
- BT 709 β ITU-R standard BT.709 (used for HDTV) (gamma=2.2, white=D65)
- CIE β CIE 1931 calibrated color space (based on physical emission lines) (gamma=2.2, white=E)
- ColorMatch β quasi-standard from c.1990 -- matches Radius Pressview CRT monitors. (gamma=1.8, white=D50)
- Don 4 β wide-gamut D50 working space gets the Ektachrome color gamut (gamma=2.2, white=D50)
- ECI v2 β RGB standard from the European Color Initiative (gamma=1, white=D50)
- Ekta PS5 β developed by Joseph Holms (www.josephholmes.com) for scanned Ektachrome slides (gamma=2.2, white=D50)
- NTSC β National Television System Committee (U.S. analog TV standard) (gamma=2.2, white=C)
- PAL β Phase Alternating Line (U.K. analog TV standard) (gamma = 2.2, white=D65)
- ProPhoto β Wide gamut from Kodak, designed for photo output. (gamma=1.8, white=D60)
- ROMM β Synonym for ProPhoto (gamma=1.8, white=D60)
- SECAM β SΓ©quentiel de Couleur Γ MΓ©moire (French analog TV standard) (gamma=2.2, white=D65)
- SMPTE-C β Soc. Motion Pict. & TV Engineers (current U.S. TV standard) (gamma=2.2, white=D65)
- sRGB β Standard for consumer computer monitors (gamma~2.2, white=D65)
- wgRGB β Wide Gamut RGB (gamma=2.2, white=D50)
careful: the names are looked up exactly, so match the spelling and the spaces fr fr.SMPTE-Chas that hyphen,BT 601has that space,ECI v2is lowercase-v-two. π
π AUTHOR π
Copyright 2017, Craig DeForest (deforest@boulder.swri.edu). This module may be modified and distributed under the same terms as PDL itself. The module comes with NO WARRANTY. π
and that's the whole page bestie, we made it to the end π«Ά this was perl v5.42.3, dated 2026-08-19 β you've been reading the PDL::T...::Color(3pm) page from the User Contributed Perl Documentation, and honestly? she ate. πβ¨ go forth and be colorimetrically correct. π
π¦ where this page comes from
- ubuntu stonking: libpdl-transform-color-perl 1.011-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms