...

Source file src/image/png/writer.go

Documentation: image/png

		 1  // Copyright 2009 The Go Authors. All rights reserved.
		 2  // Use of this source code is governed by a BSD-style
		 3  // license that can be found in the LICENSE file.
		 4  
		 5  package png
		 6  
		 7  import (
		 8  	"bufio"
		 9  	"compress/zlib"
		10  	"encoding/binary"
		11  	"hash/crc32"
		12  	"image"
		13  	"image/color"
		14  	"io"
		15  	"strconv"
		16  )
		17  
		18  // Encoder configures encoding PNG images.
		19  type Encoder struct {
		20  	CompressionLevel CompressionLevel
		21  
		22  	// BufferPool optionally specifies a buffer pool to get temporary
		23  	// EncoderBuffers when encoding an image.
		24  	BufferPool EncoderBufferPool
		25  }
		26  
		27  // EncoderBufferPool is an interface for getting and returning temporary
		28  // instances of the EncoderBuffer struct. This can be used to reuse buffers
		29  // when encoding multiple images.
		30  type EncoderBufferPool interface {
		31  	Get() *EncoderBuffer
		32  	Put(*EncoderBuffer)
		33  }
		34  
		35  // EncoderBuffer holds the buffers used for encoding PNG images.
		36  type EncoderBuffer encoder
		37  
		38  type encoder struct {
		39  	enc		 *Encoder
		40  	w			 io.Writer
		41  	m			 image.Image
		42  	cb			int
		43  	err		 error
		44  	header	[8]byte
		45  	footer	[4]byte
		46  	tmp		 [4 * 256]byte
		47  	cr			[nFilter][]uint8
		48  	pr			[]uint8
		49  	zw			*zlib.Writer
		50  	zwLevel int
		51  	bw			*bufio.Writer
		52  }
		53  
		54  // CompressionLevel indicates the compression level.
		55  type CompressionLevel int
		56  
		57  const (
		58  	DefaultCompression CompressionLevel = 0
		59  	NoCompression			CompressionLevel = -1
		60  	BestSpeed					CompressionLevel = -2
		61  	BestCompression		CompressionLevel = -3
		62  
		63  	// Positive CompressionLevel values are reserved to mean a numeric zlib
		64  	// compression level, although that is not implemented yet.
		65  )
		66  
		67  type opaquer interface {
		68  	Opaque() bool
		69  }
		70  
		71  // Returns whether or not the image is fully opaque.
		72  func opaque(m image.Image) bool {
		73  	if o, ok := m.(opaquer); ok {
		74  		return o.Opaque()
		75  	}
		76  	b := m.Bounds()
		77  	for y := b.Min.Y; y < b.Max.Y; y++ {
		78  		for x := b.Min.X; x < b.Max.X; x++ {
		79  			_, _, _, a := m.At(x, y).RGBA()
		80  			if a != 0xffff {
		81  				return false
		82  			}
		83  		}
		84  	}
		85  	return true
		86  }
		87  
		88  // The absolute value of a byte interpreted as a signed int8.
		89  func abs8(d uint8) int {
		90  	if d < 128 {
		91  		return int(d)
		92  	}
		93  	return 256 - int(d)
		94  }
		95  
		96  func (e *encoder) writeChunk(b []byte, name string) {
		97  	if e.err != nil {
		98  		return
		99  	}
	 100  	n := uint32(len(b))
	 101  	if int(n) != len(b) {
	 102  		e.err = UnsupportedError(name + " chunk is too large: " + strconv.Itoa(len(b)))
	 103  		return
	 104  	}
	 105  	binary.BigEndian.PutUint32(e.header[:4], n)
	 106  	e.header[4] = name[0]
	 107  	e.header[5] = name[1]
	 108  	e.header[6] = name[2]
	 109  	e.header[7] = name[3]
	 110  	crc := crc32.NewIEEE()
	 111  	crc.Write(e.header[4:8])
	 112  	crc.Write(b)
	 113  	binary.BigEndian.PutUint32(e.footer[:4], crc.Sum32())
	 114  
	 115  	_, e.err = e.w.Write(e.header[:8])
	 116  	if e.err != nil {
	 117  		return
	 118  	}
	 119  	_, e.err = e.w.Write(b)
	 120  	if e.err != nil {
	 121  		return
	 122  	}
	 123  	_, e.err = e.w.Write(e.footer[:4])
	 124  }
	 125  
	 126  func (e *encoder) writeIHDR() {
	 127  	b := e.m.Bounds()
	 128  	binary.BigEndian.PutUint32(e.tmp[0:4], uint32(b.Dx()))
	 129  	binary.BigEndian.PutUint32(e.tmp[4:8], uint32(b.Dy()))
	 130  	// Set bit depth and color type.
	 131  	switch e.cb {
	 132  	case cbG8:
	 133  		e.tmp[8] = 8
	 134  		e.tmp[9] = ctGrayscale
	 135  	case cbTC8:
	 136  		e.tmp[8] = 8
	 137  		e.tmp[9] = ctTrueColor
	 138  	case cbP8:
	 139  		e.tmp[8] = 8
	 140  		e.tmp[9] = ctPaletted
	 141  	case cbP4:
	 142  		e.tmp[8] = 4
	 143  		e.tmp[9] = ctPaletted
	 144  	case cbP2:
	 145  		e.tmp[8] = 2
	 146  		e.tmp[9] = ctPaletted
	 147  	case cbP1:
	 148  		e.tmp[8] = 1
	 149  		e.tmp[9] = ctPaletted
	 150  	case cbTCA8:
	 151  		e.tmp[8] = 8
	 152  		e.tmp[9] = ctTrueColorAlpha
	 153  	case cbG16:
	 154  		e.tmp[8] = 16
	 155  		e.tmp[9] = ctGrayscale
	 156  	case cbTC16:
	 157  		e.tmp[8] = 16
	 158  		e.tmp[9] = ctTrueColor
	 159  	case cbTCA16:
	 160  		e.tmp[8] = 16
	 161  		e.tmp[9] = ctTrueColorAlpha
	 162  	}
	 163  	e.tmp[10] = 0 // default compression method
	 164  	e.tmp[11] = 0 // default filter method
	 165  	e.tmp[12] = 0 // non-interlaced
	 166  	e.writeChunk(e.tmp[:13], "IHDR")
	 167  }
	 168  
	 169  func (e *encoder) writePLTEAndTRNS(p color.Palette) {
	 170  	if len(p) < 1 || len(p) > 256 {
	 171  		e.err = FormatError("bad palette length: " + strconv.Itoa(len(p)))
	 172  		return
	 173  	}
	 174  	last := -1
	 175  	for i, c := range p {
	 176  		c1 := color.NRGBAModel.Convert(c).(color.NRGBA)
	 177  		e.tmp[3*i+0] = c1.R
	 178  		e.tmp[3*i+1] = c1.G
	 179  		e.tmp[3*i+2] = c1.B
	 180  		if c1.A != 0xff {
	 181  			last = i
	 182  		}
	 183  		e.tmp[3*256+i] = c1.A
	 184  	}
	 185  	e.writeChunk(e.tmp[:3*len(p)], "PLTE")
	 186  	if last != -1 {
	 187  		e.writeChunk(e.tmp[3*256:3*256+1+last], "tRNS")
	 188  	}
	 189  }
	 190  
	 191  // An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks,
	 192  // including an 8-byte header and 4-byte CRC checksum per Write call. Such calls
	 193  // should be relatively infrequent, since writeIDATs uses a bufio.Writer.
	 194  //
	 195  // This method should only be called from writeIDATs (via writeImage).
	 196  // No other code should treat an encoder as an io.Writer.
	 197  func (e *encoder) Write(b []byte) (int, error) {
	 198  	e.writeChunk(b, "IDAT")
	 199  	if e.err != nil {
	 200  		return 0, e.err
	 201  	}
	 202  	return len(b), nil
	 203  }
	 204  
	 205  // Chooses the filter to use for encoding the current row, and applies it.
	 206  // The return value is the index of the filter and also of the row in cr that has had it applied.
	 207  func filter(cr *[nFilter][]byte, pr []byte, bpp int) int {
	 208  	// We try all five filter types, and pick the one that minimizes the sum of absolute differences.
	 209  	// This is the same heuristic that libpng uses, although the filters are attempted in order of
	 210  	// estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than
	 211  	// in their enumeration order (ftNone, ftSub, ftUp, ftAverage, ftPaeth).
	 212  	cdat0 := cr[0][1:]
	 213  	cdat1 := cr[1][1:]
	 214  	cdat2 := cr[2][1:]
	 215  	cdat3 := cr[3][1:]
	 216  	cdat4 := cr[4][1:]
	 217  	pdat := pr[1:]
	 218  	n := len(cdat0)
	 219  
	 220  	// The up filter.
	 221  	sum := 0
	 222  	for i := 0; i < n; i++ {
	 223  		cdat2[i] = cdat0[i] - pdat[i]
	 224  		sum += abs8(cdat2[i])
	 225  	}
	 226  	best := sum
	 227  	filter := ftUp
	 228  
	 229  	// The Paeth filter.
	 230  	sum = 0
	 231  	for i := 0; i < bpp; i++ {
	 232  		cdat4[i] = cdat0[i] - pdat[i]
	 233  		sum += abs8(cdat4[i])
	 234  	}
	 235  	for i := bpp; i < n; i++ {
	 236  		cdat4[i] = cdat0[i] - paeth(cdat0[i-bpp], pdat[i], pdat[i-bpp])
	 237  		sum += abs8(cdat4[i])
	 238  		if sum >= best {
	 239  			break
	 240  		}
	 241  	}
	 242  	if sum < best {
	 243  		best = sum
	 244  		filter = ftPaeth
	 245  	}
	 246  
	 247  	// The none filter.
	 248  	sum = 0
	 249  	for i := 0; i < n; i++ {
	 250  		sum += abs8(cdat0[i])
	 251  		if sum >= best {
	 252  			break
	 253  		}
	 254  	}
	 255  	if sum < best {
	 256  		best = sum
	 257  		filter = ftNone
	 258  	}
	 259  
	 260  	// The sub filter.
	 261  	sum = 0
	 262  	for i := 0; i < bpp; i++ {
	 263  		cdat1[i] = cdat0[i]
	 264  		sum += abs8(cdat1[i])
	 265  	}
	 266  	for i := bpp; i < n; i++ {
	 267  		cdat1[i] = cdat0[i] - cdat0[i-bpp]
	 268  		sum += abs8(cdat1[i])
	 269  		if sum >= best {
	 270  			break
	 271  		}
	 272  	}
	 273  	if sum < best {
	 274  		best = sum
	 275  		filter = ftSub
	 276  	}
	 277  
	 278  	// The average filter.
	 279  	sum = 0
	 280  	for i := 0; i < bpp; i++ {
	 281  		cdat3[i] = cdat0[i] - pdat[i]/2
	 282  		sum += abs8(cdat3[i])
	 283  	}
	 284  	for i := bpp; i < n; i++ {
	 285  		cdat3[i] = cdat0[i] - uint8((int(cdat0[i-bpp])+int(pdat[i]))/2)
	 286  		sum += abs8(cdat3[i])
	 287  		if sum >= best {
	 288  			break
	 289  		}
	 290  	}
	 291  	if sum < best {
	 292  		filter = ftAverage
	 293  	}
	 294  
	 295  	return filter
	 296  }
	 297  
	 298  func zeroMemory(v []uint8) {
	 299  	for i := range v {
	 300  		v[i] = 0
	 301  	}
	 302  }
	 303  
	 304  func (e *encoder) writeImage(w io.Writer, m image.Image, cb int, level int) error {
	 305  	if e.zw == nil || e.zwLevel != level {
	 306  		zw, err := zlib.NewWriterLevel(w, level)
	 307  		if err != nil {
	 308  			return err
	 309  		}
	 310  		e.zw = zw
	 311  		e.zwLevel = level
	 312  	} else {
	 313  		e.zw.Reset(w)
	 314  	}
	 315  	defer e.zw.Close()
	 316  
	 317  	bitsPerPixel := 0
	 318  
	 319  	switch cb {
	 320  	case cbG8:
	 321  		bitsPerPixel = 8
	 322  	case cbTC8:
	 323  		bitsPerPixel = 24
	 324  	case cbP8:
	 325  		bitsPerPixel = 8
	 326  	case cbP4:
	 327  		bitsPerPixel = 4
	 328  	case cbP2:
	 329  		bitsPerPixel = 2
	 330  	case cbP1:
	 331  		bitsPerPixel = 1
	 332  	case cbTCA8:
	 333  		bitsPerPixel = 32
	 334  	case cbTC16:
	 335  		bitsPerPixel = 48
	 336  	case cbTCA16:
	 337  		bitsPerPixel = 64
	 338  	case cbG16:
	 339  		bitsPerPixel = 16
	 340  	}
	 341  
	 342  	// cr[*] and pr are the bytes for the current and previous row.
	 343  	// cr[0] is unfiltered (or equivalently, filtered with the ftNone filter).
	 344  	// cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the
	 345  	// other PNG filter types. These buffers are allocated once and re-used for each row.
	 346  	// The +1 is for the per-row filter type, which is at cr[*][0].
	 347  	b := m.Bounds()
	 348  	sz := 1 + (bitsPerPixel*b.Dx()+7)/8
	 349  	for i := range e.cr {
	 350  		if cap(e.cr[i]) < sz {
	 351  			e.cr[i] = make([]uint8, sz)
	 352  		} else {
	 353  			e.cr[i] = e.cr[i][:sz]
	 354  		}
	 355  		e.cr[i][0] = uint8(i)
	 356  	}
	 357  	cr := e.cr
	 358  	if cap(e.pr) < sz {
	 359  		e.pr = make([]uint8, sz)
	 360  	} else {
	 361  		e.pr = e.pr[:sz]
	 362  		zeroMemory(e.pr)
	 363  	}
	 364  	pr := e.pr
	 365  
	 366  	gray, _ := m.(*image.Gray)
	 367  	rgba, _ := m.(*image.RGBA)
	 368  	paletted, _ := m.(*image.Paletted)
	 369  	nrgba, _ := m.(*image.NRGBA)
	 370  
	 371  	for y := b.Min.Y; y < b.Max.Y; y++ {
	 372  		// Convert from colors to bytes.
	 373  		i := 1
	 374  		switch cb {
	 375  		case cbG8:
	 376  			if gray != nil {
	 377  				offset := (y - b.Min.Y) * gray.Stride
	 378  				copy(cr[0][1:], gray.Pix[offset:offset+b.Dx()])
	 379  			} else {
	 380  				for x := b.Min.X; x < b.Max.X; x++ {
	 381  					c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
	 382  					cr[0][i] = c.Y
	 383  					i++
	 384  				}
	 385  			}
	 386  		case cbTC8:
	 387  			// We have previously verified that the alpha value is fully opaque.
	 388  			cr0 := cr[0]
	 389  			stride, pix := 0, []byte(nil)
	 390  			if rgba != nil {
	 391  				stride, pix = rgba.Stride, rgba.Pix
	 392  			} else if nrgba != nil {
	 393  				stride, pix = nrgba.Stride, nrgba.Pix
	 394  			}
	 395  			if stride != 0 {
	 396  				j0 := (y - b.Min.Y) * stride
	 397  				j1 := j0 + b.Dx()*4
	 398  				for j := j0; j < j1; j += 4 {
	 399  					cr0[i+0] = pix[j+0]
	 400  					cr0[i+1] = pix[j+1]
	 401  					cr0[i+2] = pix[j+2]
	 402  					i += 3
	 403  				}
	 404  			} else {
	 405  				for x := b.Min.X; x < b.Max.X; x++ {
	 406  					r, g, b, _ := m.At(x, y).RGBA()
	 407  					cr0[i+0] = uint8(r >> 8)
	 408  					cr0[i+1] = uint8(g >> 8)
	 409  					cr0[i+2] = uint8(b >> 8)
	 410  					i += 3
	 411  				}
	 412  			}
	 413  		case cbP8:
	 414  			if paletted != nil {
	 415  				offset := (y - b.Min.Y) * paletted.Stride
	 416  				copy(cr[0][1:], paletted.Pix[offset:offset+b.Dx()])
	 417  			} else {
	 418  				pi := m.(image.PalettedImage)
	 419  				for x := b.Min.X; x < b.Max.X; x++ {
	 420  					cr[0][i] = pi.ColorIndexAt(x, y)
	 421  					i += 1
	 422  				}
	 423  			}
	 424  
	 425  		case cbP4, cbP2, cbP1:
	 426  			pi := m.(image.PalettedImage)
	 427  
	 428  			var a uint8
	 429  			var c int
	 430  			pixelsPerByte := 8 / bitsPerPixel
	 431  			for x := b.Min.X; x < b.Max.X; x++ {
	 432  				a = a<<uint(bitsPerPixel) | pi.ColorIndexAt(x, y)
	 433  				c++
	 434  				if c == pixelsPerByte {
	 435  					cr[0][i] = a
	 436  					i += 1
	 437  					a = 0
	 438  					c = 0
	 439  				}
	 440  			}
	 441  			if c != 0 {
	 442  				for c != pixelsPerByte {
	 443  					a = a << uint(bitsPerPixel)
	 444  					c++
	 445  				}
	 446  				cr[0][i] = a
	 447  			}
	 448  
	 449  		case cbTCA8:
	 450  			if nrgba != nil {
	 451  				offset := (y - b.Min.Y) * nrgba.Stride
	 452  				copy(cr[0][1:], nrgba.Pix[offset:offset+b.Dx()*4])
	 453  			} else {
	 454  				// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
	 455  				for x := b.Min.X; x < b.Max.X; x++ {
	 456  					c := color.NRGBAModel.Convert(m.At(x, y)).(color.NRGBA)
	 457  					cr[0][i+0] = c.R
	 458  					cr[0][i+1] = c.G
	 459  					cr[0][i+2] = c.B
	 460  					cr[0][i+3] = c.A
	 461  					i += 4
	 462  				}
	 463  			}
	 464  		case cbG16:
	 465  			for x := b.Min.X; x < b.Max.X; x++ {
	 466  				c := color.Gray16Model.Convert(m.At(x, y)).(color.Gray16)
	 467  				cr[0][i+0] = uint8(c.Y >> 8)
	 468  				cr[0][i+1] = uint8(c.Y)
	 469  				i += 2
	 470  			}
	 471  		case cbTC16:
	 472  			// We have previously verified that the alpha value is fully opaque.
	 473  			for x := b.Min.X; x < b.Max.X; x++ {
	 474  				r, g, b, _ := m.At(x, y).RGBA()
	 475  				cr[0][i+0] = uint8(r >> 8)
	 476  				cr[0][i+1] = uint8(r)
	 477  				cr[0][i+2] = uint8(g >> 8)
	 478  				cr[0][i+3] = uint8(g)
	 479  				cr[0][i+4] = uint8(b >> 8)
	 480  				cr[0][i+5] = uint8(b)
	 481  				i += 6
	 482  			}
	 483  		case cbTCA16:
	 484  			// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.
	 485  			for x := b.Min.X; x < b.Max.X; x++ {
	 486  				c := color.NRGBA64Model.Convert(m.At(x, y)).(color.NRGBA64)
	 487  				cr[0][i+0] = uint8(c.R >> 8)
	 488  				cr[0][i+1] = uint8(c.R)
	 489  				cr[0][i+2] = uint8(c.G >> 8)
	 490  				cr[0][i+3] = uint8(c.G)
	 491  				cr[0][i+4] = uint8(c.B >> 8)
	 492  				cr[0][i+5] = uint8(c.B)
	 493  				cr[0][i+6] = uint8(c.A >> 8)
	 494  				cr[0][i+7] = uint8(c.A)
	 495  				i += 8
	 496  			}
	 497  		}
	 498  
	 499  		// Apply the filter.
	 500  		// Skip filter for NoCompression and paletted images (cbP8) as
	 501  		// "filters are rarely useful on palette images" and will result
	 502  		// in larger files (see http://www.libpng.org/pub/png/book/chapter09.html).
	 503  		f := ftNone
	 504  		if level != zlib.NoCompression && cb != cbP8 && cb != cbP4 && cb != cbP2 && cb != cbP1 {
	 505  			// Since we skip paletted images we don't have to worry about
	 506  			// bitsPerPixel not being a multiple of 8
	 507  			bpp := bitsPerPixel / 8
	 508  			f = filter(&cr, pr, bpp)
	 509  		}
	 510  
	 511  		// Write the compressed bytes.
	 512  		if _, err := e.zw.Write(cr[f]); err != nil {
	 513  			return err
	 514  		}
	 515  
	 516  		// The current row for y is the previous row for y+1.
	 517  		pr, cr[0] = cr[0], pr
	 518  	}
	 519  	return nil
	 520  }
	 521  
	 522  // Write the actual image data to one or more IDAT chunks.
	 523  func (e *encoder) writeIDATs() {
	 524  	if e.err != nil {
	 525  		return
	 526  	}
	 527  	if e.bw == nil {
	 528  		e.bw = bufio.NewWriterSize(e, 1<<15)
	 529  	} else {
	 530  		e.bw.Reset(e)
	 531  	}
	 532  	e.err = e.writeImage(e.bw, e.m, e.cb, levelToZlib(e.enc.CompressionLevel))
	 533  	if e.err != nil {
	 534  		return
	 535  	}
	 536  	e.err = e.bw.Flush()
	 537  }
	 538  
	 539  // This function is required because we want the zero value of
	 540  // Encoder.CompressionLevel to map to zlib.DefaultCompression.
	 541  func levelToZlib(l CompressionLevel) int {
	 542  	switch l {
	 543  	case DefaultCompression:
	 544  		return zlib.DefaultCompression
	 545  	case NoCompression:
	 546  		return zlib.NoCompression
	 547  	case BestSpeed:
	 548  		return zlib.BestSpeed
	 549  	case BestCompression:
	 550  		return zlib.BestCompression
	 551  	default:
	 552  		return zlib.DefaultCompression
	 553  	}
	 554  }
	 555  
	 556  func (e *encoder) writeIEND() { e.writeChunk(nil, "IEND") }
	 557  
	 558  // Encode writes the Image m to w in PNG format. Any Image may be
	 559  // encoded, but images that are not image.NRGBA might be encoded lossily.
	 560  func Encode(w io.Writer, m image.Image) error {
	 561  	var e Encoder
	 562  	return e.Encode(w, m)
	 563  }
	 564  
	 565  // Encode writes the Image m to w in PNG format.
	 566  func (enc *Encoder) Encode(w io.Writer, m image.Image) error {
	 567  	// Obviously, negative widths and heights are invalid. Furthermore, the PNG
	 568  	// spec section 11.2.2 says that zero is invalid. Excessively large images are
	 569  	// also rejected.
	 570  	mw, mh := int64(m.Bounds().Dx()), int64(m.Bounds().Dy())
	 571  	if mw <= 0 || mh <= 0 || mw >= 1<<32 || mh >= 1<<32 {
	 572  		return FormatError("invalid image size: " + strconv.FormatInt(mw, 10) + "x" + strconv.FormatInt(mh, 10))
	 573  	}
	 574  
	 575  	var e *encoder
	 576  	if enc.BufferPool != nil {
	 577  		buffer := enc.BufferPool.Get()
	 578  		e = (*encoder)(buffer)
	 579  
	 580  	}
	 581  	if e == nil {
	 582  		e = &encoder{}
	 583  	}
	 584  	if enc.BufferPool != nil {
	 585  		defer enc.BufferPool.Put((*EncoderBuffer)(e))
	 586  	}
	 587  
	 588  	e.enc = enc
	 589  	e.w = w
	 590  	e.m = m
	 591  
	 592  	var pal color.Palette
	 593  	// cbP8 encoding needs PalettedImage's ColorIndexAt method.
	 594  	if _, ok := m.(image.PalettedImage); ok {
	 595  		pal, _ = m.ColorModel().(color.Palette)
	 596  	}
	 597  	if pal != nil {
	 598  		if len(pal) <= 2 {
	 599  			e.cb = cbP1
	 600  		} else if len(pal) <= 4 {
	 601  			e.cb = cbP2
	 602  		} else if len(pal) <= 16 {
	 603  			e.cb = cbP4
	 604  		} else {
	 605  			e.cb = cbP8
	 606  		}
	 607  	} else {
	 608  		switch m.ColorModel() {
	 609  		case color.GrayModel:
	 610  			e.cb = cbG8
	 611  		case color.Gray16Model:
	 612  			e.cb = cbG16
	 613  		case color.RGBAModel, color.NRGBAModel, color.AlphaModel:
	 614  			if opaque(m) {
	 615  				e.cb = cbTC8
	 616  			} else {
	 617  				e.cb = cbTCA8
	 618  			}
	 619  		default:
	 620  			if opaque(m) {
	 621  				e.cb = cbTC16
	 622  			} else {
	 623  				e.cb = cbTCA16
	 624  			}
	 625  		}
	 626  	}
	 627  
	 628  	_, e.err = io.WriteString(w, pngHeader)
	 629  	e.writeIHDR()
	 630  	if pal != nil {
	 631  		e.writePLTEAndTRNS(pal)
	 632  	}
	 633  	e.writeIDATs()
	 634  	e.writeIEND()
	 635  	return e.err
	 636  }
	 637  

View as plain text