// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package pngimport ()// Encoder configures encoding PNG images.typeEncoderstruct { CompressionLevel CompressionLevel// BufferPool optionally specifies a buffer pool to get temporary // EncoderBuffers when encoding an image. BufferPool EncoderBufferPool}// EncoderBufferPool is an interface for getting and returning temporary// instances of the [EncoderBuffer] struct. This can be used to reuse buffers// when encoding multiple images.typeEncoderBufferPoolinterface {Get() *EncoderBufferPut(*EncoderBuffer)}// EncoderBuffer holds the buffers used for encoding PNG images.typeEncoderBufferencodertype encoder struct { enc *Encoder w io.Writer m image.Image cb int err error header [8]byte footer [4]byte tmp [4 * 256]byte cr [nFilter][]uint8 pr []uint8 zw *zlib.Writer zwLevel int bw *bufio.Writer}// CompressionLevel indicates the compression level.typeCompressionLevelintconst (DefaultCompressionCompressionLevel = 0NoCompressionCompressionLevel = -1BestSpeedCompressionLevel = -2BestCompressionCompressionLevel = -3// Positive CompressionLevel values are reserved to mean a numeric zlib // compression level, although that is not implemented yet.)type opaquer interface { Opaque() bool}// Returns whether or not the image is fully opaque.func opaque( image.Image) bool {if , := .(opaquer); {return .Opaque() } := .Bounds()for := .Min.Y; < .Max.Y; ++ {for := .Min.X; < .Max.X; ++ { , , , := .At(, ).RGBA()if != 0xffff {returnfalse } } }returntrue}// The absolute value of a byte interpreted as a signed int8.func abs8( uint8) int {if < 128 {returnint() }return256 - int()}func ( *encoder) ( []byte, string) {if .err != nil {return } := uint32(len())ifint() != len() { .err = UnsupportedError( + " chunk is too large: " + strconv.Itoa(len()))return }binary.BigEndian.PutUint32(.header[:4], ) .header[4] = [0] .header[5] = [1] .header[6] = [2] .header[7] = [3] := crc32.NewIEEE() .Write(.header[4:8]) .Write()binary.BigEndian.PutUint32(.footer[:4], .Sum32()) _, .err = .w.Write(.header[:8])if .err != nil {return } _, .err = .w.Write()if .err != nil {return } _, .err = .w.Write(.footer[:4])}func ( *encoder) () { := .m.Bounds()binary.BigEndian.PutUint32(.tmp[0:4], uint32(.Dx()))binary.BigEndian.PutUint32(.tmp[4:8], uint32(.Dy()))// Set bit depth and color type.switch .cb {casecbG8: .tmp[8] = 8 .tmp[9] = ctGrayscalecasecbTC8: .tmp[8] = 8 .tmp[9] = ctTrueColorcasecbP8: .tmp[8] = 8 .tmp[9] = ctPalettedcasecbP4: .tmp[8] = 4 .tmp[9] = ctPalettedcasecbP2: .tmp[8] = 2 .tmp[9] = ctPalettedcasecbP1: .tmp[8] = 1 .tmp[9] = ctPalettedcasecbTCA8: .tmp[8] = 8 .tmp[9] = ctTrueColorAlphacasecbG16: .tmp[8] = 16 .tmp[9] = ctGrayscalecasecbTC16: .tmp[8] = 16 .tmp[9] = ctTrueColorcasecbTCA16: .tmp[8] = 16 .tmp[9] = ctTrueColorAlpha } .tmp[10] = 0// default compression method .tmp[11] = 0// default filter method .tmp[12] = 0// non-interlaced .writeChunk(.tmp[:13], "IHDR")}func ( *encoder) ( color.Palette) {iflen() < 1 || len() > 256 { .err = FormatError("bad palette length: " + strconv.Itoa(len()))return } := -1for , := range { := color.NRGBAModel.Convert().(color.NRGBA) .tmp[3*+0] = .R .tmp[3*+1] = .G .tmp[3*+2] = .Bif .A != 0xff { = } .tmp[3*256+] = .A } .writeChunk(.tmp[:3*len()], "PLTE")if != -1 { .writeChunk(.tmp[3*256:3*256+1+], "tRNS") }}// An encoder is an io.Writer that satisfies writes by writing PNG IDAT chunks,// including an 8-byte header and 4-byte CRC checksum per Write call. Such calls// should be relatively infrequent, since writeIDATs uses a [bufio.Writer].//// This method should only be called from writeIDATs (via writeImage).// No other code should treat an encoder as an io.Writer.func ( *encoder) ( []byte) (int, error) { .writeChunk(, "IDAT")if .err != nil {return0, .err }returnlen(), nil}// Chooses the filter to use for encoding the current row, and applies it.// The return value is the index of the filter and also of the row in cr that has had it applied.func filter( *[nFilter][]byte, []byte, int) int {// We try all five filter types, and pick the one that minimizes the sum of absolute differences. // This is the same heuristic that libpng uses, although the filters are attempted in order of // estimated most likely to be minimal (ftUp, ftPaeth, ftNone, ftSub, ftAverage), rather than // in their enumeration order (ftNone, ftSub, ftUp, ftAverage, ftPaeth). := [0][1:] := [1][1:] := [2][1:] := [3][1:] := [4][1:] := [1:] := len()// The up filter. := 0for := 0; < ; ++ { [] = [] - [] += abs8([]) } := := ftUp// The Paeth filter. = 0for := 0; < ; ++ { [] = [] - [] += abs8([]) }for := ; < ; ++ { [] = [] - paeth([-], [], [-]) += abs8([])if >= {break } }if < { = = ftPaeth }// The none filter. = 0for := 0; < ; ++ { += abs8([])if >= {break } }if < { = = ftNone }// The sub filter. = 0for := 0; < ; ++ { [] = [] += abs8([]) }for := ; < ; ++ { [] = [] - [-] += abs8([])if >= {break } }if < { = = ftSub }// The average filter. = 0for := 0; < ; ++ { [] = [] - []/2 += abs8([]) }for := ; < ; ++ { [] = [] - uint8((int([-])+int([]))/2) += abs8([])if >= {break } }if < { = ftAverage }return}func ( *encoder) ( io.Writer, image.Image, int, int) error {if .zw == nil || .zwLevel != { , := zlib.NewWriterLevel(, )if != nil {return } .zw = .zwLevel = } else { .zw.Reset() }defer .zw.Close() := 0switch {casecbG8: = 8casecbTC8: = 24casecbP8: = 8casecbP4: = 4casecbP2: = 2casecbP1: = 1casecbTCA8: = 32casecbTC16: = 48casecbTCA16: = 64casecbG16: = 16 }// cr[*] and pr are the bytes for the current and previous row. // cr[0] is unfiltered (or equivalently, filtered with the ftNone filter). // cr[ft], for non-zero filter types ft, are buffers for transforming cr[0] under the // other PNG filter types. These buffers are allocated once and re-used for each row. // The +1 is for the per-row filter type, which is at cr[*][0]. := .Bounds() := 1 + (*.Dx()+7)/8for := range .cr {ifcap(.cr[]) < { .cr[] = make([]uint8, ) } else { .cr[] = .cr[][:] } .cr[][0] = uint8() } := .crifcap(.pr) < { .pr = make([]uint8, ) } else { .pr = .pr[:]clear(.pr) } := .pr , := .(*image.Gray) , := .(*image.RGBA) , := .(*image.Paletted) , := .(*image.NRGBA)for := .Min.Y; < .Max.Y; ++ {// Convert from colors to bytes. := 1switch {casecbG8:if != nil { := ( - .Min.Y) * .Stridecopy([0][1:], .Pix[:+.Dx()]) } else {for := .Min.X; < .Max.X; ++ { := color.GrayModel.Convert(.At(, )).(color.Gray) [0][] = .Y ++ } }casecbTC8:// We have previously verified that the alpha value is fully opaque. := [0] , := 0, []byte(nil)if != nil { , = .Stride, .Pix } elseif != nil { , = .Stride, .Pix }if != 0 { := ( - .Min.Y) * := + .Dx()*4for := ; < ; += 4 { [+0] = [+0] [+1] = [+1] [+2] = [+2] += 3 } } else {for := .Min.X; < .Max.X; ++ { , , , := .At(, ).RGBA() [+0] = uint8( >> 8) [+1] = uint8( >> 8) [+2] = uint8( >> 8) += 3 } }casecbP8:if != nil { := ( - .Min.Y) * .Stridecopy([0][1:], .Pix[:+.Dx()]) } else { := .(image.PalettedImage)for := .Min.X; < .Max.X; ++ { [0][] = .ColorIndexAt(, ) += 1 } }casecbP4, cbP2, cbP1: := .(image.PalettedImage)varuint8varint := 8 / for := .Min.X; < .Max.X; ++ { = <<uint() | .ColorIndexAt(, ) ++if == { [0][] = += 1 = 0 = 0 } }if != 0 {for != { = << uint() ++ } [0][] = }casecbTCA8:if != nil { := ( - .Min.Y) * .Stridecopy([0][1:], .Pix[:+.Dx()*4]) } elseif != nil { := [0][1:] := .Pix[.PixOffset(.Min.X, ):.PixOffset(.Max.X, )]for ; len() >= 4; , = [4:], [4:] { := (*[4]byte)() := (*[4]byte)()if [3] == 0x00 { [0] = 0 [1] = 0 [2] = 0 [3] = 0 } elseif [3] == 0xff {copy([:], [:]) } else {// This code does the same as color.NRGBAModel.Convert( // rgba.At(x, y)).(color.NRGBA) but with no extra memory // allocations or interface/function call overhead. // // The multiplier m combines 0x101 (which converts // 8-bit color to 16-bit color) and 0xffff (which, when // combined with the division-by-a, converts from // alpha-premultiplied to non-alpha-premultiplied).const = 0x101 * 0xffff := uint32([3]) * 0x101 [0] = uint8((uint32([0]) * / ) >> 8) [1] = uint8((uint32([1]) * / ) >> 8) [2] = uint8((uint32([2]) * / ) >> 8) [3] = [3] } } } else {// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.for := .Min.X; < .Max.X; ++ { := color.NRGBAModel.Convert(.At(, )).(color.NRGBA) [0][+0] = .R [0][+1] = .G [0][+2] = .B [0][+3] = .A += 4 } }casecbG16:for := .Min.X; < .Max.X; ++ { := color.Gray16Model.Convert(.At(, )).(color.Gray16) [0][+0] = uint8(.Y >> 8) [0][+1] = uint8(.Y) += 2 }casecbTC16:// We have previously verified that the alpha value is fully opaque.for := .Min.X; < .Max.X; ++ { , , , := .At(, ).RGBA() [0][+0] = uint8( >> 8) [0][+1] = uint8() [0][+2] = uint8( >> 8) [0][+3] = uint8() [0][+4] = uint8( >> 8) [0][+5] = uint8() += 6 }casecbTCA16:// Convert from image.Image (which is alpha-premultiplied) to PNG's non-alpha-premultiplied.for := .Min.X; < .Max.X; ++ { := color.NRGBA64Model.Convert(.At(, )).(color.NRGBA64) [0][+0] = uint8(.R >> 8) [0][+1] = uint8(.R) [0][+2] = uint8(.G >> 8) [0][+3] = uint8(.G) [0][+4] = uint8(.B >> 8) [0][+5] = uint8(.B) [0][+6] = uint8(.A >> 8) [0][+7] = uint8(.A) += 8 } }// Apply the filter. // Skip filter for NoCompression and paletted images (cbP8) as // "filters are rarely useful on palette images" and will result // in larger files (see http://www.libpng.org/pub/png/book/chapter09.html). := ftNoneif != zlib.NoCompression && != cbP8 && != cbP4 && != cbP2 && != cbP1 {// Since we skip paletted images we don't have to worry about // bitsPerPixel not being a multiple of 8 := / 8 = filter(&, , ) }// Write the compressed bytes.if , := .zw.Write([]); != nil {return }// The current row for y is the previous row for y+1. , [0] = [0], }returnnil}// Write the actual image data to one or more IDAT chunks.func ( *encoder) () {if .err != nil {return }if .bw == nil { .bw = bufio.NewWriterSize(, 1<<15) } else { .bw.Reset() } .err = .writeImage(.bw, .m, .cb, levelToZlib(.enc.CompressionLevel))if .err != nil {return } .err = .bw.Flush()}// This function is required because we want the zero value of// Encoder.CompressionLevel to map to zlib.DefaultCompression.func levelToZlib( CompressionLevel) int {switch {caseDefaultCompression:returnzlib.DefaultCompressioncaseNoCompression:returnzlib.NoCompressioncaseBestSpeed:returnzlib.BestSpeedcaseBestCompression:returnzlib.BestCompressiondefault:returnzlib.DefaultCompression }}func ( *encoder) () { .writeChunk(nil, "IEND") }// Encode writes the Image m to w in PNG format. Any Image may be// encoded, but images that are not [image.NRGBA] might be encoded lossily.//// Note that the exact bytes written to w are not covered by the Go 1// compatibility promise. Callers, including tests, should not depend on the// exact written bytes.func ( io.Writer, image.Image) error {varEncoderreturn .Encode(, )}// Encode writes the Image m to w in PNG format.//// Note that the exact bytes written to w are not covered by the Go 1// compatibility promise. Callers, including tests, should not depend on the// exact written bytes.func ( *Encoder) ( io.Writer, image.Image) error {// Obviously, negative widths and heights are invalid. Furthermore, the PNG // spec section 11.2.2 says that zero is invalid. Excessively large images are // also rejected. , := int64(.Bounds().Dx()), int64(.Bounds().Dy())if <= 0 || <= 0 || >= 1<<32 || >= 1<<32 {returnFormatError("invalid image size: " + strconv.FormatInt(, 10) + "x" + strconv.FormatInt(, 10)) }var *encoderif .BufferPool != nil { := .BufferPool.Get() = (*encoder)() }if == nil { = &encoder{} }if .BufferPool != nil {defer .BufferPool.Put((*EncoderBuffer)()) } .enc = .w = .m = varcolor.Palette// cbP8 encoding needs PalettedImage's ColorIndexAt method.if , := .(image.PalettedImage); { , _ = .ColorModel().(color.Palette) }if != nil {iflen() <= 2 { .cb = cbP1 } elseiflen() <= 4 { .cb = cbP2 } elseiflen() <= 16 { .cb = cbP4 } else { .cb = cbP8 } } else {switch .ColorModel() {casecolor.GrayModel: .cb = cbG8casecolor.Gray16Model: .cb = cbG16casecolor.RGBAModel, color.NRGBAModel, color.AlphaModel:ifopaque() { .cb = cbTC8 } else { .cb = cbTCA8 }default:ifopaque() { .cb = cbTC16 } else { .cb = cbTCA16 } } } _, .err = io.WriteString(, pngHeader) .writeIHDR()if != nil { .writePLTEAndTRNS() } .writeIDATs() .writeIEND()return .err}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.