| // 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 png implements a PNG image decoder and encoder. | |
| // | |
| // The PNG specification is at https://www.w3.org/TR/PNG/. | |
| package png | |
| import ( | |
| "compress/zlib" | |
| "encoding/binary" | |
| "fmt" | |
| "hash" | |
| "hash/crc32" | |
| "image" | |
| "image/color" | |
| "io" | |
| ) | |
| // Color type, as per the PNG spec. | |
| const ( | |
| ctGrayscale = 0 | |
| ctTrueColor = 2 | |
| ctPaletted = 3 | |
| ctGrayscaleAlpha = 4 | |
| ctTrueColorAlpha = 6 | |
| ) | |
| // A cb is a combination of color type and bit depth. | |
| const ( | |
| cbInvalid = iota | |
| cbG1 | |
| cbG2 | |
| cbG4 | |
| cbG8 | |
| cbGA8 | |
| cbTC8 | |
| cbP1 | |
| cbP2 | |
| cbP4 | |
| cbP8 | |
| cbTCA8 | |
| cbG16 | |
| cbGA16 | |
| cbTC16 | |
| cbTCA16 | |
| ) | |
| func cbPaletted(cb int) bool { | |
| return cbP1 <= cb && cb <= cbP8 | |
| } | |
| // Filter type, as per the PNG spec. | |
| const ( | |
| ftNone = 0 | |
| ftSub = 1 | |
| ftUp = 2 | |
| ftAverage = 3 | |
| ftPaeth = 4 | |
| nFilter = 5 | |
| ) | |
| // Interlace type. | |
| const ( | |
| itNone = 0 | |
| itAdam7 = 1 | |
| ) | |
| // interlaceScan defines the placement and size of a pass for Adam7 interlacing. | |
| type interlaceScan struct { | |
| xFactor, yFactor, xOffset, yOffset int | |
| } | |
| // interlacing defines Adam7 interlacing, with 7 passes of reduced images. | |
| // See https://www.w3.org/TR/PNG/#8Interlace | |
| var interlacing = []interlaceScan{ | |
| {8, 8, 0, 0}, | |
| {8, 8, 4, 0}, | |
| {4, 8, 0, 4}, | |
| {4, 4, 2, 0}, | |
| {2, 4, 0, 2}, | |
| {2, 2, 1, 0}, | |
| {1, 2, 0, 1}, | |
| } | |
| // Decoding stage. | |
| // The PNG specification says that the IHDR, PLTE (if present), tRNS (if | |
| // present), IDAT and IEND chunks must appear in that order. There may be | |
| // multiple IDAT chunks, and IDAT chunks must be sequential (i.e. they may not | |
| // have any other chunks between them). | |
| // https://www.w3.org/TR/PNG/#5ChunkOrdering | |
| const ( | |
| dsStart = iota | |
| dsSeenIHDR | |
| dsSeenPLTE | |
| dsSeentRNS | |
| dsSeenIDAT | |
| dsSeenIEND | |
| ) | |
| const pngHeader = "\x89PNG\r\n\x1a\n" | |
| type decoder struct { | |
| r io.Reader | |
| img image.Image | |
| crc hash.Hash32 | |
| width, height int | |
| depth int | |
| palette color.Palette | |
| cb int | |
| stage int | |
| idatLength uint32 | |
| tmp [3 * 256]byte | |
| interlace int | |
| // useTransparent and transparent are used for grayscale and truecolor | |
| // transparency, as opposed to palette transparency. | |
| useTransparent bool | |
| transparent [6]byte | |
| } | |
| // A FormatError reports that the input is not a valid PNG. | |
| type FormatError string | |
| func (e FormatError) Error() string { return "png: invalid format: " + string(e) } | |
| var chunkOrderError = FormatError("chunk out of order") | |
| // An UnsupportedError reports that the input uses a valid but unimplemented PNG feature. | |
| type UnsupportedError string | |
| func (e UnsupportedError) Error() string { return "png: unsupported feature: " + string(e) } | |
| func min(a, b int) int { | |
| if a < b { | |
| return a | |
| } | |
| return b | |
| } | |
| func (d *decoder) parseIHDR(length uint32) error { | |
| if length != 13 { | |
| return FormatError("bad IHDR length") | |
| } | |
| if _, err := io.ReadFull(d.r, d.tmp[:13]); err != nil { | |
| return err | |
| } | |
| d.crc.Write(d.tmp[:13]) | |
| if d.tmp[10] != 0 { | |
| return UnsupportedError("compression method") | |
| } | |
| if d.tmp[11] != 0 { | |
| return UnsupportedError("filter method") | |
| } | |
| if d.tmp[12] != itNone && d.tmp[12] != itAdam7 { | |
| return FormatError("invalid interlace method") | |
| } | |
| d.interlace = int(d.tmp[12]) | |
| w := int32(binary.BigEndian.Uint32(d.tmp[0:4])) | |
| h := int32(binary.BigEndian.Uint32(d.tmp[4:8])) | |
| if w <= 0 || h <= 0 { | |
| return FormatError("non-positive dimension") | |
| } | |
| nPixels := int64(w) * int64(h) | |
| if nPixels != int64(int(nPixels)) { | |
| return UnsupportedError("dimension overflow") | |
| } | |
| // There can be up to 8 bytes per pixel, for 16 bits per channel RGBA. | |
| if nPixels != (nPixels*8)/8 { | |
| return UnsupportedError("dimension overflow") | |
| } | |
| d.cb = cbInvalid | |
| d.depth = int(d.tmp[8]) | |
| switch d.depth { | |
| case 1: | |
| switch d.tmp[9] { | |
| case ctGrayscale: | |
| d.cb = cbG1 | |
| case ctPaletted: | |
| d.cb = cbP1 | |
| } | |
| case 2: | |
| switch d.tmp[9] { | |
| case ctGrayscale: | |
| d.cb = cbG2 | |
| case ctPaletted: | |
| d.cb = cbP2 | |
| } | |
| case 4: | |
| switch d.tmp[9] { | |
| case ctGrayscale: | |
| d.cb = cbG4 | |
| case ctPaletted: | |
| d.cb = cbP4 | |
| } | |
| case 8: | |
| switch d.tmp[9] { | |
| case ctGrayscale: | |
| d.cb = cbG8 | |
| case ctTrueColor: | |
| d.cb = cbTC8 | |
| case ctPaletted: | |
| d.cb = cbP8 | |
| case ctGrayscaleAlpha: | |
| d.cb = cbGA8 | |
| case ctTrueColorAlpha: | |
| d.cb = cbTCA8 | |
| } | |
| case 16: | |
| switch d.tmp[9] { | |
| case ctGrayscale: | |
| d.cb = cbG16 | |
| case ctTrueColor: | |
| d.cb = cbTC16 | |
| case ctGrayscaleAlpha: | |
| d.cb = cbGA16 | |
| case ctTrueColorAlpha: | |
| d.cb = cbTCA16 | |
| } | |
| } | |
| if d.cb == cbInvalid { | |
| return UnsupportedError(fmt.Sprintf("bit depth %d, color type %d", d.tmp[8], d.tmp[9])) | |
| } | |
| d.width, d.height = int(w), int(h) | |
| return d.verifyChecksum() | |
| } | |
| func (d *decoder) parsePLTE(length uint32) error { | |
| np := int(length / 3) // The number of palette entries. | |
| if length%3 != 0 || np <= 0 || np > 256 || np > 1<<uint(d.depth) { | |
| return FormatError("bad PLTE length") | |
| } | |
| n, err := io.ReadFull(d.r, d.tmp[:3*np]) | |
| if err != nil { | |
| return err | |
| } | |
| d.crc.Write(d.tmp[:n]) | |
| switch d.cb { | |
| case cbP1, cbP2, cbP4, cbP8: | |
| d.palette = make(color.Palette, 256) | |
| for i := 0; i < np; i++ { | |
| d.palette[i] = color.RGBA{d.tmp[3*i+0], d.tmp[3*i+1], d.tmp[3*i+2], 0xff} | |
| } | |
| for i := np; i < 256; i++ { | |
| // Initialize the rest of the palette to opaque black. The spec (section | |
| // 11.2.3) says that "any out-of-range pixel value found in the image data | |
| // is an error", but some real-world PNG files have out-of-range pixel | |
| // values. We fall back to opaque black, the same as libpng 1.5.13; | |
| // ImageMagick 6.5.7 returns an error. | |
| d.palette[i] = color.RGBA{0x00, 0x00, 0x00, 0xff} | |
| } | |
| d.palette = d.palette[:np] | |
| case cbTC8, cbTCA8, cbTC16, cbTCA16: | |
| // As per the PNG spec, a PLTE chunk is optional (and for practical purposes, | |
| // ignorable) for the ctTrueColor and ctTrueColorAlpha color types (section 4.1.2). | |
| default: | |
| return FormatError("PLTE, color type mismatch") | |
| } | |
| return d.verifyChecksum() | |
| } | |
| func (d *decoder) parsetRNS(length uint32) error { | |
| switch d.cb { | |
| case cbG1, cbG2, cbG4, cbG8, cbG16: | |
| if length != 2 { | |
| return FormatError("bad tRNS length") | |
| } | |
| n, err := io.ReadFull(d.r, d.tmp[:length]) | |
| if err != nil { | |
| return err | |
| } | |
| d.crc.Write(d.tmp[:n]) | |
| copy(d.transparent[:], d.tmp[:length]) | |
| switch d.cb { | |
| case cbG1: | |
| d.transparent[1] *= 0xff | |
| case cbG2: | |
| d.transparent[1] *= 0x55 | |
| case cbG4: | |
| d.transparent[1] *= 0x11 | |
| } | |
| d.useTransparent = true | |
| case cbTC8, cbTC16: | |
| if length != 6 { | |
| return FormatError("bad tRNS length") | |
| } | |
| n, err := io.ReadFull(d.r, d.tmp[:length]) | |
| if err != nil { | |
| return err | |
| } | |
| d.crc.Write(d.tmp[:n]) | |
| copy(d.transparent[:], d.tmp[:length]) | |
| d.useTransparent = true | |
| case cbP1, cbP2, cbP4, cbP8: | |
| if length > 256 { | |
| return FormatError("bad tRNS length") | |
| } | |
| n, err := io.ReadFull(d.r, d.tmp[:length]) | |
| if err != nil { | |
| return err | |
| } | |
| d.crc.Write(d.tmp[:n]) | |
| if len(d.palette) < n { | |
| d.palette = d.palette[:n] | |
| } | |
| for i := 0; i < n; i++ { | |
| rgba := d.palette[i].(color.RGBA) | |
| d.palette[i] = color.NRGBA{rgba.R, rgba.G, rgba.B, d.tmp[i]} | |
| } | |
| default: | |
| return FormatError("tRNS, color type mismatch") | |
| } | |
| return d.verifyChecksum() | |
| } | |
| // Read presents one or more IDAT chunks as one continuous stream (minus the | |
| // intermediate chunk headers and footers). If the PNG data looked like: | |
| // ... len0 IDAT xxx crc0 len1 IDAT yy crc1 len2 IEND crc2 | |
| // then this reader presents xxxyy. For well-formed PNG data, the decoder state | |
| // immediately before the first Read call is that d.r is positioned between the | |
| // first IDAT and xxx, and the decoder state immediately after the last Read | |
| // call is that d.r is positioned between yy and crc1. | |
| func (d *decoder) Read(p []byte) (int, error) { | |
| if len(p) == 0 { | |
| return 0, nil | |
| } | |
| for d.idatLength == 0 { | |
| // We have exhausted an IDAT chunk. Verify the checksum of that chunk. | |
| if err := d.verifyChecksum(); err != nil { | |
| return 0, err | |
| } | |
| // Read the length and chunk type of the next chunk, and check that | |
| // it is an IDAT chunk. | |
| if _, err := io.ReadFull(d.r, d.tmp[:8]); err != nil { | |
| return 0, err | |
| } | |
| d.idatLength = binary.BigEndian.Uint32(d.tmp[:4]) | |
| if string(d.tmp[4:8]) != "IDAT" { | |
| return 0, FormatError("not enough pixel data") | |
| } | |
| d.crc.Reset() | |
| d.crc.Write(d.tmp[4:8]) | |
| } | |
| if int(d.idatLength) < 0 { | |
| return 0, UnsupportedError("IDAT chunk length overflow") | |
| } | |
| n, err := d.r.Read(p[:min(len(p), int(d.idatLength))]) | |
| d.crc.Write(p[:n]) | |
| d.idatLength -= uint32(n) | |
| return n, err | |
| } | |
| // decode decodes the IDAT data into an image. | |
| func (d *decoder) decode() (image.Image, error) { | |
| r, err := zlib.NewReader(d) | |
| if err != nil { | |
| return nil, err | |
| } | |
| defer r.Close() | |
| var img image.Image | |
| if d.interlace == itNone { | |
| img, err = d.readImagePass(r, 0, false) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } else if d.interlace == itAdam7 { | |
| // Allocate a blank image of the full size. | |
| img, err = d.readImagePass(nil, 0, true) | |
| if err != nil { | |
| return nil, err | |
| } | |
| for pass := 0; pass < 7; pass++ { | |
| imagePass, err := d.readImagePass(r, pass, false) | |
| if err != nil { | |
| return nil, err | |
| } | |
| if imagePass != nil { | |
| d.mergePassInto(img, imagePass, pass) | |
| } | |
| } | |
| } | |
| // Check for EOF, to verify the zlib checksum. | |
| n := 0 | |
| for i := 0; n == 0 && err == nil; i++ { | |
| if i == 100 { | |
| return nil, io.ErrNoProgress | |
| } | |
| n, err = r.Read(d.tmp[:1]) | |
| } | |
| if err != nil && err != io.EOF { | |
| return nil, FormatError(err.Error()) | |
| } | |
| if n != 0 || d.idatLength != 0 { | |
| return nil, FormatError("too much pixel data") | |
| } | |
| return img, nil | |
| } | |
| // readImagePass reads a single image pass, sized according to the pass number. | |
| func (d *decoder) readImagePass(r io.Reader, pass int, allocateOnly bool) (image.Image, error) { | |
| bitsPerPixel := 0 | |
| pixOffset := 0 | |
| var ( | |
| gray *image.Gray | |
| rgba *image.RGBA | |
| paletted *image.Paletted | |
| nrgba *image.NRGBA | |
| gray16 *image.Gray16 | |
| rgba64 *image.RGBA64 | |
| nrgba64 *image.NRGBA64 | |
| img image.Image | |
| ) | |
| width, height := d.width, d.height | |
| if d.interlace == itAdam7 && !allocateOnly { | |
| p := interlacing[pass] | |
| // Add the multiplication factor and subtract one, effectively rounding up. | |
| width = (width - p.xOffset + p.xFactor - 1) / p.xFactor | |
| height = (height - p.yOffset + p.yFactor - 1) / p.yFactor | |
| // A PNG image can't have zero width or height, but for an interlaced | |
| // image, an individual pass might have zero width or height. If so, we | |
| // shouldn't even read a per-row filter type byte, so return early. | |
| if width == 0 || height == 0 { | |
| return nil, nil | |
| } | |
| } | |
| switch d.cb { | |
| case cbG1, cbG2, cbG4, cbG8: | |
| bitsPerPixel = d.depth | |
| if d.useTransparent { | |
| nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) | |
| img = nrgba | |
| } else { | |
| gray = image.NewGray(image.Rect(0, 0, width, height)) | |
| img = gray | |
| } | |
| case cbGA8: | |
| bitsPerPixel = 16 | |
| nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) | |
| img = nrgba | |
| case cbTC8: | |
| bitsPerPixel = 24 | |
| if d.useTransparent { | |
| nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) | |
| img = nrgba | |
| } else { | |
| rgba = image.NewRGBA(image.Rect(0, 0, width, height)) | |
| img = rgba | |
| } | |
| case cbP1, cbP2, cbP4, cbP8: | |
| bitsPerPixel = d.depth | |
| paletted = image.NewPaletted(image.Rect(0, 0, width, height), d.palette) | |
| img = paletted | |
| case cbTCA8: | |
| bitsPerPixel = 32 | |
| nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) | |
| img = nrgba | |
| case cbG16: | |
| bitsPerPixel = 16 | |
| if d.useTransparent { | |
| nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) | |
| img = nrgba64 | |
| } else { | |
| gray16 = image.NewGray16(image.Rect(0, 0, width, height)) | |
| img = gray16 | |
| } | |
| case cbGA16: | |
| bitsPerPixel = 32 | |
| nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) | |
| img = nrgba64 | |
| case cbTC16: | |
| bitsPerPixel = 48 | |
| if d.useTransparent { | |
| nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) | |
| img = nrgba64 | |
| } else { | |
| rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height)) | |
| img = rgba64 | |
| } | |
| case cbTCA16: | |
| bitsPerPixel = 64 | |
| nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height)) | |
| img = nrgba64 | |
| } | |
| if allocateOnly { | |
| return img, nil | |
| } | |
| bytesPerPixel := (bitsPerPixel + 7) / 8 | |
| // The +1 is for the per-row filter type, which is at cr[0]. | |
| rowSize := 1 + (bitsPerPixel*width+7)/8 | |
| // cr and pr are the bytes for the current and previous row. | |
| cr := make([]uint8, rowSize) | |
| pr := make([]uint8, rowSize) | |
| for y := 0; y < height; y++ { | |
| // Read the decompressed bytes. | |
| _, err := io.ReadFull(r, cr) | |
| if err != nil { | |
| if err == io.EOF || err == io.ErrUnexpectedEOF { | |
| return nil, FormatError("not enough pixel data") | |
| } | |
| return nil, err | |
| } | |
| // Apply the filter. | |
| cdat := cr[1:] | |
| pdat := pr[1:] | |
| switch cr[0] { | |
| case ftNone: | |
| // No-op. | |
| case ftSub: | |
| for i := bytesPerPixel; i < len(cdat); i++ { | |
| cdat[i] += cdat[i-bytesPerPixel] | |
| } | |
| case ftUp: | |
| for i, p := range pdat { | |
| cdat[i] += p | |
| } | |
| case ftAverage: | |
| // The first column has no column to the left of it, so it is a | |
| // special case. We know that the first column exists because we | |
| // check above that width != 0, and so len(cdat) != 0. | |
| for i := 0; i < bytesPerPixel; i++ { | |
| cdat[i] += pdat[i] / 2 | |
| } | |
| for i := bytesPerPixel; i < len(cdat); i++ { | |
| cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2) | |
| } | |
| case ftPaeth: | |
| filterPaeth(cdat, pdat, bytesPerPixel) | |
| default: | |
| return nil, FormatError("bad filter type") | |
| } | |
| // Convert from bytes to colors. | |
| switch d.cb { | |
| case cbG1: | |
| if d.useTransparent { | |
| ty := d.transparent[1] | |
| for x := 0; x < width; x += 8 { | |
| b := cdat[x/8] | |
| for x2 := 0; x2 < 8 && x+x2 < width; x2++ { | |
| ycol := (b >> 7) * 0xff | |
| acol := uint8(0xff) | |
| if ycol == ty { | |
| acol = 0x00 | |
| } | |
| nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol}) | |
| b <<= 1 | |
| } | |
| } | |
| } else { | |
| for x := 0; x < width; x += 8 { | |
| b := cdat[x/8] | |
| for x2 := 0; x2 < 8 && x+x2 < width; x2++ { | |
| gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff}) | |
| b <<= 1 | |
| } | |
| } | |
| } | |
| case cbG2: | |
| if d.useTransparent { | |
| ty := d.transparent[1] | |
| for x := 0; x < width; x += 4 { | |
| b := cdat[x/4] | |
| for x2 := 0; x2 < 4 && x+x2 < width; x2++ { | |
| ycol := (b >> 6) * 0x55 | |
| acol := uint8(0xff) | |
| if ycol == ty { | |
| acol = 0x00 | |
| } | |
| nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol}) | |
| b <<= 2 | |
| } | |
| } | |
| } else { | |
| for x := 0; x < width; x += 4 { | |
| b := cdat[x/4] | |
| for x2 := 0; x2 < 4 && x+x2 < width; x2++ { | |
| gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55}) | |
| b <<= 2 | |
| } | |
| } | |
| } | |
| case cbG4: | |
| if d.useTransparent { | |
| ty := d.transparent[1] | |
| for x := 0; x < width; x += 2 { | |
| b := cdat[x/2] | |
| for x2 := 0; x2 < 2 && x+x2 < width; x2++ { | |
| ycol := (b >> 4) * 0x11 | |
| acol := uint8(0xff) | |
| if ycol == ty { | |
| acol = 0x00 | |
| } | |
| nrgba.SetNRGBA(x+x2, y, color.NRGBA{ycol, ycol, ycol, acol}) | |
| b <<= 4 | |
| } | |
| } | |
| } else { | |
| for x := 0; x < width; x += 2 { | |
| b := cdat[x/2] | |
| for x2 := 0; x2 < 2 && x+x2 < width; x2++ { | |
| gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11}) | |
| b <<= 4 | |
| } | |
| } | |
| } | |
| case cbG8: | |
| if d.useTransparent { | |
| ty := d.transparent[1] | |
| for x := 0; x < width; x++ { | |
| ycol := cdat[x] | |
| acol := uint8(0xff) | |
| if ycol == ty { | |
| acol = 0x00 | |
| } | |
| nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, acol}) | |
| } | |
| } else { | |
| copy(gray.Pix[pixOffset:], cdat) | |
| pixOffset += gray.Stride | |
| } | |
| case cbGA8: | |
| for x := 0; x < width; x++ { | |
| ycol := cdat[2*x+0] | |
| nrgba.SetNRGBA(x, y, color.NRGBA{ycol, ycol, ycol, cdat[2*x+1]}) | |
| } | |
| case cbTC8: | |
| if d.useTransparent { | |
| pix, i, j := nrgba.Pix, pixOffset, 0 | |
| tr, tg, tb := d.transparent[1], d.transparent[3], d.transparent[5] | |
| for x := 0; x < width; x++ { | |
| r := cdat[j+0] | |
| g := cdat[j+1] | |
| b := cdat[j+2] | |
| a := uint8(0xff) | |
| if r == tr && g == tg && b == tb { | |
| a = 0x00 | |
| } | |
| pix[i+0] = r | |
| pix[i+1] = g | |
| pix[i+2] = b | |
| pix[i+3] = a | |
| i += 4 | |
| j += 3 | |
| } | |
| pixOffset += nrgba.Stride | |
| } else { | |
| pix, i, j := rgba.Pix, pixOffset, 0 | |
| for x := 0; x < width; x++ { | |
| pix[i+0] = cdat[j+0] | |
| pix[i+1] = cdat[j+1] | |
| pix[i+2] = cdat[j+2] | |
| pix[i+3] = 0xff | |
| i += 4 | |
| j += 3 | |
| } | |
| pixOffset += rgba.Stride | |
| } | |
| case cbP1: | |
| for x := 0; x < width; x += 8 { | |
| b := cdat[x/8] | |
| for x2 := 0; x2 < 8 && x+x2 < width; x2++ { | |
| idx := b >> 7 | |
| if len(paletted.Palette) <= int(idx) { | |
| paletted.Palette = paletted.Palette[:int(idx)+1] | |
| } | |
| paletted.SetColorIndex(x+x2, y, idx) | |
| b <<= 1 | |
| } | |
| } | |
| case cbP2: | |
| for x := 0; x < width; x += 4 { | |
| b := cdat[x/4] | |
| for x2 := 0; x2 < 4 && x+x2 < width; x2++ { | |
| idx := b >> 6 | |
| if len(paletted.Palette) <= int(idx) { | |
| paletted.Palette = paletted.Palette[:int(idx)+1] | |
| } | |
| paletted.SetColorIndex(x+x2, y, idx) | |
| b <<= 2 | |
| } | |
| } | |
| case cbP4: | |
| for x := 0; x < width; x += 2 { | |
| b := cdat[x/2] | |
| for x2 := 0; x2 < 2 && x+x2 < width; x2++ { | |
| idx := b >> 4 | |
| if len(paletted.Palette) <= int(idx) { | |
| paletted.Palette = paletted.Palette[:int(idx)+1] | |
| } | |
| paletted.SetColorIndex(x+x2, y, idx) | |
| b <<= 4 | |
| } | |
| } | |
| case cbP8: | |
| if len(paletted.Palette) != 255 { | |
| for x := 0; x < width; x++ { | |
| if len(paletted.Palette) <= int(cdat[x]) { | |
| paletted.Palette = paletted.Palette[:int(cdat[x])+1] | |
| } | |
| } | |
| } | |
| copy(paletted.Pix[pixOffset:], cdat) | |
| pixOffset += paletted.Stride | |
| case cbTCA8: | |
| copy(nrgba.Pix[pixOffset:], cdat) | |
| pixOffset += nrgba.Stride | |
| case cbG16: | |
| if d.useTransparent { | |
| ty := uint16(d.transparent[0])<<8 | uint16(d.transparent[1]) | |
| for x := 0; x < width; x++ { | |
| ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) | |
| acol := uint16(0xffff) | |
| if ycol == ty { | |
| acol = 0x0000 | |
| } | |
| nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol}) | |
| } | |
| } else { | |
| for x := 0; x < width; x++ { | |
| ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) | |
| gray16.SetGray16(x, y, color.Gray16{ycol}) | |
| } | |
| } | |
| case cbGA16: | |
| for x := 0; x < width; x++ { | |
| ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1]) | |
| acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3]) | |
| nrgba64.SetNRGBA64(x, y, color.NRGBA64{ycol, ycol, ycol, acol}) | |
| } | |
| case cbTC16: | |
| if d.useTransparent { | |
| tr := uint16(d.transparent[0])<<8 | uint16(d.transparent[1]) | |
| tg := uint16(d.transparent[2])<<8 | uint16(d.transparent[3]) | |
| tb := uint16(d.transparent[4])<<8 | uint16(d.transparent[5]) | |
| for x := 0; x < width; x++ { | |
| rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) | |
| gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) | |
| bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) | |
| acol := uint16(0xffff) | |
| if rcol == tr && gcol == tg && bcol == tb { | |
| acol = 0x0000 | |
| } | |
| nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol}) | |
| } | |
| } else { | |
| for x := 0; x < width; x++ { | |
| rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) | |
| gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) | |
| bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) | |
| rgba64.SetRGBA64(x, y, color.RGBA64{rcol, gcol, bcol, 0xffff}) | |
| } | |
| } | |
| case cbTCA16: | |
| for x := 0; x < width; x++ { | |
| rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1]) | |
| gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3]) | |
| bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5]) | |
| acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7]) | |
| nrgba64.SetNRGBA64(x, y, color.NRGBA64{rcol, gcol, bcol, acol}) | |
| } | |
| } | |
| // The current row for y is the previous row for y+1. | |
| pr, cr = cr, pr | |
| } | |
| return img, nil | |
| } | |
| // mergePassInto merges a single pass into a full sized image. | |
| func (d *decoder) mergePassInto(dst image.Image, src image.Image, pass int) { | |
| p := interlacing[pass] | |
| var ( | |
| srcPix []uint8 | |
| dstPix []uint8 | |
| stride int | |
| rect image.Rectangle | |
| bytesPerPixel int | |
| ) | |
| switch target := dst.(type) { | |
| case *image.Alpha: | |
| srcPix = src.(*image.Alpha).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 1 | |
| case *image.Alpha16: | |
| srcPix = src.(*image.Alpha16).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 2 | |
| case *image.Gray: | |
| srcPix = src.(*image.Gray).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 1 | |
| case *image.Gray16: | |
| srcPix = src.(*image.Gray16).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 2 | |
| case *image.NRGBA: | |
| srcPix = src.(*image.NRGBA).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 4 | |
| case *image.NRGBA64: | |
| srcPix = src.(*image.NRGBA64).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 8 | |
| case *image.Paletted: | |
| srcPix = src.(*image.Paletted).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 1 | |
| case *image.RGBA: | |
| srcPix = src.(*image.RGBA).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 4 | |
| case *image.RGBA64: | |
| srcPix = src.(*image.RGBA64).Pix | |
| dstPix, stride, rect = target.Pix, target.Stride, target.Rect | |
| bytesPerPixel = 8 | |
| } | |
| s, bounds := 0, src.Bounds() | |
| for y := bounds.Min.Y; y < bounds.Max.Y; y++ { | |
| dBase := (y*p.yFactor+p.yOffset-rect.Min.Y)*stride + (p.xOffset-rect.Min.X)*bytesPerPixel | |
| for x := bounds.Min.X; x < bounds.Max.X; x++ { | |
| d := dBase + x*p.xFactor*bytesPerPixel | |
| copy(dstPix[d:], srcPix[s:s+bytesPerPixel]) | |
| s += bytesPerPixel | |
| } | |
| } | |
| } | |
| func (d *decoder) parseIDAT(length uint32) (err error) { | |
| d.idatLength = length | |
| d.img, err = d.decode() | |
| if err != nil { | |
| return err | |
| } | |
| return d.verifyChecksum() | |
| } | |
| func (d *decoder) parseIEND(length uint32) error { | |
| if length != 0 { | |
| return FormatError("bad IEND length") | |
| } | |
| return d.verifyChecksum() | |
| } | |
| func (d *decoder) parseChunk() error { | |
| // Read the length and chunk type. | |
| n, err := io.ReadFull(d.r, d.tmp[:8]) | |
| if err != nil { | |
| return err | |
| } | |
| length := binary.BigEndian.Uint32(d.tmp[:4]) | |
| d.crc.Reset() | |
| d.crc.Write(d.tmp[4:8]) | |
| // Read the chunk data. | |
| switch string(d.tmp[4:8]) { | |
| case "IHDR": | |
| if d.stage != dsStart { | |
| return chunkOrderError | |
| } | |
| d.stage = dsSeenIHDR | |
| return d.parseIHDR(length) | |
| case "PLTE": | |
| if d.stage != dsSeenIHDR { | |
| return chunkOrderError | |
| } | |
| d.stage = dsSeenPLTE | |
| return d.parsePLTE(length) | |
| case "tRNS": | |
| if cbPaletted(d.cb) { | |
| if d.stage != dsSeenPLTE { | |
| return chunkOrderError | |
| } | |
| } else if d.stage != dsSeenIHDR { | |
| return chunkOrderError | |
| } | |
| d.stage = dsSeentRNS | |
| return d.parsetRNS(length) | |
| case "IDAT": | |
| if d.stage < dsSeenIHDR || d.stage > dsSeenIDAT || (d.stage == dsSeenIHDR && cbPaletted(d.cb)) { | |
| return chunkOrderError | |
| } else if d.stage == dsSeenIDAT { | |
| // Ignore trailing zero-length or garbage IDAT chunks. | |
| // | |
| // This does not affect valid PNG images that contain multiple IDAT | |
| // chunks, since the first call to parseIDAT below will consume all | |
| // consecutive IDAT chunks required for decoding the image. | |
| break | |
| } | |
| d.stage = dsSeenIDAT | |
| return d.parseIDAT(length) | |
| case "IEND": | |
| if d.stage != dsSeenIDAT { | |
| return chunkOrderError | |
| } | |
| d.stage = dsSeenIEND | |
| return d.parseIEND(length) | |
| } | |
| if length > 0x7fffffff { | |
| return FormatError(fmt.Sprintf("Bad chunk length: %d", length)) | |
| } | |
| // Ignore this chunk (of a known length). | |
| var ignored [4096]byte | |
| for length > 0 { | |
| n, err = io.ReadFull(d.r, ignored[:min(len(ignored), int(length))]) | |
| if err != nil { | |
| return err | |
| } | |
| d.crc.Write(ignored[:n]) | |
| length -= uint32(n) | |
| } | |
| return d.verifyChecksum() | |
| } | |
| func (d *decoder) verifyChecksum() error { | |
| if _, err := io.ReadFull(d.r, d.tmp[:4]); err != nil { | |
| return err | |
| } | |
| if binary.BigEndian.Uint32(d.tmp[:4]) != d.crc.Sum32() { | |
| return FormatError("invalid checksum") | |
| } | |
| return nil | |
| } | |
| func (d *decoder) checkHeader() error { | |
| _, err := io.ReadFull(d.r, d.tmp[:len(pngHeader)]) | |
| if err != nil { | |
| return err | |
| } | |
| if string(d.tmp[:len(pngHeader)]) != pngHeader { | |
| return FormatError("not a PNG file") | |
| } | |
| return nil | |
| } | |
| // Decode reads a PNG image from r and returns it as an image.Image. | |
| // The type of Image returned depends on the PNG contents. | |
| func Decode(r io.Reader) (image.Image, error) { | |
| d := &decoder{ | |
| r: r, | |
| crc: crc32.NewIEEE(), | |
| } | |
| if err := d.checkHeader(); err != nil { | |
| if err == io.EOF { | |
| err = io.ErrUnexpectedEOF | |
| } | |
| return nil, err | |
| } | |
| for d.stage != dsSeenIEND { | |
| if err := d.parseChunk(); err != nil { | |
| if err == io.EOF { | |
| err = io.ErrUnexpectedEOF | |
| } | |
| return nil, err | |
| } | |
| } | |
| return d.img, nil | |
| } | |
| // DecodeConfig returns the color model and dimensions of a PNG image without | |
| // decoding the entire image. | |
| func DecodeConfig(r io.Reader) (image.Config, error) { | |
| d := &decoder{ | |
| r: r, | |
| crc: crc32.NewIEEE(), | |
| } | |
| if err := d.checkHeader(); err != nil { | |
| if err == io.EOF { | |
| err = io.ErrUnexpectedEOF | |
| } | |
| return image.Config{}, err | |
| } | |
| for { | |
| if err := d.parseChunk(); err != nil { | |
| if err == io.EOF { | |
| err = io.ErrUnexpectedEOF | |
| } | |
| return image.Config{}, err | |
| } | |
| paletted := cbPaletted(d.cb) | |
| if d.stage == dsSeenIHDR && !paletted { | |
| break | |
| } | |
| if d.stage == dsSeenPLTE && paletted { | |
| break | |
| } | |
| } | |
| var cm color.Model | |
| switch d.cb { | |
| case cbG1, cbG2, cbG4, cbG8: | |
| cm = color.GrayModel | |
| case cbGA8: | |
| cm = color.NRGBAModel | |
| case cbTC8: | |
| cm = color.RGBAModel | |
| case cbP1, cbP2, cbP4, cbP8: | |
| cm = d.palette | |
| case cbTCA8: | |
| cm = color.NRGBAModel | |
| case cbG16: | |
| cm = color.Gray16Model | |
| case cbGA16: | |
| cm = color.NRGBA64Model | |
| case cbTC16: | |
| cm = color.RGBA64Model | |
| case cbTCA16: | |
| cm = color.NRGBA64Model | |
| } | |
| return image.Config{ | |
| ColorModel: cm, | |
| Width: d.width, | |
| Height: d.height, | |
| }, nil | |
| } | |
| func init() { | |
| image.RegisterFormat("png", pngHeader, Decode, DecodeConfig) | |
| } |