1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 package org.openoffice.xmerge.util;
25 
26 
27 import java.awt.Color;
28 
29 /**
30  * Utility class mapping RGB colour specifications to the colour indices used
31  * in the Pocket PC. The original converter was written for use with Pocket
32  * Word it was later put into the utils so Pocket excel could use this code
33  * also. For this reason the defualt values are those used by Pocket Word but
34  * a colour table can be passed in through the constructor to map the 16
35  * values to a colour table.
36  *
37  * These colour indices are based on the Windows VGA 16 colour palette, which
38  * later was used as the basis for the named colours in the HTML 3.2
39  * specification.
40  *
41  * In Pocket Word's case, the match to the VGA 16 palette is not exact as it
42  * swaps Grey and Silver, with Silver being the darker colour (i.e. having the
43  * lower RGB value).
44  */
45 
46 public class ColourConverter {
47 
48     /** Colour table index for Black */
49     private static final short BLACK = 0;
50 
51     /** Colour table index for Silver */
52     private static final short SILVER = 1;
53 
54     /** Colour table index for Grey */
55     private static final short GREY = 2;
56 
57     /** Colour table index for White */
58     private static final short WHITE = 3;
59 
60     /** Colour table index for Red */
61     private static final short RED = 4;
62 
63     /** Colour table index for Lime */
64     private static final short LIME = 5;
65 
66     /** Colour table index for Blue */
67     private static final short BLUE = 6;
68 
69     /** Colour table index for Aqua */
70     private static final short AQUA = 7;
71 
72     /** Colour table index for Fuchsia */
73     private static final short FUCHSIA = 8;
74 
75     /** Colour table index for Yellow */
76     private static final short YELLOW = 9;
77 
78     /** Colour table index for Maroon */
79     private static final short MAROON = 10;
80 
81     /** Colour table index for Green */
82     private static final short GREEN = 11;
83 
84     /** Colour table index for Navy */
85     private static final short NAVY = 12;
86 
87     /** Colour table index for Teal */
88     private static final short TEAL = 13;
89 
90     /** Colour table index for Purple */
91     private static final short PURPLE = 14;
92 
93     /** Colour table index for Olive */
94     public static final short OLIVE = 15;
95 
96 	private short tableLookup[] = null;
97 
98 	/**
99 	 * Default constructor used in the case where a lookup table is not
100 	 * required
101 	 */
ColourConverter()102 	public ColourConverter() {
103 
104 	}
105 
106 	/**
107 	 * Constructor that passes in the colour lookup table. This is required in
108 	 * cases where the 16 colour values are something other than there default
109 	 * values (e.g. in the case of pocket Excel)
110 	 *
111 	 * @param lookup a 16 bit array mapping the 16 colours to their values
112 	 */
ColourConverter(short lookup[])113 	public ColourConverter(short lookup[]) {
114 
115 		tableLookup = lookup;
116 	}
117 
118 	/**
119 	 * Uses the colour table it it exists to translate default values to
120 	 * values in the colorTable
121 	 */
colourLookup(short colour)122 	private short colourLookup(short colour) {
123 
124 		if(tableLookup!=null) {
125 			return tableLookup[colour];
126 		} else {
127 			return colour;
128 		}
129 	}
130 
131 	/**
132 	 * Uses the colour table it it exists to translate default values to
133 	 * values in the colorTable
134 	 */
indexLookup(short index)135 	private short indexLookup(short index) {
136 
137 		short result = 0;
138 
139 		if(tableLookup!=null) {
140 			for(short i = 0;i < tableLookup.length;i++) {
141 				if(tableLookup[i]==index)
142 					result = i;
143 			}
144 		} else {
145 			result = index;
146 		}
147 
148 		return result;
149 	}
150     /**
151      * This method maps a Pocket Word colour index value to an RGB value as
152      * used by OpenOffice.
153      *
154      * @param   colour   The index into Pocket Word's colour table.
155      *
156      * @return  A Color object representing the RGB value of the Pocket Word
157      *          colour.
158      */
convertToRGB(short colour)159     public Color convertToRGB (short colour) {
160 
161 		short index = indexLookup(colour);
162 
163         int r = 0;
164         int g = 0;
165         int b = 0;
166 
167         switch (index) {
168             case SILVER:
169                 r = g = b = 128;
170                 break;
171 
172             case GREY:
173                 r = g = b = 192;
174                 break;
175 
176             case WHITE:
177                 r = g = b = 255;
178                 break;
179 
180             case RED:
181                 r = 255;
182                 break;
183 
184             case LIME:
185                 g = 255;
186                 break;
187 
188             case BLUE:
189                 b = 255;
190                 break;
191 
192             case AQUA:
193                 g = b = 255;
194                 break;
195 
196             case FUCHSIA:
197                 r = b = 255;
198                 break;
199 
200             case YELLOW:
201                 r = g = 255;
202                 break;
203 
204             case MAROON:
205                 r = 128;
206                 break;
207 
208             case GREEN:
209                 g = 128;
210                 break;
211 
212             case NAVY:
213                 b = 128;
214                 break;
215 
216             case TEAL:
217                 b = g = 128;
218                 break;
219 
220             case PURPLE:
221                 r = b = 128;
222                 break;
223 
224             case OLIVE:
225                 r = g = 128;
226                 break;
227 
228             case BLACK:
229             default:
230                 r = g = b = 0;
231                 break;
232         }
233 
234         return new Color(r, g, b);
235     }
236 
237 
238     /**
239      * This method approximates an RGB value (as used by Writer) to one of the
240      * 16 available colours
241      *
242      * Most of the supported colours have their components set to either 0, 128
243      * or 255.  The exception is 'Grey' which is 0xC0C0C0.
244      *
245      * @param colour    Color object representing the RGB value of the colour.
246      *
247      * @return  Index into the Pocket Word colour table which represents the
248      *          closest match to the specified colour.
249      */
convertFromRGB(Color colour)250     public short convertFromRGB (Color colour) {
251         int matchedRGB = 0;
252         short indexColour = 0;
253 		int reducedMap[] =  new int[] { 0, 0, 128 };
254 
255 	  	int red = colour.getRed();
256 	  	int green = colour.getGreen();
257 	  	int blue = colour.getBlue();
258 
259 		// We need to convert the pale colors to their base color rather than
260 		// white so we modify the rgb values if the colour is sufficiently
261 		// white
262 	   	if(red>0xC0 && green>0xC0 && blue>0xC0) {
263 
264 			if(red!=0xFF)
265 				red = getClosest(red, reducedMap);
266 			if(green!=0xFF)
267 				green = getClosest(green, reducedMap);
268 			if(blue!=0xFF)
269 				blue = getClosest(blue, reducedMap);
270 		}
271 
272        	/*
273 	     * Need to derive an RGB value that has been rounded to match the ones
274          * Pocket Word knows about.
275        	 */
276 		matchedRGB += getClosest(red)   << 16;
277         matchedRGB += getClosest(green) << 8;
278        	matchedRGB += getClosest(blue);
279 
280         /*
281          * The colour map used by Pocket Word doesn't have any combinations of
282          * values beyond 0 and any other value.  A value of 255 in any RGB
283          * code indicates a dominant colour.  Other colours are only modifiers
284          * to the principal colour(s).  Thus, for this conversion, modifiers
285          * can be dropped.
286          */
287         if ((matchedRGB & 0xFF0000) == 0xFF0000 || (matchedRGB & 0xFF00) == 0xFF00
288                 || (matchedRGB & 0xFF) == 0xFF) {
289                     if ((matchedRGB & 0xFF0000) == 0x800000) {
290                         matchedRGB ^= 0x800000;
291                     }
292                     if ((matchedRGB & 0xFF00) == 0x8000) {
293                         matchedRGB ^= 0x8000;
294                     }
295                     if ((matchedRGB & 0xFF) == 0x80) {
296                         matchedRGB ^= 0x80;
297                     }
298         }
299 
300 
301         /*
302          * And now for the actual matching ...
303          *
304          * Colours are based on the Windows VGA 16 palette.  One difference
305          * though is that Pocket Word seems to switch the RGB codes for Grey
306          * and Silver.  In Pocket Word Silver is the darker colour leaving Grey
307          * is closest to White.
308          *
309          * Shades of grey will be converted to either Silver or White, where
310          * Grey may be a more appropriate colour.  This is handled specially
311          * only for Silver and White matches.
312          */
313         switch (matchedRGB) {
314             case 0x000000:
315                 indexColour = BLACK;
316                 break;
317 
318             case 0x808080:
319                 if (!isGrey(colour)) {
320                     indexColour = SILVER;
321                 }
322                 else {
323                     indexColour = GREY;
324                 }
325                 break;
326 
327             case 0xFFFFFF:
328                 if (!isGrey(colour)) {
329                     indexColour = WHITE;
330                 }
331                 else {
332                     indexColour = GREY;
333                 }
334                 break;
335 
336             case 0xFF0000:
337                 indexColour = RED;
338                 break;
339 
340             case 0x00FF00:
341                 indexColour = LIME;
342                 break;
343 
344             case 0x0000FF:
345                 indexColour = BLUE;
346                 break;
347 
348             case 0x00FFFF:
349                 indexColour = AQUA;
350                 break;
351 
352             case 0xFF00FF:
353                 indexColour = FUCHSIA;
354                 break;
355 
356             case 0xFFFF00:
357                 indexColour = YELLOW;
358                 break;
359 
360             case 0x800000:
361                 indexColour = MAROON;
362                 break;
363 
364             case 0x008000:
365                 indexColour = GREEN;
366                 break;
367 
368             case 0x000080:
369                 indexColour = NAVY;
370                 break;
371 
372             case 0x008080:
373                 indexColour = TEAL;
374                 break;
375 
376             case 0x800080:
377                 indexColour = PURPLE;
378                 break;
379 
380             case 0x808000:
381                 indexColour = OLIVE;
382                 break;
383 
384             default:        // Just in case!
385                 indexColour = BLACK;
386                 break;
387         }
388 
389         return colourLookup(indexColour);
390     }
391 
392 
393     /*
394      * Default implementation, checks for the closest of value to 0, 128 or 255.
395      */
getClosest(int value)396     private int getClosest(int value) {
397         int points[] = new int[] { 0, 128, 255 };
398 
399         return getClosest(value, points);
400     }
401 
402 
403     /*
404      * Utility method that returns the closest of the three points to the value
405      * supplied.
406      */
getClosest(int value, int[] points)407     private int getClosest(int value, int[] points) {
408 
409         if (value == points[0] || value == points[1] || value == points[2]) {
410             return value;
411         }
412 
413         if (value < points[1]) {
414             int x = value - points[0];
415             return (Math.round((float)x / (points[1] - points[0])) == 1 ? points[1] : points[0]);
416         }
417         else {
418             int x = value - points[1];
419             return (Math.round((float)x / (points[2] - points[1])) >= 1 ? points[2] : points[1]);
420         }
421     }
422 
423 
424     /*
425      * Checks to see if the supplied colour can be considered to be grey.
426      */
isGrey(Color c)427     private boolean isGrey(Color c) {
428         int matchedRGB = 0;
429         int points[] = new int[] { 128, 192, 255 };
430 
431         matchedRGB += getClosest(c.getRed(), points) << 16;
432         matchedRGB += getClosest(c.getGreen(), points) << 8;
433         matchedRGB += getClosest(c.getBlue(), points);
434 
435         if (matchedRGB == 0xC0C0C0) {
436             return true;
437         }
438 
439         return false;
440     }
441 }
442 
443