| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | package jpeg |
| |
|
| | import ( |
| | "image" |
| | "image/color" |
| | "image/internal/imageutil" |
| | "io" |
| | ) |
| |
|
| | |
| | type FormatError string |
| |
|
| | func (e FormatError) Error() string { return "invalid JPEG format: " + string(e) } |
| |
|
| | |
| | type UnsupportedError string |
| |
|
| | func (e UnsupportedError) Error() string { return "unsupported JPEG feature: " + string(e) } |
| |
|
| | var errUnsupportedSubsamplingRatio = UnsupportedError("luma/chroma subsampling ratio") |
| |
|
| | |
| | type component struct { |
| | h int |
| | v int |
| | c uint8 |
| | tq uint8 |
| | } |
| |
|
| | const ( |
| | dcTable = 0 |
| | acTable = 1 |
| | maxTc = 1 |
| | maxTh = 3 |
| | maxTq = 3 |
| |
|
| | maxComponents = 4 |
| | ) |
| |
|
| | const ( |
| | sof0Marker = 0xc0 |
| | sof1Marker = 0xc1 |
| | sof2Marker = 0xc2 |
| | dhtMarker = 0xc4 |
| | rst0Marker = 0xd0 |
| | rst7Marker = 0xd7 |
| | soiMarker = 0xd8 |
| | eoiMarker = 0xd9 |
| | sosMarker = 0xda |
| | dqtMarker = 0xdb |
| | driMarker = 0xdd |
| | comMarker = 0xfe |
| | |
| | |
| | |
| | app0Marker = 0xe0 |
| | app14Marker = 0xee |
| | app15Marker = 0xef |
| | ) |
| |
|
| | |
| | const ( |
| | adobeTransformUnknown = 0 |
| | adobeTransformYCbCr = 1 |
| | adobeTransformYCbCrK = 2 |
| | ) |
| |
|
| | |
| | |
| | |
| | var unzig = [blockSize]int{ |
| | 0, 1, 8, 16, 9, 2, 3, 10, |
| | 17, 24, 32, 25, 18, 11, 4, 5, |
| | 12, 19, 26, 33, 40, 48, 41, 34, |
| | 27, 20, 13, 6, 7, 14, 21, 28, |
| | 35, 42, 49, 56, 57, 50, 43, 36, |
| | 29, 22, 15, 23, 30, 37, 44, 51, |
| | 58, 59, 52, 45, 38, 31, 39, 46, |
| | 53, 60, 61, 54, 47, 55, 62, 63, |
| | } |
| |
|
| | |
| | |
| | type Reader interface { |
| | io.ByteReader |
| | io.Reader |
| | } |
| |
|
| | |
| | |
| | |
| | type bits struct { |
| | a uint32 |
| | m uint32 |
| | n int32 |
| | } |
| |
|
| | type decoder struct { |
| | r io.Reader |
| | bits bits |
| | |
| | |
| | |
| | bytes struct { |
| | |
| | |
| | buf [4096]byte |
| | i, j int |
| | |
| | |
| | nUnreadable int |
| | } |
| | width, height int |
| |
|
| | img1 *image.Gray |
| | img3 *image.YCbCr |
| | blackPix []byte |
| | blackStride int |
| |
|
| | ri int |
| | nComp int |
| |
|
| | |
| | |
| | |
| | |
| | |
| | baseline bool |
| | progressive bool |
| |
|
| | jfif bool |
| | adobeTransformValid bool |
| | adobeTransform uint8 |
| | eobRun uint16 |
| |
|
| | comp [maxComponents]component |
| | progCoeffs [maxComponents][]block |
| | huff [maxTc + 1][maxTh + 1]huffman |
| | quant [maxTq + 1]block |
| | tmp [2 * blockSize]byte |
| | } |
| |
|
| | |
| | |
| | func (d *decoder) fill() error { |
| | if d.bytes.i != d.bytes.j { |
| | panic("jpeg: fill called when unread bytes exist") |
| | } |
| | |
| | |
| | if d.bytes.j > 2 { |
| | d.bytes.buf[0] = d.bytes.buf[d.bytes.j-2] |
| | d.bytes.buf[1] = d.bytes.buf[d.bytes.j-1] |
| | d.bytes.i, d.bytes.j = 2, 2 |
| | } |
| | |
| | n, err := d.r.Read(d.bytes.buf[d.bytes.j:]) |
| | d.bytes.j += n |
| | if n > 0 { |
| | return nil |
| | } |
| | if err == io.EOF { |
| | err = io.ErrUnexpectedEOF |
| | } |
| | return err |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | func (d *decoder) unreadByteStuffedByte() { |
| | d.bytes.i -= d.bytes.nUnreadable |
| | d.bytes.nUnreadable = 0 |
| | if d.bits.n >= 8 { |
| | d.bits.a >>= 8 |
| | d.bits.n -= 8 |
| | d.bits.m >>= 8 |
| | } |
| | } |
| |
|
| | |
| | |
| | func (d *decoder) readByte() (x byte, err error) { |
| | for d.bytes.i == d.bytes.j { |
| | if err = d.fill(); err != nil { |
| | return 0, err |
| | } |
| | } |
| | x = d.bytes.buf[d.bytes.i] |
| | d.bytes.i++ |
| | d.bytes.nUnreadable = 0 |
| | return x, nil |
| | } |
| |
|
| | |
| | |
| | var errMissingFF00 = FormatError("missing 0xff00 sequence") |
| |
|
| | |
| | func (d *decoder) readByteStuffedByte() (x byte, err error) { |
| | |
| | if d.bytes.i+2 <= d.bytes.j { |
| | x = d.bytes.buf[d.bytes.i] |
| | d.bytes.i++ |
| | d.bytes.nUnreadable = 1 |
| | if x != 0xff { |
| | return x, err |
| | } |
| | if d.bytes.buf[d.bytes.i] != 0x00 { |
| | return 0, errMissingFF00 |
| | } |
| | d.bytes.i++ |
| | d.bytes.nUnreadable = 2 |
| | return 0xff, nil |
| | } |
| |
|
| | d.bytes.nUnreadable = 0 |
| |
|
| | x, err = d.readByte() |
| | if err != nil { |
| | return 0, err |
| | } |
| | d.bytes.nUnreadable = 1 |
| | if x != 0xff { |
| | return x, nil |
| | } |
| |
|
| | x, err = d.readByte() |
| | if err != nil { |
| | return 0, err |
| | } |
| | d.bytes.nUnreadable = 2 |
| | if x != 0x00 { |
| | return 0, errMissingFF00 |
| | } |
| | return 0xff, nil |
| | } |
| |
|
| | |
| | |
| | func (d *decoder) readFull(p []byte) error { |
| | |
| | if d.bytes.nUnreadable != 0 { |
| | if d.bits.n >= 8 { |
| | d.unreadByteStuffedByte() |
| | } |
| | d.bytes.nUnreadable = 0 |
| | } |
| |
|
| | for { |
| | n := copy(p, d.bytes.buf[d.bytes.i:d.bytes.j]) |
| | p = p[n:] |
| | d.bytes.i += n |
| | if len(p) == 0 { |
| | break |
| | } |
| | if err := d.fill(); err != nil { |
| | return err |
| | } |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | func (d *decoder) ignore(n int) error { |
| | |
| | if d.bytes.nUnreadable != 0 { |
| | if d.bits.n >= 8 { |
| | d.unreadByteStuffedByte() |
| | } |
| | d.bytes.nUnreadable = 0 |
| | } |
| |
|
| | for { |
| | m := d.bytes.j - d.bytes.i |
| | if m > n { |
| | m = n |
| | } |
| | d.bytes.i += m |
| | n -= m |
| | if n == 0 { |
| | break |
| | } |
| | if err := d.fill(); err != nil { |
| | return err |
| | } |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | func (d *decoder) processSOF(n int) error { |
| | if d.nComp != 0 { |
| | return FormatError("multiple SOF markers") |
| | } |
| | switch n { |
| | case 6 + 3*1: |
| | d.nComp = 1 |
| | case 6 + 3*3: |
| | d.nComp = 3 |
| | case 6 + 3*4: |
| | d.nComp = 4 |
| | default: |
| | return UnsupportedError("number of components") |
| | } |
| | if err := d.readFull(d.tmp[:n]); err != nil { |
| | return err |
| | } |
| | |
| | if d.tmp[0] != 8 { |
| | return UnsupportedError("precision") |
| | } |
| | d.height = int(d.tmp[1])<<8 + int(d.tmp[2]) |
| | d.width = int(d.tmp[3])<<8 + int(d.tmp[4]) |
| | if int(d.tmp[5]) != d.nComp { |
| | return FormatError("SOF has wrong length") |
| | } |
| |
|
| | for i := 0; i < d.nComp; i++ { |
| | d.comp[i].c = d.tmp[6+3*i] |
| | |
| | |
| | for j := 0; j < i; j++ { |
| | if d.comp[i].c == d.comp[j].c { |
| | return FormatError("repeated component identifier") |
| | } |
| | } |
| |
|
| | d.comp[i].tq = d.tmp[8+3*i] |
| | if d.comp[i].tq > maxTq { |
| | return FormatError("bad Tq value") |
| | } |
| |
|
| | hv := d.tmp[7+3*i] |
| | h, v := int(hv>>4), int(hv&0x0f) |
| | if h < 1 || 4 < h || v < 1 || 4 < v { |
| | return FormatError("luma/chroma subsampling ratio") |
| | } |
| | if h == 3 || v == 3 { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | switch d.nComp { |
| | case 1: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | h, v = 1, 1 |
| |
|
| | case 3: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | switch i { |
| | case 0: |
| | |
| | |
| | |
| | if v == 4 { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | case 1: |
| | if d.comp[0].h%h != 0 || d.comp[0].v%v != 0 { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | case 2: |
| | if d.comp[1].h != h || d.comp[1].v != v { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | } |
| |
|
| | case 4: |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | switch i { |
| | case 0: |
| | if hv != 0x11 && hv != 0x22 { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | case 1, 2: |
| | if hv != 0x11 { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | case 3: |
| | if d.comp[0].h != h || d.comp[0].v != v { |
| | return errUnsupportedSubsamplingRatio |
| | } |
| | } |
| | } |
| |
|
| | d.comp[i].h = h |
| | d.comp[i].v = v |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | func (d *decoder) processDQT(n int) error { |
| | loop: |
| | for n > 0 { |
| | n-- |
| | x, err := d.readByte() |
| | if err != nil { |
| | return err |
| | } |
| | tq := x & 0x0f |
| | if tq > maxTq { |
| | return FormatError("bad Tq value") |
| | } |
| | switch x >> 4 { |
| | default: |
| | return FormatError("bad Pq value") |
| | case 0: |
| | if n < blockSize { |
| | break loop |
| | } |
| | n -= blockSize |
| | if err := d.readFull(d.tmp[:blockSize]); err != nil { |
| | return err |
| | } |
| | for i := range d.quant[tq] { |
| | d.quant[tq][i] = int32(d.tmp[i]) |
| | } |
| | case 1: |
| | if n < 2*blockSize { |
| | break loop |
| | } |
| | n -= 2 * blockSize |
| | if err := d.readFull(d.tmp[:2*blockSize]); err != nil { |
| | return err |
| | } |
| | for i := range d.quant[tq] { |
| | d.quant[tq][i] = int32(d.tmp[2*i])<<8 | int32(d.tmp[2*i+1]) |
| | } |
| | } |
| | } |
| | if n != 0 { |
| | return FormatError("DQT has wrong length") |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | func (d *decoder) processDRI(n int) error { |
| | if n != 2 { |
| | return FormatError("DRI has wrong length") |
| | } |
| | if err := d.readFull(d.tmp[:2]); err != nil { |
| | return err |
| | } |
| | d.ri = int(d.tmp[0])<<8 + int(d.tmp[1]) |
| | return nil |
| | } |
| |
|
| | func (d *decoder) processApp0Marker(n int) error { |
| | if n < 5 { |
| | return d.ignore(n) |
| | } |
| | if err := d.readFull(d.tmp[:5]); err != nil { |
| | return err |
| | } |
| | n -= 5 |
| |
|
| | d.jfif = d.tmp[0] == 'J' && d.tmp[1] == 'F' && d.tmp[2] == 'I' && d.tmp[3] == 'F' && d.tmp[4] == '\x00' |
| |
|
| | if n > 0 { |
| | return d.ignore(n) |
| | } |
| | return nil |
| | } |
| |
|
| | func (d *decoder) processApp14Marker(n int) error { |
| | if n < 12 { |
| | return d.ignore(n) |
| | } |
| | if err := d.readFull(d.tmp[:12]); err != nil { |
| | return err |
| | } |
| | n -= 12 |
| |
|
| | if d.tmp[0] == 'A' && d.tmp[1] == 'd' && d.tmp[2] == 'o' && d.tmp[3] == 'b' && d.tmp[4] == 'e' { |
| | d.adobeTransformValid = true |
| | d.adobeTransform = d.tmp[11] |
| | } |
| |
|
| | if n > 0 { |
| | return d.ignore(n) |
| | } |
| | return nil |
| | } |
| |
|
| | |
| | func (d *decoder) decode(r io.Reader, configOnly bool) (image.Image, error) { |
| | d.r = r |
| |
|
| | |
| | if err := d.readFull(d.tmp[:2]); err != nil { |
| | return nil, err |
| | } |
| | if d.tmp[0] != 0xff || d.tmp[1] != soiMarker { |
| | return nil, FormatError("missing SOI marker") |
| | } |
| |
|
| | |
| | for { |
| | err := d.readFull(d.tmp[:2]) |
| | if err != nil { |
| | return nil, err |
| | } |
| | for d.tmp[0] != 0xff { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | d.tmp[0] = d.tmp[1] |
| | d.tmp[1], err = d.readByte() |
| | if err != nil { |
| | return nil, err |
| | } |
| | } |
| | marker := d.tmp[1] |
| | if marker == 0 { |
| | |
| | continue |
| | } |
| | for marker == 0xff { |
| | |
| | |
| | marker, err = d.readByte() |
| | if err != nil { |
| | return nil, err |
| | } |
| | } |
| | if marker == eoiMarker { |
| | break |
| | } |
| | if rst0Marker <= marker && marker <= rst7Marker { |
| | |
| | |
| | |
| | |
| | |
| | |
| | continue |
| | } |
| |
|
| | |
| | |
| | if err = d.readFull(d.tmp[:2]); err != nil { |
| | return nil, err |
| | } |
| | n := int(d.tmp[0])<<8 + int(d.tmp[1]) - 2 |
| | if n < 0 { |
| | return nil, FormatError("short segment length") |
| | } |
| |
|
| | switch marker { |
| | case sof0Marker, sof1Marker, sof2Marker: |
| | d.baseline = marker == sof0Marker |
| | d.progressive = marker == sof2Marker |
| | err = d.processSOF(n) |
| | if configOnly && d.jfif { |
| | return nil, err |
| | } |
| | case dhtMarker: |
| | if configOnly { |
| | err = d.ignore(n) |
| | } else { |
| | err = d.processDHT(n) |
| | } |
| | case dqtMarker: |
| | if configOnly { |
| | err = d.ignore(n) |
| | } else { |
| | err = d.processDQT(n) |
| | } |
| | case sosMarker: |
| | if configOnly { |
| | return nil, nil |
| | } |
| | err = d.processSOS(n) |
| | case driMarker: |
| | if configOnly { |
| | err = d.ignore(n) |
| | } else { |
| | err = d.processDRI(n) |
| | } |
| | case app0Marker: |
| | err = d.processApp0Marker(n) |
| | case app14Marker: |
| | err = d.processApp14Marker(n) |
| | default: |
| | if app0Marker <= marker && marker <= app15Marker || marker == comMarker { |
| | err = d.ignore(n) |
| | } else if marker < 0xc0 { |
| | err = FormatError("unknown marker") |
| | } else { |
| | err = UnsupportedError("unknown marker") |
| | } |
| | } |
| | if err != nil { |
| | return nil, err |
| | } |
| | } |
| |
|
| | if d.progressive { |
| | if err := d.reconstructProgressiveImage(); err != nil { |
| | return nil, err |
| | } |
| | } |
| | if d.img1 != nil { |
| | return d.img1, nil |
| | } |
| | if d.img3 != nil { |
| | if d.blackPix != nil { |
| | return d.applyBlack() |
| | } else if d.isRGB() { |
| | return d.convertToRGB() |
| | } |
| | return d.img3, nil |
| | } |
| | return nil, FormatError("missing SOS marker") |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | func (d *decoder) applyBlack() (image.Image, error) { |
| | if !d.adobeTransformValid { |
| | return nil, UnsupportedError("unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata") |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if d.adobeTransform != adobeTransformUnknown { |
| | |
| | |
| | |
| | |
| | bounds := d.img3.Bounds() |
| | img := image.NewRGBA(bounds) |
| | imageutil.DrawYCbCr(img, bounds, d.img3, bounds.Min) |
| | for iBase, y := 0, bounds.Min.Y; y < bounds.Max.Y; iBase, y = iBase+img.Stride, y+1 { |
| | for i, x := iBase+3, bounds.Min.X; x < bounds.Max.X; i, x = i+4, x+1 { |
| | img.Pix[i] = 255 - d.blackPix[(y-bounds.Min.Y)*d.blackStride+(x-bounds.Min.X)] |
| | } |
| | } |
| | return &image.CMYK{ |
| | Pix: img.Pix, |
| | Stride: img.Stride, |
| | Rect: img.Rect, |
| | }, nil |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | bounds := d.img3.Bounds() |
| | img := image.NewCMYK(bounds) |
| |
|
| | translations := [4]struct { |
| | src []byte |
| | stride int |
| | }{ |
| | {d.img3.Y, d.img3.YStride}, |
| | {d.img3.Cb, d.img3.CStride}, |
| | {d.img3.Cr, d.img3.CStride}, |
| | {d.blackPix, d.blackStride}, |
| | } |
| | for t, translation := range translations { |
| | subsample := d.comp[t].h != d.comp[0].h || d.comp[t].v != d.comp[0].v |
| | for iBase, y := 0, bounds.Min.Y; y < bounds.Max.Y; iBase, y = iBase+img.Stride, y+1 { |
| | sy := y - bounds.Min.Y |
| | if subsample { |
| | sy /= 2 |
| | } |
| | for i, x := iBase+t, bounds.Min.X; x < bounds.Max.X; i, x = i+4, x+1 { |
| | sx := x - bounds.Min.X |
| | if subsample { |
| | sx /= 2 |
| | } |
| | img.Pix[i] = 255 - translation.src[sy*translation.stride+sx] |
| | } |
| | } |
| | } |
| | return img, nil |
| | } |
| |
|
| | func (d *decoder) isRGB() bool { |
| | if d.jfif { |
| | return false |
| | } |
| | if d.adobeTransformValid && d.adobeTransform == adobeTransformUnknown { |
| | |
| | |
| | return true |
| | } |
| | return d.comp[0].c == 'R' && d.comp[1].c == 'G' && d.comp[2].c == 'B' |
| | } |
| |
|
| | func (d *decoder) convertToRGB() (image.Image, error) { |
| | cScale := d.comp[0].h / d.comp[1].h |
| | bounds := d.img3.Bounds() |
| | img := image.NewRGBA(bounds) |
| | for y := bounds.Min.Y; y < bounds.Max.Y; y++ { |
| | po := img.PixOffset(bounds.Min.X, y) |
| | yo := d.img3.YOffset(bounds.Min.X, y) |
| | co := d.img3.COffset(bounds.Min.X, y) |
| | for i, iMax := 0, bounds.Max.X-bounds.Min.X; i < iMax; i++ { |
| | img.Pix[po+4*i+0] = d.img3.Y[yo+i] |
| | img.Pix[po+4*i+1] = d.img3.Cb[co+i/cScale] |
| | img.Pix[po+4*i+2] = d.img3.Cr[co+i/cScale] |
| | img.Pix[po+4*i+3] = 255 |
| | } |
| | } |
| | return img, nil |
| | } |
| |
|
| | |
| | func Decode(r io.Reader) (image.Image, error) { |
| | var d decoder |
| | return d.decode(r, false) |
| | } |
| |
|
| | |
| | |
| | func DecodeConfig(r io.Reader) (image.Config, error) { |
| | var d decoder |
| | if _, err := d.decode(r, true); err != nil { |
| | return image.Config{}, err |
| | } |
| | switch d.nComp { |
| | case 1: |
| | return image.Config{ |
| | ColorModel: color.GrayModel, |
| | Width: d.width, |
| | Height: d.height, |
| | }, nil |
| | case 3: |
| | cm := color.YCbCrModel |
| | if d.isRGB() { |
| | cm = color.RGBAModel |
| | } |
| | return image.Config{ |
| | ColorModel: cm, |
| | Width: d.width, |
| | Height: d.height, |
| | }, nil |
| | case 4: |
| | return image.Config{ |
| | ColorModel: color.CMYKModel, |
| | Width: d.width, |
| | Height: d.height, |
| | }, nil |
| | } |
| | return image.Config{}, FormatError("missing SOF marker") |
| | } |
| |
|
| | func init() { |
| | image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig) |
| | } |
| |
|