Skip to content

Commit

Permalink
This closes qax-os#1405, add new function SetSheetBackgroundFromBytes…
Browse files Browse the repository at this point in the history
… used for setting background image by given image data

Change-Id: I56b5d85582e10d8a2a40b6cafab6e9b167883b1c
  • Loading branch information
houjianxin.rupert committed Nov 28, 2022
1 parent dde6b9c commit e82c457
Show file tree
Hide file tree
Showing 6 changed files with 238 additions and 8 deletions.
39 changes: 33 additions & 6 deletions PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@

## Description

<!--- Describe your changes in detail -->
Because of business need, We added a new method for struct File:`SetSheetBackgroundFromBytes`.

the function of this method is similar with existing method(SetSheetBackground), they both can add background image for excel files, but the difference between SetSheetBackground and SetSheetBackgroundFromBytes is, the new method use binary image data as image source. And SetSheetBackgroundFromBytes do not need to specify the extension of image data, It can determine the image format by itself.

由于业务需要, 我们为File结构体增加了一个新的方法:`SetSheetBackgroundFromBytes`

该方法的功能与现存SetSheetBackground方法相同,即添加背景图像,不同的是新增方法支持使用二进制数据设置背景图像,同时,SetSheetBackgroundFromBytes无需指定文件后缀,该方法可自行判断二进制数据中所包含图像的格式。

## Related Issue

Expand All @@ -13,33 +19,54 @@
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

None


## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

In our business, we want to set the binary image data requested from the upstream service as the background of the excel file directly, so we implement `SetSheetBackgroundFromBytes`.

在业务场景中,我们希望能够将从上游服务中请求到的二进制图片数据直接设置为excel文件的背景,因此实现了`SetSheetBackgroundFromBytes`



## How Has This Been Tested

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

1. We loaded all the images under `test/images/` and `excelize.svg`, and called `SetSheetBackgroundFromBytes` to set the binary data loaded into memory as the background image, the result shows that this method can distinguish the binary data format and set for background, see `TestSetBackgroundFromBytes` in `sheet_test.go`;
2. In addition to the above images, we also tested with other positive and negative example files that are not in this project, and the results show that `SetSheetBackgroundFromBytes` can be executed correctly;
3. We found that when we use the image `test/images/excel.wmz` as the background, no matter calling the existing `SetSheetBackground` or the added `SetSheetBackgroundFromBytes`, the set background cannot be displayed correctly, maybe there is a problem with the image itself;
4. We found that when we use the `.svg` file as the background image, it cannot be displayed normally in excel, and you cannot manually set the `.svg` file as the background image in excel. It seems that excel does not support setting the `.svg` file as the background image, but it can be displayed normally in WPS.


1. 我们读取了`test/images/`下的所有图像以及`excelize.svg`,并调用`SetSheetBackgroundFromBytes`将加载到内存的二进制数据设置为背景图像,结果显示该方法能正常区分二进制数据格式并设置背景,详见`sheet_test.go`中的`TestSetBackgroundFromBytes`
2. 除上述图像外,我们还使用其他不在本项目中的正例和反例文件做了测试,结果显示`SetSheetBackgroundFromBytes`均能够正确执行;
3. 我们发现使用图像`test/images/excel.wmz`作为背景时,无论调用现存的`SetSheetBackground`还是新增的`SetSheetBackgroundFromBytes`都无法正确显示设置的背景,可能是图像本身有问题;
4. 我们发现使用`.svg`文件作为背景图像时,在excel中无法正常显示,同时在excel中也无法手动设置`.svg`文件作为背景图像,似乎excel并不支持设置.svg文件作为背景,但在WPS中可以正常显示。

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Docs change / refactoring / dependency upgrade
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] My code follows the code style of this project.
- [x] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.
- [x] I have read the **CONTRIBUTING** document.
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
154 changes: 154 additions & 0 deletions picture.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ package excelize

