【问题标题】:Using java.awt.image.BufferedImage for creating BIFF8 BITMAP record takes much time - Is there any better approach?使用 java.awt.image.BufferedImage 创建 BIFF8 BITMAP 记录需要很多时间 - 有没有更好的方法?
【发布时间】:2019-02-24 10:32:48
【问题描述】:

所以我正在创建一个HSSFSheet,它使用apache poi 设置背景位图并拥有自己的低级代码。 https://www.openoffice.org/sc/excelfileformat.pdf 声明为 Record BITMAP, BIFF8

像素数据(位图高度线数组,从底线到顶线,见下文)

...

在每一行中,所有像素都是从左到右写入的。每个像素存储为 3 字节数组:像素颜色的红色、绿色和蓝色分量,按此顺序排列。通过在最后一个像素后插入零字节,将每行的大小对齐为 4 的倍数。

完整声明见PDF图片:

为了实现这一点,我的方法是使用 java.awt.image.BufferedImage 类型为 BufferedImage.TYPE_3BYTE_BGR。然后以正确的顺序(从底线到顶线)从该 BufferedImage 的栅格中获取所有字节 R G B,并在宽度(x 方向)上填充 4 的倍数。

见代码:

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

}

代码有效,但之间的部分

// --- this part takes much time but I have not found any better possibility

// ---

需要很长时间,因为需要为每个单个像素获取 3 个字节的 R G B 才能根据上述奇怪的格式获取它们。

有人知道更好的方法吗?可能上面奇怪的格式并没有我想的那么奇怪,已经有其他的用法了?

【问题讨论】:

标签: java apache-poi awt bufferedimage


【解决方案1】:

这是您的代码的修改版本,适用于我,而且速度非常快。

  1. 我一直在使用byte[](和ByteArrayOutputStream),不再使用List&lt;Byte&gt;
  2. 由于我们已经有了TYPE_3BYTE_BGRBufferedImage,我们几乎可以直接将其用作BMP 输出。我们只需要 a) 预先添加一个有效的 BMP 标头 b) 自下而上写入 c) 将每个扫描线(行)填充到 32 位边界和 d) 切换 BGR -> RGB 顺序。
  3. 我正在使用Raster 将数据行复制(填充)到输出中,因为复制较大的块比复制单个字节要快。

正如 cmets 中已经指出的,该结构是一个标准 BMP,带有BITMAPCOREHEADER(并且没有文件头)。不幸的是,ImageIOBMPImageWriter 总是写入文件头并使用 40 字节的BITMAPINFOHEADER。您可能可以绕过这些事情,并使用标准编写器,通过稍微按摩数据(提示:文件头包含偏移量为 10 处的像素数据的偏移量),但由于核心 BMP 格式实现起来很简单,它可能就像下面一样容易。

虽然文档肯定暗示直接使用其他格式,如 PNG 和 JPEG,但我没有设法正确地做到这一点。

如果您愿意,可能还有改进的余地,以避免一些字节数组复制(即使用偏移量/长度并将整个数据数组传递给Bitmap/ContinueRecords 而不是Arrays.copyOfRange())。

代码:

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

【讨论】:

  • 非常感谢。这确实是迄今为止最好的解决方案,尽管它与我在答案中的代码并没有太大区别。而且“……是标准的 BMP……” ;-) 它真的是标准的 BMP 吗?或者它不正是 OpenOffice 已经描述的那种非常特殊的 BMP?正如我在回答中所说,Excel 本身总是使用这种 BMP,而与为背景选择的图像类型无关。
  • 回复。 “标准 BMP”参见Wikipedia on the BMP file format。是的,我会说这是一个标准的 BMP。 BMP 结构 它只是在前面加上一个 Excel 特定的标题而不是 BMP 文件标题。
【解决方案2】:

通常这只是程序员自己的愚蠢;-)。德语有句谚语:“树不见林”。

只需将BufferedImageRaster 放在循环外一次,而不是一遍又一遍地将其放入循环内,从而极大地提高了速度:

...
  // 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++;
   }
  }
...

但@Gagravarr 在他的评论中的暗示看起来也很有趣。

2.4.19 BkHim

