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 }