Как часто это просто глупость самого программиста ;-).В немецком языке есть поговорка: «Я не могу видеть лес из-за деревьев».
Просто получить Raster
из BufferedImage
один раз за пределами цикла вместо того, чтобы поместить его внутрь цикла.и снова невероятно увеличивает скорость:
...
// 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, ...) могут быть сохранены напрямую.Попробую это завтра.
Ну, как часто, только бесполезные усилия с «документациями» Microsoft.https://interoperability.blob.core.windows.net/files/MS-XLS/[MS-XLS].pdf кажется правильным, но неполным, по крайней мере на стр. 211: 2.4.19 BkHim.Так что использование 0x000E
вместо 0x0009
не позволит просто сохранить нативный imageBlob
.
Но также описание 0x0009
:
Растровое изображение.Данные изображения хранятся в формате растрового изображения, как описано в [MSDN-BMP]
, только ссылки на неполное описание того, какой тип растрового изображения должен использоваться здесь.Ничего о структуре заголовка в начале и пиксельных байтах от нижней строки до верхней строки.А также ничего о необходимости выравнивания размера каждой строки с кратностью 4, вставляя нулевые байты после последнего пикселя.Но, не зная этого, даже использование растрового изображения 0x0009
не будет работать.
Когда я помещаю фоновое изображение любого типа в лист в файле *.xls
с использованием графического интерфейса пользователя Excel
, а затем получаюпосмотрите в этот файл, используя шестнадцатеричный дамп, тогда это всегда выглядит так:
0x e900 SSSS 09000100 SSSSSSSS 0c000000 WWWWHHHH 01001800 PPP ...
, где S означает размер, W означает ширину, H означает высоту и P означает пиксельные байты.
Это не зависит от того, поместил я BMP, JPG или PNGв качестве фонового изображения на листе.Всегда используется этот особый вид данных BMP.
Так что то, что OpenOffice распознал с помощью обратного инжиниринга в https://www.openoffice.org/sc/excelfileformat.pdf, более полезно, чем «документация» от Microsoft.
код для вставки фонового изображения в HSSFSheet
работает у меня, протестировано с несколькими различными типами файлов изображений (BMP, PNG, JPG).
Также изменено с помощью List<Byte> 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);
}
}
}