3dtrial/renderer3/hrend/image.go

198 lines
5.1 KiB
Go

package hrend
import (
"bytes"
"fmt"
"image"
"image/color"
"log"
"math"
"strings"
)
// Convert rgb to uint
func Col2Uint(r, g, b byte) uint {
return (uint(r) << 16) | (uint(g) << 8) | uint(b)
}
func Color2Uint(col color.Color) uint {
r, g, b, _ := col.RGBA()
//log.Print(r, g, b)
return uint(((r & 0xff00) << 8) | (g & 0xff00) | ((b & 0xff00) >> 8))
}
// Convert uint to rgb (in that order)
func Uint2Col(col uint) (byte, byte, byte) {
return byte((col >> 16) & 0xFF), byte((col >> 8) & 0xFF), byte(col & 0xFF)
}
// A simple buffer where you can set pixels
type Framebuffer interface {
Set(x uint, y uint, r byte, g byte, b byte)
Get(x uint, y uint) (byte, byte, byte)
GetUv(u float32, v float32) (byte, byte, byte)
Dims() (uint, uint)
}
// Turn framebuffer into image, useful for processing into individual frames
func ToImage(fb Framebuffer) *image.RGBA {
width, height := fb.Dims()
result := image.NewRGBA(image.Rect(0, 0, int(width), int(height)))
for y := range height {
for x := range width {
c := color.RGBA{A: 255}
c.R, c.G, c.G = fb.Get(x, y)
result.Set(int(x), int(y), c)
}
}
return result
}
// Color is in RGB (alpha not used right now)
type SimpleFramebuffer struct {
Data []byte
Width uint
Height uint
}
func (fb *SimpleFramebuffer) Dims() (uint, uint) {
return fb.Width, fb.Height
}
// Sure hope this gets inlined...
func (fb *SimpleFramebuffer) Set(x uint, y uint, r byte, g byte, b byte) {
if x >= fb.Width || y >= fb.Height {
return
}
fb.Data[(x+y*fb.Width)*3] = r
fb.Data[(x+y*fb.Width)*3+1] = g
fb.Data[(x+y*fb.Width)*3+2] = b
}
func (fb *SimpleFramebuffer) Get(x uint, y uint) (byte, byte, byte) {
if x >= fb.Width || y >= fb.Height {
return 0, 0, 0
}
return fb.Data[(x+y*fb.Width)*3],
fb.Data[(x+y*fb.Width)*3+1],
fb.Data[(x+y*fb.Width)*3+2]
}
func (fb *SimpleFramebuffer) GetUv(u float32, v float32) (byte, byte, byte) {
x := uint(float32(fb.Width)*u) & (fb.Width - 1)
y := uint(float32(fb.Height)*(1-v)) & (fb.Height - 1)
i := (x + y*fb.Width) * 3
return fb.Data[i], fb.Data[i+1], fb.Data[i+2]
// return fb.Data[(x+y*fb.Width)*3],
// fb.Data[(x+y*fb.Width)*3+1],
// fb.Data[(x+y*fb.Width)*3+2]
}
func NewSimpleFramebuffer(width uint, height uint) *SimpleFramebuffer {
return &SimpleFramebuffer{
Data: make([]byte, width*height*3),
Width: width,
Height: height,
}
}
type RenderBuffer struct {
Data Framebuffer
ZBuffer []float32 //uint16 // Apparently 16 bit z-buffers are used
Width uint
Height uint
}
// Create a new framebuffer for the given width and height.
func NewRenderbuffer(d Framebuffer, width uint, height uint) RenderBuffer {
return RenderBuffer{
Data: d,
ZBuffer: make([]float32, width*height),
Width: width,
Height: height,
}
}
func NewTexture(texture image.Image, stride int) *SimpleFramebuffer {
bounds := texture.Bounds()
width := bounds.Dx() / stride
height := bounds.Dy() / stride
result := NewSimpleFramebuffer(uint(width), uint(height))
wlog := math.Log2(float64(width))
hlog := math.Log2(float64(height))
if wlog != math.Floor(wlog) || hlog != math.Floor(hlog) {
panic("Texture must be power of two")
}
for y := bounds.Min.Y; y < bounds.Max.Y; y += stride {
for x := bounds.Min.X; x < bounds.Max.X; x += stride {
col := texture.At(x, y)
r, g, b, _ := col.RGBA()
result.Set(uint(x/stride), uint(y/stride), byte(r>>8), byte(g>>8), byte(b>>8))
}
}
return result
}
// Fill zbuffer with pixels that are max distance away
func (fb *RenderBuffer) ResetZBuffer() {
for i := range fb.ZBuffer {
fb.ZBuffer[i] = 65535 //math.MaxFloat32
}
}
// Given some image data, return a string that is the ppm of it
func (fb *RenderBuffer) ExportPPM() string {
log.Printf("ExportPPM called for framebuffer %dx%d", fb.Width, fb.Height)
var result strings.Builder
result.WriteString(fmt.Sprintf("P3\n%d %d\n255\n", fb.Width, fb.Height))
for y := range fb.Height {
for x := range fb.Width {
r, g, b := fb.Data.Get(x, y)
result.WriteString(fmt.Sprintf("%d %d %d\t", r, g, b))
}
result.WriteRune('\n')
}
return result.String()
}
func (fb *RenderBuffer) ExportPPMP6() []byte {
log.Printf("ExportPPM6 called for framebuffer %dx%d", fb.Width, fb.Height)
var result bytes.Buffer
result.WriteString(fmt.Sprintf("P6\n%d %d\n255\n", fb.Width, fb.Height))
for y := range fb.Height {
for x := range fb.Width {
r, g, b := fb.Data.Get(x, y)
result.Write([]byte{r, g, b})
}
//result.WriteString(fmt.Sprintf("%d %d %d\t", r, g, b))
}
//result.WriteRune('\n')
return result.Bytes()
}
func (fb *RenderBuffer) ZBuffer_ExportPPM() string {
var result strings.Builder
mini := float32(math.MaxFloat32)
maxi := float32(-math.MaxFloat32)
for _, f := range fb.ZBuffer {
if f == math.MaxFloat32 {
continue
}
mini = min(f, mini)
maxi = max(f, maxi)
}
result.WriteString(fmt.Sprintf("P2\n%d %d\n255\n", fb.Width, fb.Height))
for y := range fb.Height {
for x := range fb.Width {
if fb.ZBuffer[x+y*fb.Width] == math.MaxFloat32 {
result.WriteString("0 ")
} else {
zp := byte(math.Abs(float64(255 * fb.ZBuffer[x+y*fb.Width] / (maxi - mini))))
result.WriteString(fmt.Sprintf("%d ", zp))
}
}
result.WriteRune('\n')
}
return result.String()
}