Skip to content

Commit

Permalink
Image from data (unidoc#251)
Browse files Browse the repository at this point in the history
* ability to create image from in-memory data

* gitignore: vim swap files
  • Loading branch information
preciselytom authored and Alfred Hall committed Mar 29, 2019
1 parent 8b90737 commit b746ace
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*.dll
*.so
*.dylib
*.swp
coverage*
**/.DS_Store
**/main
Expand Down
Binary file modified _examples/document/image/image.docx
Binary file not shown.
28 changes: 24 additions & 4 deletions _examples/document/image/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package main

import (
"io/ioutil"
"log"

"baliance.com/gooxml/common"
Expand All @@ -16,18 +17,30 @@ var lorem = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin lobo
func main() {
doc := document.New()

img, err := common.ImageFromFile("gophercolor.png")
img1, err := common.ImageFromFile("gophercolor.png")
if err != nil {
log.Fatalf("unable to create image: %s", err)
}
img2data, err := ioutil.ReadFile("gophercolor.png")
if err != nil {
log.Fatalf("unable to read file: %s", err)
}
img2, err := common.ImageFromBytes(img2data)
if err != nil {
log.Fatalf("unable to create image: %s", err)
}

iref, err := doc.AddImage(img)
img1ref, err := doc.AddImage(img1)
if err != nil {
log.Fatalf("unable to add image to document: %s", err)
}
img2ref, err := doc.AddImage(img2)
if err != nil {
log.Fatalf("unable to add image to document: %s", err)
}

para := doc.AddParagraph()
anchored, err := para.AddRun().AddDrawingAnchored(iref)
anchored, err := para.AddRun().AddDrawingAnchored(img1ref)
if err != nil {
log.Fatalf("unable to add anchored image: %s", err)
}
Expand All @@ -44,7 +57,14 @@ func main() {

// drop an inline image in
if i == 13 {
inl, err := run.AddDrawingInline(iref)
inl, err := run.AddDrawingInline(img1ref)
if err != nil {
log.Fatalf("unable to add inline image: %s", err)
}
inl.SetSize(1*measurement.Inch, 1*measurement.Inch)
}
if i == 15 {
inl, err := run.AddDrawingInline(img2ref)
if err != nil {
log.Fatalf("unable to add inline image: %s", err)
}
Expand Down
25 changes: 24 additions & 1 deletion common/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package common

import (
"bytes"
"fmt"
"image"
"os"
Expand All @@ -21,10 +22,12 @@ import (

// Image is a container for image information. It's used as we need format and
// and size information to use images.
// It contains either the filesystem path to the image, or the image itself.
type Image struct {
Size image.Point
Format string
Path string
Data *[]byte
}

// ImageRef is a reference to an image within a document.
Expand Down Expand Up @@ -56,11 +59,16 @@ func (i ImageRef) Format() string {
return i.img.Format
}

// Path returns the path to an image file
// Path returns the path to an image file, if any.
func (i ImageRef) Path() string {
return i.img.Path
}

// Data returns the data of an image file, if any.
func (i ImageRef) Data() *[]byte {
return i.img.Data
}

// Size returns the size of an image
func (i ImageRef) Size() image.Point {
return i.img.Size
Expand Down Expand Up @@ -102,3 +110,18 @@ func ImageFromFile(path string) (Image, error) {
r.Size = imgDec.Bounds().Size()
return r, nil
}

// ImageFromBytes returns an Image struct for an in-memory image. You can also
// construct an Image directly if the file and size are known.
func ImageFromBytes(data []byte) (Image, error) {
r := Image{}
imgDec, ifmt, err := image.Decode(bytes.NewReader(data))
if err != nil {
return r, fmt.Errorf("unable to parse image: %s", err)
}

r.Data = &data
r.Format = ifmt
r.Size = imgDec.Bounds().Size()
return r, nil
}
10 changes: 7 additions & 3 deletions document/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@ func (d *Document) Save(w io.Writer) error {

for i, img := range d.Images {
fn := fmt.Sprintf("word/media/image%d.%s", i+1, strings.ToLower(img.Format()))
if img.Path() != "" {
if img.Data() != nil {
if err := zippkg.AddFileFromBytes(z, fn, *img.Data()); err != nil {
return err
}
} else if img.Path() != "" {
if err := zippkg.AddFileFromDisk(z, fn, img.Path()); err != nil {
return err
}
Expand Down Expand Up @@ -566,8 +570,8 @@ func (d *Document) validateTableCells() error {
// can be used to add the image to a run and place it in the document contents.
func (d *Document) AddImage(i common.Image) (common.ImageRef, error) {
r := common.MakeImageRef(i, &d.DocBase, d.docRels)
if i.Path == "" {
return r, errors.New("image must have a path")
if i.Data == nil && i.Path == "" {
return r, errors.New("image must have data or a path")
}

if i.Format == "" {
Expand Down
76 changes: 67 additions & 9 deletions document/document_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,39 +216,97 @@ func TestHeaderAndFooterImages(t *testing.T) {
if err != nil {
t.Fatalf("unable to create image: %s", err)
}
dir1, err := doc.AddImage(img1)
png3x3 := []byte{
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03,
0x08, 0x02, 0x00, 0x00, 0x00, 0xd9, 0x4a, 0x22,
0xe8, 0x00, 0x00, 0x00, 0x1e, 0x49, 0x44, 0x41,
0x54, 0x08, 0xd7, 0x63, 0xf8, 0xc5, 0x1e, 0xf8,
0x9d, 0xfd, 0xd7, 0x34, 0xf6, 0x5f, 0x0c, 0x10,
0x8a, 0x9d, 0xf7, 0x17, 0x03, 0x84, 0x62, 0xf7,
0xf9, 0x05, 0x00, 0xd2, 0x6f, 0x0d, 0x71, 0x26,
0x33, 0x2f, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x49,
0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
}
img3, err := common.ImageFromBytes(png3x3)
if err != nil {
t.Fatalf("unable to add image to doc: %s", err)
t.Fatalf("unable to create image: %s", err)
}
dir2, err := doc.AddImage(img2)

dir1, err := doc.AddImage(img1)
if err != nil {
t.Fatalf("unable to add image to doc: %s", err)
}

if dir1.RelID() != "rId4" {
t.Errorf("expected rId4 != %s", dir1.RelID())
}

dir2, err := doc.AddImage(img2)
if err != nil {
t.Fatalf("unable to add image to doc: %s", err)
}
if dir2.RelID() != "rId5" {
t.Errorf("expected rId5 != %s", dir2.RelID())
}

dir3, err := doc.AddImage(img3)
if err != nil {
t.Fatalf("unable to add image to doc: %s", err)
}
if dir3.RelID() != "rId6" {
t.Errorf("expected rId6 != %s", dir3.RelID())
}

hdr := doc.AddHeader()
ftr := doc.AddFooter()

hir1, err := hdr.AddImage(img1)
fir1, err := ftr.AddImage(img1)
hir2, err := hdr.AddImage(img2)
fir2, err := ftr.AddImage(img2)
if err != nil {
t.Fatalf("unable to add image to header: %s", err)
}
if hir1.RelID() != "rId1" {
t.Errorf("expected rId1 != %s", hir1.RelID())
}

hir2, err := hdr.AddImage(img2)
if err != nil {
t.Fatalf("unable to add image to header: %s", err)
}
if hir2.RelID() != "rId2" {
t.Errorf("expected rId2 != %s", hir2.RelID())
}

hir3, err := hdr.AddImage(img3)
if err != nil {
t.Fatalf("unable to add image to header: %s", err)
}
if hir3.RelID() != "rId3" {
t.Errorf("expected rId3 != %s", hir3.RelID())
}

fir1, err := ftr.AddImage(img1)
if err != nil {
t.Fatalf("unable to add image to footer: %s", err)
}
if fir1.RelID() != "rId1" {
t.Errorf("expected rId1 != %s", hir1.RelID())
t.Errorf("expected rId1 != %s", fir1.RelID())
}

fir2, err := ftr.AddImage(img2)
if err != nil {
t.Fatalf("unable to add image to footer: %s", err)
}
if fir2.RelID() != "rId2" {
t.Errorf("expected rId2 != %s", hir2.RelID())
t.Errorf("expected rId2 != %s", fir2.RelID())
}

fir3, err := ftr.AddImage(img3)
if err != nil {
t.Fatalf("unable to add image to footer: %s", err)
}
if fir3.RelID() != "rId3" {
t.Errorf("expected rId3 != %s", fir3.RelID())
}
}

Expand Down
4 changes: 2 additions & 2 deletions document/footer.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ func (f Footer) AddImage(i common.Image) (common.ImageRef, error) {
}

r := common.MakeImageRef(i, &f.d.DocBase, ftrRels)
if i.Path == "" {
return r, errors.New("image must have a path")
if i.Data == nil && i.Path == "" {
return r, errors.New("image must have data or a path")
}

if i.Format == "" {
Expand Down
4 changes: 2 additions & 2 deletions document/header.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ func (h Header) AddImage(i common.Image) (common.ImageRef, error) {
}

r := common.MakeImageRef(i, &h.d.DocBase, hdrRels)
if i.Path == "" {
return r, errors.New("image must have a path")
if i.Data == nil && i.Path == "" {
return r, errors.New("image must have data or a path")
}

if i.Format == "" {
Expand Down
4 changes: 2 additions & 2 deletions presentation/presentation.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,8 @@ func (p *Presentation) GetLayoutByName(name string) (SlideLayout, error) {
// can be used to add the image to a run and place it in the document contents.
func (p *Presentation) AddImage(i common.Image) (common.ImageRef, error) {
r := common.MakeImageRef(i, &p.DocBase, p.prels)
if i.Path == "" {
return r, errors.New("image must have a path")
if i.Data == nil && i.Path == "" {
return r, errors.New("image must have data or a path")
}

if i.Format == "" {
Expand Down
4 changes: 2 additions & 2 deletions spreadsheet/workbook.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ func (wb *Workbook) RecalculateFormulas() {
// can be used to add the image to a drawing.
func (wb *Workbook) AddImage(i common.Image) (common.ImageRef, error) {
r := common.MakeImageRef(i, &wb.DocBase, wb.wbRels)
if i.Path == "" {
return r, errors.New("image must have a path")
if i.Data == nil && i.Path == "" {
return r, errors.New("image must have data or a path")
}

if i.Format == "" {
Expand Down
11 changes: 11 additions & 0 deletions zippkg/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package zippkg

import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"io"
Expand Down Expand Up @@ -69,6 +70,16 @@ func AddFileFromDisk(z *zip.Writer, zipPath, diskPath string) error {
return err
}

// AddFileFromBytes takes a byte array and adds it at a given path to a zip file.
func AddFileFromBytes(z *zip.Writer, zipPath string, data []byte) error {
w, err := z.Create(zipPath)
if err != nil {
return fmt.Errorf("error creating %s: %s", zipPath, err)
}
_, err = io.Copy(w, bytes.NewReader(data))
return err
}

// ExtractToDiskTmp extracts a zip file to a temporary file in a given path,
// returning the name of the file.
func ExtractToDiskTmp(f *zip.File, path string) (string, error) {
Expand Down

0 comments on commit b746ace

Please sign in to comment.