123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- function varargout = cptcmap(varargin)
- %CPTCMAP Apply a .cpt file as colormap to an axis
- %
- % cptcmap(name);
- % cptcmap(name, ax);
- % cptcmap(... param, val, ...);
- % [cmap, lims, ticks, bfncol, ctable] = cptcmap(...)
- %
- % This function creates and applies a colormap defined in a color palette
- % table (.cpt file). For a full description of the cpt file format, see
- % the Generic Mapping Tools documentation (http://gmt.soest.hawaii.edu/).
- % Color palette files provide more flexible colormapping than Matlab's
- % default schemes, including both discrete and continuous gradients, as
- % well as easier direct color mapping.
- %
- % Limitations: X11 color names not supported, patterns not supported, CMYK
- % not supported yet
- %
- % Input variables:
- %
- % name: .cpt file name. You may either specify either the full
- % path, or just the file name. In the latter case, the
- % function will look for the file in the folder specified by
- % the cptpath variable in the first line of code; by default
- % this folder is located in the same location as cptcmap.m
- % and is called cptfiles.
- %
- % ax: handle of axis or axes where colormap should be applied
- % (for pre-2014b versions, colormaps will effect the entire
- % figure(s), but axis clim adjustments for direct scaling
- % will only affect the specified axes; for 2014b+, colormap
- % will only be applied to the specified axes). If no axis is
- % specified and no output variables are supplied, colormap
- % will be applied to the current axis. If no axis is
- % specified and output variables are supplied, the colormap
- % will not be applied to any axes.
- %
- % 'showall': When this option is used, a figure is created displaying
- % colorbars for all colormaps contained in the .cpt folder.
- % Color limits of each colormap are listed along with the
- % names of each. A small tick mark indicates the location of
- % 0, where applicable. NOTE: the number of columns to use
- % for display is hard-coded. As you start collecting more
- % color palettes, the figure may get too cluttered and you
- % may have to adjust this (variable ncol in the plotcmaps
- % subfunction).
- %
- % Optional input variables (passed as parameter/value pairs):
- %
- % 'mapping': 'scaled' or 'direct'. Scaled mapping spreads the colormap
- % to cover the color limits of the figure. Direct mapping
- % resets the color limits of the axes so that colors are
- % mapped to the levels specified by the .cpt file. ['scaled']
- %
- % 'ncol': number of colors in final colormap. If not included or NaN,
- % this function will try to choose the fewest number of
- % blocks needed to display the colormap as accurately as
- % possible. I have arbitrarily chosen that it will not try to
- % create more than 256 colors in the final colormap when
- % using this automatic scheme. However, you can manually set
- % ncol higher if necessary to resolve all sharp breaks and
- % gradients in the colormap.
- %
- % 'flip': if true, reverse the colormap order [false]
- %
- % Output variables:
- %
- % cmap: ncol x 3 colormap array
- %
- % lims: 1 x 2 array holding minimum and maximum values for which
- % the colormap is defined.
- %
- % ticks: vector of tick values specifying where colors were defined
- % in the original file
- %
- % bfncol: 3 x 3 colormap array specifying the colors defined for
- % background (values lower than lowest color limit),
- % foreground (values higher than highest color limit), and
- % NaN values. These do not affect the resulting colormap,
- % but can be applied by the user to replicate the behavior
- % seen in GMT.
- %
- % ctable: n x 8 color palette table, translated to Matlab color
- % space. Column 1 holds the lower limit of each color cell,
- % columns 2-4 the RGB values corresponding to the lower
- % limit, column 5 the upper limit of the color cell, and
- % columns 6-8 the RGB values of the upper limit. When the
- % lower and upper colors are the same, this defines a
- % solid-colored cell; when they are different, colors are
- % linearly interpolated between the endpoints.
- %
- % Example:
- %
- % [lat, lon, z] = satbath(10);
- % pcolor(lon, lat, z);
- % shading flat;
- % cptcmap('GMT_globe', 'mapping', 'direct');
- % colorbar;
- % Copyright 2011 Kelly Kearney
- % File Exchange update: 4/12/2011
- %------------------------------
- % Parse input
- %------------------------------
- % The .cpt file folder. By default, the cptfiles folder is located in
- % the same place as the cptcmap.m file. If you change this on your
- % computer, change the cptpath definition to reflect the new location.
- cptpath = fullfile(fileparts(which('cptcmap')), 'cptfiles');
- if ~exist(cptpath, 'dir')
- error('You have moved the cptfiles directory. Please modify the cptpath variable in this code to point to the directory where your.cpt files are stored');
- end
- if nargin < 1
- error('You must provide a colormap name');
- end
- % Check for 'showall' option first
- if nargin == 1 && strcmp(varargin{1}, 'showall')
- plotcmaps(cptpath);
- return;
- end
- % Name of file
- [blah, blah, ext] = fileparts(varargin{1});
- if isempty(ext)
- varargin{1} = [varargin{1} '.cpt'];
- end
- if exist(varargin{1}, 'file') % full filename and path given
- filename = varargin{1};
- else % only file name given
- [blah,blah,ext] = fileparts(varargin{1});
- if ~isempty(ext) % with extension
- filename = fullfile(cptpath, varargin{1});
- else % without extension
- filename = fullfile(cptpath, [varargin{1} '.cpt']);
- end
- if ~exist(filename, 'file')
- error('Specified .cpt file not found');
- end
- end
- % Axes to which colormap will be applied
- if nargin > 1 && all(ishandle(varargin{2}(:)))
- ax = varargin{2};
- pv = varargin(3:end);
- applycmap = true;
- elseif nargout == 0
- ax = gca;
- pv = varargin(2:end);
- applycmap = true;
- else
- pv = varargin(2:end);
- applycmap = false;
- end
- % Optional paramter/value pairs
-
- p = inputParser;
- p.addParamValue('mapping', 'scaled', @(x) any(strcmpi(x, {'scaled', 'direct'})));
- p.addParamValue('ncol', NaN, @(x) isscalar(x) && isnumeric(x));
- p.addParamValue('flip', false, @(x) isscalar(x) && islogical(x));
- p.parse(pv{:});
- Opt = p.Results;
-
- %------------------------------
- % Calculate colormap and apply
- %------------------------------
- [cmap, lims,ticks,bfncol,ctable] = cpt2cmap(filename, Opt.ncol);
- if Opt.flip
- if strcmp(Opt.mapping, 'direct')
- warning('Flipping colormap with direct mapping may lead to odd color breaks');
- end
- cmap = flipud(cmap);
- end
- if applycmap
- for iax = 1:numel(ax)
- if strcmp(Opt.mapping, 'direct')
- set(ax(iax), 'clim', lims);
- end
- colormap(ax(iax), cmap);
- end
- end
- %------------------------------
- % Output
- %------------------------------
- allout = {cmap, lims, ticks, bfncol, ctable};
- varargout(1:nargout) = allout(1:nargout);
- %------------------------------
- % Subfunction: Read colormap
- % from file
- %------------------------------
- function [cmap, lims, ticks, bfncol, ctable] = cpt2cmap(file, ncol)
- % Read file
- fid = fopen(file);
- txt = textscan(fid, '%s', 'delimiter', '\n');
- txt = txt{1};
- fclose(fid);
- isheader = strncmp(txt, '#', 1);
- isfooter = strncmp(txt, 'B', 1) | strncmp(txt, 'F', 1) | strncmp(txt, 'N', 1);
- % Extract color data, ignore labels (errors if other text found)
- ctabletxt = txt(~isheader & ~isfooter);
- ctable = str2num(strvcat(txt(~isheader & ~isfooter)));
- if isempty(ctable)
- nr = size(ctabletxt,1);
- ctable = cell(nr,1);
- for ir = 1:nr
- ctable{ir} = str2num(strvcat(regexp(ctabletxt{ir}, '[\d\.-]*', 'match')))';
- end
- try
- ctable = cell2mat(ctable);
- catch
- error('Cannot parse this format .cpt file yet');
- end
- end
- % Determine which color model is used (RGB, HSV, CMYK, names, patterns,
- % mixed)
- [nr, nc] = size(ctable);
- iscolmodline = cellfun(@(x) ~isempty(x), regexp(txt, 'COLOR_MODEL'));
- if any(iscolmodline)
- colmodel = regexprep(txt{iscolmodline}, 'COLOR_MODEL', '');
- colmodel = strtrim(lower(regexprep(colmodel, '[#=]', '')));
- else
- if nc == 8
- colmodel = 'rgb';
- elseif nc == 10
- colmodel = 'cmyk';
- else
- error('Cannot parse this format .cpt file yet');
- end
- end
- % try
- % temp = str2num(strvcat(txt(~isheader & ~isfooter)));
- % if size(temp,2) == 8
- % colmodel = 'rgb';
- % elseif size(temp,2) == 10
- % colmodel = 'cmyk';
- % else % grayscale, maybe others
- % error('Cannot parse this format .cpt file yet');
- % end
- % catch % color names, mixed formats, dash placeholders
- % error('Cannot parse this format .cpt file yet');
- % end
- % end
- %
- %
- % iscmod = strncmp(txt, '# COLOR_MODEL', 13);
- %
- %
- % if ~any(iscmod)
- % isrgb = true;
- % else
- % cmodel = strtrim(regexprep(txt{iscmod}, '# COLOR_MODEL =', ''));
- % if strcmp(cmodel, 'RGB')
- % isrgb = true;
- % elseif strcmp(cmodel, 'HSV')
- % isrgb = false;
- % else
- % error('Unknown color model: %s', cmodel);
- % end
- % end
- % Reformat color table into one column of colors
- cpt = zeros(nr*2, 4);
- cpt(1:2:end,:) = ctable(:,1:4);
- cpt(2:2:end,:) = ctable(:,5:8);
- % Ticks
- ticks = unique(cpt(:,1));
- % Choose number of colors for output
- if isnan(ncol)
-
- endpoints = unique(cpt(:,1));
-
- % For gradient-ed blocks, ensure at least 4 steps between endpoints
-
- issolid = all(ctable(:,2:4) == ctable(:,6:8), 2);
-
- for ie = 1:length(issolid)
- if ~issolid(ie)
- temp = linspace(endpoints(ie), endpoints(ie+1), 11)';
- endpoints = [endpoints; temp(2:end-1)];
- end
- end
-
- endpoints = sort(endpoints);
-
- % Determine largest step size that resolves all endpoints
-
- space = diff(endpoints);
- space = unique(space);
- % space = roundn(space, -3); % To avoid floating point issues when converting to integers
- space = round(space*1e3)/1e3;
-
- nspace = length(space);
- if ~isscalar(space)
-
- fac = 1;
- tol = .001;
- while 1
- if all(space >= 1 & (abs(space - round(space))) < tol)
- space = round(space);
- break;
- else
- space = space * 10;
- fac = fac * 10;
- end
- end
-
- pairs = nchoosek(space, 2);
- np = size(pairs,1);
- commonsp = zeros(np,1);
- for ip = 1:np
- commonsp(ip) = gcd(pairs(ip,1), pairs(ip,2));
- end
-
- space = min(commonsp);
- space = space/fac;
- end
-
- ncol = (max(endpoints) - min(endpoints))./space;
- ncol = min(ncol, 256);
-
- end
- % Remove replicates and mimic sharp breaks
- isrep = [false; ~any(diff(cpt),2)];
- cpt = cpt(~isrep,:);
- difc = diff(cpt(:,1));
- minspace = min(difc(difc > 0));
- isbreak = [false; difc == 0];
- cpt(isbreak,1) = cpt(isbreak,1) + .01*minspace;
- % Parse background, foreground, and nan colors
- footer = txt(isfooter);
- bfncol = nan(3,3);
- for iline = 1:length(footer)
- if strcmp(footer{iline}(1), 'B')
- bfncol(1,:) = str2num(regexprep(footer{iline}, 'B', ''));
- elseif strcmp(footer{iline}(1), 'F')
- bfncol(2,:) = str2num(regexprep(footer{iline}, 'F', ''));
- elseif strcmp(footer{iline}(1), 'N')
- bfncol(3,:) = str2num(regexprep(footer{iline}, 'N', ''));
- end
- end
- % Convert to Matlab-format colormap and color limits
- lims = [min(cpt(:,1)) max(cpt(:,1))];
- endpoints = linspace(lims(1), lims(2), ncol+1);
- midpoints = (endpoints(1:end-1) + endpoints(2:end))/2;
- cmap = interp1(cpt(:,1), cpt(:,2:4), midpoints);
- switch colmodel
- case 'rgb'
- cmap = cmap ./ 255;
- bfncol = bfncol ./ 255;
- ctable(:,[2:4 6:8]) = ctable(:,[2:4 6:8]) ./ 255;
-
- case 'hsv'
- cmap(:,1) = cmap(:,1)./360;
- cmap = hsv2rgb(cmap);
-
- bfncol(:,1) = bfncol(:,1)./360;
- bfncol = hsv2rgb(bfncol);
-
- ctable(:,2) = ctable(:,2)./360;
- ctable(:,6) = ctable(:,6)./360;
-
- ctable(:,2:4) = hsv2rgb(ctable(:,2:4));
- ctable(:,6:8) = hsv2rgb(ctable(:,6:8));
-
- case 'cmyk'
- error('CMYK color conversion not yet supported');
- end
- % Rouding issues: occasionally, the above calculations lead to values just
- % above 1, which colormap doesn't like at all. This is a bit kludgy, but
- % should solve those issues
- isnear1 = cmap > 1 & (abs(cmap-1) < 2*eps);
- cmap(isnear1) = 1;
- %------------------------------
- % Subfunction: Display all
- % colormaps
- %------------------------------
- function plotcmaps(folder)
- Files = dir(fullfile(folder, '*.cpt'));
- nfile = length(Files);
- ncol = 3;
- nr = ceil(nfile/ncol);
- width = (1 - .05*2)/ncol;
- height = (1-.05*2)/nr;
- left = .05 + (0:ncol-1)*width;
- bot = .05 + (0:nr-1)*height;
- [l, b] = meshgrid(left, bot);
- w = width * .8;
- h = height * .4;
- figure('color','w');
- ax = axes('position', [0 0 1 1]);
- hold on;
- for ifile = 1:nfile
- [cmap,blah,blah,blah,ctable] = cptcmap(Files(ifile).name);
- [x,y,c] = ctable2patch(ctable);
-
- xtick = unique(x);
- dx = max(x(:)) - min(x(:));
-
- xsc = ((x-min(xtick))./dx).*w + l(ifile);
- ysc = y.*h + b(ifile);
-
- xrect = [0 1 1 0 0] .*w + l(ifile);
- yrect = [1 1 0 0 1] .*h + b(ifile);
-
- xticksc = ((xtick-min(xtick))./dx).*w + l(ifile);
- x0 = interp1(xtick, xticksc, 0);
- y0 = b(ifile) + [0 .2*h NaN .8*h h];
- x0 = ones(size(y0))*x0;
- lbl = sprintf('%s [%g, %g]', regexprep(Files(ifile).name,'\.cpt',''), min(x(:)), max(x(:)));
-
- patch(xsc, ysc, c, 'edgecolor', 'none');
- line(xrect, yrect, 'color', 'k');
- line(x0, y0, 'color', 'k');
- text(l(ifile), b(ifile)+h, lbl, 'interpreter', 'none', 'fontsize', 10, 'verticalalignment', 'bottom', 'horizontalalignment', 'left');
-
- end
- set(ax, 'ylim', [0 1], 'xlim', [0 1], 'visible', 'off');
- % Determine patch coordinates
- function [x,y,c] = ctable2patch(ctable)
- np = size(ctable,1);
- x = zeros(4, np);
- y = zeros(4, np);
- c = zeros(4, np, 3);
- y(1:2,:) = 1;
- for ip = 1:np
- x(:,ip) = [ctable(ip,1) ctable(ip,5) ctable(ip,5) ctable(ip,1)];
- c(:,ip,:) = [ctable(ip,2:4); ctable(ip,6:8); ctable(ip,6:8); ctable(ip,2:4)];
- end
|