gopdf/png.go

214 lines
5.3 KiB
Go

/*
* Copyright (c) 2013-2016 Kurt Jung (Gmail: kurt.w.jung)
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package gofpdf
import (
"bytes"
"fmt"
"strings"
)
func (f *Fpdf) pngColorSpace(ct byte) (colspace string, colorVal int) {
colorVal = 1
switch ct {
case 0, 4:
colspace = "DeviceGray"
case 2, 6:
colspace = "DeviceRGB"
colorVal = 3
case 3:
colspace = "Indexed"
default:
f.err = fmt.Errorf("unknown color type in PNG buffer: %d", ct)
}
return
}
func (f *Fpdf) parsepngstream(buf *bytes.Buffer, readdpi bool) (info *ImageInfoType) {
info = f.newImageInfo()
// Check signature
if string(buf.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" {
f.err = fmt.Errorf("incorrect PNG buffer")
return
}
w := f.readBeInt32(buf)
h := f.readBeInt32(buf)
bpc := f.readByte(buf)
if bpc > 8 {
f.err = fmt.Errorf("16-bit depth not supported in PNG file")
}
ct := f.readByte(buf)
var colspace string
var colorVal int
colspace, colorVal = f.pngColorSpace(ct)
if f.err != nil {
return
}
if f.readByte(buf) != 0 {
f.err = fmt.Errorf("'unknown compression method in PNG buffer")
return
}
if f.readByte(buf) != 0 {
f.err = fmt.Errorf("'unknown filter method in PNG buffer")
return
}
if f.readByte(buf) != 0 {
f.err = fmt.Errorf("interlacing not supported in PNG buffer")
return
}
_ = buf.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
for loop {
n := int(f.readBeInt32(buf))
// dbg("Loop [%d]", n)
switch string(buf.Next(4)) {
case "PLTE":
// dbg("PLTE")
// Read palette
pal = buf.Next(n)
_ = buf.Next(4)
case "tRNS":
// dbg("tRNS")
// Read transparency info
t := buf.Next(n)
switch ct {
case 0:
trns = []int{int(t[1])} // ord(substr($t,1,1)));
case 2:
trns = []int{int(t[1]), int(t[3]), int(t[5])} // array(ord(substr($t,1,1)), ord(substr($t,3,1)), ord(substr($t,5,1)));
default:
pos := strings.Index(string(t), "\x00")
if pos >= 0 {
trns = []int{pos} // array($pos);
}
}
_ = buf.Next(4)
case "IDAT":
// dbg("IDAT")
// Read image data block
data = append(data, buf.Next(n)...)
_ = buf.Next(4)
case "IEND":
// dbg("IEND")
loop = false
case "pHYs":
// dbg("pHYs")
// png files theoretically support different x/y dpi
// 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]
// 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
if x == y && readdpi {
switch units {
// if units is 1 then measurement is px/meter
case 1:
info.dpi = float64(x) / 39.3701 // inches per meter
default:
info.dpi = float64(x)
}
}
_ = buf.Next(4)
default:
// dbg("default")
_ = buf.Next(n + 4)
}
if loop {
loop = n > 0
}
}
if colspace == "Indexed" && len(pal) == 0 {
f.err = fmt.Errorf("missing palette in PNG buffer")
}
info.w = float64(w)
info.h = float64(h)
info.cs = colspace
info.bpc = int(bpc)
info.f = "FlateDecode"
info.dp = dp
info.pal = pal
info.trns = trns
// dbg("ct [%d]", ct)
if ct >= 4 {
// Separate alpha and color channels
var err error
data, err = sliceUncompress(data)
if err != nil {
f.err = err
return
}
var color, alpha bytes.Buffer
if ct == 4 {
// Gray image
width := int(w)
height := int(h)
length := 2 * width
var pos, elPos int
for i := 0; i < height; i++ {
pos = (1 + length) * i
color.WriteByte(data[pos])
alpha.WriteByte(data[pos])
elPos = pos + 1
for k := 0; k < width; k++ {
color.WriteByte(data[elPos])
alpha.WriteByte(data[elPos+1])
elPos += 2
}
}
} else {
// RGB image
width := int(w)
height := int(h)
length := 4 * width
var pos, elPos int
for i := 0; i < height; i++ {
pos = (1 + length) * i
color.WriteByte(data[pos])
alpha.WriteByte(data[pos])
elPos = pos + 1
for k := 0; k < width; k++ {
color.Write(data[elPos : elPos+3])
alpha.WriteByte(data[elPos+3])
elPos += 4
}
}
}
data = sliceCompress(color.Bytes())
info.smask = sliceCompress(alpha.Bytes())
if f.pdfVersion < "1.4" {
f.pdfVersion = "1.4"
}
}
info.data = data
return
}