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