% optimals.rnw % Time-stamp: "optimals.rnw" \documentclass[11pt]{article} % Set margins to be narrow \RequirePackage[left=1in,top=0.75in,right=1in,bottom=0.75in]{geometry} %\VignetteIndexEntry{Viewing Object Colors in a Gallery} %\VignetteEngine{knitr::knitr} \RequirePackage{color} \RequirePackage{fancyvrb} \RequirePackage[T1]{fontenc} \RequirePackage{ae} % ComputerModern Fonts \RequirePackage{fancyhdr} \RequirePackage{float} \RequirePackage{hyperref} \usepackage{lastpage} % block of definecolor's moved down here on Dec 17 2021. Kurt Hornik \definecolor{darkblue}{rgb}{0,0,0.5} \definecolor{blue}{rgb}{0,0,0.8} \definecolor{lightblue}{rgb}{0.2,0.2,0.9} \definecolor{darkred}{rgb}{0.6,0.0,0.0} \definecolor{red}{rgb}{0.7,0,0} \definecolor{darkgreen}{rgb}{0.0,0.4,0.0} \definecolor{lightgray}{rgb}{0.7,0.7,0.7} \definecolor{darkorange}{rgb}{0.75, 0.45, 0} \definecolor{purple}{rgb}{0.65, 0, 0.75} \definecolor{goldenrod}{rgb}{0.80, 0.61, 0.11} \definecolor{lightyellow}{rgb}{0.98,0.94,0.83} \pagestyle{fancy} \cfoot{page \thepage\ of \pageref{LastPage}} \renewcommand{\headrulewidth}{0pt} % \code mini environment ttfamily->texttt \newcommand\code{\bgroup\@codex} \def\@codex#1{{\color{darkred} \normalfont\ttfamily\hyphenchar\font=-1 #1}\egroup} % This environment defines the look of R ouput \DefineVerbatimEnvironment{Soutput}{Verbatim}{ fontsize=\small, formatcom=\color{darkblue} } \begin{document} % \SweaveOpts{concordance=TRUE} \title{ {\Huge Viewing Object Colors in a Gallery} } \author{Glenn Davis \url{ }} \maketitle % \thispagestyle{fancy} % Setup stuff. <>= require("knitr",quietly=TRUE) opts_chunk$set(fig.path="figs/ag2-", fig.align="center", fig.width=7, fig.height=7, comment="") knit_hooks$set(output = function(x, options) { paste('\\begin{Soutput}\n', x, '\\end{Soutput}\n', sep = '') }) options(width=90) par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) if(!file.exists("figs")) dir.create("figs") @ % ---------------------------------------------------------------------------- \section*{Introduction} The goal of this \textbf{colorSpec} vignette is to display rendered images of a popular color target with different illuminants, both with and without chromatic adaption methods. The figures are best viewed on a display calibrated for sRGB. Featured functions in this vignette are: \code{extradata()}, and \code{product()}. <>= library( colorSpec ) library( spacesXYZ ) # for function standardXYZ() library( spacesRGB ) # for functions RGBfromXYZ() and plotPatchesRGB() @ Read the target spectra. This data has been kindly provided in CGATS format by \cite{Pascale}. \emph{ColorChecker} is a Registered Trademark of X-Rite, and X-Rite is a Trademark. <>= # read the Macbeth ColorCheck target path = system.file( 'extdata/targets/CC_Avg30_spectrum_CGATS.txt', package='colorSpec') MacbethCC = readSpectra( path ) # MacbethCC is a 'colorSpec' object MacbethCC = MacbethCC[ order(MacbethCC$SAMPLE_ID), ] # still class 'colorSpec' print( extradata(MacbethCC), row.names=F ) @ Note that \code{MacbethCC} is organized as \code{'df.row'} and contains extra data for each spectrum, notably the coordinates of the patch rectangle. % ---------------------------------------------------------------------------- \section*{Viewing with Illuminant D65} Build the "material responder" from Illuminant D65 and standard CMFs: \setcounter{figure}{0} <>= D65.eye = product( D65.1nm, "artwork", xyz1931.1nm, wave='auto' ) # calibrate according to the ASTM and CIE standards, # except normalize to Y=1 instead of Y=100 # X, Y, and Z are scaled by the same factor PRD = neutralMaterial( 1, wavelength(D65.eye) ) D65.eye = calibrate( D65.eye, stimulus=PRD, response=c(NA,1,NA), method='scaling' ) @ Calculate XYZ and then RGB: <>= XYZ = product( MacbethCC, D65.eye, wave='auto' ) RGB = RGBfromXYZ( XYZ, space='sRGB', which='scene' )$RGB # this is *signal* sRGB # add the rectangle data to RGB, so they can be plotted in proper places obj = extradata(MacbethCC) obj$RGB = RGB # display in proper location, and use the sRGB display transfer function par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) plotPatchesRGB( obj, space='sRGB', which='signal', back='gray20', labels=FALSE ) obj.first = obj # save this reference object for later @ Here are the 8-bit device values: <>= RGB8 = round( 255 * RGB ) print( RGB8 ) @ Note that all of these patches are inside the sRGB gamut, exept for Cyan. Another way to do the same thing is use the built-in theoretical camera \code{BT.709.RGB} that computes sRGB directly from spectra, and has already been calibrated. <>= RGB = product( D65.1nm, MacbethCC, BT.709.RGB, wave='auto' ) # this is *linear* sRGB obj = extradata(MacbethCC) obj$RGB = RGB par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) plotPatchesRGB( obj, space='sRGB', which='scene', back='gray20', labels=FALSE ) @ \section*{Viewing with Illuminant D50} Build the "material responder" from Illuminant D50 and standard CMFs: <>= D50.eye = product( D50.5nm, "artwork", xyz1931.5nm, wave='auto' ) # calibrate according to the ASTM and CIE standards, # except normalize to Y=1 instead of Y=100 PRD = neutralMaterial( 1, wavelength(D50.eye) ) D50.eye = calibrate( D50.eye, stimulus=PRD, response=c(NA,1,NA), method='scaling' ) @ Calculate XYZ and then RGB: <>= XYZ = product( MacbethCC, D50.eye, wave='auto' ) obj = extradata(MacbethCC) obj$RGB = RGBfromXYZ( XYZ, space='sRGB' )$RGB # this is *signal* sRGB par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) plotPatchesRGB( obj, space='sRGB', which='signal', back='gray20', labels=FALSE ) @ Since D50 is yellower than D65, the result has a yellow cast. Start over, but this time calibrate and adapt to D65 using the Bradford method. <>= D50.eye = product( D50.5nm, "artwork", xyz1931.5nm, wave='auto' ) # calibrate so the response to the perfect-reflecting-diffuser is the 'official XYZ' of D65 # with this chromatic adaption the destination XYZ is a 3x3 matrix times the source XYZ PRD = neutralMaterial( 1, wavelength(D50.eye) ) XYZ.D65 = spacesXYZ::standardXYZ('D65') D50toD65.eye = calibrate( D50.eye, stimulus=PRD, response=XYZ.D65, method='Bradford' ) XYZ = product( MacbethCC, D50toD65.eye, wave='auto' ) obj = extradata(MacbethCC) obj$RGB = RGBfromXYZ( XYZ, space='sRGB' )$RGB # this is *signal* sRGB par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) plotPatchesRGB( obj, space='sRGB', which='signal', back='gray20', labels=FALSE ) @ The white-balance here is much improved. But it hard to compare colors in this figure with the ones way back in Figure 1. So combine the original D65 rendering in Figure 1 with this D50 rendering in Figure 4 by splitting each square into 2 triangles. We can do this by setting \code{add=T} in the second plot. <>= par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) plotPatchesRGB( obj.first, space='sRGB', back='gray20', labels=F ) plotPatchesRGB( obj, space='sRGB', labels=F, shape='bottomright', add=T ) @ The top-left triangle has the color from Figure 1 and the bottom-right triangle has the color from Figure 4. There is a noticeable difference in the \textbf{Red} and \textbf{Magenta} patches. \section*{A Rendering with a Scanner} Here we calculate a rendering on an RGB scanner. This is not really a gallery situation, but illustrates the similarity of the 2 RGB calculations. <>= # Build a scanner from Illuminant F11 and the Flea2 camera scanner = product( subset(Fs.5nm,'F11'), 'artwork', Flea2.RGB, wave='auto' ) # calibrate scanner so the response to the perfect-reflecting-diffuser is RGB=(1,1,1) # set the RGB gains independently PRD = neutralMaterial( 1, wavelength(scanner) ) scanner = calibrate( scanner, stimulus=PRD, response=1, method='scaling' ) obj = extradata(MacbethCC) obj$RGB = product( MacbethCC, scanner, wave='auto' ) # this linear RGB is not linear sRGB par( omi=c(0,0,0,0), mai=c(0.2,0.2,0.2,0.2) ) plotPatchesRGB( obj, space='sRGB', which='scene', back='gray20', labels=FALSE ) @ The colors are too pale; this time \code{Cyan} has a substantial Red signal. Some sort of color management is necessary in this scanner to improve accuracy. For an interactive viewer along these lines, see \cite{Lindbloom}. % \pagebreak \bibliographystyle{plain} \bibliography{gallery} % ---------------------------------------------------------------------------- \section*{Appendix} This document was prepared \today \quad with the following configuration: <>= knit_hooks$set(output = function(x, options) { x }) toLatex(sessionInfo(), locale=FALSE) @ \end{document}