-
Notifications
You must be signed in to change notification settings - Fork 0
/
cptcmap.m
512 lines (422 loc) · 15.4 KB
/
cptcmap.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
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.
%
% The use of two separate folders here is just to make my life easier when
% uploading this enry to github, so I can only distribute a small sample of
% colormaps, and keep my own ones separate. It also means users can keep
% their own collection without worrying about their colormaps being
% overwritten when downloading updates of this function. The cptfiles
% folder is included in the distribution; cptfiles_personal is not.
cptpath = {...
fullfile(fileparts(which('cptcmap')), 'cptfiles')
fullfile(fileparts(which('cptcmap')), 'cptfiles_personal')
};
if ischar(cptpath)
cptpath = {cptpath};
end
isfound = logical(cellfun(@(x) exist(x, 'dir'), cptpath));
cptpath = cptpath(isfound);
if ~any(isfound)
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});
for ii = 1:length(cptpath)
if ~isempty(ext) % with extension
filename = fullfile(cptpath{ii}, varargin{1});
else % without extension
filename = fullfile(cptpath{ii}, [varargin{1} '.cpt']);
end
if exist(filename, 'file')
break
end
end
if ~exist(filename, 'file') % Not found in any folders
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)
for ii = 1:length(folder)
if ii == 1
Files = dir(fullfile(folder{ii}, '*.cpt'));
else
Files = [Files; dir(fullfile(folder{ii}, '*.cpt'))];
end
end
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