From d598e68c030a62353bc395095bf874dc533b7530 Mon Sep 17 00:00:00 2001 From: faiface Date: Tue, 28 Feb 2017 18:24:25 +0100 Subject: [PATCH] implement and document PictureData --- data.go | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++ graphics.go | 234 ------------------------------------------- 2 files changed, 281 insertions(+), 234 deletions(-) create mode 100644 data.go delete mode 100644 graphics.go diff --git a/data.go b/data.go new file mode 100644 index 0000000..1406660 --- /dev/null +++ b/data.go @@ -0,0 +1,281 @@ +package pixel + +import ( + "fmt" + "image" + "image/color" + "image/draw" + "math" +) + +// TrianglesData specifies a list of Triangles vertices with three common properties: Position, +// Color and Texture. +type TrianglesData []struct { + Position Vec + Color NRGBA + Picture Vec +} + +// MakeTrianglesData creates TrianglesData of length len initialized with default property values. +// +// Prefer this function to make(TrianglesData, len), because make zeros them, while this function +// does a correct intialization. +func MakeTrianglesData(len int) TrianglesData { + td := TrianglesData{} + td.SetLen(len) + return td +} + +// Len returns the number of vertices in TrianglesData. +func (td *TrianglesData) Len() int { + return len(*td) +} + +// SetLen resizes TrianglesData to len, while keeping the original content. +// +// If len is greater than TrianglesData's current length, the new data is filled with default +// values ((0, 0), white, (-1, -1)). +func (td *TrianglesData) SetLen(len int) { + if len > td.Len() { + needAppend := len - td.Len() + for i := 0; i < needAppend; i++ { + *td = append(*td, struct { + Position Vec + Color NRGBA + Picture Vec + }{V(0, 0), NRGBA{1, 1, 1, 1}, V(-1, -1)}) + } + } + if len < td.Len() { + *td = (*td)[:len] + } +} + +// Slice returns a sub-Triangles of this TrianglesData. +func (td *TrianglesData) Slice(i, j int) Triangles { + s := TrianglesData((*td)[i:j]) + return &s +} + +func (td *TrianglesData) updateData(t Triangles) { + // fast path optimization + if t, ok := t.(*TrianglesData); ok { + copy(*td, *t) + return + } + + // slow path manual copy + if t, ok := t.(TrianglesPosition); ok { + for i := range *td { + (*td)[i].Position = t.Position(i) + } + } + if t, ok := t.(TrianglesColor); ok { + for i := range *td { + (*td)[i].Color = t.Color(i) + } + } + if t, ok := t.(TrianglesPicture); ok { + for i := range *td { + (*td)[i].Picture = t.Picture(i) + } + } +} + +// Update copies vertex properties from the supplied Triangles into this TrianglesData. +// +// TrianglesPosition, TrianglesColor and TrianglesTexture are supported. +func (td *TrianglesData) Update(t Triangles) { + if td.Len() != t.Len() { + panic(fmt.Errorf("%T.Update: invalid triangles length", td)) + } + td.updateData(t) +} + +// Copy returns an exact independent copy of this TrianglesData. +func (td *TrianglesData) Copy() Triangles { + copyTd := TrianglesData{} + copyTd.SetLen(td.Len()) + copyTd.Update(td) + return ©Td +} + +// Position returns the position property of i-th vertex. +func (td *TrianglesData) Position(i int) Vec { + return (*td)[i].Position +} + +// Color returns the color property of i-th vertex. +func (td *TrianglesData) Color(i int) NRGBA { + return (*td)[i].Color +} + +// Picture returns the picture property of i-th vertex. +func (td *TrianglesData) Picture(i int) Vec { + return (*td)[i].Picture +} + +// PictureData specifies an in-memory rectangular area of NRGBA pixels and implements Picture and +// PictureColor. +// +// Pixels are small rectangles of unit size of form (x, y, x+1, y+1), where x and y are integers. +// PictureData contains and assigns a color to all pixels that are at least partially contained +// within it's Bounds (Rect). +// +// The struct's innards are exposed for convenience, manual modification is at your own risk. +type PictureData struct { + Pix []NRGBA + Stride int + Rect Rect +} + +// MakePictureData creates a zero-initialized PictureData covering the given rectangle. +func MakePictureData(rect Rect) PictureData { + w := int(math.Ceil(rect.Pos.X()+rect.Size.X())) - int(math.Floor(rect.Pos.X())) + h := int(math.Ceil(rect.Pos.Y()+rect.Size.Y())) - int(math.Floor(rect.Pos.Y())) + pd := PictureData{ + Stride: w, + Rect: rect, + } + pd.Pix = make([]NRGBA, w*h) + return pd +} + +func verticalFlip(nrgba *image.NRGBA) { + bounds := nrgba.Bounds() + width := bounds.Dx() + + tmpRow := make([]uint8, width*4) + for i, j := 0, bounds.Dy()-1; i < j; i, j = i+1, j-1 { + iRow := nrgba.Pix[i*nrgba.Stride : i*nrgba.Stride+width*4] + jRow := nrgba.Pix[j*nrgba.Stride : j*nrgba.Stride+width*4] + + copy(tmpRow, iRow) + copy(iRow, jRow) + copy(jRow, tmpRow) + } +} + +// PictureDataFromImage converts an image.Image into PictureData. +// +// The resulting PictureData's Bounds will be the equivalent of the supplied image.Image's Bounds. +func PictureDataFromImage(img image.Image) PictureData { + nrgba := image.NewNRGBA(image.Rect( + 0, 0, + img.Bounds().Dx(), img.Bounds().Dy(), + )) + draw.Draw(nrgba, nrgba.Bounds(), img, img.Bounds().Min, draw.Src) + + verticalFlip(nrgba) + + pd := MakePictureData(R( + float64(nrgba.Bounds().Min.X), + float64(nrgba.Bounds().Min.Y), + float64(nrgba.Bounds().Dx()), + float64(nrgba.Bounds().Dy()), + )) + + for i := range pd.Pix { + pd.Pix[i] = NRGBA{ + R: float64(nrgba.Pix[i*4+0]) / 255, + G: float64(nrgba.Pix[i*4+1]) / 255, + B: float64(nrgba.Pix[i*4+2]) / 255, + A: float64(nrgba.Pix[i*4+3]) / 255, + } + } + + return pd +} + +// PictureDataFromPicture converts an arbitrary Picture into PictureData (the conversion may be +// lossy, because PictureData works with unit-sized pixels). +// +// Bounds are preserved. +func PictureDataFromPicture(pic Picture) PictureData { + if pd, ok := pic.(PictureData); ok { + return pd + } + + bounds := pic.Bounds() + pd := MakePictureData(bounds) + + if pic, ok := pic.(PictureColor); ok { + for y := bounds.Pos.Y(); y < bounds.Pos.Y()+bounds.Size.Y(); y++ { + for x := bounds.Pos.X(); x < bounds.Pos.X()+bounds.Size.X(); x++ { + at := V(x, y) + pd.SetColor(at, pic.Color(at)) + } + } + } + + return pd +} + +// Image converts PictureData into an image.NRGBA. +// +// The resulting image.NRGBA's Bounds will be equivalent of the PictureData's Bounds. +func (pd PictureData) Image() *image.NRGBA { + fmt.Println(pd.Rect.Pos + pd.Rect.Size) + bounds := image.Rect( + int(math.Floor(pd.Rect.Pos.X())), + int(math.Floor(pd.Rect.Pos.Y())), + int(math.Ceil(pd.Rect.Pos.X()+pd.Rect.Size.X())), + int(math.Ceil(pd.Rect.Pos.Y()+pd.Rect.Size.Y())), + ) + nrgba := image.NewNRGBA(bounds) + fmt.Println(nrgba.Rect) + + i := 0 + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + for x := bounds.Min.X; x < bounds.Max.X; x++ { + off := pd.offset(V(float64(x), float64(y))) + nrgba.Pix[i*4+0] = uint8(pd.Pix[off].R * 255) + nrgba.Pix[i*4+1] = uint8(pd.Pix[off].G * 255) + nrgba.Pix[i*4+2] = uint8(pd.Pix[off].B * 255) + nrgba.Pix[i*4+3] = uint8(pd.Pix[off].A * 255) + i++ + } + } + + verticalFlip(nrgba) + + fmt.Println(len(nrgba.Pix) / 4) + + return nrgba +} + +func (pd PictureData) offset(at Vec) int { + at -= pd.Rect.Pos + x, y := int(at.X()), int(at.Y()) + return y*pd.Stride + x +} + +// Bounds returns the bounds of this PictureData. +func (pd PictureData) Bounds() Rect { + return pd.Rect +} + +// Slice returns a sub-Picture of this PictureData inside the supplied rectangle. +func (pd PictureData) Slice(r Rect) Picture { + return PictureData{ + Pix: pd.Pix[pd.offset(r.Pos):], + Stride: pd.Stride, + Rect: r, + } +} + +// Color returns the color located at the given position. +func (pd PictureData) Color(at Vec) NRGBA { + if !pd.Rect.Contains(at) { + return NRGBA{0, 0, 0, 0} + } + return pd.Pix[pd.offset(at)] +} + +// SetColor changes the color located at the given position. +func (pd PictureData) SetColor(at Vec, color color.Color) { + if !pd.Rect.Contains(at) { + return + } + pd.Pix[pd.offset(at)] = NRGBAModel.Convert(color).(NRGBA) +} diff --git a/graphics.go b/graphics.go deleted file mode 100644 index 9b78898..0000000 --- a/graphics.go +++ /dev/null @@ -1,234 +0,0 @@ -package pixel - -import ( - "fmt" - "image/color" -) - -// TrianglesData specifies a list of Triangles vertices with three common properties: Position, -// Color and Texture. -type TrianglesData []struct { - Position Vec - Color NRGBA - Picture Vec -} - -// MakeTrianglesData creates TrianglesData of length len initialized with default property values. -// -// Prefer this function to make(TrianglesData, len), because make zeros them, while this function -// does a correct intialization. -func MakeTrianglesData(len int) TrianglesData { - td := TrianglesData{} - td.SetLen(len) - return td -} - -// Len returns the number of vertices in TrianglesData. -func (td *TrianglesData) Len() int { - return len(*td) -} - -// SetLen resizes TrianglesData to len, while keeping the original content. -// -// If len is greater than TrianglesData's current length, the new data is filled with default -// values ((0, 0), white, (-1, -1)). -func (td *TrianglesData) SetLen(len int) { - if len > td.Len() { - needAppend := len - td.Len() - for i := 0; i < needAppend; i++ { - *td = append(*td, struct { - Position Vec - Color NRGBA - Picture Vec - }{V(0, 0), NRGBA{1, 1, 1, 1}, V(-1, -1)}) - } - } - if len < td.Len() { - *td = (*td)[:len] - } -} - -// Slice returns a sub-Triangles of this TrianglesData. -func (td *TrianglesData) Slice(i, j int) Triangles { - s := TrianglesData((*td)[i:j]) - return &s -} - -func (td *TrianglesData) updateData(t Triangles) { - // fast path optimization - if t, ok := t.(*TrianglesData); ok { - copy(*td, *t) - return - } - - // slow path manual copy - if t, ok := t.(TrianglesPosition); ok { - for i := range *td { - (*td)[i].Position = t.Position(i) - } - } - if t, ok := t.(TrianglesColor); ok { - for i := range *td { - (*td)[i].Color = t.Color(i) - } - } - if t, ok := t.(TrianglesPicture); ok { - for i := range *td { - (*td)[i].Picture = t.Picture(i) - } - } -} - -// Update copies vertex properties from the supplied Triangles into this TrianglesData. -// -// TrianglesPosition, TrianglesColor and TrianglesTexture are supported. -func (td *TrianglesData) Update(t Triangles) { - if td.Len() != t.Len() { - panic(fmt.Errorf("%T.Update: invalid triangles length", td)) - } - td.updateData(t) -} - -// Copy returns an exact independent copy of this TrianglesData. -func (td *TrianglesData) Copy() Triangles { - copyTd := TrianglesData{} - copyTd.SetLen(td.Len()) - copyTd.Update(td) - return ©Td -} - -// Position returns the position property of i-th vertex. -func (td *TrianglesData) Position(i int) Vec { - return (*td)[i].Position -} - -// Color returns the color property of i-th vertex. -func (td *TrianglesData) Color(i int) NRGBA { - return (*td)[i].Color -} - -// Picture returns the picture property of i-th vertex. -func (td *TrianglesData) Picture(i int) Vec { - return (*td)[i].Picture -} - -// Sprite is a picture that can be drawn onto a Target. To change the position/rotation/scale of -// the Sprite, use Target's SetTransform method. -type Sprite struct { - data TrianglesData - d Drawer -} - -// NewSprite creates a Sprite with the supplied Picture. The dimensions of the returned Sprite match -// the dimensions of the Picture. -func NewSprite(pic Picture) *Sprite { - s := &Sprite{ - data: TrianglesData{ - {Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Picture: V(0, 0)}, - {Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Picture: V(1, 0)}, - {Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Picture: V(1, 1)}, - {Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Picture: V(0, 0)}, - {Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Picture: V(1, 1)}, - {Position: V(0, 0), Color: NRGBA{1, 1, 1, 1}, Picture: V(0, 1)}, - }, - } - s.d = Drawer{Triangles: &s.data} - s.SetPicture(pic) - return s -} - -// SetPicture changes the Picture of the Sprite and resizes it accordingly. -func (s *Sprite) SetPicture(pic Picture) { - oldPic := s.d.Picture - s.d.Picture = pic - if oldPic != nil && oldPic.Bounds().Size == pic.Bounds().Size { - return - } - w, h := pic.Bounds().Size.XY() - s.data[0].Position = V(0, 0) - s.data[1].Position = V(w, 0) - s.data[2].Position = V(w, h) - s.data[3].Position = V(0, 0) - s.data[4].Position = V(w, h) - s.data[5].Position = V(0, h) - s.d.Dirty() -} - -// Picture returns the current Picture of the Sprite. -func (s *Sprite) Picture() Picture { - return s.d.Picture -} - -// Draw draws the Sprite onto the provided Target. -func (s *Sprite) Draw(t Target) { - s.d.Draw(t) -} - -// Polygon is a convex polygon shape filled with a single color. -type Polygon struct { - data TrianglesData - d Drawer - col NRGBA -} - -// NewPolygon creates a Polygon with specified color and points. Points can be in clock-wise or -// counter-clock-wise order, it doesn't matter. They should however form a convex polygon. -func NewPolygon(c color.Color, points ...Vec) *Polygon { - p := &Polygon{ - data: TrianglesData{}, - } - p.d = Drawer{Triangles: &p.data} - p.SetColor(c) - p.SetPoints(points...) - return p -} - -// SetColor changes the color of the Polygon. -// -// If the Polygon is very large, this method might end up being too expensive. Consider using -// a color mask on a Target, in such a case. -func (p *Polygon) SetColor(c color.Color) { - p.col = NRGBAModel.Convert(c).(NRGBA) - for i := range p.data { - p.data[i].Color = p.col - } - p.d.Dirty() -} - -// Color returns the current color of the Polygon. -func (p *Polygon) Color() NRGBA { - return p.col -} - -// SetPoints sets the points of the Polygon. The number of points might differ from the original -// count. -// -// This method is more effective, than creating a new Polygon with the given points. -// -// However, it is less expensive than using a transform on a Target. -func (p *Polygon) SetPoints(points ...Vec) { - p.data.SetLen(3 * (len(points) - 2)) - for i := 2; i < len(points); i++ { - p.data[(i-2)*3+0].Position = points[0] - p.data[(i-2)*3+1].Position = points[i-1] - p.data[(i-2)*3+2].Position = points[i] - } - for i := range p.data { - p.data[i].Color = p.col - } - p.d.Dirty() -} - -// Points returns a slice of points of the Polygon in the order they where supplied. -func (p *Polygon) Points() []Vec { - points := make([]Vec, p.data.Len()) - for i := range p.data { - points[i] = p.data[i].Position - } - return points -} - -// Draw draws the Polygon onto the Target. -func (p *Polygon) Draw(t Target) { - p.d.Draw(t) -}