1/*	$NetBSD$	*/
2
3// -*- C++ -*-
4
5/* <groff_src_dir>/src/libs/libgroff/color.cpp
6
7Last update: 26 May 2004
8
9Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
10    Written by Gaius Mulley <gaius@glam.ac.uk>
11
12This file is part of groff.
13
14groff is free software; you can redistribute it and/or modify it under
15the terms of the GNU General Public License as published by the Free
16Software Foundation; either version 2, or (at your option) any later
17version.
18
19groff is distributed in the hope that it will be useful, but WITHOUT ANY
20WARRANTY; without even the implied warranty of MERCHANTABILITY or
21FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
22for more details.
23
24You should have received a copy of the GNU General Public License along
25with groff; see the file COPYING.  If not, write to the Free Software
26Foundation, 51 Franklin St - Fifth Floor, Boston, MA 02110-1301, USA. */
27
28#include "lib.h"
29#include "color.h"
30#include "cset.h"
31#ifdef HAVE_UNISTD_H
32#include <unistd.h>
33#endif
34
35#include <assert.h>
36#include <stdio.h>
37#include <fcntl.h>
38#include <stdlib.h>
39#include "errarg.h"
40#include "error.h"
41
42static inline unsigned int
43min(const unsigned int a, const unsigned int b)
44{
45  if (a < b)
46    return a;
47  else
48    return b;
49}
50
51color *color::free_list = 0;
52
53void *color::operator new(size_t n)
54{
55  assert(n == sizeof(color));
56  if (!free_list) {
57    const int BLOCK = 128;
58    free_list = (color *)new char[sizeof(color)*BLOCK];
59    for (int i = 0; i < BLOCK - 1; i++)
60      free_list[i].next = free_list + i + 1;
61    free_list[BLOCK-1].next = 0;
62  }
63  color *p = free_list;
64  free_list = (color *)(free_list->next);
65  p->next = 0;
66  return p;
67}
68
69void color::operator delete(void *p)
70{
71  if (p) {
72    ((color *)p)->next = free_list;
73    free_list = (color *)p;
74  }
75}
76
77color::color(const color * const c)
78{
79  nm = c->nm;
80  scheme = c->scheme;
81  components[0] = c->components[0];
82  components[1] = c->components[1];
83  components[2] = c->components[2];
84  components[3] = c->components[3];
85}
86
87color::~color()
88{
89}
90
91int color::operator==(const color & c) const
92{
93  if (scheme != c.scheme)
94    return 0;
95  switch (scheme) {
96  case DEFAULT:
97    break;
98  case RGB:
99    if (Red != c.Red || Green != c.Green || Blue != c.Blue)
100      return 0;
101    break;
102  case CMYK:
103    if (Cyan != c.Cyan || Magenta != c.Magenta
104	|| Yellow != c.Yellow || Black != c.Black)
105      return 0;
106    break;
107  case GRAY:
108    if (Gray != c.Gray)
109      return 0;
110    break;
111  case CMY:
112    if (Cyan != c.Cyan || Magenta != c.Magenta || Yellow != c.Yellow)
113      return 0;
114    break;
115  }
116  return 1;
117}
118
119int color::operator!=(const color & c) const
120{
121  return !(*this == c);
122}
123
124color_scheme color::get_components(unsigned int *c) const
125{
126#if 0
127  if (sizeof (c) < sizeof (unsigned int) * 4)
128    fatal("argument is not big enough to store 4 color components");
129#endif
130  c[0] = components[0];
131  c[1] = components[1];
132  c[2] = components[2];
133  c[3] = components[3];
134  return scheme;
135}
136
137void color::set_default()
138{
139  scheme = DEFAULT;
140}
141
142// (0, 0, 0) is black
143
144void color::set_rgb(const unsigned int r, const unsigned int g,
145		    const unsigned int b)
146{
147  scheme = RGB;
148  Red = min(MAX_COLOR_VAL, r);
149  Green = min(MAX_COLOR_VAL, g);
150  Blue = min(MAX_COLOR_VAL, b);
151}
152
153// (0, 0, 0) is white
154
155void color::set_cmy(const unsigned int c, const unsigned int m,
156		    const unsigned int y)
157{
158  scheme = CMY;
159  Cyan = min(MAX_COLOR_VAL, c);
160  Magenta = min(MAX_COLOR_VAL, m);
161  Yellow = min(MAX_COLOR_VAL, y);
162}
163
164// (0, 0, 0, 0) is white
165
166void color::set_cmyk(const unsigned int c, const unsigned int m,
167		     const unsigned int y, const unsigned int k)
168{
169  scheme = CMYK;
170  Cyan = min(MAX_COLOR_VAL, c);
171  Magenta = min(MAX_COLOR_VAL, m);
172  Yellow = min(MAX_COLOR_VAL, y);
173  Black = min(MAX_COLOR_VAL, k);
174}
175
176// (0) is black
177
178void color::set_gray(const unsigned int g)
179{
180  scheme = GRAY;
181  Gray = min(MAX_COLOR_VAL, g);
182}
183
184/*
185 *  atoh - computes the decimal value of a hexadecimal number string.
186 *         `length' characters of `s' are read.  Returns 1 if successful.
187 */
188
189static int atoh(unsigned int *result,
190		const char * const s, const size_t length)
191{
192  size_t i = 0;
193  unsigned int val = 0;
194  while ((i < length) && csxdigit(s[i])) {
195    if (csdigit(s[i]))
196      val = val*0x10 + (s[i]-'0');
197    else if (csupper(s[i]))
198      val = val*0x10 + (s[i]-'A') + 10;
199    else
200      val = val*0x10 + (s[i]-'a') + 10;
201    i++;
202  }
203  if (i != length)
204    return 0;
205  *result = val;
206  return 1;
207}
208
209/*
210 *  read_encoding - set color from a hexadecimal color string.
211 *
212 *  Use color scheme `cs' to parse `n' color components from string `s'.
213 *  Returns 1 if successful.
214 */
215
216int color::read_encoding(const color_scheme cs, const char * const s,
217			 const size_t n)
218{
219  size_t hex_length = 2;
220  scheme = cs;
221  char *p = (char *) s;
222  p++;
223  if (*p == '#') {
224    hex_length = 4;
225    p++;
226  }
227  for (size_t i = 0; i < n; i++) {
228    if (!atoh(&(components[i]), p, hex_length))
229      return 0;
230    if (hex_length == 2)
231      components[i] *= 0x101;	// scale up -- 0xff should become 0xffff
232    p += hex_length;
233  }
234  return 1;
235}
236
237int color::read_rgb(const char * const s)
238{
239  return read_encoding(RGB, s, 3);
240}
241
242int color::read_cmy(const char * const s)
243{
244  return read_encoding(CMY, s, 3);
245}
246
247int color::read_cmyk(const char * const s)
248{
249  return read_encoding(CMYK, s, 4);
250}
251
252int color::read_gray(const char * const s)
253{
254  return read_encoding(GRAY, s, 1);
255}
256
257void
258color::get_rgb(unsigned int *r, unsigned int *g, unsigned int *b) const
259{
260  switch (scheme) {
261  case RGB:
262    *r = Red;
263    *g = Green;
264    *b = Blue;
265    break;
266  case CMY:
267    *r = MAX_COLOR_VAL - Cyan;
268    *g = MAX_COLOR_VAL - Magenta;
269    *b = MAX_COLOR_VAL - Yellow;
270    break;
271  case CMYK:
272    *r = MAX_COLOR_VAL
273	 - min(MAX_COLOR_VAL,
274	       Cyan * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
275    *g = MAX_COLOR_VAL
276	 - min(MAX_COLOR_VAL,
277	       Magenta * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
278    *b = MAX_COLOR_VAL
279	 - min(MAX_COLOR_VAL,
280	       Yellow * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
281    break;
282  case GRAY:
283    *r = *g = *b = Gray;
284    break;
285  default:
286    assert(0);
287    break;
288  }
289}
290
291void
292color::get_cmy(unsigned int *c, unsigned int *m, unsigned int *y) const
293{
294  switch (scheme) {
295  case RGB:
296    *c = MAX_COLOR_VAL - Red;
297    *m = MAX_COLOR_VAL - Green;
298    *y = MAX_COLOR_VAL - Blue;
299    break;
300  case CMY:
301    *c = Cyan;
302    *m = Magenta;
303    *y = Yellow;
304    break;
305  case CMYK:
306    *c = min(MAX_COLOR_VAL,
307	     Cyan * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
308    *m = min(MAX_COLOR_VAL,
309	     Magenta * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
310    *y = min(MAX_COLOR_VAL,
311	     Yellow * (MAX_COLOR_VAL - Black) / MAX_COLOR_VAL + Black);
312    break;
313  case GRAY:
314    *c = *m = *y = MAX_COLOR_VAL - Gray;
315    break;
316  default:
317    assert(0);
318    break;
319  }
320}
321
322void color::get_cmyk(unsigned int *c, unsigned int *m,
323		     unsigned int *y, unsigned int *k) const
324{
325  switch (scheme) {
326  case RGB:
327    *k = min(MAX_COLOR_VAL - Red,
328	     min(MAX_COLOR_VAL - Green, MAX_COLOR_VAL - Blue));
329    if (MAX_COLOR_VAL == *k) {
330      *c = MAX_COLOR_VAL;
331      *m = MAX_COLOR_VAL;
332      *y = MAX_COLOR_VAL;
333    }
334    else {
335      *c = (MAX_COLOR_VAL * (MAX_COLOR_VAL - Red - *k))
336	   / (MAX_COLOR_VAL - *k);
337      *m = (MAX_COLOR_VAL * (MAX_COLOR_VAL - Green - *k))
338	   / (MAX_COLOR_VAL - *k);
339      *y = (MAX_COLOR_VAL * (MAX_COLOR_VAL - Blue - *k))
340	   / (MAX_COLOR_VAL - *k);
341    }
342    break;
343  case CMY:
344    *k = min(Cyan, min(Magenta, Yellow));
345    if (MAX_COLOR_VAL == *k) {
346      *c = MAX_COLOR_VAL;
347      *m = MAX_COLOR_VAL;
348      *y = MAX_COLOR_VAL;
349    }
350    else {
351      *c = (MAX_COLOR_VAL * (Cyan - *k)) / (MAX_COLOR_VAL - *k);
352      *m = (MAX_COLOR_VAL * (Magenta - *k)) / (MAX_COLOR_VAL - *k);
353      *y = (MAX_COLOR_VAL * (Yellow - *k)) / (MAX_COLOR_VAL - *k);
354    }
355    break;
356  case CMYK:
357    *c = Cyan;
358    *m = Magenta;
359    *y = Yellow;
360    *k = Black;
361    break;
362  case GRAY:
363    *c = *m = *y = 0;
364    *k = MAX_COLOR_VAL - Gray;
365    break;
366  default:
367    assert(0);
368    break;
369  }
370}
371
372// we use `0.222r + 0.707g + 0.071b' (this is the ITU standard)
373// as an approximation for gray
374
375void color::get_gray(unsigned int *g) const
376{
377  switch (scheme) {
378  case RGB:
379    *g = (222*Red + 707*Green + 71*Blue) / 1000;
380    break;
381  case CMY:
382    *g = MAX_COLOR_VAL - (222*Cyan + 707*Magenta + 71*Yellow) / 1000;
383    break;
384  case CMYK:
385    *g = (MAX_COLOR_VAL - (222*Cyan + 707*Magenta + 71*Yellow) / 1000)
386	 * (MAX_COLOR_VAL - Black);
387    break;
388  case GRAY:
389    *g = Gray;
390    break;
391  default:
392    assert(0);
393    break;
394  }
395}
396
397char *color::print_color()
398{
399  char *s = new char[30];
400  switch (scheme) {
401  case DEFAULT:
402    sprintf(s, "default");
403    break;
404  case RGB:
405    sprintf(s, "rgb %.2ff %.2ff %.2ff",
406	    double(Red) / MAX_COLOR_VAL,
407	    double(Green) / MAX_COLOR_VAL,
408	    double(Blue) / MAX_COLOR_VAL);
409    break;
410  case CMY:
411    sprintf(s, "cmy %.2ff %.2ff %.2ff",
412	    double(Cyan) / MAX_COLOR_VAL,
413	    double(Magenta) / MAX_COLOR_VAL,
414	    double(Yellow) / MAX_COLOR_VAL);
415    break;
416  case CMYK:
417    sprintf(s, "cmyk %.2ff %.2ff %.2ff %.2ff",
418	    double(Cyan) / MAX_COLOR_VAL,
419	    double(Magenta) / MAX_COLOR_VAL,
420	    double(Yellow) / MAX_COLOR_VAL,
421	    double(Black) / MAX_COLOR_VAL);
422    break;
423  case GRAY:
424    sprintf(s, "gray %.2ff",
425	    double(Gray) / MAX_COLOR_VAL);
426    break;
427  }
428  return s;
429}
430
431color default_color;
432