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 43KB

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