Browse Source

Now pixel package

master v0.1.2
maze 10 months ago
parent
commit
dfbff8e2c7
23 changed files with 583 additions and 94 deletions
  1. +194
    -28
      color.go
  2. +273
    -0
      color_test.go
  3. +4
    -0
      doc.go
  4. +10
    -10
      draw.go
  5. +1
    -1
      draw_test.go
  6. +1
    -1
      fx.go
  7. +1
    -1
      fx_bit.go
  8. +1
    -1
      fx_test.go
  9. +1
    -1
      go.mod
  10. +44
    -9
      image.go
  11. +1
    -1
      image_test.go
  12. +24
    -24
      pixeleffect/fade.go
  13. +5
    -5
      pixeleffect/fade_test.go
  14. +0
    -0
      pixeleffect/testdata/gopher.png
  15. +7
    -6
      pixelfont/font.go
  16. +1
    -1
      pixelfont/font_glcd_5x8.go
  17. +5
    -3
      pixelfont/font_test.go
  18. +0
    -0
      pixelfont/testdata/16bit.ttf
  19. +0
    -0
      pixelfont/testdata/8bit.ttf
  20. +0
    -0
      pixelfont/testdata/pixelmix.ttf
  21. +8
    -0
      pixelfont/util.go
  22. +1
    -1
      util.go
  23. +1
    -1
      util_test.go

+ 194
- 28
color.go View File

