À l'aide de java.awt.image.BufferedImage pour créer un enregistrement BITMAP BIFF8 prend beaucoup de temps-Y a-t-il une meilleure approche?
Je crée donc un HSSFSheet
ayant un bitmap d'arrière-plan défini en utilisant apache poi
et mon propre code de bas niveau. Le https://www.openoffice.org/sc/excelfileformat.pdf déclare pour la Record BITMAP, BIFF8
:
Données pixel (tableau de lignes de hauteur du bitmap, de la ligne du bas à la ligne du haut, voir ci-dessous)
...
Dans chaque ligne, tous les pixels sont écrits de gauche à droite. Chaque pixel est stocké sous forme de tableau de 3 octets: la composante rouge, verte et bleue de la couleur du pixel, dans cet ordre. La taille de chaque ligne est alignée sur des multiples de 4 en insérant zéro octet après le dernier pixel.
Voir l'image du PDF pour la déclaration complète:
Pour remplir cela, mon approche utilise java.awt.image.BufferedImage
ayant le type BufferedImage.TYPE_3BYTE_BGR
. Ensuite, obtenir tous les octets R G B du raster de ce BufferedImage dans le bon ordre (de la ligne du bas à la ligne du haut) et remplir jusqu'à un multiple de 4 de 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<Byte> getBackgroundBitmapData(String filePath) throws Exception {
//see https://www.openoffice.org/sc/excelfileformat.pdf - BITMAP
List<Byte> data = new ArrayList<Byte>();
// get file byte data in 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();
// each pixel has 3 bytes but the width bytes must be filled up to multiple of 4
int widthBytesMultOf4 = (int)((width * 3 + 3) / 4 * 4);
// --- this part takes much time but I have not found any better possibility
// put the bytes R G B into the data; lines of the bitmap must be from bottom line to top line
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++;
}
// fill up x with 0 bytes up to multiple of 4
for (int x = width * 3; x < widthBytesMultOf4; x++) {
data.add(Byte.valueOf((byte)0));
bytes++;
}
}
// ---
// size 12 bytes (additional headers, see below) + picture bytes
int size = 12 + bytes;
// get size int as LITTLE_ENDIAN bytes
ByteBuffer bSize = ByteBuffer.allocate(4);
bSize.order(ByteOrder.LITTLE_ENDIAN);
bSize.putInt(size);
// get width short as LITTLE_ENDIAN bytes
ByteBuffer bWidth = ByteBuffer.allocate(2);
bWidth.order(ByteOrder.LITTLE_ENDIAN);
bWidth.putShort(width);
// get height short as LITTLE_ENDIAN bytes
ByteBuffer bHeight = ByteBuffer.allocate(2);
bHeight.order(ByteOrder.LITTLE_ENDIAN);
bHeight.putShort(height);
// put the record headers into the data
Byte[] dataPart = new Byte[] { 0x09, 0x00, 0x01, 0x00,
bSize.array()[0], bSize.array()[1], bSize.array()[2], bSize.array()[3], // size
//now 12 bytes follow
0x0C, 0x00, 0x00, 0x00,
bWidth.array()[0], bWidth.array()[1], // width
bHeight.array()[0], bHeight.array()[1], // height
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"); // this sheet gets the background image set
// we need the binary records of the sheet
// get InternalSheet
Field _sheet = HSSFSheet.class.getDeclaredField("_sheet");
_sheet.setAccessible(true);
InternalSheet internalsheet = (InternalSheet)_sheet.get(sheet);
// get List of RecordBase
Field _records = InternalSheet.class.getDeclaredField("_records");
_records.setAccessible(true);
@SuppressWarnings("unchecked")
List<RecordBase> records = (List<RecordBase>)_records.get(internalsheet);
// get bytes of the image file
List<Byte> data = getBackgroundBitmapData("dummyText.png"); //PNG must not have transparency
// do creating BitmapRecord and ContinueRecords from the data in parts of 8220 bytes
BitmapRecord bitmapRecord = null;
List<ContinueRecord> continueRecords = new ArrayList<ContinueRecord>();
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);
}
// add the records after 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);
}
// debug output
for (RecordBase r : internalsheet.getRecords()) {
System.out.println(r);
}
// write out workbook
workbook.write(new FileOutputStream("CreateExcelHSSFSheetBackgroundBitmap.xls"));
workbook.close();
}
static class BitmapRecord extends StandardRecord {
//see https://www.openoffice.org/sc/excelfileformat.pdf - BITMAP
List<Byte> data = new ArrayList<Byte>();
BitmapRecord(List<Byte> 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 {
//see https://www.openoffice.org/sc/excelfileformat.pdf - CONTINUE
List<Byte> data = new ArrayList<Byte>();
ContinueRecord(List<Byte> 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 le code fonctionne mais la partie entre
// --- this part takes much time but I have not found any better possibility
Et
// ---
Prend beaucoup de temps car 3 octets R G B pour chaque pixel doit être obtenu pour les obtenir selon le format étrange ci-dessus.
Quelqu'un connaît-il une meilleure approche? Peut-être que le format étrange ci-dessus n'est pas aussi étrange que je le pense et qu'il en existe déjà d'autres usages?
2 answers
Voici une version modifiée de votre code qui fonctionne pour moi, ET est assez rapide.
- J'utilise
byte[]
(etByteArrayOutputStream
) tout autour, pas plusList<Byte>
. - , Comme nous avons déjà un
BufferedImage
deTYPE_3BYTE_BGR
, nous pouvons l'utiliser presque directement comme le BMP de sortie. Nous avons juste besoin de a) ajouter un en-tête BMP valide et b) écrire de bas en haut, c) pad chaque scanline (ligne) à une limite de 32 bits et d) commutateur BGR -> RGB ordre. - J'utilise le
Raster
pour copier des lignes (rembourrées) de données dans la sortie, comme la copie de gros morceaux est plus rapide que la copie d'octets simples.
Comme déjà indiqué dans les commentaires, la structure est un BMP standard avec BITMAPCOREHEADER
(et pas d'en-tête de fichier). Malheureusement, le ImageIO
BMPImageWriter
toujours écrire l'en-tête du fichier et utilise le BITMAPINFOHEADER
qui est de 40 octets. Vous pourriez probablement contourner ces choses, et utiliser l'écrivain standard, en massant un peu les données (indice: l'en-tête du fichier contient un décalage par rapport aux données de pixel au décalage 10), mais comme le format BMP de base est trivial pour mettre en œuvre, il pourrait être tout aussi facile à faire que ci-dessous.
Bien que la documentation implique certainement qu'en utilisant directement d'autres formats comme PNG et JPEG, je n'ai pas réussi à le faire correctement.
Il y a probablement encore place à l'amélioration si vous le souhaitez, pour éviter une copie de tableau d'octets (ie. utilisez offset / length et passez la totalité du tableau de données aux Bitmap/ContinueRecord
s au lieu de Arrays.copyOfRange()
).
Code:
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.imageio.ImageIO;
import org.apache.poi.hssf.model.InternalSheet;
import org.apache.poi.hssf.record.RecordBase;
import org.apache.poi.hssf.record.StandardRecord;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.util.LittleEndianOutput;
public class CreateExcelHSSFSheetBackgroundBitmap {
static byte[] getBackgroundBitmapData(String filePath) throws Exception {
//see https://www.openoffice.org/sc/excelfileformat.pdf - BITMAP
// get file byte data in 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();
// calculate row size (c)
int rowSize = ((24 * image.getWidth() + 31) / 32) * 4;
ByteArrayOutputStream output = new ByteArrayOutputStream(image.getHeight() * rowSize * 3 + 1024);
// put the record headers into the data
ByteBuffer header = ByteBuffer.allocate(8 + 12);
header.order(ByteOrder.LITTLE_ENDIAN);
// Undocumented XLS stuff
header.putShort((short) 0x09);
header.putShort((short) 0x01);
header.putInt(image.getHeight() * rowSize + 12); // Size of image stream
// BITMAPCOREHEADER (a)
header.putInt(12);
header.putShort((short) image.getWidth());
header.putShort((short) image.getHeight()); // Use -height if writing top-down
header.putShort((short) 1); // planes, always 1
header.putShort((short) 24); // bitcount
output.write(header.array());
// Output rows bottom-up (b)
Raster raster = image.getRaster()
.createChild(0, 0, image.getWidth(), image.getHeight(), 0, 0, new int[]{2, 1, 0}); // Reverse BGR -> RGB (d)
byte[] row = new byte[rowSize]; // padded (c)
for (int i = image.getHeight() - 1; i >= 0; i--) {
row = (byte[]) raster.getDataElements(0, i, image.getWidth(), 1, row);
output.write(row);
}
return output.toByteArray();
}
public static void main(String[] args) throws Exception {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet2"); // this sheet gets the background image set
// we need the binary records of the sheet
// get InternalSheet
Field _sheet = HSSFSheet.class.getDeclaredField("_sheet");
_sheet.setAccessible(true);
InternalSheet internalsheet = (InternalSheet)_sheet.get(sheet);
// get List of RecordBase
Field _records = InternalSheet.class.getDeclaredField("_records");
_records.setAccessible(true);
@SuppressWarnings("unchecked")
List<RecordBase> records = (List<RecordBase>)_records.get(internalsheet);
// get bytes of the image file
byte[] data = getBackgroundBitmapData("dummy.png"); //PNG must not have transparency
// do creating BitmapRecord and ContinueRecords from the data in parts of 8220 bytes
BitmapRecord bitmapRecord;
List<ContinueRecord> continueRecords = new ArrayList<>();
int bytes;
if (data.length > 8220) {
bitmapRecord = new BitmapRecord(Arrays.copyOfRange(data, 0, 8220));
bytes = 8220;
while (bytes < data.length) {
if ((bytes + 8220) < data.length) {
continueRecords.add(new ContinueRecord(Arrays.copyOfRange(data, bytes, bytes + 8220)));
bytes += 8220;
} else {
continueRecords.add(new ContinueRecord(Arrays.copyOfRange(data, bytes, data.length)));
break;
}
}
} else {
bitmapRecord = new BitmapRecord(data);
}
// add the records after 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);
}
// debug output
for (RecordBase r : internalsheet.getRecords()) {
System.out.println(r);
}
// write out workbook
workbook.write(new FileOutputStream("backgroundImage.xls"));
workbook.close();
}
static class BitmapRecord extends StandardRecord {
//see https://www.openoffice.org/sc/excelfileformat.pdf - BITMAP
byte[] data;
BitmapRecord(byte[] data) {
this.data = data;
}
public int getDataSize() {
return data.length;
}
public short getSid() {
return (short)0x00E9;
}
public void serialize(LittleEndianOutput out) {
out.write(data);
}
}
static class ContinueRecord extends StandardRecord {
//see https://www.openoffice.org/sc/excelfileformat.pdf - CONTINUE
byte[] data;
ContinueRecord(byte[] data) {
this.data = data;
}
public int getDataSize() {
return data.length;
}
public short getSid() {
return (short)0x003C;
}
public void serialize(LittleEndianOutput out) {
out.write(data);
}
}
}
Comme souvent c'est simplement la stupidité du programmeur lui-même; -). En allemand, il y a un dicton: "Ne peut pas voir la forêt à cause des arbres.".
Obtenir simplement le {[2] } du BufferedImage
une fois à l'extérieur des boucles au lieu de le faire à l'intérieur des boucles encore et encore augmente énormément la vitesse:
...
// put the bytes R G B into the data; lines of the bitmap must be from bottom line to top line
int bytes = 0;
Raster raster = image.getData();
for (short y = (short)(height - 1); y >= 0; y--) {
for (short x = 0; x < width; x++) {
int r = raster.getSample(x, y, 2);
data.add(Byte.valueOf((byte)r));
bytes++;
int g = raster.getSample(x, y, 1);
data.add(Byte.valueOf((byte)g));
bytes++;
int b = raster.getSample(x, y, 0);
data.add(Byte.valueOf((byte)b));
bytes++;
}
// fill up x with 0 bytes up to multiple of 4
for (int x = width * 3; x < widthBytesMultOf4; x++) {
data.add(Byte.valueOf((byte)0));
bytes++;
}
}
...
Mais l'indice de @Gagravarr dans son commentaire semble également intéressant.
2.4.19 BkHim
Le BkHim enregistrement spécifie les données d'image pour un fond de feuille (1)
Cf (2 octets): Un entier signé qui spécifie le format d'image. DOIT être une valeur de ce qui suit:
Valeur: 0x0009
Signification: Format Bitmap. Les données d'image sont stockées dans un format bitmap comme décrit dans [MSDN-BMP]
Valeur: 0x000E
Signification: Format natif. Les données d'image sont stockées dans le format natif d'une autre application et ne peuvent pas être directement transformés.
Cela ressemble à s'il est 0x000E
les premiers octets au lieu de 0x0009
puis les octets d'image natifs (PNG, JPG, BMP, ...) pourrait être stocké directement. Vais essayer cela demain.
Eh bien, comme souvent seulement un effort futile avec les "documentations"de Microsoft. Le https://interoperability.blob.core.windows.net/files/MS-XLS/[MS-XLS]. pdf semble correct mais incomplet, du moins à la page 211: 2.4.19
Bkhim. Donc, utiliser 0x000E
au lieu de {[5] } ne permettra pas simplement stockage d'un imageBlob
natif.
, Mais aussi la description de 0x0009
:
Format bitmap. Les données d'image sont stockées dans un format bitmap, comme décrit dans [MSDN-BMP]
Seulement des liens vers une description incomplète du type de bitmap à utiliser ici. Rien sur la structure d'en-tête au début et les octets de pixels de la ligne du bas à la ligne du haut. Et aussi rien sur la nécessité d'aligner la taille de chaque ligne sur des multiples de 4 en insérant zéro octets après le dernier pixel. Mais sans le savoir, même en utilisant un bitmap 0x0009
ne fonctionnera pas.
Lorsque je mets n'importe quel type d'image d'arrière-plan dans une feuille de calcul dans un fichier *.xls
en utilisant l'interface graphique de Excel
, puis que je regarde ce fichier en utilisant un vidage hexadécimal, cela ressemble toujours à:
0xe900SSSS09000100SSSSSSSS0c000000WWWWHHHH01001800PPP...
Où S signifie taille, W signifie largeur, H signifie hauteur et P signifie pixel octet.
Ceci est indépendant du fait que je mets un BMP, JPG ou PNG comme image d'arrière-plan dans une feuille de calcul. Toujours ce type spécial de données BMP est utilisé.
Alors ce qu'OpenOffice a reconnu en utilisant la rétro-ingénierie dans https://www.openoffice.org/sc/excelfileformat.pdf est plus utile que la "documentation" de Microsoft.
Donc, le code suivant pour mettre l'image d'arrière-plan dans HSSFSheet
fonctionne pour moi testé avec plusieurs types de fichiers d'image différents (BMP, PNG, JPG).
A également changé en utilisant List<Byte> data
en byte[] data
. Donc org.Apache.pi.hssf.dossier.ContinueRecord peut être utilisé directement et ne doit pas être recréé.
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
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.record.ContinueRecord;
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.image.Raster;
import java.awt.Graphics2D;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import javax.imageio.ImageIO;
public class CreateExcelHSSFSheetBackgroundBMP {
static byte[] getBackgroundBitmapData(String filePath) throws Exception {
// see https://www.openoffice.org/sc/excelfileformat.pdf - 5.6 BITMAP
// and https://interoperability.blob.core.windows.net/files/MS-XLS/[MS-XLS].pdf - 2.4.19 BkHim
// get file byte data in 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();
// each pixel has 3 bytes but the width bytes must be filled up to multiple of 4
int widthBytesMultOf4 = (int)((width * 3 + 3) / 4 * 4);
// size 12 bytes (additional headers, see below) + picture bytes
int size = 12 + height * widthBytesMultOf4;
// create the header section
ByteBuffer headers = ByteBuffer.allocate(20);
headers.order(ByteOrder.LITTLE_ENDIAN);
headers.putShort((short)0x09); // 0x0009 = signed integer that specifies the image format BMP
headers.putShort((short)0x01); // reserved (2 bytes): MUST be 0x0001
headers.putInt(size); // signed integer that specifies the size of imageBlob in bytes
// BMP header section:
headers.putInt(0x0C); // length 0x0C = 12 bytes
headers.putShort(width); // pixels width
headers.putShort(height); // pixels heigth
headers.putShort((short)0x01); // number of planes: always 1
headers.putShort((short)0x18); // color depth 0x018 = 24 bit
//create data ByteArrayOutputStream
ByteArrayOutputStream data = new ByteArrayOutputStream();
// write headers section
data.write(headers.array());
// put the bytes R G B into the data; lines of the bitmap must be from bottom line to top line
Raster raster = image.getData();
for (short y = (short)(height - 1); y >= 0; y--) {
for (short x = 0; x < width; x++) {
int r = raster.getSample(x, y, 2);
data.write((byte)r);
int g = raster.getSample(x, y, 1);
data.write((byte)g);
int b = raster.getSample(x, y, 0);
data.write((byte)b);
}
// fill up x with 0 bytes up to multiple of 4
for (int x = width * 3; x < widthBytesMultOf4; x++) {
data.write((byte)0);
}
}
return data.toByteArray();
}
public static void main(String[] args) throws Exception {
HSSFWorkbook workbook = new HSSFWorkbook();
HSSFSheet sheet = workbook.createSheet("Sheet1");
sheet = workbook.createSheet("Sheet2"); // this sheet gets the background image set
// we need the binary records of the sheet
// get InternalSheet
Field _sheet = HSSFSheet.class.getDeclaredField("_sheet");
_sheet.setAccessible(true);
InternalSheet internalsheet = (InternalSheet)_sheet.get(sheet);
// get List of RecordBase
Field _records = InternalSheet.class.getDeclaredField("_records");
_records.setAccessible(true);
@SuppressWarnings("unchecked")
List<RecordBase> records = (List<RecordBase>)_records.get(internalsheet);
// get bytes of the image file
byte[] data = getBackgroundBitmapData("dummyText.png"); //PNG must not have transparency
// do creating BitmapRecord and ContinueRecords from the data in parts of 8220 bytes
BitmapRecord bitmapRecord = null;
List<ContinueRecord> continueRecords = new ArrayList<ContinueRecord>();
int bytes = 0;
if (data.length > 8220) {
bitmapRecord = new BitmapRecord(Arrays.copyOfRange(data, 0, 8220));
bytes = 8220;
while (bytes < data.length) {
if ((bytes + 8220) < data.length) {
continueRecords.add(new ContinueRecord(Arrays.copyOfRange(data, bytes, bytes + 8220)));
bytes += 8220;
} else {
continueRecords.add(new ContinueRecord(Arrays.copyOfRange(data, bytes, data.length)));
break;
}
}
} else {
bitmapRecord = new BitmapRecord(data);
}
// add the records after 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);
}
// debug output
for (RecordBase r : internalsheet.getRecords()) {
System.out.println(r.getClass());
}
// write out workbook
FileOutputStream out = new FileOutputStream("CreateExcelHSSFSheetBackgroundBMP.xls");
workbook.write(out);
workbook.close();
out.close();
}
static class BitmapRecord extends StandardRecord {
// see https://www.openoffice.org/sc/excelfileformat.pdf - 5.6 BITMAP
// and https://interoperability.blob.core.windows.net/files/MS-XLS/[MS-XLS].pdf - 2.4.19 BkHim
byte[] data;
BitmapRecord(byte[] data) {
this.data = data;
}
public int getDataSize() {
return data.length;
}
public short getSid() {
return (short)0x00E9;
}
public void serialize(LittleEndianOutput out) {
out.write(data);
}
}
}