mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-12-24 13:08:06 +08:00
ebiten: add premultipliedAlpha flag to imageToBytes
This is a preparation for ColorEncoding option at NewImageFromImage. Updates #3314
This commit is contained in:
2
image.go
2
image.go
@@ -1511,7 +1511,7 @@ func NewImageFromImageWithOptions(source image.Image, options *NewImageFromImage
|
||||
return i
|
||||
}
|
||||
|
||||
i.WritePixels(imageToBytes(source))
|
||||
i.WritePixels(imageToBytes(source, true))
|
||||
return i
|
||||
}
|
||||
|
||||
|
||||
@@ -21,12 +21,13 @@ import (
|
||||
)
|
||||
|
||||
// imageToBytes gets RGBA bytes from img.
|
||||
// premultipliedAlpha specifies whether the returned bytes are in premultiplied alpha format or not.
|
||||
//
|
||||
// Basically imageToBytes just calls draw.Draw.
|
||||
// If img is a paletted image, an optimized copying method is used.
|
||||
//
|
||||
// If img is *image.RGBA and its length is same as 4*width*height, imageToBytes returns its Pix.
|
||||
func imageToBytes(img image.Image) []byte {
|
||||
// imageToBytes might return img.Pix directly without copying when possible.
|
||||
func imageToBytes(img image.Image, premultipliedAlpha bool) []byte {
|
||||
size := img.Bounds().Size()
|
||||
w, h := size.X, size.Y
|
||||
|
||||
@@ -41,48 +42,70 @@ func imageToBytes(img image.Image) []byte {
|
||||
y1 := b.Max.Y
|
||||
|
||||
palette := make([]uint8, len(img.Palette)*4)
|
||||
for i, c := range img.Palette {
|
||||
// Create a temporary slice to reduce boundary checks.
|
||||
pl := palette[4*i : 4*i+4]
|
||||
rgba := color.RGBAModel.Convert(c).(color.RGBA)
|
||||
pl[0] = rgba.R
|
||||
pl[1] = rgba.G
|
||||
pl[2] = rgba.B
|
||||
pl[3] = rgba.A
|
||||
if premultipliedAlpha {
|
||||
for i, c := range img.Palette {
|
||||
// Create a temporary slice to reduce boundary checks.
|
||||
pl := palette[4*i : 4*i+4]
|
||||
rgba := color.RGBAModel.Convert(c).(color.RGBA)
|
||||
pl[0] = rgba.R
|
||||
pl[1] = rgba.G
|
||||
pl[2] = rgba.B
|
||||
pl[3] = rgba.A
|
||||
}
|
||||
} else {
|
||||
for i, c := range img.Palette {
|
||||
// Create a temporary slice to reduce boundary checks.
|
||||
pl := palette[4*i : 4*i+4]
|
||||
nrgba := color.NRGBAModel.Convert(c).(color.NRGBA)
|
||||
pl[0] = nrgba.R
|
||||
pl[1] = nrgba.G
|
||||
pl[2] = nrgba.B
|
||||
pl[3] = nrgba.A
|
||||
}
|
||||
}
|
||||
// Even img is a subimage of another image, Pix starts with 0-th index.
|
||||
idx0 := 0
|
||||
idx1 := 0
|
||||
var srcIdx, dstIdx int
|
||||
d := img.Stride - (x1 - x0)
|
||||
for j := 0; j < y1-y0; j++ {
|
||||
for i := 0; i < x1-x0; i++ {
|
||||
p := int(img.Pix[idx0])
|
||||
copy(bs[idx1:idx1+4], palette[4*p:4*p+4])
|
||||
idx0++
|
||||
idx1 += 4
|
||||
for range y1 - y0 {
|
||||
for range x1 - x0 {
|
||||
p := int(img.Pix[srcIdx])
|
||||
copy(bs[dstIdx:dstIdx+4], palette[4*p:4*p+4])
|
||||
srcIdx++
|
||||
dstIdx += 4
|
||||
}
|
||||
idx0 += d
|
||||
srcIdx += d
|
||||
}
|
||||
return bs
|
||||
case *image.RGBA:
|
||||
if len(img.Pix) == 4*w*h {
|
||||
if premultipliedAlpha && len(img.Pix) == 4*w*h {
|
||||
return img.Pix
|
||||
}
|
||||
case *image.NRGBA:
|
||||
if !premultipliedAlpha && len(img.Pix) == 4*w*h {
|
||||
return img.Pix
|
||||
}
|
||||
return imageToBytesSlow(img)
|
||||
default:
|
||||
return imageToBytesSlow(img)
|
||||
}
|
||||
return imageToBytesSlow(img, premultipliedAlpha)
|
||||
}
|
||||
|
||||
func imageToBytesSlow(img image.Image) []byte {
|
||||
func imageToBytesSlow(img image.Image, premultipliedAlpha bool) []byte {
|
||||
size := img.Bounds().Size()
|
||||
w, h := size.X, size.Y
|
||||
bs := make([]byte, 4*w*h)
|
||||
|
||||
dstImg := &image.RGBA{
|
||||
Pix: bs,
|
||||
Stride: 4 * w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
var dstImg draw.Image
|
||||
if premultipliedAlpha {
|
||||
dstImg = &image.RGBA{
|
||||
Pix: bs,
|
||||
Stride: 4 * w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
}
|
||||
} else {
|
||||
dstImg = &image.NRGBA{
|
||||
Pix: bs,
|
||||
Stride: 4 * w,
|
||||
Rect: image.Rect(0, 0, w, h),
|
||||
}
|
||||
}
|
||||
draw.Draw(dstImg, image.Rect(0, 0, w, h), img, img.Bounds().Min, draw.Src)
|
||||
return bs
|
||||
|
||||
@@ -39,61 +39,123 @@ func TestImageToBytes(t *testing.T) {
|
||||
}
|
||||
bigPalette := color.Palette(p)
|
||||
cases := []struct {
|
||||
In image.Image
|
||||
Out []uint8
|
||||
Image image.Image
|
||||
Premul bool
|
||||
Out []uint8
|
||||
}{
|
||||
{
|
||||
In: &image.Paletted{
|
||||
Pix: []uint8{0, 1, 1, 0},
|
||||
Image: &image.Paletted{
|
||||
Pix: []uint8{0, 1, 1, 2},
|
||||
Stride: 2,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
Palette: color.Palette([]color.Color{
|
||||
color.Transparent, color.White,
|
||||
color.Transparent, color.White, color.RGBA{0x80, 0x80, 0x80, 0x80},
|
||||
}),
|
||||
},
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
|
||||
Premul: true,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x80, 0x80, 0x80},
|
||||
},
|
||||
{
|
||||
In: image.NewPaletted(image.Rect(0, 0, 240, 160), pal).SubImage(image.Rect(238, 158, 240, 160)),
|
||||
Out: []uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
Image: &image.Paletted{
|
||||
Pix: []uint8{0, 1, 1, 2},
|
||||
Stride: 2,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
Palette: color.Palette([]color.Color{
|
||||
color.Transparent, color.White, color.RGBA{0x80, 0x80, 0x80, 0x80},
|
||||
}),
|
||||
},
|
||||
Premul: false,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80},
|
||||
},
|
||||
{
|
||||
In: &image.RGBA{
|
||||
Image: image.NewPaletted(image.Rect(0, 0, 240, 160), pal).SubImage(image.Rect(238, 158, 240, 160)),
|
||||
Premul: true,
|
||||
Out: []uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
{
|
||||
Image: image.NewPaletted(image.Rect(0, 0, 240, 160), pal).SubImage(image.Rect(238, 158, 240, 160)),
|
||||
Premul: false,
|
||||
Out: []uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
{
|
||||
Image: &image.RGBA{
|
||||
Pix: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
|
||||
Stride: 8,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
|
||||
Premul: true,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
In: &image.NRGBA{
|
||||
Image: &image.RGBA{
|
||||
Pix: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
|
||||
Stride: 8,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
Premul: false,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
Image: &image.NRGBA{
|
||||
Pix: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0, 0, 0, 0},
|
||||
Stride: 8,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
Out: []uint8{0, 0, 0, 0, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x40, 0x80, 0, 0, 0, 0},
|
||||
Premul: true,
|
||||
Out: []uint8{0, 0, 0, 0, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x40, 0x80, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
In: &image.Paletted{
|
||||
Image: &image.NRGBA{
|
||||
Pix: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0, 0, 0, 0},
|
||||
Stride: 8,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
},
|
||||
Premul: false,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
Image: &image.Paletted{
|
||||
Pix: []uint8{0, 64, 0, 0},
|
||||
Stride: 2,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
Palette: bigPalette,
|
||||
},
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
Premul: true,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
In: (&image.Paletted{
|
||||
Image: &image.Paletted{
|
||||
Pix: []uint8{0, 64, 0, 0},
|
||||
Stride: 2,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
Palette: bigPalette,
|
||||
},
|
||||
Premul: false,
|
||||
Out: []uint8{0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0, 0, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
Image: (&image.Paletted{
|
||||
Pix: []uint8{0, 64, 0, 0},
|
||||
Stride: 2,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
Palette: bigPalette,
|
||||
}).SubImage(image.Rect(1, 0, 2, 1)),
|
||||
Out: []uint8{0xff, 0xff, 0xff, 0xff},
|
||||
Premul: true,
|
||||
Out: []uint8{0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
{
|
||||
Image: (&image.Paletted{
|
||||
Pix: []uint8{0, 64, 0, 0},
|
||||
Stride: 2,
|
||||
Rect: image.Rect(0, 0, 2, 2),
|
||||
Palette: bigPalette,
|
||||
}).SubImage(image.Rect(1, 0, 2, 1)),
|
||||
Premul: false,
|
||||
Out: []uint8{0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
}
|
||||
for i, c := range cases {
|
||||
got := ebiten.ImageToBytes(c.In)
|
||||
got := ebiten.ImageToBytes(c.Image, c.Premul)
|
||||
want := c.Out
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Errorf("Test %d: got: %v, want: %v", i, got, want)
|
||||
@@ -104,23 +166,23 @@ func TestImageToBytes(t *testing.T) {
|
||||
func BenchmarkImageToBytesRGBA(b *testing.B) {
|
||||
img := image.NewRGBA(image.Rect(0, 0, 4096, 4096))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ebiten.ImageToBytes(img)
|
||||
for range b.N {
|
||||
ebiten.ImageToBytes(img, true)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkImageToBytesNRGBA(b *testing.B) {
|
||||
img := image.NewNRGBA(image.Rect(0, 0, 4096, 4096))
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ebiten.ImageToBytes(img)
|
||||
for range b.N {
|
||||
ebiten.ImageToBytes(img, true)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkImageToBytesPaletted(b *testing.B) {
|
||||
img := image.NewPaletted(image.Rect(0, 0, 4096, 4096), palette.Plan9)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
ebiten.ImageToBytes(img)
|
||||
for range b.N {
|
||||
ebiten.ImageToBytes(img, true)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user