Skip to content

Commit

Permalink
Add X11 colors + str2rgb generic wrapper + rgb.view abstract viewer
Browse files Browse the repository at this point in the history
 > rgb.X11 implements the very same functionality of rgb.xkcd, but for
   the legacy UNIX colorscheme (etc/X11/rgb.txt), which is the original
   source for html, svg and CSS colors. As for today is still the most
   widely used colorscheme in latex (xcolors), in R and Mathematica.

 > str2rgb provides a high-level, agnostic, interface, with the ability
   to associate an arbitrary charvec / string / cell of chars/strings to
   an rgb triplet. If the association files the stringlookup built-in in
   rgb.X11 and rgb.xkcd provides an ergonomic method to retrieve a valid
   colorname. str2rgb does not provide access to additional features of
   the rgb functions, such as the html-chart viewer. Just a robust name-
   to-color converter, to be used within plotting-procedure calls.

 > rgb.viewer takes instead the job of providing a comfortable and fast
   generic visualizer for arbitrary abstract color-objects... they can
   be either standard rgb triplets, arbitrary colornames (processed by
   str2rgb), arrays of rgb values (Nx3 matrices), cell-arrays of names.
   Either way a matrix-like or, optionally, barplot-like, figure will
   display with solid color blocks, corresponding to the passed input.

 NB) str2rgb implements also some intrinsics ('r','g','b','k'...), so to
     avoid breaking generality and two handy qualitative color-orderings
     MATLAB default, lines(7), and Matplotlib default, tab10. They can
     be accessed through the 'matlab1',...'matlab7' and 'pyplot1',...
     'pyplot10' colornames. For other colormaps just explicitly use the
     +palette namespace (palette.brewer, palette.tab20, palette.crameri)

 TESTING) Everything has been thoroughly tested on both R2020b and GNU
          Octave 5.2.0. Everything appears to work except the 'show' key
          for both rgb.xkcd and rgb.X11 on GNU (cprintf would never work
          with Octave, given it's nature but the rest I just cannot get)

 TODO) Write the global README, it's really time to do it; furthermore
       we really should start writing a proper test-suite, since the
       codebase complexity is rising and rising and things need to get
       stable as soon as possible.
  • Loading branch information
beddalumia committed Jun 1, 2022
1 parent 9da7e0a commit 50be7a1
Show file tree
Hide file tree
Showing 10 changed files with 5,824 additions and 8 deletions.
230 changes: 230 additions & 0 deletions +rgb/X11.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
function [RGB] = X11(varargin)
% X11 returns rgb triplets according to historical X11/rgb.txt UNIX file
%
%
%% SYNTAX
%
% col = rgb.X11('Color Name')
% col = rgb.X11('Color Name 1','Color Name 2',...,'Color Name N')
% col = rgb.X11({'Color Name 1','Color Name 2',...,'Color Name N'})
% rgb.X11("show");
%
%% DESCRIPTION
%
% col = X11('Color Name') returns the rgb triplet of the color described by the
% string 'Color Name'. Try any color name you can think of, it'll probably work.
% If not we'll provide well-targeted suggestions for you :)
%
% col = X11('Color Name 1','Color Name 2',...,'Color Name N') returns an
% N by 3 matrix containing RGB triplets for each color name.
%
% col = X11({'Color Name 1','Color Name 2',...,'Color Name N'}) accepts
% list of color names as a character array, too.
%
% X11('show') will try to open a local html chart on the default browser.
% If not possible it would resort to CPRINTF to print all the available
% names on stdout, with a colored '⬜' as a sample.
% Please note that this would be quite slow. Furthermore, being based on
% (undocumented) java features of the MATLAB runtime, it will /not/ work
% outside of the official MATLAB command window (i.e. no external terminals
% nor ssh servers, nor GNU Octave, etc.). Finally if everything else fails,
% all available names will be printed in black, leaving it to your intuition.
%
% NB) Name recognition is case-insensitive, through strcmpi. No spaces though.
%
%% USAGE
%
% EXAMPLE 1
% rgb.X11('navyblue')
%
% EXAMPLE 2
% rgb.X11('SpringGreen','CornflowerBlue','sapphire','radioactive green')
%
% EXAMPLE 3
% x = -pi:.1:pi;
% y = cos(x);
% plot(x,y,'linewidth',4,'color',rgb.X11('CornflowerBlue'))
% hold on
% plot(x,y-1,'*','color',rgb.X11('Brown1'))
% plot(x,y-2,'linewidth',4,'color',rgb.X11('SeaGreen'))
% legend('cornflower','brown1 (wtf?!)','SeaGreen')
% set(gca,'color',rgb.X11('seashell'))
% text(-1,-2,'This text is chocolate (wtf?!).','fontweight','bold','color',...
% rgb.X11('chocolate'));
%
% As you can see, the X11 colors do not always match commons sense. A nice,
% large-numbers-driven alternative is provided by the rgb.xkcd function :)
%
%% AUTHOR INFO
%
% This function was originally written by Chad A. Greene of the University
% of Texas at Austin's Institute for Geophysics. Then heavily upgraded and
% embedded in the wider +rgb package by Gabriele Bellomia. Many thanks also
% to Florian Klimm and Cedric Wannaz for their contributions. License @EOF.
%
% See also rgb.xkcd, str2rgb, hex2rgb, rgb2hex, strcmpi and cprintf.