import (
"bytes"
"compress/gzip"
"encoding/json"
"encoding/xml"
"image"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
Expand Down Expand Up @@ -721,3 +723,155 @@ func (f *File) drawingResize(sheet, cell string, width, height float64, opts *pi
w, h = int(width*opts.XScale), int(height*opts.YScale)
return
}

// getImageDataFormat provides a function to query format of image data
// by given data.
func getImageDataFormat(data []byte) string {
if len(data) == 0 {
return ""
}
for ext, cmpFunc := range supportedImageHeaderFormat {
if cmpFunc(data) {
return ext
}
}
return ""
}

// isGIF provides a function to determine whether the format of given image data is gif
func isGIF(data []byte) bool {
pattern := []byte{0x47, 0x49, 0x46, 0x38}
if len(data) < len(pattern) {
return false
}
return bytes.Equal(data[0:len(pattern)], pattern)
}

func isJPEG(data []byte) bool {
pattern := []byte{0xFF, 0xD8, 0xFF}
if len(data) < len(pattern) {
return false
}
return bytes.Equal(data[0:len(pattern)], pattern)
}

// isPNG provides a function to determine whether the format of given image data is png
func isPNG(data []byte) bool {
pattern := []byte{0x89, 0x50, 0x4E}
if len(data) < len(pattern) {
return false
}
return bytes.Equal(data[0:len(pattern)], pattern)
}

// isTIFF provides a function to determine whether the format of given image data is tiff
func isTIFF(data []byte) bool {
patterns := [][]byte{
{0x4D, 0x4D},
{0x49, 0x49},
}
for _, pattern := range patterns {
if len(data) < len(pattern) {
continue
}
if bytes.Equal(data[0:len(pattern)], pattern) {
return true
}
}
return false
}

// isEMF provides a function to determine whether the format of given image data is emf
func isEMF(data []byte) bool {
patterns := [][]byte{
{0x01, 0x00, 0x00, 0x00, 0x58, 0x00, 0x00, 0x00},
{0x01, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00},
}
for _, pattern := range patterns {
if len(data) < len(pattern) {
continue
}
if bytes.Equal(data[0:len(pattern)], pattern) {
return true
}
}
return false
}

// isWMF provides a function to determine whether the format of given image data is wmf
func isWMF(data []byte) bool {
patterns := [][]byte{
{0x01, 0x00, 0x09, 0x00},
{0x02, 0x00, 0x09, 0x00},
{0xD7, 0xCD, 0xC6, 0x9A},
}
for _, pattern := range patterns {
if len(data) < len(pattern) {
continue
}
if bytes.Equal(data[0:len(pattern)], pattern) {
return true
}
}
return false
}

// isEMZ provides a function to determine whether the format of given image data is emz
func isEMZ(data []byte) bool {
pattern := []byte{0x1F, 0x8B}
if len(data) < len(pattern) {
return false
}
isEqual := bytes.Equal(data[0:len(pattern)], pattern)
if !isEqual {
return false
}
b := bytes.NewBuffer(data)
reader, err := gzip.NewReader(b)
if err != nil {
return false
}
res, err := ioutil.ReadAll(reader)
if err != nil {
return false
}
return isEMF(res)
}

// isWMZ provides a function to determine whether the format of given image data is wmz
func isWMZ(data []byte) bool {
pattern := []byte{0x1F, 0x8B}
if len(data) < len(pattern) {
return false
}
isEqual := bytes.Equal(data[0:len(pattern)], pattern)
if !isEqual {
return false
}
b := bytes.NewBuffer(data)
reader, err := gzip.NewReader(b)
if err != nil {
return false
}
res, err := ioutil.ReadAll(reader)
if err != nil {
return false
}
return isWMF(res)
}

// isSVG provides a function to determine whether the format of given image data is svg
func isSVG(data []byte) bool {
type Result struct {
XMLName xml.Name `xml:"svg"`
}
var res Result
err := xml.Unmarshal(data, &res)
if err != nil {
return false
}
if res.XMLName.Local == "svg" {
return true
}
return false
}
20 changes: 18 additions & 2 deletions sheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,11 +497,27 @@ func (f *File) SetSheetBackground(sheet, picture string) error {
return ErrImgExt
}
file, _ := os.ReadFile(filepath.Clean(picture))
name := f.addMedia(file, ext)
return f.setSheetBackground(sheet, ext, file)
}

