Skip to content

Commit

Permalink
fpdf: introduce buffer pool for (de)flating buffers
Browse files Browse the repository at this point in the history
```
  name              old time/op    new time/op    delta
  ParsePNG_rgb-8      22.8ms ± 1%    16.0ms ± 3%  -29.81%  (p=0.000 n=25+30)
  ParsePNG_gray-8      660µs ± 3%     624µs ± 4%   -5.54%  (p=0.000 n=28+30)
  ParsePNG_small-8    2.49µs ± 2%    2.12µs ± 3%  -14.83%  (p=0.000 n=30+30)
  ParseJPG-8          36.3µs ± 4%    36.8µs ± 5%     ~     (p=0.070 n=30+30)
  ParseGIF-8           410µs ± 3%     409µs ± 4%     ~     (p=0.562 n=30+29)

  name              old alloc/op   new alloc/op   delta
  ParsePNG_rgb-8      26.4MB ± 0%     4.2MB ±10%  -84.04%  (p=0.000 n=29+30)
  ParsePNG_gray-8     2.56MB ± 0%    2.49MB ± 0%   -2.73%  (p=0.000 n=30+30)
  ParsePNG_small-8    8.96kB ± 0%    9.84kB ± 0%   +9.73%  (p=0.000 n=30+30)
  ParseJPG-8           274kB ± 0%     274kB ± 0%   -0.00%  (p=0.002 n=30+30)
  ParseGIF-8           897kB ± 0%     898kB ± 0%   +0.10%  (p=0.000 n=30+30)

  name              old allocs/op  new allocs/op  delta
  ParsePNG_rgb-8         400 ± 0%       292 ± 0%  -27.00%  (p=0.000 n=30+28)
  ParsePNG_gray-8        164 ± 0%       114 ± 0%  -30.49%  (p=0.000 n=30+30)
  ParsePNG_small-8      35.0 ± 0%      10.0 ± 0%  -71.43%  (p=0.000 n=30+30)
  ParseJPG-8            12.0 ± 0%      12.0 ± 0%     ~     (all equal)
  ParseGIF-8             214 ± 0%       191 ± 0%  -10.75%  (p=0.000 n=30+30)
```
  • Loading branch information
