Skip to content

Commit 77fe4dc

Browse files
authored
Merge pull request #578 from FlorianRauscha/master
Add image support for worksheets (PNG, JPEG, GIF, SVG)
2 parents eb806ad + 4f2ae73 commit 77fe4dc

File tree

8 files changed

+1109
-6
lines changed

8 files changed

+1109
-6
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2016 Dhatim.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.dhatim.fastexcel;
17+
18+
/**
19+
* Supported image types for embedding in worksheets.
20+
*/
21+
public enum ImageType {
22+
PNG("png", "image/png", false),
23+
JPEG("jpeg", "image/jpeg", false),
24+
GIF("gif", "image/gif", false),
25+
SVG("svg", "image/svg+xml", true);
26+
27+
private final String extension;
28+
private final String contentType;
29+
private final boolean vector;
30+
31+
ImageType(String extension, String contentType, boolean vector) {
32+
this.extension = extension;
33+
this.contentType = contentType;
34+
this.vector = vector;
35+
}
36+
37+
/**
38+
* Check if this is a vector image format (e.g., SVG).
39+
*
40+
* @return true if vector format, false if raster
41+
*/
42+
public boolean isVector() {
43+
return vector;
44+
}
45+
46+
/**
47+
* Get the file extension for this image type.
48+
*
49+
* @return File extension without the dot (e.g., "png", "jpeg")
50+
*/
51+
public String getExtension() {
52+
return extension;
53+
}
54+
55+
/**
56+
* Get the MIME content type for this image type.
57+
*
58+
* @return MIME content type (e.g., "image/png")
59+
*/
60+
public String getContentType() {
61+
return contentType;
62+
}
63+
64+
/**
65+
* Detect image type from byte array header.
66+
*
67+
* @param data Image bytes
68+
* @return Detected ImageType
69+
* @throws IllegalArgumentException if the image format is not supported or data is invalid
70+
*/
71+
public static ImageType fromBytes(byte[] data) {
72+
if (data == null || data.length < 8) {
73+
throw new IllegalArgumentException("Invalid image data: data is null or too short");
74+
}
75+
// PNG signature: 89 50 4E 47 0D 0A 1A 0A
76+
if (data[0] == (byte) 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47
77+
&& data[4] == 0x0D && data[5] == 0x0A && data[6] == 0x1A && data[7] == 0x0A) {
78+
return PNG;
79+
}
80+
// JPEG signature: FF D8 FF
81+
if (data[0] == (byte) 0xFF && data[1] == (byte) 0xD8 && data[2] == (byte) 0xFF) {
82+
return JPEG;
83+
}
84+
// GIF signature: 47 49 46 38 (GIF8)
85+
if (data[0] == 0x47 && data[1] == 0x49 && data[2] == 0x46 && data[3] == 0x38) {
86+
return GIF;
87+
}
88+
// SVG detection: look for <?xml or <svg in the beginning (text-based format)
89+
String header = new String(data, 0, Math.min(data.length, 256), java.nio.charset.StandardCharsets.UTF_8);
90+
if (header.contains("<svg") || (header.contains("<?xml") && header.contains("<svg"))) {
91+
return SVG;
92+
}
93+
throw new IllegalArgumentException("Unsupported image format. Supported formats: PNG, JPEG, GIF, SVG");
94+
}
95+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright 2016 Dhatim.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.dhatim.fastexcel;
17+
18+
import java.io.IOException;
19+
20+
/**
21+
* Represents an image embedded in a worksheet.
22+
*/
23+
public class Picture {
24+
25+
private final int id;
26+
private final String name;
27+
private final PictureAnchor anchor;
28+
private final byte[] imageData;
29+
private final ImageType imageType;
30+
private final boolean lockAspectRatio;
31+
32+
// Relationship ID (set when writing)
33+
private String relationshipId;
34+
35+
Picture(int id, String name, PictureAnchor anchor, byte[] imageData, ImageType imageType,
36+
boolean lockAspectRatio) {
37+
this.id = id;
38+
this.name = name != null ? name : "Picture " + id;
39+
this.anchor = anchor;
40+
this.imageData = imageData;
41+
this.imageType = imageType;
42+
this.lockAspectRatio = lockAspectRatio;
43+
}
44+
45+
void setRelationshipId(String relationshipId) {
46+
this.relationshipId = relationshipId;
47+
}
48+
49+
String getRelationshipId() {
50+
return relationshipId;
51+
}
52+
53+
int getId() {
54+
return id;
55+
}
56+
57+
byte[] getImageData() {
58+
return imageData;
59+
}
60+
61+
ImageType getImageType() {
62+
return imageType;
63+
}
64+
65+
PictureAnchor getAnchor() {
66+
return anchor;
67+
}
68+
69+
/**
70+
* Get the name of this picture.
71+
*
72+
* @return Picture name
73+
*/
74+
public String getName() {
75+
return name;
76+
}
77+
78+
/**
79+
* Write the picture element to the drawing XML.
80+
*/
81+
void write(Writer w) throws IOException {
82+
if (anchor.isTwoCellAnchor()) {
83+
writeTwoCellAnchor(w);
84+
} else {
85+
writeOneCellAnchor(w);
86+
}
87+
}
88+
89+
private void writeOneCellAnchor(Writer w) throws IOException {
90+
w.append("<xdr:oneCellAnchor>");
91+
anchor.writeFrom(w);
92+
anchor.writeExt(w);
93+
writePicElement(w);
94+
w.append("<xdr:clientData/>");
95+
w.append("</xdr:oneCellAnchor>");
96+
}
97+
98+
private void writeTwoCellAnchor(Writer w) throws IOException {
99+
w.append("<xdr:twoCellAnchor>");
100+
anchor.writeFrom(w);
101+
anchor.writeTo(w);
102+
writePicElement(w);
103+
w.append("<xdr:clientData/>");
104+
w.append("</xdr:twoCellAnchor>");
105+
}
106+
107+
private void writePicElement(Writer w) throws IOException {
108+
w.append("<xdr:pic>");
109+
110+
// Non-visual properties
111+
w.append("<xdr:nvPicPr>");
112+
w.append("<xdr:cNvPr id=\"").append(id).append("\" name=\"");
113+
w.appendEscaped(name);
114+
w.append("\"/>");
115+
w.append("<xdr:cNvPicPr>");
116+
if (lockAspectRatio) {
117+
w.append("<a:picLocks noChangeAspect=\"1\"/>");
118+
}
119+
w.append("</xdr:cNvPicPr>");
120+
w.append("</xdr:nvPicPr>");
121+
122+
// Blip fill (image reference)
123+
w.append("<xdr:blipFill>");
124+
if (imageType == ImageType.SVG) {
125+
// SVG uses extension element with svgBlip (Office 2016+)
126+
w.append("<a:blip xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">");
127+
w.append("<a:extLst>");
128+
w.append("<a:ext uri=\"{96DAC541-7B7A-43D3-8B79-37D633B846F1}\">");
129+
w.append("<asvg:svgBlip xmlns:asvg=\"http://schemas.microsoft.com/office/drawing/2016/SVG/main\" ");
130+
w.append("r:embed=\"").append(relationshipId).append("\"/>");
131+
w.append("</a:ext>");
132+
w.append("</a:extLst>");
133+
w.append("</a:blip>");
134+
} else {
135+
// Raster images (PNG, JPEG, GIF)
136+
w.append("<a:blip xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" ");
137+
w.append("r:embed=\"").append(relationshipId).append("\"/>");
138+
}
139+
w.append("<a:stretch><a:fillRect/></a:stretch>");
140+
w.append("</xdr:blipFill>");
141+
142+
// Shape properties
143+
w.append("<xdr:spPr>");
144+
w.append("<a:xfrm>");
145+
w.append("<a:off x=\"0\" y=\"0\"/>");
146+
if (anchor.isTwoCellAnchor()) {
147+
w.append("<a:ext cx=\"0\" cy=\"0\"/>");
148+
} else {
149+
w.append("<a:ext cx=\"").append(anchor.getWidthEmu()).append("\" cy=\"")
150+
.append(anchor.getHeightEmu()).append("\"/>");
151+
}
152+
w.append("</a:xfrm>");
153+
w.append("<a:prstGeom prst=\"rect\"><a:avLst/></a:prstGeom>");
154+
w.append("</xdr:spPr>");
155+
156+
w.append("</xdr:pic>");
157+
}
158+
}

0 commit comments

Comments
 (0)