mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
graphics: Lazy image processing (especailly for mobile platforms)
This commit is contained in:
parent
99baaf18b0
commit
61ed13e322
142
image.go
142
image.go
@ -19,11 +19,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"runtime"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var imageM sync.Mutex
|
var imageM sync.Mutex
|
||||||
@ -69,11 +67,16 @@ func (i *Image) Fill(clr color.Color) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) fill(clr color.Color) (err error) {
|
func (i *Image) fill(clr color.Color) (err error) {
|
||||||
if i.isDisposed() {
|
c := &fillCommand{
|
||||||
return errors.New("ebiten: image is already disposed")
|
dst: i,
|
||||||
|
color: clr,
|
||||||
}
|
}
|
||||||
i.pixels = nil
|
if imageCommandQueue != nil {
|
||||||
return i.framebuffer.Fill(glContext, clr)
|
imageCommandQueue = append(imageCommandQueue, c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return c.Exec()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DrawImage draws the given image on the receiver image.
|
// DrawImage draws the given image on the receiver image.
|
||||||
@ -119,15 +122,22 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
|
|||||||
|
|
||||||
imageM.Lock()
|
imageM.Lock()
|
||||||
defer imageM.Unlock()
|
defer imageM.Unlock()
|
||||||
if i.isDisposed() {
|
|
||||||
return errors.New("ebiten: image is already disposed")
|
|
||||||
}
|
|
||||||
if i == image {
|
if i == image {
|
||||||
return errors.New("ebiten: Image.DrawImage: image should be different from the receiver")
|
return errors.New("ebiten: Image.DrawImage: image should be different from the receiver")
|
||||||
}
|
}
|
||||||
i.pixels = nil
|
c := &drawImageCommand{
|
||||||
m := opengl.CompositeMode(options.CompositeMode)
|
dst: i,
|
||||||
return i.framebuffer.DrawTexture(glContext, image.texture, vertices[:16*n], &options.GeoM, &options.ColorM, m)
|
src: image,
|
||||||
|
vertices: vertices[:16*n],
|
||||||
|
geoM: options.GeoM,
|
||||||
|
colorM: options.ColorM,
|
||||||
|
compositeMode: options.CompositeMode,
|
||||||
|
}
|
||||||
|
if imageCommandQueue != nil {
|
||||||
|
imageCommandQueue = append(imageCommandQueue, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.Exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bounds returns the bounds of the image.
|
// Bounds returns the bounds of the image.
|
||||||
@ -148,11 +158,16 @@ func (i *Image) ColorModel() color.Model {
|
|||||||
//
|
//
|
||||||
// This method loads pixels from VRAM to system memory if necessary.
|
// This method loads pixels from VRAM to system memory if necessary.
|
||||||
//
|
//
|
||||||
|
// This method can't be called before the main loop (ebiten.Run) starts (as of version 1.4.0-alpha).
|
||||||
|
//
|
||||||
// This function is concurrent-safe.
|
// This function is concurrent-safe.
|
||||||
func (i *Image) At(x, y int) color.Color {
|
func (i *Image) At(x, y int) color.Color {
|
||||||
// TODO: What if At is called internaly (like from image parts?)
|
// TODO: What if At is called internaly (like from image parts?)
|
||||||
imageM.Lock()
|
imageM.Lock()
|
||||||
defer imageM.Unlock()
|
defer imageM.Unlock()
|
||||||
|
if imageCommandQueue != nil {
|
||||||
|
panic("ebiten: At can't be called when the GL context is not initialized")
|
||||||
|
}
|
||||||
if i.isDisposed() {
|
if i.isDisposed() {
|
||||||
return color.Transparent
|
return color.Transparent
|
||||||
}
|
}
|
||||||
@ -178,26 +193,17 @@ func (i *Image) At(x, y int) color.Color {
|
|||||||
func (i *Image) Dispose() error {
|
func (i *Image) Dispose() error {
|
||||||
imageM.Lock()
|
imageM.Lock()
|
||||||
defer imageM.Unlock()
|
defer imageM.Unlock()
|
||||||
if i.isDisposed() {
|
c := &disposeCommand{
|
||||||
return errors.New("ebiten: image is already disposed")
|
image: i,
|
||||||
}
|
}
|
||||||
if i.framebuffer != nil {
|
if imageCommandQueue != nil {
|
||||||
if err := i.framebuffer.Dispose(glContext); err != nil {
|
imageCommandQueue = append(imageCommandQueue, c)
|
||||||
return err
|
return nil
|
||||||
}
|
|
||||||
i.framebuffer = nil
|
|
||||||
}
|
}
|
||||||
if i.texture != nil {
|
return c.Exec()
|
||||||
if err := i.texture.Dispose(glContext); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i.texture = nil
|
|
||||||
}
|
|
||||||
i.pixels = nil
|
|
||||||
runtime.SetFinalizer(i, nil)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: This returns true when image is temporal state!
|
||||||
func (i *Image) isDisposed() bool {
|
func (i *Image) isDisposed() bool {
|
||||||
// i.texture can be nil even when the image is not disposed,
|
// i.texture can be nil even when the image is not disposed,
|
||||||
// so we need to check if both are nil.
|
// so we need to check if both are nil.
|
||||||
@ -215,15 +221,20 @@ func (i *Image) isDisposed() bool {
|
|||||||
func (i *Image) ReplacePixels(p []uint8) error {
|
func (i *Image) ReplacePixels(p []uint8) error {
|
||||||
imageM.Lock()
|
imageM.Lock()
|
||||||
defer imageM.Unlock()
|
defer imageM.Unlock()
|
||||||
if i.isDisposed() {
|
|
||||||
return errors.New("ebiten: image is already disposed")
|
|
||||||
}
|
|
||||||
// Don't set i.pixels here because i.pixels is used not every time.
|
// Don't set i.pixels here because i.pixels is used not every time.
|
||||||
i.pixels = nil
|
i.pixels = nil
|
||||||
if l := 4 * i.width * i.height; len(p) != l {
|
if l := 4 * i.width * i.height; len(p) != l {
|
||||||
return fmt.Errorf("ebiten: p's length must be %d", l)
|
return fmt.Errorf("ebiten: p's length must be %d", l)
|
||||||
}
|
}
|
||||||
return i.framebuffer.ReplacePixels(glContext, i.texture, p)
|
c := &replacePixelsCommand{
|
||||||
|
dst: i,
|
||||||
|
pixels: p,
|
||||||
|
}
|
||||||
|
if imageCommandQueue != nil {
|
||||||
|
imageCommandQueue = append(imageCommandQueue, c)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return c.Exec()
|
||||||
}
|
}
|
||||||
|
|
||||||
// A DrawImageOptions represents options to render an image on an image.
|
// A DrawImageOptions represents options to render an image on an image.
|
||||||
@ -248,27 +259,23 @@ type DrawImageOptions struct {
|
|||||||
func NewImage(width, height int, filter Filter) (*Image, error) {
|
func NewImage(width, height int, filter Filter) (*Image, error) {
|
||||||
imageM.Lock()
|
imageM.Lock()
|
||||||
defer imageM.Unlock()
|
defer imageM.Unlock()
|
||||||
|
c := &newImageCommand{
|
||||||
texture, err := graphics.NewTexture(glContext, width, height, glFilter(glContext, filter))
|
width: width,
|
||||||
if err != nil {
|
height: height,
|
||||||
|
filter: filter,
|
||||||
|
result: &Image{
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if imageCommandQueue != nil {
|
||||||
|
imageCommandQueue = append(imageCommandQueue, c)
|
||||||
|
return c.result, nil
|
||||||
|
}
|
||||||
|
if err := c.Exec(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
framebuffer, err := graphics.NewFramebufferFromTexture(glContext, texture)
|
return c.result, nil
|
||||||
if err != nil {
|
|
||||||
// TODO: texture should be removed here?
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
img := &Image{
|
|
||||||
framebuffer: framebuffer,
|
|
||||||
texture: texture,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(img, (*Image).Dispose)
|
|
||||||
if err := img.clear(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return img, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImageFromImage creates a new image with the given image (img).
|
// NewImageFromImage creates a new image with the given image (img).
|
||||||
@ -282,23 +289,22 @@ func NewImage(width, height int, filter Filter) (*Image, error) {
|
|||||||
func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
|
func NewImageFromImage(img image.Image, filter Filter) (*Image, error) {
|
||||||
imageM.Lock()
|
imageM.Lock()
|
||||||
defer imageM.Unlock()
|
defer imageM.Unlock()
|
||||||
|
size := img.Bounds().Size()
|
||||||
texture, err := graphics.NewTextureFromImage(glContext, img, glFilter(glContext, filter))
|
w, h := size.X, size.Y
|
||||||
if err != nil {
|
c := &newImageFromImageCommand{
|
||||||
|
image: img,
|
||||||
|
filter: filter,
|
||||||
|
result: &Image{
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if imageCommandQueue != nil {
|
||||||
|
imageCommandQueue = append(imageCommandQueue, c)
|
||||||
|
return c.result, nil
|
||||||
|
}
|
||||||
|
if err := c.Exec(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
framebuffer, err := graphics.NewFramebufferFromTexture(glContext, texture)
|
return c.result, nil
|
||||||
if err != nil {
|
|
||||||
// TODO: texture should be removed here?
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w, h := framebuffer.Size()
|
|
||||||
eimg := &Image{
|
|
||||||
framebuffer: framebuffer,
|
|
||||||
texture: texture,
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
}
|
|
||||||
runtime.SetFinalizer(eimg, (*Image).Dispose)
|
|
||||||
return eimg, nil
|
|
||||||
}
|
}
|
||||||
|
281
image_test.go
281
image_test.go
@ -1,281 +0,0 @@
|
|||||||
// Copyright 2014 Hajime Hoshi
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package ebiten_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/hajimehoshi/ebiten"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
|
||||||
_ "image/png"
|
|
||||||
"math"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ebitenImageBin = ""
|
|
||||||
|
|
||||||
func openImage(path string) (image.Image, error) {
|
|
||||||
file, err := readFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
img, _, err := image.Decode(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return img, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func openEbitenImage(path string) (*Image, image.Image, error) {
|
|
||||||
img, err := openImage(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
eimg, err := NewImageFromImage(img, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return eimg, img, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func diff(x, y uint8) uint8 {
|
|
||||||
if x <= y {
|
|
||||||
return y - x
|
|
||||||
}
|
|
||||||
return x - y
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImagePixels(t *testing.T) {
|
|
||||||
img0, img, err := openEbitenImage("testdata/ebiten.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := img0.Bounds().Size(); got != img.Bounds().Size() {
|
|
||||||
t.Errorf("img size: got %d; want %d", got, img.Bounds().Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := 0; j < img0.Bounds().Size().Y; j++ {
|
|
||||||
for i := 0; i < img0.Bounds().Size().X; i++ {
|
|
||||||
got := img0.At(i, j)
|
|
||||||
want := color.RGBAModel.Convert(img.At(i, j))
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("img0 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageComposition(t *testing.T) {
|
|
||||||
img2Color := color.NRGBA{0x24, 0x3f, 0x6a, 0x88}
|
|
||||||
img3Color := color.NRGBA{0x85, 0xa3, 0x08, 0xd3}
|
|
||||||
|
|
||||||
// TODO: Rename this to img0
|
|
||||||
img1, _, err := openEbitenImage("testdata/ebiten.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w, h := img1.Bounds().Size().X, img1.Bounds().Size().Y
|
|
||||||
|
|
||||||
img2, err := NewImage(w, h, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
img3, err := NewImage(w, h, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
img2.Fill(img2Color)
|
|
||||||
img3.Fill(img3Color)
|
|
||||||
img_12_3, err := NewImage(w, h, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
img2.DrawImage(img1, nil)
|
|
||||||
img3.DrawImage(img2, nil)
|
|
||||||
img_12_3.DrawImage(img3, nil)
|
|
||||||
|
|
||||||
img2.Fill(img2Color)
|
|
||||||
img3.Fill(img3Color)
|
|
||||||
img_1_23, err := NewImage(w, h, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
img3.DrawImage(img2, nil)
|
|
||||||
img3.DrawImage(img1, nil)
|
|
||||||
img_1_23.DrawImage(img3, nil)
|
|
||||||
|
|
||||||
for j := 0; j < h; j++ {
|
|
||||||
for i := 0; i < w; i++ {
|
|
||||||
c1 := img_12_3.At(i, j).(color.RGBA)
|
|
||||||
c2 := img_1_23.At(i, j).(color.RGBA)
|
|
||||||
if 1 < diff(c1.R, c2.R) || 1 < diff(c1.G, c2.G) || 1 < diff(c1.B, c2.B) || 1 < diff(c1.A, c2.A) {
|
|
||||||
t.Errorf("img_12_3.At(%d, %d) = %#v; img_1_23.At(%[1]d, %[2]d) = %#[4]v", i, j, c1, c2)
|
|
||||||
}
|
|
||||||
if c1.A == 0 {
|
|
||||||
t.Fatalf("img_12_3.At(%d, %d).A = 0; nothing is rendered?", i, j)
|
|
||||||
}
|
|
||||||
if c2.A == 0 {
|
|
||||||
t.Fatalf("img_1_23.At(%d, %d).A = 0; nothing is rendered?", i, j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageSelf(t *testing.T) {
|
|
||||||
img, _, err := openEbitenImage("testdata/ebiten.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := img.DrawImage(img, nil); err == nil {
|
|
||||||
t.Fatalf("img.DrawImage(img, nil) doesn't return error; an error should be returned")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageDotByDotInversion(t *testing.T) {
|
|
||||||
img0, _, err := openEbitenImage("testdata/ebiten.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w, h := img0.Size()
|
|
||||||
img1, err := NewImage(w, h, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
op := &DrawImageOptions{}
|
|
||||||
op.GeoM.Rotate(math.Pi)
|
|
||||||
op.GeoM.Translate(float64(w), float64(h))
|
|
||||||
img1.DrawImage(img0, op)
|
|
||||||
|
|
||||||
for j := 0; j < h; j++ {
|
|
||||||
for i := 0; i < w; i++ {
|
|
||||||
c0 := img0.At(i, j).(color.RGBA)
|
|
||||||
c1 := img1.At(w-i-1, h-j-1).(color.RGBA)
|
|
||||||
if c0 != c1 {
|
|
||||||
t.Errorf("img0.At(%[1]d, %[2]d) should equal to img1.At(%[3]d, %[4]d) but not: %[5]v vs %[6]v", i, j, w-i-1, h-j-1, c0, c1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplacePixels(t *testing.T) {
|
|
||||||
origImg, err := openImage("testdata/ebiten.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Convert to RGBA
|
|
||||||
img := image.NewRGBA(origImg.Bounds())
|
|
||||||
draw.Draw(img, img.Bounds(), origImg, image.ZP, draw.Src)
|
|
||||||
|
|
||||||
size := img.Bounds().Size()
|
|
||||||
img0, err := NewImage(size.X, size.Y, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
img0.ReplacePixels(img.Pix)
|
|
||||||
for j := 0; j < img0.Bounds().Size().Y; j++ {
|
|
||||||
for i := 0; i < img0.Bounds().Size().X; i++ {
|
|
||||||
got := img0.At(i, j)
|
|
||||||
want := img.At(i, j)
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("img0 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := make([]uint8, 4*size.X*size.Y)
|
|
||||||
for i, _ := range p {
|
|
||||||
p[i] = 0x80
|
|
||||||
}
|
|
||||||
img0.ReplacePixels(p)
|
|
||||||
for j := 0; j < img0.Bounds().Size().Y; j++ {
|
|
||||||
for i := 0; i < img0.Bounds().Size().X; i++ {
|
|
||||||
got := img0.At(i, j)
|
|
||||||
want := color.RGBA{p[4*i], p[4*i+1], p[4*i+2], p[4*i+3]}
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("img0 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageDispose(t *testing.T) {
|
|
||||||
img, err := NewImage(16, 16, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := img.Dispose(); err != nil {
|
|
||||||
t.Errorf("img.Dipose() returns error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestImageCompositeModeLighter(t *testing.T) {
|
|
||||||
img0, _, err := openEbitenImage("testdata/ebiten.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w, h := img0.Size()
|
|
||||||
img1, err := NewImage(w, h, FilterNearest)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
img1.Fill(color.RGBA{0x01, 0x02, 0x03, 0x04})
|
|
||||||
op := &DrawImageOptions{}
|
|
||||||
op.CompositeMode = CompositeModeLighter
|
|
||||||
img1.DrawImage(img0, op)
|
|
||||||
for j := 0; j < img1.Bounds().Size().Y; j++ {
|
|
||||||
for i := 0; i < img1.Bounds().Size().X; i++ {
|
|
||||||
got := img1.At(i, j).(color.RGBA)
|
|
||||||
want := img0.At(i, j).(color.RGBA)
|
|
||||||
want.R = uint8(min(0xff, int(want.R)+1))
|
|
||||||
want.G = uint8(min(0xff, int(want.G)+2))
|
|
||||||
want.B = uint8(min(0xff, int(want.B)+3))
|
|
||||||
want.A = uint8(min(0xff, int(want.A)+4))
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("img1 At(%d, %d): got %#v; want %#v", i, j, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Add more tests (e.g. DrawImage with color matrix)
|
|
163
imagecommand.go
Normal file
163
imagecommand.go
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
// Copyright 2016 Hajime Hoshi
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package ebiten
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||||
|
"github.com/hajimehoshi/ebiten/internal/graphics/opengl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands are used only before the GL Context is created.
|
||||||
|
|
||||||
|
type imageCommand interface {
|
||||||
|
Exec() error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
imageCommandQueue = []imageCommand{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func execBufferedImageCommands() error {
|
||||||
|
imageM.Lock()
|
||||||
|
defer imageM.Unlock()
|
||||||
|
for _, c := range imageCommandQueue {
|
||||||
|
if err := c.Exec(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
imageCommandQueue = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fillCommand struct {
|
||||||
|
dst *Image
|
||||||
|
color color.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *fillCommand) Exec() error {
|
||||||
|
if c.dst.isDisposed() {
|
||||||
|
return errors.New("ebiten: image is already disposed")
|
||||||
|
}
|
||||||
|
c.dst.pixels = nil
|
||||||
|
return c.dst.framebuffer.Fill(glContext, c.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
type drawImageCommand struct {
|
||||||
|
dst *Image
|
||||||
|
src *Image
|
||||||
|
vertices []int16
|
||||||
|
geoM GeoM
|
||||||
|
colorM ColorM
|
||||||
|
compositeMode CompositeMode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *drawImageCommand) Exec() error {
|
||||||
|
if c.dst.isDisposed() {
|
||||||
|
return errors.New("ebiten: image is already disposed")
|
||||||
|
}
|
||||||
|
c.dst.pixels = nil
|
||||||
|
m := opengl.CompositeMode(c.compositeMode)
|
||||||
|
return c.dst.framebuffer.DrawTexture(glContext, c.src.texture, c.vertices, &c.geoM, &c.colorM, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
type replacePixelsCommand struct {
|
||||||
|
dst *Image
|
||||||
|
pixels []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *replacePixelsCommand) Exec() error {
|
||||||
|
if c.dst.isDisposed() {
|
||||||
|
return errors.New("ebiten: image is already disposed")
|
||||||
|
}
|
||||||
|
return c.dst.framebuffer.ReplacePixels(glContext, c.dst.texture, c.pixels)
|
||||||
|
}
|
||||||
|
|
||||||
|
type disposeCommand struct {
|
||||||
|
image *Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *disposeCommand) Exec() error {
|
||||||
|
if c.image.isDisposed() {
|
||||||
|
return errors.New("ebiten: image is already disposed")
|
||||||
|
}
|
||||||
|
if c.image.framebuffer != nil {
|
||||||
|
if err := c.image.framebuffer.Dispose(glContext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.image.framebuffer = nil
|
||||||
|
}
|
||||||
|
if c.image.texture != nil {
|
||||||
|
if err := c.image.texture.Dispose(glContext); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.image.texture = nil
|
||||||
|
}
|
||||||
|
c.image.pixels = nil
|
||||||
|
runtime.SetFinalizer(c.image, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type newImageCommand struct {
|
||||||
|
result *Image
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
filter Filter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *newImageCommand) Exec() error {
|
||||||
|
texture, err := graphics.NewTexture(glContext, c.width, c.height, glFilter(glContext, c.filter))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
framebuffer, err := graphics.NewFramebufferFromTexture(glContext, texture)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: texture should be removed here?
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.result.framebuffer = framebuffer
|
||||||
|
c.result.texture = texture
|
||||||
|
runtime.SetFinalizer(c.result, (*Image).Dispose)
|
||||||
|
if err := c.result.framebuffer.Fill(glContext, color.Transparent); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type newImageFromImageCommand struct {
|
||||||
|
image image.Image
|
||||||
|
filter Filter
|
||||||
|
result *Image
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *newImageFromImageCommand) Exec() error {
|
||||||
|
texture, err := graphics.NewTextureFromImage(glContext, c.image, glFilter(glContext, c.filter))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
framebuffer, err := graphics.NewFramebufferFromTexture(glContext, texture)
|
||||||
|
if err != nil {
|
||||||
|
// TODO: texture should be removed here?
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.result.framebuffer = framebuffer
|
||||||
|
c.result.texture = texture
|
||||||
|
runtime.SetFinalizer(c.result, (*Image).Dispose)
|
||||||
|
return nil
|
||||||
|
}
|
4
run.go
4
run.go
@ -189,6 +189,10 @@ func run(f func(*Image) error, width, height, scale int, title string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := execBufferedImageCommands(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
frames := 0
|
frames := 0
|
||||||
n := ui.Now()
|
n := ui.Now()
|
||||||
beforeForUpdate := n
|
beforeForUpdate := n
|
||||||
|
Loading…
Reference in New Issue
Block a user