BkHim 记录指定工作表 (1) 背景的图像数据

cf(2 字节):指定图像格式的有符号整数。必须是以下值:

值:0x0009

含义:位图格式。图像数据以 [MSDN-BMP] 中所述的位图格式存储

值:0x000E

含义:原生格式。图像数据以其他应用程序的原生格式存储,无法直接处理。

这听起来好像是0x000E 第一个字节而不是0x0009 然后可以直接存储本机图片字节(PNG、JPG、BMP...)。明天试试这个。


好吧,微软的“文档”通常只是徒劳的努力。 https://interoperability.blob.core.windows.net/files/MS-XLS/[MS-XLS].pdf 似乎正确但不完整,至少在第 211 页:2.4.19 BK他。因此,使用0x000E 而不是0x0009 将不会简单地允许存储本机imageBlob

还有0x0009的描述:

位图格式。图像数据以位图格式存储,如 [MSDN-BMP]

仅链接到此处必须使用哪种位图的不完整描述。没有关于开头的标题结构和从底线到顶线的像素字节。而且也不需要通过在最后一个像素之后插入零字节来将每行的大小对齐为 4 的倍数。但是在不知道这一切的情况下,即使使用位图0x0009 也是行不通的。

当我使用 Excel 的 GUI 将任何类型的背景图像放入 *.xls 文件中的工作表中,然后使用十六进制转储查看该文件时,这总是如下所示:

0xe900SSSS09000100SSSSSSSS0c000000WWWWHHHH01001800PPP...

其中 S 表示大小,W 表示宽度,H 表示高度,P 表示像素字节。

这与我是否将 BMP、JPG 或 PNG 作为背景图像放入工作表无关。总是使用这种特殊类型的 BMP 数据。

因此,OpenOffice 在https://www.openoffice.org/sc/excelfileformat.pdf 中使用逆向工程所认可的内容比 Microsoft 的“文档”更有用。

因此,以下用于将背景图像放入 HSSFSheet 的代码对我来说是使用多种不同类型的图像文件(BMP、PNG、JPG)进行测试的。

还使用List&lt;Byte&gt; data 更改为byte[] data。所以org.apache.poi.hssf.record.ContinueRecord可以直接使用,不能重新创建。

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

}

【讨论】:

  • 在我看来你只需要一个标准的 BMP 结构,所以你应该没问题,只需使用 ImageIO.write(image, "BMP", &lt;a byte array output stream&gt;) 编写,然后在数据中包含生成的字节数组(前面加上 8字节0x09000100&lt;32 bit size as LE&gt;) 。您在“now 12 bytes follow” 之后写的标头实际上是BITMAPCOREHEADER。另请注意,List&lt;Byte&gt; 是一系列字节的非常低效的表示,使用byte[] 可能会快一个数量级(或者如果您不知道前面的大小,请使用ByteArrayOutputStream)。
  • PS:我确实相信(通过阅读 MSDN-BMP 链接)除了 BMP 之外,您还应该能够将图像存储为 PNG、JPEG、GIF 或 TIFF(因为这些由视窗 GDI)。全部使用0x0009 值。 “神奇”值0x0C000000 只是BITMAPCOREHEADER 结构的长度,是用来识别它的全部。填充到 32 位边界是 BMP 文件格式的一个属性。
  • PPS:使用我上面写的ImageIO.write(image, "BMP", &lt;a byte array output stream&gt;)可能无法直接工作,因为它还会在BITMAPCOREHEADER之前写入一个14字节的BMP 文件头。您可能需要去掉这 14 个字节。 :-)
  • @haraldK:尝试了您提到的所有方法,但均未成功。因此,除了您正确提示不使用 List&lt;Byte&gt;(这是早期测试的产物)以及 0x0C000000 只是标题结构的长度之外,您的提示都没有用。我现在在答案中的代码中考虑了您的两个有用提示。但这是一个悬而未决的问题,并且给出了完整的工作代码。因此,请通过使用默认 BMP 甚至 PNG 或 JPG 直接提供自己的答案来说服我;-)。
猜你喜欢
  • 1970-01-01
  • 2011-08-02
  • 2019-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多