mirror of
https://github.com/golang/go
synced 2024-11-20 04:54:44 -07:00
image/png: interlacing support for png.
Fixes #6293. Image "testdata/benchRGB-interlace.png" was generated by opening "testdata/benchRGB.png" in the editor Gimp and saving it with interlacing enabled. Benchmark: BenchmarkDecodeRGB 500 7014194 ns/op 37.37 MB/s ok pkg/image/png 4.657s BenchmarkDecodeInterlacing 100 10623241 ns/op 24.68 MB/s ok pkg/image/png 1.339s LGTM=nigeltao R=nigeltao, andybons, matrixik CC=golang-codereviews https://golang.org/cl/102130044
This commit is contained in:
parent
0e694298e9
commit
5c2f01f392
@ -57,6 +57,29 @@ const (
|
||||
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 http://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), IDAT and IEND
|
||||
// chunks must appear in that order. There may be multiple IDAT chunks, and
|
||||
@ -84,6 +107,7 @@ type decoder struct {
|
||||
stage int
|
||||
idatLength uint32
|
||||
tmp [3 * 256]byte
|
||||
interlace int
|
||||
}
|
||||
|
||||
// A FormatError reports that the input is not a valid PNG.
|
||||
@ -113,9 +137,16 @@ func (d *decoder) parseIHDR(length uint32) error {
|
||||
return err
|
||||
}
|
||||
d.crc.Write(d.tmp[:13])
|
||||
if d.tmp[10] != 0 || d.tmp[11] != 0 || d.tmp[12] != 0 {
|
||||
return UnsupportedError("compression, filter or interlace method")
|
||||
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 {
|
||||
@ -287,7 +318,42 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
bitsPerPixel := 0
|
||||
var img image.Image
|
||||
if d.interlace == itNone {
|
||||
img, err = d.readImagePass(r, 0, false)
|
||||
} else if d.interlace == itAdam7 {
|
||||
// Allocate a blank image of the full size.
|
||||
img, err = d.readImagePass(nil, 0, true)
|
||||
for pass := 0; pass < 7; pass++ {
|
||||
imagePass, err := d.readImagePass(r, pass, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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) {
|
||||
var bitsPerPixel int = 0
|
||||
pixOffset := 0
|
||||
var (
|
||||
gray *image.Gray
|
||||
@ -299,52 +365,63 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
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
|
||||
}
|
||||
switch d.cb {
|
||||
case cbG1, cbG2, cbG4, cbG8:
|
||||
bitsPerPixel = d.depth
|
||||
gray = image.NewGray(image.Rect(0, 0, d.width, d.height))
|
||||
gray = image.NewGray(image.Rect(0, 0, width, height))
|
||||
img = gray
|
||||
case cbGA8:
|
||||
bitsPerPixel = 16
|
||||
nrgba = image.NewNRGBA(image.Rect(0, 0, d.width, d.height))
|
||||
nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
img = nrgba
|
||||
case cbTC8:
|
||||
bitsPerPixel = 24
|
||||
rgba = image.NewRGBA(image.Rect(0, 0, d.width, d.height))
|
||||
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, d.width, d.height), d.palette)
|
||||
paletted = image.NewPaletted(image.Rect(0, 0, width, height), d.palette)
|
||||
img = paletted
|
||||
case cbTCA8:
|
||||
bitsPerPixel = 32
|
||||
nrgba = image.NewNRGBA(image.Rect(0, 0, d.width, d.height))
|
||||
nrgba = image.NewNRGBA(image.Rect(0, 0, width, height))
|
||||
img = nrgba
|
||||
case cbG16:
|
||||
bitsPerPixel = 16
|
||||
gray16 = image.NewGray16(image.Rect(0, 0, d.width, d.height))
|
||||
gray16 = image.NewGray16(image.Rect(0, 0, width, height))
|
||||
img = gray16
|
||||
case cbGA16:
|
||||
bitsPerPixel = 32
|
||||
nrgba64 = image.NewNRGBA64(image.Rect(0, 0, d.width, d.height))
|
||||
nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
|
||||
img = nrgba64
|
||||
case cbTC16:
|
||||
bitsPerPixel = 48
|
||||
rgba64 = image.NewRGBA64(image.Rect(0, 0, d.width, d.height))
|
||||
rgba64 = image.NewRGBA64(image.Rect(0, 0, width, height))
|
||||
img = rgba64
|
||||
case cbTCA16:
|
||||
bitsPerPixel = 64
|
||||
nrgba64 = image.NewNRGBA64(image.Rect(0, 0, d.width, d.height))
|
||||
nrgba64 = image.NewNRGBA64(image.Rect(0, 0, width, height))
|
||||
img = nrgba64
|
||||
}
|
||||
if allocateOnly {
|
||||
return img, nil
|
||||
}
|
||||
bytesPerPixel := (bitsPerPixel + 7) / 8
|
||||
|
||||
// cr and pr are the bytes for the current and previous row.
|
||||
// The +1 is for the per-row filter type, which is at cr[0].
|
||||
cr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8)
|
||||
pr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8)
|
||||
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 < d.height; y++ {
|
||||
for y := 0; y < height; y++ {
|
||||
// Read the decompressed bytes.
|
||||
_, err := io.ReadFull(r, cr)
|
||||
if err != nil {
|
||||
@ -381,25 +458,25 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
// Convert from bytes to colors.
|
||||
switch d.cb {
|
||||
case cbG1:
|
||||
for x := 0; x < d.width; x += 8 {
|
||||
for x := 0; x < width; x += 8 {
|
||||
b := cdat[x/8]
|
||||
for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
|
||||
for x2 := 0; x2 < 8 && x+x2 < width; x2++ {
|
||||
gray.SetGray(x+x2, y, color.Gray{(b >> 7) * 0xff})
|
||||
b <<= 1
|
||||
}
|
||||
}
|
||||
case cbG2:
|
||||
for x := 0; x < d.width; x += 4 {
|
||||
for x := 0; x < width; x += 4 {
|
||||
b := cdat[x/4]
|
||||
for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
|
||||
for x2 := 0; x2 < 4 && x+x2 < width; x2++ {
|
||||
gray.SetGray(x+x2, y, color.Gray{(b >> 6) * 0x55})
|
||||
b <<= 2
|
||||
}
|
||||
}
|
||||
case cbG4:
|
||||
for x := 0; x < d.width; x += 2 {
|
||||
for x := 0; x < width; x += 2 {
|
||||
b := cdat[x/2]
|
||||
for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
|
||||
for x2 := 0; x2 < 2 && x+x2 < width; x2++ {
|
||||
gray.SetGray(x+x2, y, color.Gray{(b >> 4) * 0x11})
|
||||
b <<= 4
|
||||
}
|
||||
@ -408,13 +485,13 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
copy(gray.Pix[pixOffset:], cdat)
|
||||
pixOffset += gray.Stride
|
||||
case cbGA8:
|
||||
for x := 0; x < d.width; x++ {
|
||||
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:
|
||||
pix, i, j := rgba.Pix, pixOffset, 0
|
||||
for x := 0; x < d.width; x++ {
|
||||
for x := 0; x < width; x++ {
|
||||
pix[i+0] = cdat[j+0]
|
||||
pix[i+1] = cdat[j+1]
|
||||
pix[i+2] = cdat[j+2]
|
||||
@ -424,9 +501,9 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
}
|
||||
pixOffset += rgba.Stride
|
||||
case cbP1:
|
||||
for x := 0; x < d.width; x += 8 {
|
||||
for x := 0; x < width; x += 8 {
|
||||
b := cdat[x/8]
|
||||
for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ {
|
||||
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]
|
||||
@ -436,9 +513,9 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
}
|
||||
}
|
||||
case cbP2:
|
||||
for x := 0; x < d.width; x += 4 {
|
||||
for x := 0; x < width; x += 4 {
|
||||
b := cdat[x/4]
|
||||
for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ {
|
||||
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]
|
||||
@ -448,9 +525,9 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
}
|
||||
}
|
||||
case cbP4:
|
||||
for x := 0; x < d.width; x += 2 {
|
||||
for x := 0; x < width; x += 2 {
|
||||
b := cdat[x/2]
|
||||
for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ {
|
||||
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]
|
||||
@ -461,7 +538,7 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
}
|
||||
case cbP8:
|
||||
if len(paletted.Palette) != 255 {
|
||||
for x := 0; x < d.width; x++ {
|
||||
for x := 0; x < width; x++ {
|
||||
if len(paletted.Palette) <= int(cdat[x]) {
|
||||
paletted.Palette = paletted.Palette[:int(cdat[x])+1]
|
||||
}
|
||||
@ -473,25 +550,25 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
copy(nrgba.Pix[pixOffset:], cdat)
|
||||
pixOffset += nrgba.Stride
|
||||
case cbG16:
|
||||
for x := 0; x < d.width; x++ {
|
||||
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 < d.width; x++ {
|
||||
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:
|
||||
for x := 0; x < d.width; x++ {
|
||||
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 < d.width; x++ {
|
||||
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])
|
||||
@ -504,24 +581,68 @@ func (d *decoder) decode() (image.Image, error) {
|
||||
pr, cr = cr, pr
|
||||
}
|
||||
|
||||
// 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(pr[: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
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
@ -30,6 +30,7 @@ var filenames = []string{
|
||||
"basn3p01",
|
||||
"basn3p02",
|
||||
"basn3p04",
|
||||
"basn3p04-31i",
|
||||
"basn3p08",
|
||||
"basn3p08-trns",
|
||||
"basn4a08",
|
||||
@ -186,6 +187,13 @@ func sng(w io.WriteCloser, filename string, png image.Image) {
|
||||
c = 0
|
||||
}
|
||||
}
|
||||
if c != 0 {
|
||||
for c != 8/bitdepth {
|
||||
b = b << uint(bitdepth)
|
||||
c++
|
||||
}
|
||||
fmt.Fprintf(w, "%02x", b)
|
||||
}
|
||||
}
|
||||
io.WriteString(w, "\n")
|
||||
}
|
||||
@ -348,3 +356,7 @@ func BenchmarkDecodePaletted(b *testing.B) {
|
||||
func BenchmarkDecodeRGB(b *testing.B) {
|
||||
benchmarkDecode(b, "testdata/benchRGB.png", 4)
|
||||
}
|
||||
|
||||
func BenchmarkDecodeInterlacing(b *testing.B) {
|
||||
benchmarkDecode(b, "testdata/benchRGB-interlace.png", 4)
|
||||
}
|
||||
|
BIN
src/pkg/image/png/testdata/benchRGB-interlace.png
vendored
Normal file
BIN
src/pkg/image/png/testdata/benchRGB-interlace.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
BIN
src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png
vendored
Normal file
BIN
src/pkg/image/png/testdata/pngsuite/basn3p04-31i.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 358 B |
57
src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng
vendored
Normal file
57
src/pkg/image/png/testdata/pngsuite/basn3p04-31i.sng
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
#SNG: from basn3p04-31i.png
|
||||
IHDR {
|
||||
width: 31; height: 31; bitdepth: 4;
|
||||
using color palette;
|
||||
}
|
||||
gAMA {1.0000}
|
||||
PLTE {
|
||||
( 34, 0,255) # rgb = (0x22,0x00,0xff)
|
||||
( 0,255,255) # rgb = (0x00,0xff,0xff)
|
||||
(136, 0,255) # rgb = (0x88,0x00,0xff)
|
||||
( 34,255, 0) # rgb = (0x22,0xff,0x00)
|
||||
( 0,153,255) # rgb = (0x00,0x99,0xff)
|
||||
(255,102, 0) # rgb = (0xff,0x66,0x00)
|
||||
(221, 0,255) # rgb = (0xdd,0x00,0xff)
|
||||
(119,255, 0) # rgb = (0x77,0xff,0x00)
|
||||
(255, 0, 0) # rgb = (0xff,0x00,0x00)
|
||||
( 0,255,153) # rgb = (0x00,0xff,0x99)
|
||||
(221,255, 0) # rgb = (0xdd,0xff,0x00)
|
||||
(255, 0,187) # rgb = (0xff,0x00,0xbb)
|
||||
(255,187, 0) # rgb = (0xff,0xbb,0x00)
|
||||
( 0, 68,255) # rgb = (0x00,0x44,0xff)
|
||||
( 0,255, 68) # rgb = (0x00,0xff,0x44)
|
||||
}
|
||||
IMAGE {
|
||||
pixels hex
|
||||
88885555ccccaaaa77773333eeee9990
|
||||
88885555ccccaaaa77773333eeee9990
|
||||
88885555ccccaaaa77773333eeee9990
|
||||
88885555ccccaaaa77773333eeee9990
|
||||
5555ccccaaaa77773333eeee99991110
|
||||
5555ccccaaaa77773333eeee99991110
|
||||
5555ccccaaaa77773333eeee99991110
|
||||
5555ccccaaaa77773333eeee99991110
|
||||
ccccaaaa77773333eeee999911114440
|
||||
ccccaaaa77773333eeee999911114440
|
||||
ccccaaaa77773333eeee999911114440
|
||||
ccccaaaa77773333eeee999911114440
|
||||
aaaa77773333eeee999911114444ddd0
|
||||
aaaa77773333eeee999911114444ddd0
|
||||
aaaa77773333eeee999911114444ddd0
|
||||
aaaa77773333eeee999911114444ddd0
|
||||
77773333eeee999911114444dddd0000
|
||||
77773333eeee999911114444dddd0000
|
||||
77773333eeee999911114444dddd0000
|
||||
77773333eeee999911114444dddd0000
|
||||
3333eeee999911114444dddd00002220
|
||||
3333eeee999911114444dddd00002220
|
||||
3333eeee999911114444dddd00002220
|
||||
3333eeee999911114444dddd00002220
|
||||
eeee999911114444dddd000022226660
|
||||
eeee999911114444dddd000022226660
|
||||
eeee999911114444dddd000022226660
|
||||
eeee999911114444dddd000022226660
|
||||
999911114444dddd000022226666bbb0
|
||||
999911114444dddd000022226666bbb0
|
||||
999911114444dddd000022226666bbb0
|
||||
}
|
Loading…
Reference in New Issue
Block a user