Bug 51222 - XSSFColor.getARGBHex() returns wrong color for Excel 2007 xlsx file

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1621393 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2014-08-29 22:14:55 +00:00
parent 67fcf46d4c
commit 0cbbbfe5d5
6 changed files with 265 additions and 184 deletions

View File

@ -23,20 +23,22 @@ import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.xssf.usermodel.XSSFColor; import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject; import org.openxmlformats.schemas.drawingml.x2006.main.CTColor;
import org.openxmlformats.schemas.drawingml.x2006.main.CTColorScheme; import org.openxmlformats.schemas.drawingml.x2006.main.CTColorScheme;
import org.openxmlformats.schemas.drawingml.x2006.main.ThemeDocument; import org.openxmlformats.schemas.drawingml.x2006.main.ThemeDocument;
import org.openxmlformats.schemas.drawingml.x2006.main.CTColor;
/** /**
* Class that represents theme of XLSX document. The theme includes specific * Class that represents theme of XLSX document. The theme includes specific
* colors and fonts. * colors and fonts.
*
* @author Petr Udalau(Petr.Udalau at exigenservices.com) - theme colors
*/ */
public class ThemesTable extends POIXMLDocumentPart { public class ThemesTable extends POIXMLDocumentPart {
private ThemeDocument theme; private ThemeDocument theme;
/**
* Construct a ThemesTable.
* @param part A PackagePart.
* @param rel A PackageRelationship.
*/
public ThemesTable(PackagePart part, PackageRelationship rel) throws IOException { public ThemesTable(PackagePart part, PackageRelationship rel) throws IOException {
super(part, rel); super(part, rel);
@ -47,34 +49,52 @@ public class ThemesTable extends POIXMLDocumentPart {
} }
} }
/**
* Construct a ThemesTable from an existing ThemeDocument.
* @param theme A ThemeDocument.
*/
public ThemesTable(ThemeDocument theme) { public ThemesTable(ThemeDocument theme) {
this.theme = theme; this.theme = theme;
} }
/**
* Convert a theme "index" into a color.
* @param idx A theme "index"
* @return The mapped XSSFColor, or null if not mapped.
*/
public XSSFColor getThemeColor(int idx) { public XSSFColor getThemeColor(int idx) {
// Theme color references are NOT positional indices into the color scheme,
// i.e. these keys are NOT the same as the order in which theme colors appear
// in theme1.xml. They are keys to a mapped color.
CTColorScheme colorScheme = theme.getTheme().getThemeElements().getClrScheme(); CTColorScheme colorScheme = theme.getTheme().getThemeElements().getClrScheme();
CTColor ctColor = null; CTColor ctColor;
int cnt = 0; switch (idx) {
for (XmlObject obj : colorScheme.selectPath("./*")) { case 0: ctColor = colorScheme.getLt1(); break;
if (obj instanceof org.openxmlformats.schemas.drawingml.x2006.main.CTColor) { case 1: ctColor = colorScheme.getDk1(); break;
if (cnt == idx) { case 2: ctColor = colorScheme.getLt2(); break;
ctColor = (org.openxmlformats.schemas.drawingml.x2006.main.CTColor) obj; case 3: ctColor = colorScheme.getDk2(); break;
case 4: ctColor = colorScheme.getAccent1(); break;
byte[] rgb = null; case 5: ctColor = colorScheme.getAccent2(); break;
if (ctColor.getSrgbClr() != null) { case 6: ctColor = colorScheme.getAccent3(); break;
// Colour is a regular one case 7: ctColor = colorScheme.getAccent4(); break;
rgb = ctColor.getSrgbClr().getVal(); case 8: ctColor = colorScheme.getAccent5(); break;
} else if (ctColor.getSysClr() != null) { case 9: ctColor = colorScheme.getAccent6(); break;
// Colour is a tint of white or black case 10: ctColor = colorScheme.getHlink(); break;
rgb = ctColor.getSysClr().getLastClr(); case 11: ctColor = colorScheme.getFolHlink(); break;
} default: return null;
return new XSSFColor(rgb);
}
cnt++;
}
} }
return null;
byte[] rgb = null;
if (ctColor.isSetSrgbClr()) {
// Color is a regular one
rgb = ctColor.getSrgbClr().getVal();
} else if (ctColor.isSetSysClr()) {
// Color is a tint of white or black
rgb = ctColor.getSysClr().getLastClr();
} else {
return null;
}
return new XSSFColor(rgb);
} }
/** /**

View File

@ -26,14 +26,14 @@ import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTColor;
*/ */
public class XSSFColor implements Color { public class XSSFColor implements Color {
private CTColor ctColor; private CTColor ctColor;
/** /**
* Create an instance of XSSFColor from the supplied XML bean * Create an instance of XSSFColor from the supplied XML bean
*/ */
public XSSFColor(CTColor color) { public XSSFColor(CTColor color) {
this.ctColor = color; this.ctColor = color;
} }
/** /**
* Create an new instance of XSSFColor * Create an new instance of XSSFColor
@ -56,50 +56,29 @@ public class XSSFColor implements Color {
* A boolean value indicating the ctColor is automatic and system ctColor dependent. * A boolean value indicating the ctColor is automatic and system ctColor dependent.
*/ */
public boolean isAuto() { public boolean isAuto() {
return ctColor.getAuto(); return ctColor.getAuto();
} }
/** /**
* A boolean value indicating the ctColor is automatic and system ctColor dependent. * A boolean value indicating the ctColor is automatic and system ctColor dependent.
*/ */
public void setAuto(boolean auto) { public void setAuto(boolean auto) {
ctColor.setAuto(auto); ctColor.setAuto(auto);
} }
/** /**
* Indexed ctColor value. Only used for backwards compatibility. References a ctColor in indexedColors. * Indexed ctColor value. Only used for backwards compatibility. References a ctColor in indexedColors.
*/ */
public short getIndexed() { public short getIndexed() {
return (short)ctColor.getIndexed(); return (short)ctColor.getIndexed();
} }
/** /**
* Indexed ctColor value. Only used for backwards compatibility. References a ctColor in indexedColors. * Indexed ctColor value. Only used for backwards compatibility. References a ctColor in indexedColors.
*/ */
public void setIndexed(int indexed) { public void setIndexed(int indexed) {
ctColor.setIndexed(indexed); ctColor.setIndexed(indexed);
} }
/**
* For RGB colours, but not ARGB (we think...)
* Excel gets black and white the wrong way around, so switch them
*/
private byte[] correctRGB(byte[] rgb) {
if(rgb.length == 4) {
// Excel doesn't appear to get these wrong
// Nothing to change
return rgb;
} else {
// Excel gets black and white the wrong way around, so switch them
if (rgb[0] == 0 && rgb[1] == 0 && rgb[2] == 0) {
rgb = new byte[] {-1, -1, -1};
}
else if (rgb[0] == -1 && rgb[1] == -1 && rgb[2] == -1) {
rgb = new byte[] {0, 0, 0};
}
return rgb;
}
}
/** /**
* Standard Red Green Blue ctColor value (RGB). * Standard Red Green Blue ctColor value (RGB).
@ -158,9 +137,7 @@ public class XSSFColor implements Color {
// Grab the colour // Grab the colour
rgb = ctColor.getRgb(); rgb = ctColor.getRgb();
return rgb;
// Correct it as needed, and return
return correctRGB(rgb);
} }
/** /**
@ -186,43 +163,42 @@ public class XSSFColor implements Color {
* Return the ARGB value in hex format, eg FF00FF00. * Return the ARGB value in hex format, eg FF00FF00.
* Works for both regular and indexed colours. * Works for both regular and indexed colours.
*/ */
public String getARGBHex() { public String getARGBHex() {
StringBuffer sb = new StringBuffer(); StringBuffer sb = new StringBuffer();
byte[] rgb = getARgb(); byte[] rgb = getARgb();
if(rgb == null) { if(rgb == null) {
return null; return null;
} }
for(byte c : rgb) { for(byte c : rgb) {
int i = (int)c; int i = (int)c;
if(i < 0) { if(i < 0) {
i += 256; i += 256;
} }
String cs = Integer.toHexString(i); String cs = Integer.toHexString(i);
if(cs.length() == 1) { if(cs.length() == 1) {
sb.append('0'); sb.append('0');
} }
sb.append(cs); sb.append(cs);
} }
return sb.toString().toUpperCase(); return sb.toString().toUpperCase();
} }
private static byte applyTint(int lum, double tint){ private static byte applyTint(int lum, double tint){
if(tint > 0){ if(tint > 0){
return (byte)(lum * (1.0-tint) + (255 - 255 * (1.0-tint))); return (byte)(lum * (1.0-tint) + (255 - 255 * (1.0-tint)));
} else if (tint < 0){ } else if (tint < 0){
return (byte)(lum*(1+tint)); return (byte)(lum*(1+tint));
} else { } else {
return (byte)lum; return (byte)lum;
} }
} }
/** /**
* Standard Alpha Red Green Blue ctColor value (ARGB). * Standard Alpha Red Green Blue ctColor value (ARGB).
*/ */
public void setRgb(byte[] rgb) { public void setRgb(byte[] rgb) {
// Correct it and save ctColor.setRgb(rgb);
ctColor.setRgb(correctRGB(rgb)); }
}
/** /**
* Index into the <clrScheme> collection, referencing a particular <sysClr> or * Index into the <clrScheme> collection, referencing a particular <sysClr> or
@ -230,15 +206,15 @@ public class XSSFColor implements Color {
*/ */
public int getTheme() { public int getTheme() {
return (int)ctColor.getTheme(); return (int)ctColor.getTheme();
} }
/** /**
* Index into the <clrScheme> collection, referencing a particular <sysClr> or * Index into the <clrScheme> collection, referencing a particular <sysClr> or
* <srgbClr> value expressed in the Theme part. * <srgbClr> value expressed in the Theme part.
*/ */
public void setTheme(int theme) { public void setTheme(int theme) {
ctColor.setTheme(theme); ctColor.setTheme(theme);
} }
/** /**
* Specifies the tint value applied to the ctColor. * Specifies the tint value applied to the ctColor.
@ -282,8 +258,8 @@ public class XSSFColor implements Color {
* @return the tint value * @return the tint value
*/ */
public double getTint() { public double getTint() {
return ctColor.getTint(); return ctColor.getTint();
} }
/** /**
* Specifies the tint value applied to the ctColor. * Specifies the tint value applied to the ctColor.
@ -326,9 +302,9 @@ public class XSSFColor implements Color {
* *
* @param tint the tint value * @param tint the tint value
*/ */
public void setTint(double tint) { public void setTint(double tint) {
ctColor.setTint(tint); ctColor.setTint(tint);
} }
/** /**
* Returns the underlying XML bean * Returns the underlying XML bean

View File

@ -0,0 +1,78 @@
/* ====================================================================
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.xssf.model;
import static org.junit.Assert.assertEquals;
import java.io.FileOutputStream;
import org.apache.commons.codec.binary.Hex;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFColor;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
public class TestThemesTable {
private String testFile = "Themes.xlsx";
@Test
public void testThemesTableColors() throws Exception {
XSSFWorkbook workbook = XSSFTestDataSamples.openSampleWorkbook(testFile);
String rgbExpected[] = {
"ffffff", // Lt1
"000000", // Dk1
"eeece1", // Lt2
"1f497d", // DK2
"4f81bd", // Accent1
"c0504d", // Accent2
"9bbb59", // Accent3
"8064a2", // Accent4
"4bacc6", // Accent5
"f79646", // Accent6
"0000ff", // Hlink
"800080" // FolHlink
};
boolean createFile = false;
int i=0;
for (Row row : workbook.getSheetAt(0)) {
XSSFFont font = ((XSSFRow)row).getCell(0).getCellStyle().getFont();
XSSFColor color = font.getXSSFColor();
assertEquals("Failed color theme "+i, rgbExpected[i], Hex.encodeHexString(color.getRgb()));
long themeIdx = font.getCTFont().getColorArray(0).getTheme();
assertEquals("Failed color theme "+i, i, themeIdx);
if (createFile) {
XSSFCellStyle cs = (XSSFCellStyle)row.getSheet().getWorkbook().createCellStyle();
cs.setFillForegroundColor(color);
cs.setFillPattern(CellStyle.SOLID_FOREGROUND);
row.createCell(1).setCellStyle(cs);
}
i++;
}
if (createFile) {
FileOutputStream fos = new FileOutputStream("foobaa.xlsx");
workbook.write(fos);
fos.close();
}
}
}

View File

@ -93,15 +93,17 @@ public final class TestXSSFColor extends TestCase {
assertEquals(-1, rgb3.getARgb()[3]); assertEquals(-1, rgb3.getARgb()[3]);
// Tint doesn't have the alpha // Tint doesn't have the alpha
// tint = -0.34999
// 255 * (1 + tint) = 165 truncated
// or (byte) -91 (which is 165 - 256)
assertEquals(3, rgb3.getRgbWithTint().length); assertEquals(3, rgb3.getRgbWithTint().length);
assertEquals(0, rgb3.getRgbWithTint()[0]); assertEquals(-91, rgb3.getRgbWithTint()[0]);
assertEquals(0, rgb3.getRgbWithTint()[1]); assertEquals(-91, rgb3.getRgbWithTint()[1]);
assertEquals(0, rgb3.getRgbWithTint()[2]); assertEquals(-91, rgb3.getRgbWithTint()[2]);
// Set the colour to black, will get translated internally // Set the color to black (no theme).
// (Excel stores 3 colour white and black wrong!) rgb3.setRgb(new byte[] {0, 0, 0});
rgb3.setRgb(new byte[] {-1,-1,-1}); assertEquals("FF000000", rgb3.getARGBHex());
assertEquals("FFFFFFFF", rgb3.getARGBHex());
assertEquals(0, rgb3.getCTColor().getRgb()[0]); assertEquals(0, rgb3.getCTColor().getRgb()[0]);
assertEquals(0, rgb3.getCTColor().getRgb()[1]); assertEquals(0, rgb3.getCTColor().getRgb()[1]);
assertEquals(0, rgb3.getCTColor().getRgb()[2]); assertEquals(0, rgb3.getCTColor().getRgb()[2]);

View File

@ -32,33 +32,33 @@ import junit.framework.TestCase;
public class TestXSSFCellFill extends TestCase { public class TestXSSFCellFill extends TestCase {
public void testGetFillBackgroundColor() { public void testGetFillBackgroundColor() {
CTFill ctFill = CTFill.Factory.newInstance(); CTFill ctFill = CTFill.Factory.newInstance();
XSSFCellFill cellFill = new XSSFCellFill(ctFill); XSSFCellFill cellFill = new XSSFCellFill(ctFill);
CTPatternFill ctPatternFill = ctFill.addNewPatternFill(); CTPatternFill ctPatternFill = ctFill.addNewPatternFill();
CTColor bgColor = ctPatternFill.addNewBgColor(); CTColor bgColor = ctPatternFill.addNewBgColor();
assertNotNull(cellFill.getFillBackgroundColor()); assertNotNull(cellFill.getFillBackgroundColor());
bgColor.setIndexed(2); bgColor.setIndexed(2);
assertEquals(2, cellFill.getFillBackgroundColor().getIndexed()); assertEquals(2, cellFill.getFillBackgroundColor().getIndexed());
} }
public void testGetFillForegroundColor() { public void testGetFillForegroundColor() {
CTFill ctFill = CTFill.Factory.newInstance(); CTFill ctFill = CTFill.Factory.newInstance();
XSSFCellFill cellFill = new XSSFCellFill(ctFill); XSSFCellFill cellFill = new XSSFCellFill(ctFill);
CTPatternFill ctPatternFill = ctFill.addNewPatternFill(); CTPatternFill ctPatternFill = ctFill.addNewPatternFill();
CTColor fgColor = ctPatternFill.addNewFgColor(); CTColor fgColor = ctPatternFill.addNewFgColor();
assertNotNull(cellFill.getFillForegroundColor()); assertNotNull(cellFill.getFillForegroundColor());
fgColor.setIndexed(8); fgColor.setIndexed(8);
assertEquals(8, cellFill.getFillForegroundColor().getIndexed()); assertEquals(8, cellFill.getFillForegroundColor().getIndexed());
} }
public void testGetSetPatternType() { public void testGetSetPatternType() {
CTFill ctFill = CTFill.Factory.newInstance(); CTFill ctFill = CTFill.Factory.newInstance();
XSSFCellFill cellFill = new XSSFCellFill(ctFill); XSSFCellFill cellFill = new XSSFCellFill(ctFill);
CTPatternFill ctPatternFill = ctFill.addNewPatternFill(); CTPatternFill ctPatternFill = ctFill.addNewPatternFill();
ctPatternFill.setPatternType(STPatternType.SOLID); ctPatternFill.setPatternType(STPatternType.SOLID);
//assertEquals(FillPatternType.SOLID_FOREGROUND.ordinal(), cellFill.getPatternType().ordinal()); //assertEquals(FillPatternType.SOLID_FOREGROUND.ordinal(), cellFill.getPatternType().ordinal());
} }
public void testGetNotModifies() { public void testGetNotModifies() {
CTFill ctFill = CTFill.Factory.newInstance(); CTFill ctFill = CTFill.Factory.newInstance();
@ -75,11 +75,16 @@ public class TestXSSFCellFill extends TestCase {
XSSFColor foregroundColor = cellWithThemeColor.getCellStyle().getFillForegroundXSSFColor(); XSSFColor foregroundColor = cellWithThemeColor.getCellStyle().getFillForegroundXSSFColor();
byte[] rgb = foregroundColor.getRgb(); byte[] rgb = foregroundColor.getRgb();
byte[] rgbWithTint = foregroundColor.getRgbWithTint(); byte[] rgbWithTint = foregroundColor.getRgbWithTint();
assertEquals(rgb[0],-18); // Dk2
assertEquals(rgb[1],-20); assertEquals(rgb[0],31);
assertEquals(rgb[2],-31); assertEquals(rgb[1],73);
assertEquals(rgbWithTint[0],-12); assertEquals(rgb[2],125);
assertEquals(rgbWithTint[1],-13); // Dk2, lighter 40% (tint is about 0.39998)
assertEquals(rgbWithTint[2],-20); // 31 * (1.0 - 0.39998) + (255 - 255 * (1.0 - 0.39998)) = 120.59552 => 120 (byte)
// 73 * (1.0 - 0.39998) + (255 - 255 * (1.0 - 0.39998)) = 145.79636 => -111 (byte)
// 125 * (1.0 - 0.39998) + (255 - 255 * (1.0 - 0.39998)) = 176.99740 => -80 (byte)
assertEquals(rgbWithTint[0],120);
assertEquals(rgbWithTint[1],-111);
assertEquals(rgbWithTint[2],-80);
} }
} }

Binary file not shown.