sbinet committed Jun 23, 2021
1 parent 0532bfd commit fc0b6fc
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 119 deletions.
4 changes: 3 additions & 1 deletion attachments.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ func checksum(data []byte) string {
func (f *Fpdf) writeCompressedFileObject(content []byte) {
lenUncompressed := len(content)
sum := checksum(content)
compressed := sliceCompress(content)
mem := xmem.compress(content)
defer mem.release()
compressed := mem.bytes()
lenCompressed := len(compressed)
f.newobj()
f.outf("<< /Type /EmbeddedFile /Length %d /Filter /FlateDecode /Params << /CheckSum <%s> /Size %d >> >>\n",
Expand Down
39 changes: 15 additions & 24 deletions fpdf.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ package fpdf

import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"image"
Expand Down Expand Up @@ -3913,33 +3912,17 @@ func (f *Fpdf) parsejpg(r io.Reader) (info *ImageInfoType) {

// parsepng extracts info from a PNG data
func (f *Fpdf) parsepng(r io.Reader, readdpi bool) (info *ImageInfoType) {
buf, err := bufferFromReader(r)
buf, err := newRBuffer(r)
if err != nil {
f.err = err
return
}
return f.parsepngstream(buf, readdpi)
}

func (f *Fpdf) readBeInt32(r io.Reader) (val int32) {
err := binary.Read(r, binary.BigEndian, &val)
if err != nil && err != io.EOF {
f.err = err
}
return
}

func (f *Fpdf) readByte(r io.Reader) (val byte) {
err := binary.Read(r, binary.BigEndian, &val)
if err != nil {
f.err = err
}
return
}

// parsegif extracts info from a GIF data (via PNG conversion)
func (f *Fpdf) parsegif(r io.Reader) (info *ImageInfoType) {
data, err := bufferFromReader(r)
data, err := newRBuffer(r)
if err != nil {
f.err = err
return
Expand All @@ -3956,7 +3939,7 @@ func (f *Fpdf) parsegif(r io.Reader) (info *ImageInfoType) {
f.err = err
return
}
return f.parsepngstream(pngBuf, false)
return f.parsepngstream(&rbuffer{p: pngBuf.Bytes()}, false)
}

// newobj begins a new object
Expand Down Expand Up @@ -4194,9 +4177,11 @@ func (f *Fpdf) putpages() {
// Page content
f.newobj()
if f.compress {
data := sliceCompress(f.pages[n].Bytes())
mem := xmem.compress(f.pages[n].Bytes())
data := mem.bytes()
f.outf("<</Filter /FlateDecode /Length %d>>", len(data))
f.putstream(data)
mem.release()
} else {
f.outf("<</Length %d>>", f.pages[n].Len())
f.putstream(f.pages[n].Bytes())
Expand Down Expand Up @@ -4363,7 +4348,6 @@ func (f *Fpdf) putfonts() {
delete(usedRunes, 0)
utf8FontStream := font.utf8File.GenerateCutFont(usedRunes)
utf8FontSize := len(utf8FontStream)
compressedFontStream := sliceCompress(utf8FontStream)
CodeSignDictionary := font.utf8File.CodeSymbolDictionary
delete(CodeSignDictionary, 0)

Expand Down Expand Up @@ -4418,20 +4402,25 @@ func (f *Fpdf) putfonts() {
cidToGidMap[cc*2+1] = byte(glyph & 0xFF)
}

cidToGidMap = sliceCompress(cidToGidMap)
mem := xmem.compress(cidToGidMap)
cidToGidMap = mem.bytes()
f.newobj()
f.out("<</Length " + strconv.Itoa(len(cidToGidMap)) + "/Filter /FlateDecode>>")
f.putstream(cidToGidMap)
f.out("endobj")
mem.release()

//Font file
mem = xmem.compress(utf8FontStream)
compressedFontStream := mem.bytes()
f.newobj()
f.out("<</Length " + strconv.Itoa(len(compressedFontStream)))
f.out("/Filter /FlateDecode")
f.out("/Length1 " + strconv.Itoa(utf8FontSize))
f.out(">>")
f.putstream(compressedFontStream)
f.out("endobj")
mem.release()
default:
f.err = fmt.Errorf("unsupported font type: %s", tp)
return
Expand Down Expand Up @@ -4679,9 +4668,11 @@ func (f *Fpdf) putimage(info *ImageInfoType) {
if info.cs == "Indexed" {
f.newobj()
if f.compress {
pal := sliceCompress(info.pal)
mem := xmem.compress(info.pal)
pal := mem.bytes()
f.outf("<</Filter /FlateDecode /Length %d>>", len(pal))
f.putstream(pal)
mem.release()
} else {
f.outf("<</Length %d>>", len(info.pal))
f.putstream(info.pal)
Expand Down
2 changes: 2 additions & 0 deletions fpdf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2934,6 +2934,7 @@ func BenchmarkLineTo(b *testing.B) {
pdf := fpdf.New("P", "mm", "A4", "")
pdf.AddPage()

b.ResetTimer()
for i := 0; i < b.N; i++ {
pdf.LineTo(170, 20)
}
Expand All @@ -2943,6 +2944,7 @@ func BenchmarkCurveTo(b *testing.B) {
pdf := fpdf.New("P", "mm", "A4", "")
pdf.AddPage()

b.ResetTimer()
for i := 0; i < b.N; i++ {
pdf.CurveTo(190, 100, 105, 100)
}
Expand Down
111 changes: 68 additions & 43 deletions png.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package fpdf

import (
"bytes"
"fmt"
"strings"
)
Expand All @@ -38,64 +37,67 @@ func (f *Fpdf) pngColorSpace(ct byte) (colspace string, colorVal int) {
return
}

func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoType) {
func (f *Fpdf) parsepngstream(r *rbuffer, readdpi bool) (info *ImageInfoType) {
info = f.newImageInfo()
// Check signature
if string(buf.Next(8)) != "\x89PNG\x0d\x0a\x1a\x0a" {
if string(r.Next(8)) != "\x89PNG\x0d\x0a\x1a\x0a" {
f.err = fmt.Errorf("not a PNG buffer")
return
}
// Read header chunk
_ = buf.Next(4)
if string(buf.Next(4)) != "IHDR" {
_ = r.Next(4)
if string(r.Next(4)) != "IHDR" {
f.err = fmt.Errorf("incorrect PNG buffer")
return
}
w := f.readBeInt32(buf)
h := f.readBeInt32(buf)
bpc := f.readByte(buf)
w := r.i32()
h := r.i32()
bpc := r.u8()
if bpc > 8 {
f.err = fmt.Errorf("16-bit depth not supported in PNG file")
}
ct := f.readByte(buf)
ct := r.u8()
var colspace string
var colorVal int
colspace, colorVal = f.pngColorSpace(ct)
if f.err != nil {
return
}
if f.readByte(buf) != 0 {
if r.u8() != 0 {
f.err = fmt.Errorf("'unknown compression method in PNG buffer")
return
}
if f.readByte(buf) != 0 {
if r.u8() != 0 {
f.err = fmt.Errorf("'unknown filter method in PNG buffer")
return
}
if f.readByte(buf) != 0 {
if r.u8() != 0 {
f.err = fmt.Errorf("interlacing not supported in PNG buffer")
return
}
_ = buf.Next(4)
_ = r.Next(4)
dp := sprintf("/Predictor 15 /Colors %d /BitsPerComponent %d /Columns %d", colorVal, bpc, w)
// Scan chunks looking for palette, transparency and image data
pal := make([]byte, 0, 32)
var trns []int
data := make([]byte, 0, 32)
loop := true
var (
pal []byte
trns []int
npix = w * h
data = make([]byte, 0, npix/8)
loop = true
)
for loop {
n := int(f.readBeInt32(buf))
n := int(r.i32())
// dbg("Loop [%d]", n)
switch string(buf.Next(4)) {
switch string(r.Next(4)) {
case "PLTE":
// dbg("PLTE")
// Read palette
pal = buf.Next(n)
_ = buf.Next(4)
pal = r.Next(n)
_ = r.Next(4)
case "tRNS":
// dbg("tRNS")
// Read transparency info
t := buf.Next(n)
t := r.Next(n)
switch ct {
case 0:
trns = []int{int(t[1])} // ord(substr($t,1,1)));
Expand All @@ -107,12 +109,12 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoT
trns = []int{pos} // array($pos);
}
}
_ = buf.Next(4)
_ = r.Next(4)
case "IDAT":
// dbg("IDAT")
// Read image data block
data = append(data, buf.Next(n)...)
_ = buf.Next(4)
data = append(data, r.Next(n)...)
_ = r.Next(4)
case "IEND":
// dbg("IEND")
loop = false
Expand All @@ -122,9 +124,9 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoT
// but we ignore files like this
// but if they're the same then we can stamp our info
// object with it
x := int(f.readBeInt32(buf))
y := int(f.readBeInt32(buf))
units := buf.Next(1)[0]
x := int(r.i32())
y := int(r.i32())
units := r.u8()
// fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n",
// x, y, int(units), readdpi)
// only modify the info block if the user wants us to
Expand All @@ -137,10 +139,10 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoT
info.dpi = float64(x)
}
}
_ = buf.Next(4)
_ = r.Next(4)
default:
// dbg("default")
_ = buf.Next(n + 4)
_ = r.Next(n + 4)
}
if loop {
loop = n > 0
Expand All @@ -160,27 +162,33 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoT
// dbg("ct [%d]", ct)
if ct >= 4 {
// Separate alpha and color channels
var err error
data, err = sliceUncompress(data)
mem, err := xmem.uncompress(data)
if err != nil {
f.err = err
return
}
var color, alpha bytes.Buffer
data = mem.bytes()
var (
color wbuffer
alpha wbuffer
)
if ct == 4 {
// Gray image
width := int(w)
height := int(h)
length := 2 * width
sz := height * (width + 1)
color.p = data[:sz] // reuse decompressed data buffer.
alpha.p = make([]byte, sz)
var pos, elPos int
for i := 0; i < height; i++ {
pos = (1 + length) * i
color.WriteByte(data[pos])
alpha.WriteByte(data[pos])
color.u8(data[pos])
alpha.u8(data[pos])
elPos = pos + 1
for k := 0; k < width; k++ {
color.WriteByte(data[elPos])
alpha.WriteByte(data[elPos+1])
color.u8(data[elPos])
alpha.u8(data[elPos+1])
elPos += 2
}
}
Expand All @@ -189,21 +197,38 @@ func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoT
width := int(w)
height := int(h)
length := 4 * width
sz := width * height
color.p = data[:sz*3+height] // reuse decompressed data buffer.
alpha.p = make([]byte, sz+height)
var pos, elPos int
for i := 0; i < height; i++ {
pos = (1 + length) * i
color.WriteByte(data[pos])
alpha.WriteByte(data[pos])
color.u8(data[pos])
alpha.u8(data[pos])
elPos = pos + 1
for k := 0; k < width; k++ {
color.Write(data[elPos : elPos+3])
alpha.WriteByte(data[elPos+3])
tmp := data[elPos : elPos+4]
color.u8(tmp[0])
color.u8(tmp[1])
color.u8(tmp[2])
alpha.u8(tmp[3])
elPos += 4
}
}
}
data = sliceCompress(color.Bytes())
info.smask = sliceCompress(alpha.Bytes())

xc := xmem.compress(color.bytes())
data = xc.copy()
xc.release()

// release uncompressed data buffer, after the color buffer
// has been compressed.
mem.release()

xa := xmem.compress(alpha.bytes())
info.smask = xa.copy()
xa.release()

if f.pdfVersion < "1.4" {
f.pdfVersion = "1.4"
}
Expand Down
Loading

0 comments on commit fc0b6fc

Please sign in to comment.