Donc, je crée une HSSFSheet
ayant un fond bitmap défini en utilisant apache poi
et un code de bas niveau. Le https://www.openoffice.org/sc/excelfileformat.pdf déclare pour le Record BITMAP, BIFF8
:
Données de pixels (tableau de lignes de hauteur du bitmap, de la ligne inférieure à la ligne supérieure, voir ci-dessous)
...
Dans chaque ligne, tous les pixels sont écrits de gauche à droite. Chaque pixel est stocké sous forme d'un tableau de 3 octets : composant rouge, vert et bleu de la couleur du pixel, dans cet ordre. La taille de chaque ligne est alignée sur des multiples de 4 en insérant des octets nuls après le dernier pixel.
Voir l'image du PDF pour la déclaration complète :
Pour réaliser cela, j'utilise java.awt.image.BufferedImage
ayant le type BufferedImage.TYPE_3BYTE_BGR
. Ensuite, obtenir tous les octets R V B de ce raster BufferedImage dans le bon ordre (de la ligne inférieure à la ligne supérieure) et remplir jusqu'à un multiple de 4 en largeur (direction x).
Voir le code :
import java.io.FileOutputStream;
import java.io.FileInputStream;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.StandardRecord;
import org.apache.poi.hssf.model.InternalSheet;
import org.apache.poi.util.LittleEndianOutput;
import java.lang.reflect.Field;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.imageio.ImageIO;
public class CreateExcelHSSFSheetBackgroundBitmap {
static List getBackgroundBitmapData(String filePath) throws Exception {
//voir https://www.openoffice.org/sc/excelfileformat.pdf - BITMAP
List data = new ArrayList();
// obtenir les données d'octets de fichier en type BufferedImage.TYPE_3BYTE_BGR
BufferedImage in = ImageIO.read(new FileInputStream(filePath));
BufferedImage image = new BufferedImage(in.getWidth(), in.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D graphics = image.createGraphics();
graphics.drawImage(in, null, 0, 0);
graphics.dispose();
short width = (short)image.getWidth();
short height = (short)image.getHeight();
// chaque pixel a 3 octets mais les octets en largeur doivent être remplis jusqu'à un multiple de 4
int widthBytesMultOf4 = (int)((width * 3 + 3) / 4 * 4);
// --- cette partie prend beaucoup de temps mais je n'ai pas trouvé de meilleure possibilité
// mettre les octets RVB dans les données ; les lignes du bitmap doivent être de la ligne inférieure à la ligne supérieure
int bytes = 0;
for (short y = (short)(height - 1); y >= 0; y--) {
for (short x = 0; x < width; x++) {
int r = image.getData().getSample(x, y, 2);
data.add(Byte.valueOf((byte)r));
bytes++;
int g = image.getData().getSample(x, y, 1);
data.add(Byte.valueOf((byte)g));
bytes++;
int b = image.getData().getSample(x, y, 0);
data.add(Byte.valueOf((byte)b));
bytes++;
}
// remplir x avec des octets 0 jusqu'à un multiple de 4
for (int x = width * 3; x < widthBytesMultOf4; x++) {
data.add(Byte.valueOf((byte)0));
bytes++;
}
}
// ---
// taille 12 octets (entêtes supplémentaires, voir ci-dessous) + octets de l'image
int size = 12 + bytes;
// obtenir la taille int en tant qu'octets LITTLE_ENDIAN
ByteBuffer bSize = ByteBuffer.allocate(4);
bSize.order(ByteOrder.LITTLE_ENDIAN);
bSize.putInt(size);
// obtenir la largeur short en tant qu'octets LITTLE_ENDIAN
ByteBuffer bWidth = ByteBuffer.allocate(2);
bWidth.order(ByteOrder.LITTLE_ENDIAN);
bWidth.putShort(width);
// obtenir la hauteur short en tant qu'octets LITTLE_ENDIAN
ByteBuffer bHeight = ByteBuffer.allocate(2);
bHeight.order(ByteOrder.LITTLE_ENDIAN);
bHeight.putShort(height);
// mettre les en-têtes de l'enregistrement dans les données
Byte[] dataPart = new Byte[] { 0x09, 0x00, 0x01, 0x00,
bSize.array()[0], bSize.array()[1], bSize.array()[2], bSize.array()[3], // taille
//maintenant 12 octets suivent
0x0C, 0x00, 0x00, 0x00,
bWidth.array()[0], bWidth.array()[1], // largeur
bHeight.array()[0], bHeight.array()[1], // hauteur
0x01, 0x00, 0x18, 0x00
};
data.addAll(0, Arrays.asList(dataPart));
return data;
}
public static void main(String[] args) throws Exception {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet1");
sheet = workbook.createSheet("Sheet2"); // cette feuille reçoit l'image de fond définie
// nous avons besoin des enregistrements binaires de la feuille
// obtenir InternalSheet
Field _sheet = HSSFSheet.class.getDeclaredField("_sheet");
_sheet.setAccessible(true);
InternalSheet internalsheet = (InternalSheet)_sheet.get(sheet);
// obtenir la liste de RecordBase
Field _records = InternalSheet.class.getDeclaredField("_records");
_records.setAccessible(true);
@SuppressWarnings("unchecked")
List records = (List)_records.get(internalsheet);
// obtenir les octets du fichier image
List data = getBackgroundBitmapData("dummyText.png"); // PNG ne doit pas avoir de transparence
// créer BitmapRecord et ContinueRecords à partir des données en parties de 8220 octets
BitmapRecord bitmapRecord = null;
List continueRecords = new ArrayList();
int bytes = 0;
if (data.size() > 8220) {
bitmapRecord = new BitmapRecord(data.subList(0, 8220));
bytes = 8220;
while (bytes < data.size()) {
if ((bytes + 8220) < data.size()) {
continueRecords.add(new ContinueRecord(data.subList(bytes, bytes + 8220)));
bytes += 8220;
} else {
continueRecords.add(new ContinueRecord(data.subList(bytes, data.size())));
break;
}
}
} else {
bitmapRecord = new BitmapRecord(data);
}
// ajouter les enregistrements après PageSettingsBlock
int i = 0;
for (RecordBase r : records) {
if (r instanceof org.apache.poi.hssf.record.aggregates.PageSettingsBlock) {
break;
}
i++;
}
records.add(++i, bitmapRecord);
for (ContinueRecord continueRecord : continueRecords) {
records.add(++i, continueRecord);
}
// sortie de débogage
for (RecordBase r : internalsheet.getRecords()) {
System.out.println(r);
}
// écrire le classeur
workbook.write(new FileOutputStream("CreateExcelHSSFSheetBackgroundBitmap.xls"));
workbook.close();
}
static class BitmapRecord extends StandardRecord {
//voir https://www.openoffice.org/sc/excelfileformat.pdf - BITMAP
List data = new ArrayList();
BitmapRecord(List data) {
this.data = data;
}
public int getDataSize() {
return data.size();
}
public short getSid() {
return (short)0x00E9;
}
public void serialize(LittleEndianOutput out) {
for (Byte b : data) {
out.writeByte(b);
}
}
}
static class ContinueRecord extends StandardRecord {
//voir https://www.openoffice.org/sc/excelfileformat.pdf - CONTINUE
List data = new ArrayList();
ContinueRecord(List data) {
this.data = data;
}
public int getDataSize() {
return data.size();
}
public short getSid() {
return (short)0x003C;
}
public void serialize(LittleEndianOutput out) {
for (Byte b : data) {
out.writeByte(b);
}
}
}
}
Le code fonctionne mais la partie entre
// --- cette partie prend beaucoup de temps mais je n'ai pas trouvé de meilleure possibilité
et
// ---
prend beaucoup de temps car 3 octets RVB pour chaque pixel individuel doivent être obtenus pour les obtenir selon le format étrange ci-dessus.
Est-ce que quelqu'un connaît une meilleure approche ? Peut-être que le format étrange ci-dessus n'est pas aussi étrange que je le pense et qu'il est déjà utilisé autre part ?