@ -1,14 +1,90 @@
package bitmap
package pixel
import "image/color"
import (
"image/color"
)
// Color models supported by this package.
var (
BitModel = color.ModelFunc(convertBit)
RGB565Model = color.ModelFunc(convertRGB565)
RGB888Model = color.ModelFunc(convertRGB888)
BitModel = color.ModelFunc(bitModel)
RGB332Model = color.ModelFunc(rgb332Model)
RGB565Model = color.ModelFunc(rgb565Model)
RGB888Model = color.ModelFunc(rgb888Model)
RGBA4444Model = color.ModelFunc(rgba4444Model)
RGBA5551Model = color.ModelFunc(rgba5551Model)
)
func bitModel(c color.Color) color.Color {
if _, ok := c.(Bit); ok {
return c
}
r, g, b, _ := c.RGBA()
// These coefficients (the fractions 0.299, 0.587 and 0.114) are the same
// as those given by the JFIF specification and used by func RGBToYCbCr in
// ycbcr.go.
//
// Note that 19595 + 38470 + 7471 equals 65536.
y := (19595*r + 38470*g + 7471*b + 1<<15) >> 24
return Bit(uint8(y) >= 0x80)
//return Bit((r | g | b) >= 0x8000)
}
func rgb332Model(c color.Color) color.Color {
if _, ok := c.(RGB332); ok {
return c
}
r, g, b, _ := c.RGBA()
r >>= 13
g >>= 13
b >>= 14
return RGB332(r<<5 | g<<2 | b)
}
func rgb565Model(c color.Color) color.Color {
if _, ok := c.(RGB565); ok {
return c
}
r, g, b, _ := c.RGBA()
// RRRRRGGGGGGBBBBB
return RGB565((r & 0xf800) | ((g & 0xfc00) >> 5) | ((b & 0xf800) >> 11))
}
func rgb888Model(c color.Color) color.Color {
if _, ok := c.(RGB888); ok {
return c
}
r, g, b, _ := c.RGBA()
return RGB888{uint8(r), uint8(g), uint8(b)}
}
func rgba4444Model(c color.Color) color.Color {
if _, ok := c.(RGBA4444); ok {
return c
}
r, g, b, a := c.RGBA()
r >>= 12
g >>= 12
b >>= 12
a >>= 12
return RGBA4444(r<<12 | g<<8 | b<<4 | a)
}
func rgba5551Model(c color.Color) color.Color {
if _, ok := c.(RGBA5551); ok {
return c
}
r, g, b, a := c.RGBA()
r >>= 11
g >>= 11
b >>= 11
if a > 0 {
a = 1
}
return RGBA5551(r<<11 | g<<6 | b<<1 | a)
}
// Bit is a 1-bit color.
type Bit bool
@ -25,11 +101,7 @@ func (c Bit) RGBA() (r, g, b, a uint32) {
return 0, 0, 0, 0xffff
}
func convertBit(c color.Color) color.Color {
return convertBitColor(c)
}
func convertBitColor(c color.Color) Bit {
func toBit(c color.Color) Bit {
switch c := c.(type) {
case Bit:
return c
@ -39,6 +111,36 @@ func convertBitColor(c color.Color) Bit {
}
}
// RGB332 is a 8-bit RGB color with no alpha channel.
type RGB332 uint8 // 3-3-2 RGB
func (c RGB332) RGBA() (r, g, b, a uint32) {
r = uint32(c&0b111_000_00) >> 4 // ............rrr0
r |= r << 3 // .........rrrrrr0
r |= r << 6 // ......rrrrrrrrr0
r |= r << 12 // rrrrrrrrrrrrrrr0
g = uint32(c&0b000_111_00) >> 1 // ............ggg0
g |= g << 3 // .........gggggg0
g |= g << 6 // ......ggggggggg0
g |= g << 12 // ggggggggggggggg0
b = uint32(c&0b000_000_11) >> 0 // ..............bb
b |= b << 2 // ............bbbb
b |= b << 4 // ........bbbbbbbb
b |= b << 8 // bbbbbbbbbbbbbbbb
return
}
func toRGB332(c color.Color) RGB332 {
if c, ok := c.(RGB332); ok {
return c
}
r, g, b, _ := c.RGBA()
r >>= 13
g >>= 13
b >>= 14
return RGB332(r<<5 | g<<2 | b)
}
// RGB565 is a 16-bit RGB color with no alpha channel.
type RGB565 uint16 // 5-6-5 RGB
@ -60,9 +162,9 @@ func (c RGB565) RGBA() (r, g, b, a uint32) {
//
// Alpha is always 100% opaque since this model does not support
// transparency.
rBits := uint32(c & 0xF800) // RRRRR00000000000
gBits := uint32(c & 0x7E0) // 00000GGGGGG00000
bBits := uint32(c & 0x1F) // 00000000000BBBBB
rBits := uint32(c & 0b11111_000000_00000)
gBits := uint32(c & 0b00000_111111_00000)
bBits := uint32(c & 0b00000_000000_11111)
r = uint32(rBits | rBits>>5 | rBits>>10 | rBits>>15)
g = uint32(gBits<<5 | gBits>>1 | gBits>>7)
b = uint32(bBits<<11 | bBits<<6 | bBits<<1 | bBits>>4)
@ -70,32 +172,96 @@ func (c RGB565) RGBA() (r, g, b, a uint32) {
return
}
func convertRGB565(c color.Color) color.Color {
return convertRGB565Color(c)
}
func convertRGB565Color(c color.Color) RGB565 {
func toRGB565(c color.Color) RGB565 {
r, g, b, _ := c.RGBA()
// RRRRRGGGGGGBBBBB
return RGB565((r & 0xf800) + ((g & 0xfc00) >> 5) + ((b & 0xf800) >> 11))
}
// RGB888 is a 24-bit color with no alpha channel.
type RGB888 [3]byte // 8-8-8 RGB
type RGB888 struct {
R, G, B uint8
}
func (c RGB888) RGBA() (r, g, b, a uint32) {
r = uint32(c[0]) | uint32(c[0])<<8
g = uint32(c[1]) | uint32(c[1])<<8
b = uint32(c[2]) | uint32(c[2])<<8
a = 0xffff
r = uint32(c.R)
r |= r << 8
g = uint32(c.G)
g |= g << 8
b = uint32(c.B)
b |= b << 8
return
}
func convertRGB888(c color.Color) color.Color {
return convertRGB888Color(c)
}
func convertRGB888Color(c color.Color) RGB888 {
func toRGB888(c color.Color) RGB888 {
r, g, b, _ := c.RGBA()
return RGB888{byte(r), byte(g), byte(b)}
}
// RGBA4444 is a 16-bit RGB color with alpha channel.
type RGBA4444 uint16
func (c RGBA4444) RGBA() (r, g, b, a uint32) {
r = uint32(c&0b1111_0000_0000_0000) >> 12
r |= r << 4
r |= r << 8
r |= r << 12
g = uint32(c&0b0000_1111_0000_0000) >> 8
g |= g << 4
g |= g << 8
g |= g << 12
b = uint32(c&0b0000_0000_1111_0000) >> 4
b |= b << 4
b |= b << 8
b |= b << 12
a = uint32(c&0b0000_0000_0000_1111) >> 0
a |= a << 4
a |= a << 8
a |= a << 12
return
}
func toRGBA4444(c color.Color) RGBA4444 {
if c, ok := c.(RGBA4444); ok {
return c
}
r, g, b, a := c.RGBA()
r >>= 12
g >>= 12
b >>= 12
a >>= 12
return RGBA4444(r<<12 | g<<8 | b<<4 | a)
}
// RGBA5551 is a 16-bit RGB color with alpha channel.
type RGBA5551 uint16
func (c RGBA5551) RGBA() (r, g, b, a uint32) {
var (
rBits = uint32(c&0b11111_00000_00000_0) >> 11
gBits = uint32(c&0b00000_11111_00000_0) >> 6
bBits = uint32(c&0b00000_00000_11111_0) >> 1
aBits = uint32(c&0b00000_00000_00000_1) >> 0
)
r = (rBits | rBits<<5 | rBits<<10) << 1
g = (gBits | gBits<<5 | gBits<<10) << 1
b = (bBits | bBits<<5 | bBits<<10) << 1
if aBits == 1 {
a = 0xffff
}
return
}
func toRGBA5551(c color.Color) RGBA5551 {
if c, ok := c.(RGBA5551); ok {
return c
}
r, g, b, a := c.RGBA()
r >>= 11
g >>= 11
b >>= 11
if a > 0 {
a = 1
}
return RGBA5551(r<<11 | g<<6 | b<<1 | a)
}

+ 273
- 0
color_test.go View File

@ -0,0 +1,273 @@
package pixel
import (
"image/color"
"math/bits"
"testing"
)
var (
testBlack = color.RGBA{0x00, 0x00, 0x00, 0x00}
testWhite = color.RGBA{0xff, 0xff, 0xff, 0x00}
testAmber = color.RGBA{0xff, 0x7f, 0x00, 0x00}
testIndigo = color.RGBA{0x50, 0x00, 0x82, 0x00}
testTurquoise = color.RGBA{0x00, 0xCE, 0xD1, 0x00}
testGray25 = color.RGBA{0x3f, 0x3f, 0x3f, 0x3f}
testGray50 = color.RGBA{0x7f, 0x7f, 0x7f, 0x7f}
testGray75 = color.RGBA{0xbf, 0xbf, 0xbf, 0xbf}
testColorSink color.Color
testColors = []struct {
Name string
Color color.Color
}{
{"black", testBlack}, // black
{"white", testWhite}, // white
{"amber", testAmber}, // amber
{"indigo", testIndigo}, // indigo
{"turquoise", testTurquoise}, // turquoise
{"gray25", testGray25}, // gray 25%
{"gray50", testGray50}, // gray 50%
{"gray75", testGray75}, // gray 75%
}
)
func TestRGB323(t *testing.T) {
tests := []struct {
Name string
Test RGB332
Want color.RGBA
}{
{"black", 0b000_000_00, testBlack},
{"white", 0b111_111_11, testWhite},
{"amber", 0b111_011_00, testAmber},
{"indigo", 0b010_000_10, testIndigo},
{"turquoise", 0b000_110_11, testTurquoise},
{"gray25", 0b001_001_00, testGray25},
{"gray50", 0b011_011_01, testGray50},
{"gray75", 0b101_101_10, testGray75},
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
testColorSink = color.RGBAModel.Convert(test.Test)
})
}
}
func TestRGB332Model(t *testing.T) {
want := map[string]RGB332{
"black": 0b000_000_00,
"white": 0b111_111_11,
"amber": 0b111_011_00,
"indigo": 0b010_000_10,
"turquoise": 0b000_110_11,
"gray25": 0b001_001_00,
"gray50": 0b011_011_01,
"gray75": 0b101_101_10,
}
for _, test := range testColors {
t.Run(test.Name, func(it *testing.T) {
v := RGB332Model.Convert(test.Color).(RGB332)
if v != want[test.Name] {
it.Fatalf("expected %q (%+v) to return %#08b, got %#08b", test.Name, test.Color, want[test.Name], v)
}
})
}
}
func TestRGB565(t *testing.T) {
tests := []struct {
Name string
Test RGB565
Want color.RGBA
}{
{"black", 0b00000_000000_00000, testBlack},
{"white", 0b11111_111111_11111, testWhite},
{"amber", 0b11111_011111_00000, testAmber},
{"indigo", 0b01010_000000_10000, testIndigo},
{"turquoise", 0b00000_110011_11010, testTurquoise},
{"gray25", 0b00111_001111_00111, testGray25},
{"gray50", 0b01111_011111_01111, testGray50},
{"gray75", 0b10111_101111_10111, testGray75},
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
v := color.RGBAModel.Convert(test.Test).(color.RGBA)
testColorBitErrors(it, test.Want, v, 3, false)
})
}
}
func TestRGB565Model(t *testing.T) {
want := map[string]RGB565{
"black": 0b00000_000000_00000,
"white": 0b11111_111111_11111,
"amber": 0b11111_011111_00000,
"indigo": 0b01010_000000_10000,
"turquoise": 0b00000_110011_11010,
"gray25": 0b00111_001111_00111,
"gray50": 0b01111_011111_01111,
"gray75": 0b10111_101111_10111,
}
for _, test := range testColors {
t.Run(test.Name, func(it *testing.T) {
v := RGB565Model.Convert(test.Color).(RGB565)
if v != want[test.Name] {
it.Fatalf("expected %q (%+v) to return %#016b, got %#016b", test.Name, test.Color, want[test.Name], v)
}
})
}
}
func TestRGB888(t *testing.T) {
tests := []struct {
Name string
Test RGB888
Want color.RGBA
}{
{"black", RGB888{0x00, 0x00, 0x00}, testBlack},
{"white", RGB888{0xff, 0xff, 0xff}, testWhite},
{"amber", RGB888{0xff, 0x7f, 0x00}, testAmber},
{"indigo", RGB888{0x50, 0x00, 0x82}, testIndigo},
{"turquoise", RGB888{0x00, 0xCE, 0xD1}, testTurquoise},
{"gray25", RGB888{0x3f, 0x3f, 0x3f}, testGray25},
{"gray50", RGB888{0x7f, 0x7f, 0x7f}, testGray50},
{"gray75", RGB888{0xbf, 0xbf, 0xbf}, testGray75},
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
v := color.RGBAModel.Convert(test.Test).(color.RGBA)
testColorBitErrors(it, test.Want, v, 2, false)
})
}
}
func TestRGB888Model(t *testing.T) {
want := map[string]RGB888{
"black": {0x00, 0x00, 0x00}, // black
"white": {0xff, 0xff, 0xff}, // white
"amber": {0xff, 0x7f, 0x00}, // amber
"indigo": {0x50, 0x00, 0x82}, // indigo
"turquoise": {0x00, 0xCE, 0xD1}, // turquoise
"gray25": {0x3f, 0x3f, 0x3f}, // gray 25%
"gray50": {0x7f, 0x7f, 0x7f}, // gray 50%
"gray75": {0xbf, 0xbf, 0xbf}, // gray 75%
}
for _, test := range testColors {
t.Run(test.Name, func(it *testing.T) {
v := RGB888Model.Convert(test.Color).(RGB888)
if v != want[test.Name] {
it.Fatalf("expected %q (%+v) to return %+v, got %+v", test.Name, test.Color, want[test.Name], v)
}
})
}
}
func TestRGBA4444(t *testing.T) {
tests := []struct {
Name string
Test RGBA4444
Want color.RGBA
}{
{"black", 0b0000_0000_0000_0000, testBlack},
{"white", 0b1111_1111_1111_0000, testWhite},
{"amber", 0b1111_0111_0000_0000, testAmber},
{"indigo", 0b0101_0000_1000_0000, testIndigo},
{"turquoise", 0b0000_1100_1101_0000, testTurquoise},
{"gray25", 0b0011_0011_0011_0011, testGray25},
{"gray50", 0b0111_0111_0111_0111, testGray50},
{"gray75", 0b1011_1011_1011_1011, testGray75},
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
v := color.RGBAModel.Convert(test.Test).(color.RGBA)
testColorBitErrors(it, test.Want, v, 2, true)
})
}
}
func TestRGBA444Model(t *testing.T) {
want := map[string]RGBA4444{
"black": 0b0000_0000_0000_0000, // black
"white": 0b1111_1111_1111_0000, // white
"amber": 0b1111_0111_0000_0000, // amber
"indigo": 0b0101_0000_1000_0000, // indigo
"turquoise": 0b0000_1100_1101_0000, // turquoise
"gray25": 0b0011_0011_0011_0011, // gray 50%
"gray50": 0b0111_0111_0111_0111, // gray 50%
"gray75": 0b1011_1011_1011_1011, // gray 50%
}
for _, test := range testColors {
t.Run(test.Name, func(it *testing.T) {
v := RGBA4444Model.Convert(test.Color).(RGBA4444)
if v != want[test.Name] {
it.Fatalf("expected %q (%+v) to return %#016b, got %#016b", test.Name, test.Color, want[test.Name], v)
}
})
}
}
func TestRGBA5551(t *testing.T) {
tests := []struct {
Name string
Test RGBA5551
Want color.RGBA // A not used
Alpha bool
}{
{"black", 0b00000_00000_00000_0, testBlack, false},
{"white", 0b11111_11111_11111_0, testWhite, false},
{"amber", 0b11111_01111_00000_0, testAmber, false},
{"indigo", 0b01010_00000_10000_0, testIndigo, false},
{"turquoise", 0b00000_11001_11010_0, testTurquoise, false},
{"gray25", 0b00111_00111_00111_1, testGray25, true},
{"gray50", 0b01111_01111_01111_1, testGray50, true},
{"gray75", 0b10111_10111_10111_1, testGray75, true},
}
for _, test := range tests {
t.Run(test.Name, func(it *testing.T) {
v := color.RGBAModel.Convert(test.Test).(color.RGBA)
testColorBitErrors(it, test.Want, v, 3, false)
if v.A > 0 != test.Alpha {
it.Errorf("expected alpha %t, got %t: %+v", test.Alpha, v.A > 0, v)
}
})
}
}
func TestRGBA5551Model(t *testing.T) {
want := map[string]RGBA5551{
"black": 0b00000_00000_00000_0, // black
"white": 0b11111_11111_11111_0, // white
"amber": 0b11111_01111_00000_0, // amber
"indigo": 0b01010_00000_10000_0, // indigo
"turquoise": 0b00000_11001_11010_0, // turquoise
"gray25": 0b00111_00111_00111_1, // gray 25%
"gray50": 0b01111_01111_01111_1, // gray 50%
"gray75": 0b10111_10111_10111_1, // gray 75%
}
for _, test := range testColors {
t.Run(test.Name, func(it *testing.T) {
v := RGBA5551Model.Convert(test.Color).(RGBA5551)
if v != want[test.Name] {
it.Fatalf("expected %q (%+v) to return %#016b, got %#016b", test.Name, test.Color, want[test.Name], v)
}
})
}
}
func testColorBitErrors(t *testing.T, want color.RGBA, v color.RGBA, errors int, alpha bool) {
t.Helper()
if n := bits.OnesCount8(v.R ^ want.R); n > errors {
t.Errorf("R channel has %d bit errors: want %#02x (%#08b), got %#02x (%#08b)", n, want.R, want.R, v.R, v.R)
}
if n := bits.OnesCount8(v.G ^ want.G); n > errors {
t.Errorf("G channel has %d bit errors: want %#02x (%#08b), got %#02x (%#08b)", n, want.G, want.G, v.G, v.G)
}
if n := bits.OnesCount8(v.B ^ want.B); n > errors {
t.Errorf("B channel has %d bit errors: want %#02x (%#08b), got %#02x (%#08b)", n, want.B, want.B, v.B, v.B)
}
if alpha {
if n := bits.OnesCount8(v.A ^ want.A); n > errors {
t.Errorf("A channel has %d bit errors: want %#02x (%#08b), got %#02x (%#08b)", n, want.A, want.A, v.A, v.A)
}
}
}

