| /* ==================================================================== |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| ==================================================================== */ |
| |
| package org.apache.poi.hssf.usermodel; |
| |
| import java.util.Locale; |
| |
| import org.apache.poi.hssf.record.PaletteRecord; |
| import org.apache.poi.hssf.util.HSSFColor; |
| import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined; |
| |
| /** |
| * Represents a workbook color palette. |
| * Internally, the XLS format refers to colors using an offset into the palette |
| * record. Thus, the first color in the palette has the index 0x8, the second |
| * has the index 0x9, etc. through 0x40 |
| */ |
| public final class HSSFPalette { |
| private PaletteRecord _palette; |
| |
| protected HSSFPalette(PaletteRecord palette) |
| { |
| _palette = palette; |
| } |
| |
| /** |
| * Retrieves the color at a given index |
| * |
| * @param index the palette index, between 0x8 to 0x40 inclusive |
| * @return the color, or null if the index is not populated |
| */ |
| public HSSFColor getColor(short index) |
| { |
| //Handle the special AUTOMATIC case |
| if (index == HSSFColorPredefined.AUTOMATIC.getIndex()) { |
| return HSSFColorPredefined.AUTOMATIC.getColor(); |
| } |
| byte[] b = _palette.getColor(index); |
| return (b == null) ? null : new CustomColor(index, b); |
| } |
| |
| /** |
| * Retrieves the color at a given index |
| * |
| * @param index the palette index, between 0x8 to 0x40 inclusive |
| * @return the color, or null if the index is not populated |
| */ |
| public HSSFColor getColor(int index) { |
| return getColor((short)index); |
| } |
| |
| /** |
| * Finds the first occurrence of a given color |
| * |
| * @param red the RGB red component, between 0 and 255 inclusive |
| * @param green the RGB green component, between 0 and 255 inclusive |
| * @param blue the RGB blue component, between 0 and 255 inclusive |
| * @return the color, or null if the color does not exist in this palette |
| */ |
| public HSSFColor findColor(byte red, byte green, byte blue) |
| { |
| byte[] b = _palette.getColor(PaletteRecord.FIRST_COLOR_INDEX); |
| for (short i = PaletteRecord.FIRST_COLOR_INDEX; b != null; |
| b = _palette.getColor(++i)) |
| { |
| if (b[0] == red && b[1] == green && b[2] == blue) |
| { |
| return new CustomColor(i, b); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Finds the closest matching color in the custom palette. The |
| * method for finding the distance between the colors is fairly |
| * primative. |
| * |
| * @param red The red component of the color to match. |
| * @param green The green component of the color to match. |
| * @param blue The blue component of the color to match. |
| * @return The closest color or null if there are no custom |
| * colors currently defined. |
| */ |
| public HSSFColor findSimilarColor(byte red, byte green, byte blue) { |
| return findSimilarColor(unsignedInt(red), unsignedInt(green), unsignedInt(blue)); |
| } |
| /** |
| * Finds the closest matching color in the custom palette. The |
| * method for finding the distance between the colors is fairly |
| * primative. |
| * |
| * @param red The red component of the color to match. |
| * @param green The green component of the color to match. |
| * @param blue The blue component of the color to match. |
| * @return The closest color or null if there are no custom |
| * colors currently defined. |
| */ |
| public HSSFColor findSimilarColor(int red, int green, int blue) { |
| HSSFColor result = null; |
| int minColorDistance = Integer.MAX_VALUE; |
| byte[] b = _palette.getColor(PaletteRecord.FIRST_COLOR_INDEX); |
| for (short i = PaletteRecord.FIRST_COLOR_INDEX; b != null; |
| b = _palette.getColor(++i)) |
| { |
| int colorDistance = Math.abs(red - unsignedInt(b[0])) + |
| Math.abs(green - unsignedInt(b[1])) + |
| Math.abs(blue - unsignedInt(b[2])); |
| if (colorDistance < minColorDistance) |
| { |
| minColorDistance = colorDistance; |
| result = getColor(i); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Turn a byte of between -127 and 127 into something between |
| * 0 and 255, so distance calculations work as expected. |
| */ |
| private int unsignedInt(byte b) { |
| return 0xFF & b; |
| } |
| |
| /** |
| * Sets the color at the given offset |
| * |
| * @param index the palette index, between 0x8 to 0x40 inclusive |
| * @param red the RGB red component, between 0 and 255 inclusive |
| * @param green the RGB green component, between 0 and 255 inclusive |
| * @param blue the RGB blue component, between 0 and 255 inclusive |
| */ |
| public void setColorAtIndex(short index, byte red, byte green, byte blue) |
| { |
| _palette.setColor(index, red, green, blue); |
| } |
| |
| /** |
| * Adds a new color into an empty color slot. |
| * @param red The red component |
| * @param green The green component |
| * @param blue The blue component |
| * |
| * @return The new custom color. |
| * |
| * @throws RuntimeException if there are more more free color indexes. |
| */ |
| public HSSFColor addColor( byte red, byte green, byte blue ) |
| { |
| byte[] b = _palette.getColor(PaletteRecord.FIRST_COLOR_INDEX); |
| short i; |
| for (i = PaletteRecord.FIRST_COLOR_INDEX; i < PaletteRecord.STANDARD_PALETTE_SIZE + PaletteRecord.FIRST_COLOR_INDEX; b = _palette.getColor(++i)) |
| { |
| if (b == null) |
| { |
| setColorAtIndex( i, red, green, blue ); |
| return getColor(i); |
| } |
| } |
| throw new RuntimeException("Could not find free color index"); |
| } |
| |
| private static final class CustomColor extends HSSFColor { |
| private short _byteOffset; |
| private byte _red; |
| private byte _green; |
| private byte _blue; |
| |
| public CustomColor(short byteOffset, byte[] colors) |
| { |
| this(byteOffset, colors[0], colors[1], colors[2]); |
| } |
| |
| private CustomColor(short byteOffset, byte red, byte green, byte blue) |
| { |
| _byteOffset = byteOffset; |
| _red = red; |
| _green = green; |
| _blue = blue; |
| } |
| |
| @Override |
| public short getIndex() |
| { |
| return _byteOffset; |
| } |
| |
| @Override |
| public short[] getTriplet() |
| { |
| return new short[] |
| { |
| (short) (_red & 0xff), |
| (short) (_green & 0xff), |
| (short) (_blue & 0xff) |
| }; |
| } |
| |
| @Override |
| public String getHexString() { |
| return getGnumericPart(_red) + ":" + getGnumericPart(_green) + ":" + getGnumericPart(_blue); |
| } |
| |
| private String getGnumericPart(byte color) |
| { |
| String s; |
| if (color == 0) |
| { |
| s = "0"; |
| } |
| else |
| { |
| int c = color & 0xff; //as unsigned |
| c = (c << 8) | c; //pad to 16-bit |
| s = Integer.toHexString(c).toUpperCase(Locale.ROOT); |
| while (s.length() < 4) |
| { |
| s = "0" + s; |
| } |
| } |
| return s; |
| } |
| } |
| } |