codeberg.org/go-pdf/fpdf@v0.11.1/png.go (about)

     1  // Copyright ©2023 The go-pdf Authors. All rights reserved.
     2  // Use of this source code is governed by a MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  /*
     6   * Copyright (c) 2013-2016 Kurt Jung (Gmail: kurt.w.jung)
     7   *
     8   * Permission to use, copy, modify, and distribute this software for any
     9   * purpose with or without fee is hereby granted, provided that the above
    10   * copyright notice and this permission notice appear in all copies.
    11   *
    12   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
    13   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    14   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    15   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    16   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    17   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    18   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    19   */
    20  
    21  package fpdf
    22  
    23  import (
    24  	"fmt"
    25  	"strings"
    26  )
    27  
    28  func (f *Fpdf) pngColorSpace(ct byte) (colspace string, colorVal int) {
    29  	colorVal = 1
    30  	switch ct {
    31  	case 0, 4:
    32  		colspace = "DeviceGray"
    33  	case 2, 6:
    34  		colspace = "DeviceRGB"
    35  		colorVal = 3
    36  	case 3:
    37  		colspace = "Indexed"
    38  	default:
    39  		f.err = fmt.Errorf("unknown color type in PNG buffer: %d", ct)
    40  	}
    41  	return
    42  }
    43  
    44  func (f *Fpdf) parsepngstream(r *rbuffer, readdpi bool) (info *ImageInfoType) {
    45  	info = f.newImageInfo()
    46  	// 	Check signature
    47  	if string(r.Next(8)) != "\x89PNG\x0d\x0a\x1a\x0a" {
    48  		f.err = fmt.Errorf("not a PNG buffer")
    49  		return
    50  	}
    51  	// Read header chunk
    52  	_ = r.Next(4)
    53  	if string(r.Next(4)) != "IHDR" {
    54  		f.err = fmt.Errorf("incorrect PNG buffer")
    55  		return
    56  	}
    57  	w := r.i32()
    58  	h := r.i32()
    59  	bpc := r.u8()
    60  	if bpc > 8 {
    61  		if f.pdfVersion < pdfVers1_5 {
    62  			f.pdfVersion = pdfVers1_5
    63  		}
    64  	}
    65  	ct := r.u8()
    66  	var colspace string
    67  	var colorVal int
    68  	colspace, colorVal = f.pngColorSpace(ct)
    69  	if f.err != nil {
    70  		return
    71  	}
    72  	if r.u8() != 0 {
    73  		f.err = fmt.Errorf("'unknown compression method in PNG buffer")
    74  		return
    75  	}
    76  	if r.u8() != 0 {
    77  		f.err = fmt.Errorf("'unknown filter method in PNG buffer")
    78  		return
    79  	}
    80  	if r.u8() != 0 {
    81  		f.err = fmt.Errorf("interlacing not supported in PNG buffer")
    82  		return
    83  	}
    84  	_ = r.Next(4)
    85  	dp := sprintf("/Predictor 15 /Colors %d /BitsPerComponent %d /Columns %d", colorVal, bpc, w)
    86  	// Scan chunks looking for palette, transparency and image data
    87  	var (
    88  		pal  []byte
    89  		trns []int
    90  		npix = w * h
    91  		data = make([]byte, 0, npix/8)
    92  		loop = true
    93  	)
    94  	for loop {
    95  		n := int(r.i32())
    96  		// dbg("Loop [%d]", n)
    97  		switch string(r.Next(4)) {
    98  		case "PLTE":
    99  			// dbg("PLTE")
   100  			// Read palette
   101  			pal = r.Next(n)
   102  			_ = r.Next(4)
   103  		case "tRNS":
   104  			// dbg("tRNS")
   105  			// Read transparency info
   106  			t := r.Next(n)
   107  			switch ct {
   108  			case 0:
   109  				trns = []int{int(t[1])} // ord(substr($t,1,1)));
   110  			case 2:
   111  				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)));
   112  			default:
   113  				pos := strings.Index(string(t), "\x00")
   114  				if pos >= 0 {
   115  					trns = []int{pos} // array($pos);
   116  				}
   117  			}
   118  			_ = r.Next(4)
   119  		case "IDAT":
   120  			// dbg("IDAT")
   121  			// Read image data block
   122  			data = append(data, r.Next(n)...)
   123  			_ = r.Next(4)
   124  		case "IEND":
   125  			// dbg("IEND")
   126  			loop = false
   127  		case "pHYs":
   128  			// dbg("pHYs")
   129  			// png files theoretically support different x/y dpi
   130  			// but we ignore files like this
   131  			// but if they're the same then we can stamp our info
   132  			// object with it
   133  			x := int(r.i32())
   134  			y := int(r.i32())
   135  			units := r.u8()
   136  			// fmt.Printf("got a pHYs block, x=%d, y=%d, u=%d, readdpi=%t\n",
   137  			// x, y, int(units), readdpi)
   138  			// only modify the info block if the user wants us to
   139  			if x == y && readdpi {
   140  				switch units {
   141  				// if units is 1 then measurement is px/meter
   142  				case 1:
   143  					info.dpi = float64(x) / 39.3701 // inches per meter
   144  				default:
   145  					info.dpi = float64(x)
   146  				}
   147  			}
   148  			_ = r.Next(4)
   149  		default:
   150  			// dbg("default")
   151  			_ = r.Next(n + 4)
   152  		}
   153  		if loop {
   154  			loop = n > 0
   155  		}
   156  	}
   157  	if colspace == "Indexed" && len(pal) == 0 {
   158  		f.err = fmt.Errorf("missing palette in PNG buffer")
   159  	}
   160  	info.w = float64(w)
   161  	info.h = float64(h)
   162  	info.cs = colspace
   163  	info.bpc = int(bpc)
   164  	info.f = "FlateDecode"
   165  	info.dp = dp
   166  	info.pal = pal
   167  	info.trns = trns
   168  	// dbg("ct [%d]", ct)
   169  	if ct >= 4 {
   170  		// Separate alpha and color channels
   171  		mem, err := xmem.uncompress(data)
   172  		if err != nil {
   173  			f.err = err
   174  			return
   175  		}
   176  		data = mem.bytes()
   177  		var (
   178  			color wbuffer
   179  			alpha wbuffer
   180  		)
   181  		if ct == 4 {
   182  			// Gray image
   183  			width := int(w)
   184  			height := int(h)
   185  			length := 2 * width
   186  			sz := height * (width + 1)
   187  			color.p = data[:sz] // reuse decompressed data buffer.
   188  			alpha.p = make([]byte, sz)
   189  			var pos, elPos int
   190  			for i := 0; i < height; i++ {
   191  				pos = (1 + length) * i
   192  				color.u8(data[pos])
   193  				alpha.u8(data[pos])
   194  				elPos = pos + 1
   195  				for k := 0; k < width; k++ {
   196  					color.u8(data[elPos])
   197  					alpha.u8(data[elPos+1])
   198  					elPos += 2
   199  				}
   200  			}
   201  		} else {
   202  			// RGB image
   203  			width := int(w)
   204  			height := int(h)
   205  			length := 4 * width
   206  			sz := width * height
   207  			color.p = data[:sz*3+height] // reuse decompressed data buffer.
   208  			alpha.p = make([]byte, sz+height)
   209  			var pos, elPos int
   210  			for i := 0; i < height; i++ {
   211  				pos = (1 + length) * i
   212  				color.u8(data[pos])
   213  				alpha.u8(data[pos])
   214  				elPos = pos + 1
   215  				for k := 0; k < width; k++ {
   216  					tmp := data[elPos : elPos+4]
   217  					color.u8(tmp[0])
   218  					color.u8(tmp[1])
   219  					color.u8(tmp[2])
   220  					alpha.u8(tmp[3])
   221  					elPos += 4
   222  				}
   223  			}
   224  		}
   225  
   226  		xc := xmem.compress(color.bytes())
   227  		data = xc.copy()
   228  		xc.release()
   229  
   230  		// release uncompressed data buffer, after the color buffer
   231  		// has been compressed.
   232  		mem.release()
   233  
   234  		xa := xmem.compress(alpha.bytes())
   235  		info.smask = xa.copy()
   236  		xa.release()
   237  
   238  		if f.pdfVersion < pdfVers1_4 {
   239  			f.pdfVersion = pdfVers1_4
   240  		}
   241  	}
   242  	info.data = data
   243  	return
   244  }