You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ColorUtil.java 44KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with
  4. * this work for additional information regarding copyright ownership.
  5. * The ASF licenses this file to You under the Apache License, Version 2.0
  6. * (the "License"); you may not use this file except in compliance with
  7. * the License. You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. /* $Id$ */
  18. package org.apache.fop.util;
  19. import java.awt.Color;
  20. import java.awt.color.ColorSpace;
  21. import java.awt.color.ICC_ColorSpace;
  22. import java.awt.color.ICC_Profile;
  23. import java.io.IOException;
  24. import java.util.Collections;
  25. import java.util.Map;
  26. import org.apache.commons.logging.Log;
  27. import org.apache.commons.logging.LogFactory;
  28. import org.apache.xmlgraphics.java2d.color.CIELabColorSpace;
  29. import org.apache.xmlgraphics.java2d.color.ColorSpaceOrigin;
  30. import org.apache.xmlgraphics.java2d.color.ColorSpaces;
  31. import org.apache.xmlgraphics.java2d.color.ColorWithAlternatives;
  32. import org.apache.xmlgraphics.java2d.color.DeviceCMYKColorSpace;
  33. import org.apache.xmlgraphics.java2d.color.NamedColorSpace;
  34. import org.apache.xmlgraphics.java2d.color.RenderingIntent;
  35. import org.apache.xmlgraphics.java2d.color.profile.NamedColorProfile;
  36. import org.apache.xmlgraphics.java2d.color.profile.NamedColorProfileParser;
  37. import org.apache.fop.apps.FOUserAgent;
  38. import org.apache.fop.fo.expr.PropertyException;
  39. import org.apache.fop.util.OCAColor.OCAColorValue;
  40. /**
  41. * Generic Color helper class.
  42. * <p>
  43. * This class supports parsing string values into color values and creating
  44. * color values for strings. It provides a list of standard color names.
  45. */
  46. public final class ColorUtil {
  47. //ColorWithFallback is used to preserve the sRGB fallback exclusively for the purpose
  48. //of regenerating textual color functions as specified in XSL-FO.
  49. /** The name for the uncalibrated CMYK pseudo-profile */
  50. public static final String CMYK_PSEUDO_PROFILE = "#CMYK";
  51. /** The name for the Separation pseudo-profile used for spot colors */
  52. public static final String SEPARATION_PSEUDO_PROFILE = "#Separation";
  53. /**
  54. * Keeps all the predefined and parsed colors.
  55. * <p>
  56. * This map is used to predefine given colors, as well as speeding up
  57. * parsing of already parsed colors.
  58. * <p>
  59. * Important: The use of this color map assumes that all Color instances are immutable!
  60. */
  61. private static Map<String, Color> colorMap;
  62. /** Logger instance */
  63. private static final Log log = LogFactory.getLog(ColorUtil.class);
  64. static {
  65. initializeColorMap();
  66. }
  67. /**
  68. * Private constructor since this is an utility class.
  69. */
  70. private ColorUtil() {
  71. }
  72. /**
  73. * Creates a color from a given string.
  74. * <p>
  75. * This function supports a wide variety of inputs.
  76. * <ul>
  77. * <li>#RGB (hex 0..f)</li>
  78. * <li>#RGBA (hex 0..f)</li>
  79. * <li>#RRGGBB (hex 00..ff)</li>
  80. * <li>#RRGGBBAA (hex 00..ff)</li>
  81. * <li>rgb(r,g,b) (0..255 or 0%..100%)</li>
  82. * <li>java.awt.Color[r=r,g=g,b=b] (0..255)</li>
  83. * <li>system-color(colorname)</li>
  84. * <li>transparent</li>
  85. * <li>colorname</li>
  86. * <li>fop-rgb-icc(r,g,b,cs,cs-src,[num]+) (r/g/b: 0..1, num: 0..1)</li>
  87. * <li>cmyk(c,m,y,k) (0..1)</li>
  88. * </ul>
  89. *
  90. * @param foUserAgent FOUserAgent object
  91. * @param value
  92. * the string to parse.
  93. * @return a Color representing the string if possible
  94. * @throws PropertyException
  95. * if the string is not parsable or does not follow any of the
  96. * given formats.
  97. */
  98. public static Color parseColorString(FOUserAgent foUserAgent, String value)
  99. throws PropertyException {
  100. if (value == null) {
  101. return null;
  102. }
  103. Color parsedColor = colorMap.get(value.toLowerCase());
  104. if (parsedColor == null) {
  105. if (value.startsWith("#")) {
  106. parsedColor = parseWithHash(value);
  107. } else if (value.startsWith("rgb(")) {
  108. parsedColor = parseAsRGB(value);
  109. } else if (value.startsWith("url(")) {
  110. throw new PropertyException(
  111. "Colors starting with url( are not yet supported!");
  112. } else if (value.startsWith("java.awt.Color")) {
  113. parsedColor = parseAsJavaAWTColor(value);
  114. } else if (value.startsWith("system-color(")) {
  115. parsedColor = parseAsSystemColor(value);
  116. } else if (value.startsWith("fop-rgb-icc")) {
  117. parsedColor = parseAsFopRgbIcc(foUserAgent, value);
  118. } else if (value.startsWith("fop-rgb-named-color")) {
  119. parsedColor = parseAsFopRgbNamedColor(foUserAgent, value);
  120. } else if (value.startsWith("cie-lab-color")) {
  121. parsedColor = parseAsCIELabColor(foUserAgent, value);
  122. } else if (value.startsWith("cmyk")) {
  123. parsedColor = parseAsCMYK(value);
  124. } else if (value.startsWith("oca")) {
  125. parsedColor = parseAsOCA(value);
  126. }
  127. if (parsedColor == null) {
  128. throw new PropertyException("Unknown Color: " + value);
  129. }
  130. colorMap.put(value, parsedColor);
  131. }
  132. return parsedColor;
  133. }
  134. /**
  135. * Tries to parse a color given with the system-color() function.
  136. *
  137. * @param value
  138. * the complete line
  139. * @return a color if possible
  140. * @throws PropertyException
  141. * if the format is wrong.
  142. */
  143. private static Color parseAsSystemColor(String value)
  144. throws PropertyException {
  145. int poss = value.indexOf("(");
  146. int pose = value.indexOf(")");
  147. if (poss != -1 && pose != -1) {
  148. value = value.substring(poss + 1, pose);
  149. } else {
  150. throw new PropertyException("Unknown color format: " + value
  151. + ". Must be system-color(x)");
  152. }
  153. return colorMap.get(value);
  154. }
  155. /**
  156. * Tries to parse the standard java.awt.Color toString output.
  157. *
  158. * @param value
  159. * the complete line
  160. * @return a color if possible
  161. * @throws PropertyException
  162. * if the format is wrong.
  163. * @see java.awt.Color#toString()
  164. */
  165. private static Color parseAsJavaAWTColor(String value)
  166. throws PropertyException {
  167. float red = 0.0f;
  168. float green = 0.0f;
  169. float blue = 0.0f;
  170. int poss = value.indexOf("[");
  171. int pose = value.indexOf("]");
  172. try {
  173. if (poss != -1 && pose != -1) {
  174. value = value.substring(poss + 1, pose);
  175. String[] args = value.split(",");
  176. if (args.length != 3) {
  177. throw new PropertyException(
  178. "Invalid number of arguments for a java.awt.Color: " + value);
  179. }
  180. red = Float.parseFloat(args[0].trim().substring(2)) / 255f;
  181. green = Float.parseFloat(args[1].trim().substring(2)) / 255f;
  182. blue = Float.parseFloat(args[2].trim().substring(2)) / 255f;
  183. if ((red < 0.0 || red > 1.0)
  184. || (green < 0.0 || green > 1.0)
  185. || (blue < 0.0 || blue > 1.0)) {
  186. throw new PropertyException("Color values out of range");
  187. }
  188. } else {
  189. throw new IllegalArgumentException(
  190. "Invalid format for a java.awt.Color: " + value);
  191. }
  192. } catch (RuntimeException re) {
  193. throw new PropertyException(re);
  194. }
  195. return new Color(red, green, blue);
  196. }
  197. /**
  198. * Parse a color given with the rgb() function.
  199. *
  200. * @param value
  201. * the complete line
  202. * @return a color if possible
  203. * @throws PropertyException
  204. * if the format is wrong.
  205. */
  206. private static Color parseAsRGB(String value) throws PropertyException {
  207. Color parsedColor;
  208. int poss = value.indexOf("(");
  209. int pose = value.indexOf(")");
  210. if (poss != -1 && pose != -1) {
  211. value = value.substring(poss + 1, pose);
  212. try {
  213. String[] args = value.split(",");
  214. if (args.length != 3) {
  215. throw new PropertyException(
  216. "Invalid number of arguments: rgb(" + value + ")");
  217. }
  218. float red = parseComponent255(args[0], value);
  219. float green = parseComponent255(args[1], value);
  220. float blue = parseComponent255(args[2], value);
  221. //Convert to ints to synchronize the behaviour with toRGBFunctionCall()
  222. int r = (int)(red * 255 + 0.5);
  223. int g = (int)(green * 255 + 0.5);
  224. int b = (int)(blue * 255 + 0.5);
  225. parsedColor = new Color(r, g, b);
  226. } catch (RuntimeException re) {
  227. //wrap in a PropertyException
  228. throw new PropertyException(re);
  229. }
  230. } else {
  231. throw new PropertyException("Unknown color format: " + value
  232. + ". Must be rgb(r,g,b)");
  233. }
  234. return parsedColor;
  235. }
  236. private static float parseComponent255(String str, String function)
  237. throws PropertyException {
  238. float component;
  239. str = str.trim();
  240. if (str.endsWith("%")) {
  241. component = Float.parseFloat(str.substring(0,
  242. str.length() - 1)) / 100f;
  243. } else {
  244. component = Float.parseFloat(str) / 255f;
  245. }
  246. if ((component < 0.0 || component > 1.0)) {
  247. throw new PropertyException("Color value out of range for " + function + ": "
  248. + str + ". Valid range: [0..255] or [0%..100%]");
  249. }
  250. return component;
  251. }
  252. private static float parseComponent1(String argument, String function)
  253. throws PropertyException {
  254. return parseComponent(argument, 0f, 1f, function);
  255. }
  256. private static float parseComponent(String argument, float min, float max, String function)
  257. throws PropertyException {
  258. float component = Float.parseFloat(argument.trim());
  259. if ((component < min || component > max)) {
  260. throw new PropertyException("Color value out of range for " + function + ": "
  261. + argument + ". Valid range: [" + min + ".." + max + "]");
  262. }
  263. return component;
  264. }
  265. private static Color parseFallback(String[] args, String value) throws PropertyException {
  266. float red = parseComponent1(args[0], value);
  267. float green = parseComponent1(args[1], value);
  268. float blue = parseComponent1(args[2], value);
  269. //Sun's classlib rounds differently with this constructor than when converting to sRGB
  270. //via CIE XYZ.
  271. Color sRGB = new Color(red, green, blue);
  272. return sRGB;
  273. }
  274. /**
  275. * Parse a color given in the #.... format.
  276. *
  277. * @param value
  278. * the complete line
  279. * @return a color if possible
  280. * @throws PropertyException
  281. * if the format is wrong.
  282. */
  283. private static Color parseWithHash(String value) throws PropertyException {
  284. Color parsedColor;
  285. try {
  286. int len = value.length();
  287. int alpha;
  288. if (len == 5 || len == 9) {
  289. alpha = Integer.parseInt(
  290. value.substring((len == 5) ? 3 : 7), 16);
  291. } else {
  292. alpha = 0xFF;
  293. }
  294. int red = 0;
  295. int green = 0;
  296. int blue = 0;
  297. if ((len == 4) || (len == 5)) {
  298. //multiply by 0x11 = 17 = 255/15
  299. red = Integer.parseInt(value.substring(1, 2), 16) * 0x11;
  300. green = Integer.parseInt(value.substring(2, 3), 16) * 0x11;
  301. blue = Integer.parseInt(value.substring(3, 4), 16) * 0X11;
  302. } else if ((len == 7) || (len == 9)) {
  303. red = Integer.parseInt(value.substring(1, 3), 16);
  304. green = Integer.parseInt(value.substring(3, 5), 16);
  305. blue = Integer.parseInt(value.substring(5, 7), 16);
  306. } else {
  307. throw new NumberFormatException();
  308. }
  309. parsedColor = new Color(red, green, blue, alpha);
  310. } catch (RuntimeException re) {
  311. throw new PropertyException("Unknown color format: " + value
  312. + ". Must be #RGB. #RGBA, #RRGGBB, or #RRGGBBAA");
  313. }
  314. return parsedColor;
  315. }
  316. /**
  317. * Parse a color specified using the fop-rgb-icc() function.
  318. *
  319. * @param value the function call
  320. * @return a color if possible
  321. * @throws PropertyException if the format is wrong.
  322. */
  323. private static Color parseAsFopRgbIcc(FOUserAgent foUserAgent, String value)
  324. throws PropertyException {
  325. Color parsedColor;
  326. int poss = value.indexOf("(");
  327. int pose = value.indexOf(")");
  328. if (poss != -1 && pose != -1) {
  329. String[] args = value.substring(poss + 1, pose).split(",");
  330. try {
  331. if (args.length < 5) {
  332. throw new PropertyException("Too few arguments for rgb-icc() function");
  333. }
  334. //Set up fallback sRGB value
  335. Color sRGB = parseFallback(args, value);
  336. /* Get and verify ICC profile name */
  337. String iccProfileName = args[3].trim();
  338. if (iccProfileName == null || "".equals(iccProfileName)) {
  339. throw new PropertyException("ICC profile name missing");
  340. }
  341. ColorSpace colorSpace = null;
  342. String iccProfileSrc = null;
  343. if (isPseudoProfile(iccProfileName)) {
  344. if (CMYK_PSEUDO_PROFILE.equalsIgnoreCase(iccProfileName)) {
  345. colorSpace = ColorSpaces.getDeviceCMYKColorSpace();
  346. } else if (SEPARATION_PSEUDO_PROFILE.equalsIgnoreCase(iccProfileName)) {
  347. colorSpace = new NamedColorSpace(args[5], sRGB,
  348. SEPARATION_PSEUDO_PROFILE, null);
  349. } else {
  350. assert false : "Incomplete implementation";
  351. }
  352. } else {
  353. /* Get and verify ICC profile source */
  354. iccProfileSrc = args[4].trim();
  355. if (iccProfileSrc == null || "".equals(iccProfileSrc)) {
  356. throw new PropertyException("ICC profile source missing");
  357. }
  358. iccProfileSrc = unescapeString(iccProfileSrc);
  359. }
  360. /* ICC profile arguments */
  361. int componentStart = 4;
  362. if (colorSpace instanceof NamedColorSpace) {
  363. componentStart++;
  364. }
  365. float[] iccComponents = new float[args.length - componentStart - 1];
  366. for (int ix = componentStart; ++ix < args.length;) {
  367. iccComponents[ix - componentStart - 1] = Float.parseFloat(args[ix].trim());
  368. }
  369. if (colorSpace instanceof NamedColorSpace && iccComponents.length == 0) {
  370. iccComponents = new float[] {1.0f}; //full tint if not specified
  371. }
  372. /* Ask FOP factory to get ColorSpace for the specified ICC profile source */
  373. if (foUserAgent != null && iccProfileSrc != null) {
  374. RenderingIntent renderingIntent = RenderingIntent.AUTO;
  375. //TODO connect to fo:color-profile/@rendering-intent
  376. colorSpace = foUserAgent.getColorSpaceCache().get(
  377. iccProfileName, iccProfileSrc, renderingIntent);
  378. }
  379. if (colorSpace != null) {
  380. // ColorSpace is available
  381. if (ColorSpaces.isDeviceColorSpace(colorSpace)) {
  382. //Device-specific colors are handled differently:
  383. //sRGB is the primary color with the CMYK as the alternative
  384. Color deviceColor = new Color(colorSpace, iccComponents, 1.0f);
  385. float[] rgbComps = sRGB.getRGBColorComponents(null);
  386. parsedColor = new ColorWithAlternatives(
  387. rgbComps[0], rgbComps[1], rgbComps[2],
  388. new Color[] {deviceColor});
  389. } else {
  390. parsedColor = new ColorWithFallback(
  391. colorSpace, iccComponents, 1.0f, null, sRGB);
  392. }
  393. } else {
  394. // ICC profile could not be loaded - use rgb replacement values */
  395. log.warn("Color profile '" + iccProfileSrc
  396. + "' not found. Using sRGB replacement values.");
  397. parsedColor = sRGB;
  398. }
  399. } catch (RuntimeException re) {
  400. throw new PropertyException(re);
  401. }
  402. } else {
  403. throw new PropertyException("Unknown color format: " + value
  404. + ". Must be fop-rgb-icc(r,g,b,NCNAME,src,....)");
  405. }
  406. return parsedColor;
  407. }
  408. /**
  409. * Parse a color specified using the fop-rgb-named-color() function.
  410. *
  411. * @param value the function call
  412. * @return a color if possible
  413. * @throws PropertyException if the format is wrong.
  414. */
  415. private static Color parseAsFopRgbNamedColor(FOUserAgent foUserAgent, String value)
  416. throws PropertyException {
  417. Color parsedColor;
  418. int poss = value.indexOf("(");
  419. int pose = value.indexOf(")");
  420. if (poss != -1 && pose != -1) {
  421. String[] args = value.substring(poss + 1, pose).split(",");
  422. try {
  423. if (args.length != 6) {
  424. throw new PropertyException("rgb-named-color() function must have 6 arguments");
  425. }
  426. //Set up fallback sRGB value
  427. Color sRGB = parseFallback(args, value);
  428. /* Get and verify ICC profile name */
  429. String iccProfileName = args[3].trim();
  430. if (iccProfileName == null || "".equals(iccProfileName)) {
  431. throw new PropertyException("ICC profile name missing");
  432. }
  433. ICC_ColorSpace colorSpace = null;
  434. String iccProfileSrc;
  435. if (isPseudoProfile(iccProfileName)) {
  436. throw new IllegalArgumentException(
  437. "Pseudo-profiles are not allowed with fop-rgb-named-color()");
  438. } else {
  439. /* Get and verify ICC profile source */
  440. iccProfileSrc = args[4].trim();
  441. if (iccProfileSrc == null || "".equals(iccProfileSrc)) {
  442. throw new PropertyException("ICC profile source missing");
  443. }
  444. iccProfileSrc = unescapeString(iccProfileSrc);
  445. }
  446. // color name
  447. String colorName = unescapeString(args[5].trim());
  448. /* Ask FOP factory to get ColorSpace for the specified ICC profile source */
  449. if (foUserAgent != null && iccProfileSrc != null) {
  450. RenderingIntent renderingIntent = RenderingIntent.AUTO;
  451. //TODO connect to fo:color-profile/@rendering-intent
  452. colorSpace = (ICC_ColorSpace)foUserAgent.getColorSpaceCache().get(
  453. iccProfileName, iccProfileSrc, renderingIntent);
  454. }
  455. if (colorSpace != null) {
  456. ICC_Profile profile = colorSpace.getProfile();
  457. if (NamedColorProfileParser.isNamedColorProfile(profile)) {
  458. NamedColorProfileParser parser = new NamedColorProfileParser();
  459. NamedColorProfile ncp = parser.parseProfile(profile,
  460. iccProfileName, iccProfileSrc);
  461. NamedColorSpace ncs = ncp.getNamedColor(colorName);
  462. if (ncs != null) {
  463. parsedColor = new ColorWithFallback(ncs,
  464. new float[] {1.0f}, 1.0f, null, sRGB);
  465. } else {
  466. log.warn("Color '" + colorName
  467. + "' does not exist in named color profile: " + iccProfileSrc);
  468. parsedColor = sRGB;
  469. }
  470. } else {
  471. log.warn("ICC profile is no named color profile: " + iccProfileSrc);
  472. parsedColor = sRGB;
  473. }
  474. } else {
  475. // ICC profile could not be loaded - use rgb replacement values */
  476. log.warn("Color profile '" + iccProfileSrc
  477. + "' not found. Using sRGB replacement values.");
  478. parsedColor = sRGB;
  479. }
  480. } catch (IOException ioe) {
  481. //wrap in a PropertyException
  482. throw new PropertyException(ioe);
  483. } catch (RuntimeException re) {
  484. throw new PropertyException(re);
  485. //wrap in a PropertyException
  486. }
  487. } else {
  488. throw new PropertyException("Unknown color format: " + value
  489. + ". Must be fop-rgb-named-color(r,g,b,NCNAME,src,color-name)");
  490. }
  491. return parsedColor;
  492. }
  493. /**
  494. * Parse a color specified using the cie-lab-color() function.
  495. *
  496. * @param value the function call
  497. * @return a color if possible
  498. * @throws PropertyException if the format is wrong.
  499. */
  500. private static Color parseAsCIELabColor(FOUserAgent foUserAgent, String value)
  501. throws PropertyException {
  502. Color parsedColor;
  503. int poss = value.indexOf("(");
  504. int pose = value.indexOf(")");
  505. if (poss != -1 && pose != -1) {
  506. try {
  507. String[] args = value.substring(poss + 1, pose).split(",");
  508. if (args.length != 6) {
  509. throw new PropertyException("cie-lab-color() function must have 6 arguments");
  510. }
  511. //Set up fallback sRGB value
  512. float red = parseComponent255(args[0], value);
  513. float green = parseComponent255(args[1], value);
  514. float blue = parseComponent255(args[2], value);
  515. Color sRGB = new Color(red, green, blue);
  516. float l = parseComponent(args[3], 0f, 100f, value);
  517. float a = parseComponent(args[4], -127f, 127f, value);
  518. float b = parseComponent(args[5], -127f, 127f, value);
  519. //Assuming the XSL-FO spec uses the D50 white point
  520. CIELabColorSpace cs = ColorSpaces.getCIELabColorSpaceD50();
  521. //use toColor() to have components normalized
  522. Color labColor = cs.toColor(l, a, b, 1.0f);
  523. //Convert to ColorWithFallback
  524. parsedColor = new ColorWithFallback(labColor, sRGB);
  525. } catch (RuntimeException re) {
  526. throw new PropertyException(re);
  527. }
  528. } else {
  529. throw new PropertyException("Unknown color format: " + value
  530. + ". Must be cie-lab-color(r,g,b,Lightness,a-value,b-value)");
  531. }
  532. return parsedColor;
  533. }
  534. private static String unescapeString(String iccProfileSrc) {
  535. if (iccProfileSrc.startsWith("\"") || iccProfileSrc.startsWith("'")) {
  536. iccProfileSrc = iccProfileSrc.substring(1);
  537. }
  538. if (iccProfileSrc.endsWith("\"") || iccProfileSrc.endsWith("'")) {
  539. iccProfileSrc = iccProfileSrc.substring(0, iccProfileSrc.length() - 1);
  540. }
  541. return iccProfileSrc;
  542. }
  543. /**
  544. * Parse a color given with the cmyk() function.
  545. *
  546. * @param value
  547. * the complete line
  548. * @return a color if possible
  549. * @throws PropertyException
  550. * if the format is wrong.
  551. */
  552. private static Color parseAsCMYK(String value) throws PropertyException {
  553. Color parsedColor;
  554. int poss = value.indexOf("(");
  555. int pose = value.indexOf(")");
  556. if (poss != -1 && pose != -1) {
  557. value = value.substring(poss + 1, pose);
  558. String[] args = value.split(",");
  559. try {
  560. if (args.length != 4) {
  561. throw new PropertyException(
  562. "Invalid number of arguments: cmyk(" + value + ")");
  563. }
  564. float cyan = parseComponent1(args[0], value);
  565. float magenta = parseComponent1(args[1], value);
  566. float yellow = parseComponent1(args[2], value);
  567. float black = parseComponent1(args[3], value);
  568. float[] comps = new float[] {cyan, magenta, yellow, black};
  569. Color cmykColor = DeviceCMYKColorSpace.createCMYKColor(comps);
  570. float[] rgbComps = cmykColor.getRGBColorComponents(null);
  571. parsedColor = new ColorWithAlternatives(rgbComps[0], rgbComps[1], rgbComps[2],
  572. new Color[] {cmykColor});
  573. } catch (RuntimeException re) {
  574. throw new PropertyException(re);
  575. }
  576. } else {
  577. throw new PropertyException("Unknown color format: " + value
  578. + ". Must be cmyk(c,m,y,k)");
  579. }
  580. return parsedColor;
  581. }
  582. private static Color parseAsOCA(String value) throws PropertyException {
  583. int poss = value.indexOf("(");
  584. int pose = value.indexOf(")");
  585. if (poss != -1 && pose != -1) {
  586. value = value.substring(poss + 1, pose);
  587. OCAColorValue colorValue;
  588. if (value.equals("blue")) {
  589. colorValue = OCAColorValue.BLUE;
  590. } else if (value.equals("red")) {
  591. colorValue = OCAColorValue.RED;
  592. } else if (value.equals("magenta")) {
  593. colorValue = OCAColorValue.MAGENTA;
  594. } else if (value.equals("green")) {
  595. colorValue = OCAColorValue.GREEN;
  596. } else if (value.equals("cyan")) {
  597. colorValue = OCAColorValue.CYAN;
  598. } else if (value.equals("yellow")) {
  599. colorValue = OCAColorValue.YELLOW;
  600. } else if (value.equals("black")) {
  601. colorValue = OCAColorValue.BLACK;
  602. } else if (value.equals("brown")) {
  603. colorValue = OCAColorValue.BROWN;
  604. } else if (value.equals("medium-color")) {
  605. colorValue = OCAColorValue.MEDIUM_COLOR;
  606. } else if (value.equals("device-default")) {
  607. colorValue = OCAColorValue.DEVICE_DEFAULT;
  608. } else {
  609. throw new PropertyException("Unknwon OCA color: " + value);
  610. }
  611. return new OCAColor(colorValue);
  612. } else {
  613. throw new PropertyException("Unknown color format: " + value
  614. + ". Must be oca(color-name)");
  615. }
  616. }
  617. /**
  618. * Creates a re-parsable string representation of the given color.
  619. * <p>
  620. * First, the color will be converted into the sRGB colorspace. It will then
  621. * be printed as #rrggbb, or as #rrrggbbaa if an alpha value is present.
  622. *
  623. * @param color
  624. * the color to represent.
  625. * @return a re-parsable string representadion.
  626. */
  627. public static String colorToString(Color color) {
  628. ColorSpace cs = color.getColorSpace();
  629. if (color instanceof ColorWithAlternatives) {
  630. return toFunctionCall((ColorWithAlternatives)color);
  631. } else if (cs != null && cs.getType() == ColorSpace.TYPE_CMYK) {
  632. StringBuffer sbuf = new StringBuffer(24);
  633. float[] cmyk = color.getColorComponents(null);
  634. sbuf.append("cmyk(").append(cmyk[0])
  635. .append(",").append(cmyk[1])
  636. .append(",").append(cmyk[2])
  637. .append(",").append(cmyk[3]).append(")");
  638. return sbuf.toString();
  639. } else {
  640. return toRGBFunctionCall(color);
  641. }
  642. }
  643. private static String toRGBFunctionCall(Color color) {
  644. StringBuffer sbuf = new StringBuffer();
  645. sbuf.append('#');
  646. String s = Integer.toHexString(color.getRed());
  647. if (s.length() == 1) {
  648. sbuf.append('0');
  649. }
  650. sbuf.append(s);
  651. s = Integer.toHexString(color.getGreen());
  652. if (s.length() == 1) {
  653. sbuf.append('0');
  654. }
  655. sbuf.append(s);
  656. s = Integer.toHexString(color.getBlue());
  657. if (s.length() == 1) {
  658. sbuf.append('0');
  659. }
  660. sbuf.append(s);
  661. if (color.getAlpha() != 255) {
  662. s = Integer.toHexString(color.getAlpha());
  663. if (s.length() == 1) {
  664. sbuf.append('0');
  665. }
  666. sbuf.append(s);
  667. }
  668. return sbuf.toString();
  669. }
  670. private static Color getsRGBFallback(ColorWithAlternatives color) {
  671. Color fallbackColor;
  672. if (color instanceof ColorWithFallback) {
  673. fallbackColor = ((ColorWithFallback)color).getFallbackColor();
  674. if (!fallbackColor.getColorSpace().isCS_sRGB()) {
  675. fallbackColor = toSRGBColor(fallbackColor);
  676. }
  677. } else {
  678. fallbackColor = toSRGBColor(color);
  679. }
  680. return fallbackColor;
  681. }
  682. private static Color toSRGBColor(Color color) {
  683. float[] comps;
  684. ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
  685. comps = color.getRGBColorComponents(null);
  686. float[] allComps = color.getComponents(null);
  687. float alpha = allComps[allComps.length - 1]; //Alpha is on last component
  688. return new Color(sRGB, comps, alpha);
  689. }
  690. /**
  691. * Create string representation of a fop-rgb-icc (or fop-rgb-named-color) function call from
  692. * the given color.
  693. * @param color the color to turn into a function call
  694. * @return the string representing the internal fop-rgb-icc() or fop-rgb-named-color()
  695. * function call
  696. */
  697. private static String toFunctionCall(ColorWithAlternatives color) {
  698. ColorSpace cs = color.getColorSpace();
  699. if (cs.isCS_sRGB() && !color.hasAlternativeColors()) {
  700. return toRGBFunctionCall(color);
  701. }
  702. if (cs instanceof CIELabColorSpace) {
  703. return toCIELabFunctionCall(color);
  704. }
  705. Color specColor = color;
  706. if (color.hasAlternativeColors()) {
  707. Color alt = color.getAlternativeColors()[0];
  708. if (ColorSpaces.isDeviceColorSpace(alt.getColorSpace())) {
  709. cs = alt.getColorSpace();
  710. specColor = alt;
  711. }
  712. }
  713. ColorSpaceOrigin origin = ColorSpaces.getColorSpaceOrigin(cs);
  714. String functionName;
  715. Color fallbackColor = getsRGBFallback(color);
  716. float[] rgb = fallbackColor.getColorComponents(null);
  717. assert rgb.length == 3;
  718. StringBuffer sb = new StringBuffer(40);
  719. sb.append("(");
  720. sb.append(rgb[0]).append(",");
  721. sb.append(rgb[1]).append(",");
  722. sb.append(rgb[2]).append(",");
  723. String profileName = origin.getProfileName();
  724. sb.append(profileName).append(",");
  725. if (origin.getProfileURI() != null) {
  726. sb.append("\"").append(origin.getProfileURI()).append("\"");
  727. }
  728. if (cs instanceof NamedColorSpace) {
  729. NamedColorSpace ncs = (NamedColorSpace)cs;
  730. if (SEPARATION_PSEUDO_PROFILE.equalsIgnoreCase(profileName)) {
  731. functionName = "fop-rgb-icc";
  732. } else {
  733. functionName = "fop-rgb-named-color";
  734. }
  735. sb.append(",").append(ncs.getColorName());
  736. } else {
  737. functionName = "fop-rgb-icc";
  738. float[] colorComponents = specColor.getColorComponents(null);
  739. for (float colorComponent : colorComponents) {
  740. sb.append(",");
  741. sb.append(colorComponent);
  742. }
  743. }
  744. sb.append(")");
  745. return functionName + sb;
  746. }
  747. private static String toCIELabFunctionCall(ColorWithAlternatives color) {
  748. Color fallbackColor = getsRGBFallback(color);
  749. StringBuffer sb = new StringBuffer("cie-lab-color(");
  750. sb.append(fallbackColor.getRed()).append(',');
  751. sb.append(fallbackColor.getGreen()).append(',');
  752. sb.append(fallbackColor.getBlue());
  753. CIELabColorSpace cs = (CIELabColorSpace)color.getColorSpace();
  754. float[] lab = cs.toNativeComponents(color.getColorComponents(null));
  755. for (int i = 0; i < 3; i++) {
  756. sb.append(',').append(lab[i]);
  757. }
  758. sb.append(')');
  759. return sb.toString();
  760. }
  761. private static Color createColor(int r, int g, int b) {
  762. return new Color(r, g, b);
  763. }
  764. /**
  765. * Initializes the colorMap with some predefined values.
  766. */
  767. private static void initializeColorMap() {
  768. colorMap = Collections.synchronizedMap(new java.util.HashMap<String, Color>());
  769. colorMap.put("aliceblue", createColor(240, 248, 255));
  770. colorMap.put("antiquewhite", createColor(250, 235, 215));
  771. colorMap.put("aqua", createColor(0, 255, 255));
  772. colorMap.put("aquamarine", createColor(127, 255, 212));
  773. colorMap.put("azure", createColor(240, 255, 255));
  774. colorMap.put("beige", createColor(245, 245, 220));
  775. colorMap.put("bisque", createColor(255, 228, 196));
  776. colorMap.put("black", createColor(0, 0, 0));
  777. colorMap.put("blanchedalmond", createColor(255, 235, 205));
  778. colorMap.put("blue", createColor(0, 0, 255));
  779. colorMap.put("blueviolet", createColor(138, 43, 226));
  780. colorMap.put("brown", createColor(165, 42, 42));
  781. colorMap.put("burlywood", createColor(222, 184, 135));
  782. colorMap.put("cadetblue", createColor(95, 158, 160));
  783. colorMap.put("chartreuse", createColor(127, 255, 0));
  784. colorMap.put("chocolate", createColor(210, 105, 30));
  785. colorMap.put("coral", createColor(255, 127, 80));
  786. colorMap.put("cornflowerblue", createColor(100, 149, 237));
  787. colorMap.put("cornsilk", createColor(255, 248, 220));
  788. colorMap.put("crimson", createColor(220, 20, 60));
  789. colorMap.put("cyan", createColor(0, 255, 255));
  790. colorMap.put("darkblue", createColor(0, 0, 139));
  791. colorMap.put("darkcyan", createColor(0, 139, 139));
  792. colorMap.put("darkgoldenrod", createColor(184, 134, 11));
  793. colorMap.put("darkgray", createColor(169, 169, 169));
  794. colorMap.put("darkgreen", createColor(0, 100, 0));
  795. colorMap.put("darkgrey", createColor(169, 169, 169));
  796. colorMap.put("darkkhaki", createColor(189, 183, 107));
  797. colorMap.put("darkmagenta", createColor(139, 0, 139));
  798. colorMap.put("darkolivegreen", createColor(85, 107, 47));
  799. colorMap.put("darkorange", createColor(255, 140, 0));
  800. colorMap.put("darkorchid", createColor(153, 50, 204));
  801. colorMap.put("darkred", createColor(139, 0, 0));
  802. colorMap.put("darksalmon", createColor(233, 150, 122));
  803. colorMap.put("darkseagreen", createColor(143, 188, 143));
  804. colorMap.put("darkslateblue", createColor(72, 61, 139));
  805. colorMap.put("darkslategray", createColor(47, 79, 79));
  806. colorMap.put("darkslategrey", createColor(47, 79, 79));
  807. colorMap.put("darkturquoise", createColor(0, 206, 209));
  808. colorMap.put("darkviolet", createColor(148, 0, 211));
  809. colorMap.put("deeppink", createColor(255, 20, 147));
  810. colorMap.put("deepskyblue", createColor(0, 191, 255));
  811. colorMap.put("dimgray", createColor(105, 105, 105));
  812. colorMap.put("dimgrey", createColor(105, 105, 105));
  813. colorMap.put("dodgerblue", createColor(30, 144, 255));
  814. colorMap.put("firebrick", createColor(178, 34, 34));
  815. colorMap.put("floralwhite", createColor(255, 250, 240));
  816. colorMap.put("forestgreen", createColor(34, 139, 34));
  817. colorMap.put("fuchsia", createColor(255, 0, 255));
  818. colorMap.put("gainsboro", createColor(220, 220, 220));
  819. colorMap.put("ghostwhite", createColor(248, 248, 255));
  820. colorMap.put("gold", createColor(255, 215, 0));
  821. colorMap.put("goldenrod", createColor(218, 165, 32));
  822. colorMap.put("gray", createColor(128, 128, 128));
  823. colorMap.put("green", createColor(0, 128, 0));
  824. colorMap.put("greenyellow", createColor(173, 255, 47));
  825. colorMap.put("grey", createColor(128, 128, 128));
  826. colorMap.put("honeydew", createColor(240, 255, 240));
  827. colorMap.put("hotpink", createColor(255, 105, 180));
  828. colorMap.put("indianred", createColor(205, 92, 92));
  829. colorMap.put("indigo", createColor(75, 0, 130));
  830. colorMap.put("ivory", createColor(255, 255, 240));
  831. colorMap.put("khaki", createColor(240, 230, 140));
  832. colorMap.put("lavender", createColor(230, 230, 250));
  833. colorMap.put("lavenderblush", createColor(255, 240, 245));
  834. colorMap.put("lawngreen", createColor(124, 252, 0));
  835. colorMap.put("lemonchiffon", createColor(255, 250, 205));
  836. colorMap.put("lightblue", createColor(173, 216, 230));
  837. colorMap.put("lightcoral", createColor(240, 128, 128));
  838. colorMap.put("lightcyan", createColor(224, 255, 255));
  839. colorMap.put("lightgoldenrodyellow", createColor(250, 250, 210));
  840. colorMap.put("lightgray", createColor(211, 211, 211));
  841. colorMap.put("lightgreen", createColor(144, 238, 144));
  842. colorMap.put("lightgrey", createColor(211, 211, 211));
  843. colorMap.put("lightpink", createColor(255, 182, 193));
  844. colorMap.put("lightsalmon", createColor(255, 160, 122));
  845. colorMap.put("lightseagreen", createColor(32, 178, 170));
  846. colorMap.put("lightskyblue", createColor(135, 206, 250));
  847. colorMap.put("lightslategray", createColor(119, 136, 153));
  848. colorMap.put("lightslategrey", createColor(119, 136, 153));
  849. colorMap.put("lightsteelblue", createColor(176, 196, 222));
  850. colorMap.put("lightyellow", createColor(255, 255, 224));
  851. colorMap.put("lime", createColor(0, 255, 0));
  852. colorMap.put("limegreen", createColor(50, 205, 50));
  853. colorMap.put("linen", createColor(250, 240, 230));
  854. colorMap.put("magenta", createColor(255, 0, 255));
  855. colorMap.put("maroon", createColor(128, 0, 0));
  856. colorMap.put("mediumaquamarine", createColor(102, 205, 170));
  857. colorMap.put("mediumblue", createColor(0, 0, 205));
  858. colorMap.put("mediumorchid", createColor(186, 85, 211));
  859. colorMap.put("mediumpurple", createColor(147, 112, 219));
  860. colorMap.put("mediumseagreen", createColor(60, 179, 113));
  861. colorMap.put("mediumslateblue", createColor(123, 104, 238));
  862. colorMap.put("mediumspringgreen", createColor(0, 250, 154));
  863. colorMap.put("mediumturquoise", createColor(72, 209, 204));
  864. colorMap.put("mediumvioletred", createColor(199, 21, 133));
  865. colorMap.put("midnightblue", createColor(25, 25, 112));
  866. colorMap.put("mintcream", createColor(245, 255, 250));
  867. colorMap.put("mistyrose", createColor(255, 228, 225));
  868. colorMap.put("moccasin", createColor(255, 228, 181));
  869. colorMap.put("navajowhite", createColor(255, 222, 173));
  870. colorMap.put("navy", createColor(0, 0, 128));
  871. colorMap.put("oldlace", createColor(253, 245, 230));
  872. colorMap.put("olive", createColor(128, 128, 0));
  873. colorMap.put("olivedrab", createColor(107, 142, 35));
  874. colorMap.put("orange", createColor(255, 165, 0));
  875. colorMap.put("orangered", createColor(255, 69, 0));
  876. colorMap.put("orchid", createColor(218, 112, 214));
  877. colorMap.put("palegoldenrod", createColor(238, 232, 170));
  878. colorMap.put("palegreen", createColor(152, 251, 152));
  879. colorMap.put("paleturquoise", createColor(175, 238, 238));
  880. colorMap.put("palevioletred", createColor(219, 112, 147));
  881. colorMap.put("papayawhip", createColor(255, 239, 213));
  882. colorMap.put("peachpuff", createColor(255, 218, 185));
  883. colorMap.put("peru", createColor(205, 133, 63));
  884. colorMap.put("pink", createColor(255, 192, 203));
  885. colorMap.put("plum ", createColor(221, 160, 221));
  886. colorMap.put("plum", createColor(221, 160, 221));
  887. colorMap.put("powderblue", createColor(176, 224, 230));
  888. colorMap.put("purple", createColor(128, 0, 128));
  889. colorMap.put("red", createColor(255, 0, 0));
  890. colorMap.put("rosybrown", createColor(188, 143, 143));
  891. colorMap.put("royalblue", createColor(65, 105, 225));
  892. colorMap.put("saddlebrown", createColor(139, 69, 19));
  893. colorMap.put("salmon", createColor(250, 128, 114));
  894. colorMap.put("sandybrown", createColor(244, 164, 96));
  895. colorMap.put("seagreen", createColor(46, 139, 87));
  896. colorMap.put("seashell", createColor(255, 245, 238));
  897. colorMap.put("sienna", createColor(160, 82, 45));
  898. colorMap.put("silver", createColor(192, 192, 192));
  899. colorMap.put("skyblue", createColor(135, 206, 235));
  900. colorMap.put("slateblue", createColor(106, 90, 205));
  901. colorMap.put("slategray", createColor(112, 128, 144));
  902. colorMap.put("slategrey", createColor(112, 128, 144));
  903. colorMap.put("snow", createColor(255, 250, 250));
  904. colorMap.put("springgreen", createColor(0, 255, 127));
  905. colorMap.put("steelblue", createColor(70, 130, 180));
  906. colorMap.put("tan", createColor(210, 180, 140));
  907. colorMap.put("teal", createColor(0, 128, 128));
  908. colorMap.put("thistle", createColor(216, 191, 216));
  909. colorMap.put("tomato", createColor(255, 99, 71));
  910. colorMap.put("turquoise", createColor(64, 224, 208));
  911. colorMap.put("violet", createColor(238, 130, 238));
  912. colorMap.put("wheat", createColor(245, 222, 179));
  913. colorMap.put("white", createColor(255, 255, 255));
  914. colorMap.put("whitesmoke", createColor(245, 245, 245));
  915. colorMap.put("yellow", createColor(255, 255, 0));
  916. colorMap.put("yellowgreen", createColor(154, 205, 50));
  917. colorMap.put("transparent", new ColorWithAlternatives(0, 0, 0, 0, null));
  918. }
  919. /**
  920. * Lightens up a color for groove, ridge, inset and outset border effects.
  921. * @param col the color to lighten up
  922. * @param factor factor by which to lighten up (negative values darken the color)
  923. * @return the modified color
  924. */
  925. public static Color lightenColor(Color col, float factor) {
  926. return org.apache.xmlgraphics.java2d.color.ColorUtil.lightenColor(col, factor);
  927. }
  928. /**
  929. * Indicates whether the given color profile name is one of the pseudo-profiles supported
  930. * by FOP (ex. #CMYK).
  931. * @param colorProfileName the color profile name to check
  932. * @return true if the color profile name is of a built-in pseudo-profile
  933. */
  934. public static boolean isPseudoProfile(String colorProfileName) {
  935. return CMYK_PSEUDO_PROFILE.equalsIgnoreCase(colorProfileName)
  936. || SEPARATION_PSEUDO_PROFILE.equalsIgnoreCase(colorProfileName);
  937. }
  938. /**
  939. * Indicates whether the color is a gray value.
  940. * @param col the color
  941. * @return true if it is a gray value
  942. */
  943. public static boolean isGray(Color col) {
  944. return org.apache.xmlgraphics.java2d.color.ColorUtil.isGray(col);
  945. }
  946. /**
  947. * Creates an uncalibrated CMYK color with the given gray value.
  948. * @param black the gray component (0 - 1)
  949. * @return the CMYK color
  950. */
  951. public static Color toCMYKGrayColor(float black) {
  952. return org.apache.xmlgraphics.java2d.color.ColorUtil.toCMYKGrayColor(black);
  953. }
  954. }