+ 4
- 0
doc.go View File

@ -0,0 +1,4 @@
/*
Package pixel contains common pixel formats.
*/
package pixel

+ 10
- 10
draw.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
import (
"encoding/binary"
@ -9,18 +9,18 @@ import (
func Fill(b Image, c color.Color) {
switch b := b.(type) {
case *Bitmap:
if convertBitColor(c) {
if toBit(c) {
memset(b.Pix, 0xff)
} else {
memset(b.Pix, 0x00)
}
case *RGB565Image:
var v [2]byte
binary.BigEndian.PutUint16(v[:], uint16(convertRGB565Color(c)))
binary.BigEndian.PutUint16(v[:], uint16(toRGB565(c)))
memsetSlice(b.Pix, v[:])
case *RGB888Image:
v := convertRGB888Color(c)
memsetSlice(b.Pix, v[:])
v := toRGB888(c)
memsetSlice(b.Pix, []byte{v.R, v.G, v.B})
default:
// Naive and slow set pixel implementation.
r := b.Bounds()
@ -43,7 +43,7 @@ func fillRectangle(b Image, x, y, width, height int, c color.Color) {
switch b.Format {
case MHMSBFormat:
var v byte
if convertBitColor(c) {
if toBit(c) {
v = 0x01
}
for xx := x; xx < x+width; xx++ {
@ -57,7 +57,7 @@ func fillRectangle(b Image, x, y, width, height int, c color.Color) {
case MVLSBFormat:
var v byte
if convertBitColor(c) {
if toBit(c) {
v = 0x01
}
for ; height > 0; height, y = height-1, y+1 {
@ -77,7 +77,7 @@ func fillRectangle(b Image, x, y, width, height int, c color.Color) {
r = b.Bounds()
xe = min(x+width, r.Max.X)
ye = min(y+height, r.Max.Y)
c = convertRGB565Color(c)
c = toRGB565(c)
v [2]byte
)
binary.BigEndian.PutUint16(v[:], uint16(c))
@ -91,11 +91,11 @@ func fillRectangle(b Image, x, y, width, height int, c color.Color) {
r = b.Bounds()
xe = min(x+width, r.Max.X)
ye = min(y+height, r.Max.Y)
v = convertRGB888Color(c)
v = toRGB888(c)
)
for ; y < ye; y++ {
o := y*b.Stride + x*3
memsetSlice(b.Pix[o:o+(xe-x)*3], v[:])
memsetSlice(b.Pix[o:o+(xe-x)*3], []byte{v.R, v.G, v.B})
}
default:


+ 1
- 1
draw_test.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
import (
"image"


+ 1
- 1
fx.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
/*
func Scroll(buffer Image, xstep, ystep int) {


+ 1
- 1
fx_bit.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
import "time"


+ 1
- 1
fx_test.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
import (
"image"


+ 1
- 1
go.mod View File

@ -1,4 +1,4 @@
module maze.io/x/bitmap
module maze.io/x/pixel
go 1.14


bitmap.go → image.go View File


bitmap_test.go → image_test.go View File


fx/fade.go → pixeleffect/fade.go View File


fx/fade_test.go → pixeleffect/fade_test.go View File


fx/testdata/gopher.png → pixeleffect/testdata/gopher.png View File


font.go → pixelfont/font.go View File


font_glcd_5x8.go → pixelfont/font_glcd_5x8.go View File


font_test.go → pixelfont/font_test.go View File


testdata/16bit.ttf → pixelfont/testdata/16bit.ttf View File


testdata/8bit.ttf → pixelfont/testdata/8bit.ttf View File


testdata/pixelmix.ttf → pixelfont/testdata/pixelmix.ttf View File


+ 8
- 0
pixelfont/util.go View File

@ -0,0 +1,8 @@
package pixelfont
func max(a, b int) int {
if a > b {
return a
}
return b
}

+ 1
- 1
util.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
func abs(v int) int {
if v < 0 {


+ 1
- 1
util_test.go View File

@ -1,4 +1,4 @@
package bitmap
package pixel
import (
"bytes"


Loading…
Cancel
Save