// SetSheetBackgroundFromBytes provides a function to set background picture
// by given worksheet name and image data
func (f *File) SetSheetBackgroundFromBytes(sheet string, picture []byte) error {
ext, ok := supportedImageTypes[getImageDataFormat(picture)]
if !ok {
return ErrImgExt
}
return f.setSheetBackground(sheet, ext, picture)
}

// setSheetBackground provides a function to set background picture by given
// worksheet name, file name extension and image data.
func (f *File) setSheetBackground(sheet, ext string, imageData []byte) error {
name := f.addMedia(imageData, ext)
sheetXMLPath, _ := f.getSheetXMLPath(sheet)
sheetRels := "xl/worksheets/_rels/" + strings.TrimPrefix(sheetXMLPath, "xl/worksheets/") + ".rels"
rID := f.addRels(sheetRels, SourceRelationshipImage, strings.Replace(name, "xl", "..", 1), "")
if err = f.addSheetPicture(sheet, rID); err != nil {
if err := f.addSheetPicture(sheet, rID); err != nil {
return err
}
f.addSheetNameSpace(sheet, SourceRelationship)
Expand Down
20 changes: 20 additions & 0 deletions sheet_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package excelize
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
Expand Down Expand Up @@ -463,3 +465,21 @@ func TestAttrValToFloat(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, 42.1, got)
}

func TestSetSheetBackgroundFromBytes(t *testing.T) {
files := []string{"../../excelize.svg", "excel.emf", "excel.emz", "excel.gif", "excel.jpg", "excel.png", "excel.tif", "excel.wmf", "excel.wmz"}
f, err := OpenFile("./test/EmptyWorkbook.xlsx")
assert.NoError(t, err)
for i, file := range files {
img, err := os.Open(filepath.Join("test/images", file))
assert.NoError(t, err)
content, err := ioutil.ReadAll(img)
assert.NoError(t, err)
assert.NoError(t, img.Close())
assert.NoError(t, f.SetSheetBackgroundFromBytes("sheet1", content))
assert.NoError(t, f.SaveAs(fmt.Sprintf("test/TestSetSheetBackgroundFromBytes%d.xlsx", i)))
}
assert.Error(t, f.SetSheetBackgroundFromBytes("sheet1", nil), ErrImgExt)
assert.Error(t, f.SetSheetBackgroundFromBytes("sheet1", []byte{123, 243, 235, 34, 6, 56, 134, 87, 36, 255, 23, 52}), ErrImgExt)
assert.NoError(t, f.Close())
}
Binary file added test/EmptyWorkbook.xlsx
Binary file not shown.
13 changes: 13 additions & 0 deletions xmlDrawing.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ var supportedImageTypes = map[string]string{
".tiff": ".tiff", ".wmf": ".wmf", ".wmz": ".wmz",
}

// supportedImageHeaderFormat defined supported image data format.
var supportedImageHeaderFormat = map[string]func([]byte) bool{
".gif": isGIF,
".jpeg": isJPEG,
".png": isPNG,
".tiff": isTIFF,
".emf": isEMF,
".wmf": isWMF,
".emz": isEMZ,
".wmz": isWMZ,
".svg": isSVG,
}

// supportedContentTypes defined supported file format types.
var supportedContentTypes = map[string]string{
".xlam": ContentTypeAddinMacro,
Expand Down

0 comments on commit e82c457

Please sign in to comment.