À 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: entrez la description de l'image ici

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?

Author: Axel Richter, 2018-09-20

2 answers

Voici une version modifiée de votre code qui fonctionne pour moi, ET est assez rapide.

  1. J'utilise byte[] (et ByteArrayOutputStream) tout autour, pas plus List<Byte>.
  2. , Comme nous avons déjà un BufferedImage de TYPE_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.
  3. 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/ContinueRecords 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);
        }
    }
}
 2
Author: haraldK, 2018-09-24 08:15:55

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);
  }
 }

}
 0
Author: Axel Richter, 2018-09-22 06:10:20