%% Load X11/RGB data:

here = fileparts(mfilename('fullpath'));
try % MATLAB apparently cannot load GNU-generated .mat files...
load([here,'/private/X11_rgb_data.mat'],'namelist','rgblist');
catch
% Make sure the function can load the data, rebuild it if necessary
disp 'Cannot load X11/rgb database. I will try to import from local X11.txt file now.'
X11_install
% Load data created by xkcd_install
load([here,'/private/X11_rgb_data.mat'],'namelist','rgblist');
end


%% Input wrangling:

if nargin < 1
help rgb.X11
return
end

ColorNames = varargin;

if iscell(ColorNames{1})
ColorNames = ColorNames{1};
end

if isstring(ColorNames{1})
ColorNames = cellstr(ColorNames{1});
end

if isequal(ColorNames,"show")
try
open([here,'/resources/X11.html']);
catch
try % VERY VERY SLOW, FOR THAT WE HAVE LOADS OF COLORS HERE!
for i = 2:950
cprintf(rgblist(i,:),'\t');
cprintf([0,0,0],[namelist{i},'\n']);
end
catch % In case cprintf breaks (based on undocumented features)
disp(namelist(2:end));
end
end
return
end

assert(~isnumeric(ColorNames),'Colornames should be passed as strings.')


%% Search for color, return rgb value:

% Number of input color names:
numcolors = length(ColorNames);

% Preallocate a matrix to fill with RGB triplets:
RGB = NaN(numcolors,3);

% Find rgb triplet for each input color string:
for k = 1:numcolors
ColorName = ColorNames{k};

% If a color name is not found in the database, display error message
% and look for near matches:
if sum(strcmpi(namelist,ColorName))==0
disp(['Color ''',ColorName,''' not found in X11/rgb.'])
disp(['Consider one of these options:'])

% Special thanks to Cedric Wannaz for writing this bit of code. He came up with a
% quite clever solution wherein the spectrum of the input color
% name is compared to the spectra of available options. So cool.
spec = @(name) accumarray(upper(name.')-31, ones(size(name)), [60 1]) ;
spec_mycol = spec(ColorName); % spectrum of input color name
spec_dist = cellfun(@(name) norm(spec(name)-spec_mycol), namelist);
[sds,idx] = sort(spec_dist) ;
nearbyNames = namelist(idx(sds<=1.5));
if isempty(nearbyNames)
nearbyNames = namelist(idx(1:3));
end
disp(nearbyNames);

clear RGB
return % gives up and makes the user try again.

end

RGB(k,:) = rgblist(strcmpi(namelist,ColorName),:);

end

end

%% Web-installation subfunction:

function X11_install

% Entering the appropriate private path
udir = pwd;
self = mfilename('fullpath');
here = fileparts(self); cd(here)

% Making the private folder (if not found)
if ~exist('private','dir')
mkdir private
end

% Entering the private folder
cd private

if ~exist('X11.txt','file')
disp 'Cannot find X11.txt file.'
try
%websave('rgb.txt','https://X11.com/color/rgb.txt');
catch
try
%urlwrite('https://X11.com/color/rgb.txt','rgb.txt'); %#ok <GNU>
catch
disp('Having trouble downloading the txt file. You''ll need to download it manually')
disp('from http://X11.com/color/rgb.txt and place it in current folder. Then run again.')
return
end
end
end

fid = fopen('X11.txt');
RGB = textscan(fid,'%d %d %d\t\t%s');
fclose(fid); % DO NOT delete('X11.txt')!

R = double(RGB{1})/255;
G = double(RGB{2})/255;
B = double(RGB{3})/255;
namelist = RGB{4};
rgblist = [R,G,B];

save('X11_rgb_data.mat','namelist','rgblist')
disp('X11/rgb succesfully installed.'); cd(udir);
end

%% LICENSE
% Copyright (c) 2014 Chad Greene, original rgb function on FEX
% Copyright (c) 2022 Gabriele Bellomia, rgb.X11 adaptation
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are
% met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in
% the documentation and/or other materials provided with the distribution
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
Loading

0 comments on commit 50be7a1

Please sign in to comment.