Source File
reader.go
Belonging Package
image/jpeg
package jpeg
import (
)
type FormatError string
func ( FormatError) () string { return "invalid JPEG format: " + string() }
type UnsupportedError string
func ( UnsupportedError) () string { return "unsupported JPEG feature: " + string() }
var errUnsupportedSubsamplingRatio = UnsupportedError("luma/chroma subsampling ratio")
type component struct {
h int // Horizontal sampling factor.
v int // Vertical sampling factor.
c uint8 // Component identifier.
tq uint8 // Quantization table destination selector.
}
const (
dcTable = 0
acTable = 1
maxTc = 1
maxTh = 3
maxTq = 3
maxComponents = 4
)
const (
sof0Marker = 0xc0 // Start Of Frame (Baseline Sequential).
sof1Marker = 0xc1 // Start Of Frame (Extended Sequential).
sof2Marker = 0xc2 // Start Of Frame (Progressive).
dhtMarker = 0xc4 // Define Huffman Table.
rst0Marker = 0xd0 // ReSTart (0).
rst7Marker = 0xd7 // ReSTart (7).
soiMarker = 0xd8 // Start Of Image.
eoiMarker = 0xd9 // End Of Image.
sosMarker = 0xda // Start Of Scan.
dqtMarker = 0xdb // Define Quantization Table.
driMarker = 0xdd // Define Restart Interval.
app0Marker = 0xe0
app14Marker = 0xee
app15Marker = 0xef
)
const (
adobeTransformUnknown = 0
adobeTransformYCbCr = 1
adobeTransformYCbCrK = 2
)
type Reader interface {
io.ByteReader
io.Reader
}
buf [4096]byte
baseline bool
progressive bool
jfif bool
adobeTransformValid bool
adobeTransform uint8
eobRun uint16 // End-of-Band run, specified in section G.1.2.2.
comp [maxComponents]component
progCoeffs [maxComponents][]block // Saved state between progressive-mode scans.
huff [maxTc + 1][maxTh + 1]huffman
quant [maxTq + 1]block // Quantization tables, in zig-zag order.
tmp [2 * blockSize]byte
}
var errMissingFF00 = FormatError("missing 0xff00 sequence")
if .bytes.i+2 <= .bytes.j {
= .bytes.buf[.bytes.i]
.bytes.i++
.bytes.nUnreadable = 1
if != 0xff {
return ,
}
if .bytes.buf[.bytes.i] != 0x00 {
return 0, errMissingFF00
}
.bytes.i++
.bytes.nUnreadable = 2
return 0xff, nil
}
.bytes.nUnreadable = 0
, = .readByte()
if != nil {
return 0,
}
.bytes.nUnreadable = 1
if != 0xff {
return , nil
}
, = .readByte()
if != nil {
return 0,
}
.bytes.nUnreadable = 2
if != 0x00 {
return 0, errMissingFF00
}
return 0xff, nil
}
if .bytes.nUnreadable != 0 {
if .bits.n >= 8 {
.unreadByteStuffedByte()
}
.bytes.nUnreadable = 0
}
for {
:= .bytes.j - .bytes.i
if > {
=
}
.bytes.i +=
-=
if == 0 {
break
}
if := .fill(); != nil {
if == io.EOF {
= io.ErrUnexpectedEOF
}
return
}
}
return nil
}
func ( *decoder) ( int) error {
if .nComp != 0 {
return FormatError("multiple SOF markers")
}
switch {
case 6 + 3*1: // Grayscale image.
.nComp = 1
case 6 + 3*3: // YCbCr or RGB image.
.nComp = 3
case 6 + 3*4: // YCbCrK or CMYK image.
.nComp = 4
default:
return UnsupportedError("number of components")
}
if := .readFull(.tmp[:]); != nil {
return
for := 0; < ; ++ {
if .comp[].c == .comp[].c {
return FormatError("repeated component identifier")
}
}
.comp[].tq = .tmp[8+3*]
if .comp[].tq > maxTq {
return FormatError("bad Tq value")
}
:= .tmp[7+3*]
, := int(>>4), int(&0x0f)
if < 1 || 4 < || < 1 || 4 < {
return FormatError("luma/chroma subsampling ratio")
}
if == 3 || == 3 {
return errUnsupportedSubsamplingRatio
}
switch .nComp {
, = 1, 1
switch {
if == 4 {
return errUnsupportedSubsamplingRatio
}
case 1: // Cb.
if .comp[0].h% != 0 || .comp[0].v% != 0 {
return errUnsupportedSubsamplingRatio
}
case 2: // Cr.
if .comp[1].h != || .comp[1].v != {
return errUnsupportedSubsamplingRatio
}
}
switch {
case 0:
if != 0x11 && != 0x22 {
return errUnsupportedSubsamplingRatio
}
case 1, 2:
if != 0x11 {
return errUnsupportedSubsamplingRatio
}
case 3:
if .comp[0].h != || .comp[0].v != {
return errUnsupportedSubsamplingRatio
}
}
}
.comp[].h =
.comp[].v =
}
return nil
}
func ( *decoder) ( int) error {
:
for > 0 {
--
, := .readByte()
if != nil {
return
}
:= & 0x0f
if > maxTq {
return FormatError("bad Tq value")
}
switch >> 4 {
default:
return FormatError("bad Pq value")
case 0:
if < blockSize {
break
}
-= blockSize
if := .readFull(.tmp[:blockSize]); != nil {
return
}
for := range .quant[] {
.quant[][] = int32(.tmp[])
}
case 1:
if < 2*blockSize {
break
}
-= 2 * blockSize
if := .readFull(.tmp[:2*blockSize]); != nil {
return
}
for := range .quant[] {
.quant[][] = int32(.tmp[2*])<<8 | int32(.tmp[2*+1])
}
}
}
if != 0 {
return FormatError("DQT has wrong length")
}
return nil
}
func ( *decoder) ( int) error {
if != 2 {
return FormatError("DRI has wrong length")
}
if := .readFull(.tmp[:2]); != nil {
return
}
.ri = int(.tmp[0])<<8 + int(.tmp[1])
return nil
}
func ( *decoder) ( int) error {
if < 5 {
return .ignore()
}
if := .readFull(.tmp[:5]); != nil {
return
}
-= 5
.jfif = .tmp[0] == 'J' && .tmp[1] == 'F' && .tmp[2] == 'I' && .tmp[3] == 'F' && .tmp[4] == '\x00'
if > 0 {
return .ignore()
}
return nil
}
func ( *decoder) ( int) error {
if < 12 {
return .ignore()
}
if := .readFull(.tmp[:12]); != nil {
return
}
-= 12
if .tmp[0] == 'A' && .tmp[1] == 'd' && .tmp[2] == 'o' && .tmp[3] == 'b' && .tmp[4] == 'e' {
.adobeTransformValid = true
.adobeTransform = .tmp[11]
}
if > 0 {
return .ignore()
}
return nil
}
continue
}
continue
}
if = .readFull(.tmp[:2]); != nil {
return nil,
}
:= int(.tmp[0])<<8 + int(.tmp[1]) - 2
if < 0 {
return nil, FormatError("short segment length")
}
switch {
case sof0Marker, sof1Marker, sof2Marker:
.baseline = == sof0Marker
.progressive = == sof2Marker
= .processSOF()
if && .jfif {
return nil,
}
case dhtMarker:
if {
= .ignore()
} else {
= .processDHT()
}
case dqtMarker:
if {
= .ignore()
} else {
= .processDQT()
}
case sosMarker:
if {
return nil, nil
}
= .processSOS()
case driMarker:
if {
= .ignore()
} else {
= .processDRI()
}
case app0Marker:
= .processApp0Marker()
case app14Marker:
= .processApp14Marker()
default:
if app0Marker <= && <= app15Marker || == comMarker {
= .ignore()
} else if < 0xc0 { // See Table B.1 "Marker code assignments".
= FormatError("unknown marker")
} else {
= UnsupportedError("unknown marker")
}
}
if != nil {
return nil,
}
}
if .progressive {
if := .reconstructProgressiveImage(); != nil {
return nil,
}
}
if .img1 != nil {
return .img1, nil
}
if .img3 != nil {
if .blackPix != nil {
return .applyBlack()
} else if .isRGB() {
return .convertToRGB()
}
return .img3, nil
}
return nil, FormatError("missing SOS marker")
}
func ( *decoder) () (image.Image, error) {
if !.adobeTransformValid {
return nil, UnsupportedError("unknown color model: 4-component JPEG doesn't have Adobe APP14 metadata")
}
:= .img3.Bounds()
:= image.NewCMYK()
:= [4]struct {
[]byte
int
}{
{.img3.Y, .img3.YStride},
{.img3.Cb, .img3.CStride},
{.img3.Cr, .img3.CStride},
{.blackPix, .blackStride},
}
for , := range {
:= .comp[].h != .comp[0].h || .comp[].v != .comp[0].v
for , := 0, .Min.Y; < .Max.Y; , = +.Stride, +1 {
:= - .Min.Y
if {
/= 2
}
for , := +, .Min.X; < .Max.X; , = +4, +1 {
:= - .Min.X
if {
/= 2
}
.Pix[] = 255 - .[*.+]
}
}
}
return , nil
}
func ( *decoder) () bool {
if .jfif {
return false
}
return true
}
return .comp[0].c == 'R' && .comp[1].c == 'G' && .comp[2].c == 'B'
}
func ( *decoder) () (image.Image, error) {
:= .comp[0].h / .comp[1].h
:= .img3.Bounds()
:= image.NewRGBA()
for := .Min.Y; < .Max.Y; ++ {
:= .PixOffset(.Min.X, )
:= .img3.YOffset(.Min.X, )
:= .img3.COffset(.Min.X, )
for , := 0, .Max.X-.Min.X; < ; ++ {
.Pix[+4*+0] = .img3.Y[+]
.Pix[+4*+1] = .img3.Cb[+/]
.Pix[+4*+2] = .img3.Cr[+/]
.Pix[+4*+3] = 255
}
}
return , nil
}
func ( io.Reader) (image.Config, error) {
var decoder
if , := .decode(, true); != nil {
return image.Config{},
}
switch .nComp {
case 1:
return image.Config{
ColorModel: color.GrayModel,
Width: .width,
Height: .height,
}, nil
case 3:
:= color.YCbCrModel
if .isRGB() {
= color.RGBAModel
}
return image.Config{
ColorModel: ,
Width: .width,
Height: .height,
}, nil
case 4:
return image.Config{
ColorModel: color.CMYKModel,
Width: .width,
Height: .height,
}, nil
}
return image.Config{}, FormatError("missing SOF marker")
}
func () {
image.RegisterFormat("jpeg", "\xff\xd8", Decode, DecodeConfig)
![]() |
The pages are generated with Golds v0.3.2-preview. (GOOS=darwin 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 @Go100and1 (reachable from the left QR code) to get the latest news of Golds. |