mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 10:35:44 +00:00
Convert colors in C
This commit is contained in:
parent
8dd9e9c9be
commit
85cef0dfec
8 changed files with 513 additions and 486 deletions
142
pkg/worker/caged/libretro/image/canvas.c
Normal file
142
pkg/worker/caged/libretro/image/canvas.c
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#include "canvas.h"
|
||||
|
||||
__inline int rot_x(int t, int x, int y, int w, int h) {
|
||||
switch (t) {
|
||||
case 1:
|
||||
return r90_x(x,y,w,h);
|
||||
break;
|
||||
case 2:
|
||||
return r180_x(x,y,w,h);
|
||||
break;
|
||||
case 3:
|
||||
return r270_x(x,y,w,h);
|
||||
break;
|
||||
case 4:
|
||||
return fy180_x(x,y,w,h);
|
||||
break;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
__inline int rot_y(int t, int x, int y, int w, int h) {
|
||||
switch (t) {
|
||||
case 1:
|
||||
return r90_y(x,y,w,h);
|
||||
break;
|
||||
case 2:
|
||||
return r180_y(x,y,w,h);
|
||||
break;
|
||||
case 3:
|
||||
return r270_y(x,y,w,h);
|
||||
break;
|
||||
case 4:
|
||||
return fy180_y(x,y,w,h);
|
||||
break;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
|
||||
void RGBA(int pix, void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot) {
|
||||
switch (pix) {
|
||||
case BIT_SHORT5551:
|
||||
break;
|
||||
case BIT_INT_8888REV:
|
||||
if (rot == 0) {
|
||||
i8888(destination, source, yy, yn, xw, pad);
|
||||
} else {
|
||||
i8888r(destination, source, yy, yn, xw, xh, dw, pad, rot);
|
||||
}
|
||||
break;
|
||||
case BIT_SHORT565:
|
||||
if (rot == 0) {
|
||||
i565(destination, source, yy, yn, xw, pad);
|
||||
} else {
|
||||
i565r(destination, source, yy, yn, xw, xh, dw, pad, rot);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void i565(void *destination, void *source, int yy, int yn, int xw, int pad) {
|
||||
uint8_t *src = source; // must be in bytes because of possible padding in bytes
|
||||
uint32_t *dst = destination;
|
||||
|
||||
int y, x;
|
||||
uint32_t px;
|
||||
|
||||
for (y = yy; y < yn; ++y) {
|
||||
for (x = 0; x < xw; ++x) {
|
||||
px = *(uint16_t *)src;
|
||||
src += 2;
|
||||
*dst++ = _565(px);
|
||||
}
|
||||
src += pad;
|
||||
}
|
||||
}
|
||||
|
||||
void i565r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot) {
|
||||
uint8_t *src = source;
|
||||
uint32_t *dst = destination;
|
||||
|
||||
uint32_t px;
|
||||
|
||||
int x, y, dx, dy;
|
||||
|
||||
for (y = yy; y < yn; ++y) {
|
||||
for (x = 0; x < xw; ++x) {
|
||||
px = *(uint16_t *)src;
|
||||
src += 2;
|
||||
|
||||
dx = rot_x(rot, x, y, xw, xh);
|
||||
dy = rot_y(rot, x, y, xw, xh);
|
||||
|
||||
dst[dx+dy*dw] = _565(px);
|
||||
}
|
||||
src += pad;
|
||||
}
|
||||
}
|
||||
|
||||
void i8888r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot) {
|
||||
uint8_t *src = source;
|
||||
uint32_t *dst = destination;
|
||||
|
||||
int y, x;
|
||||
uint32_t px;
|
||||
|
||||
int dx, dy;
|
||||
|
||||
for (y = yy; y < yn; ++y) {
|
||||
for (x = 0; x < xw; ++x) {
|
||||
px = *(uint32_t *)src;
|
||||
|
||||
dx = rot_x(rot, x, y, xw, xh);
|
||||
dy = rot_y(rot, x, y, xw, xh);
|
||||
|
||||
dst[dx+dy*dw] = _8888rev(px);
|
||||
src += 4;
|
||||
}
|
||||
src += pad;
|
||||
}
|
||||
}
|
||||
|
||||
void i8888(void *destination, void *source, int yy, int yn, int xw, int pad) {
|
||||
uint8_t *src = source; // must be in bytes because of possible padding in bytes
|
||||
uint32_t *dst = destination;
|
||||
|
||||
int y, x;
|
||||
uint32_t px;
|
||||
|
||||
for (y = yy; y < yn; ++y) {
|
||||
for (x = 0; x < xw; ++x) {
|
||||
px = *(uint32_t *)src;
|
||||
src += 4;
|
||||
*dst++ = _8888rev(px);
|
||||
}
|
||||
src += pad;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t px8888rev(uint32_t px) {
|
||||
return _8888rev(px);
|
||||
}
|
||||
|
|
@ -2,11 +2,18 @@ package image
|
|||
|
||||
import (
|
||||
"image"
|
||||
"math/bits"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -Wall
|
||||
#include "canvas.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Canvas is a stateful drawing surface, i.e. image.RGBA
|
||||
type Canvas struct {
|
||||
enabled bool
|
||||
|
|
@ -30,6 +37,35 @@ const (
|
|||
BitFormatShort565 // BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits
|
||||
)
|
||||
|
||||
const (
|
||||
ScaleNot = iota // skips image interpolation
|
||||
ScaleNearestNeighbour // nearest neighbour interpolation
|
||||
ScaleBilinear // bilinear interpolation
|
||||
)
|
||||
|
||||
func Resize(scaleType int, src *image.RGBA, out *image.RGBA) {
|
||||
// !to do set it once instead switching on each iteration
|
||||
switch scaleType {
|
||||
case ScaleBilinear:
|
||||
draw.ApproxBiLinear.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
|
||||
case ScaleNot:
|
||||
fallthrough
|
||||
case ScaleNearestNeighbour:
|
||||
fallthrough
|
||||
default:
|
||||
draw.NearestNeighbor.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
|
||||
}
|
||||
}
|
||||
|
||||
type Rotation uint
|
||||
|
||||
const (
|
||||
A90 Rotation = iota + 1
|
||||
A180
|
||||
A270
|
||||
F180 // F180 is flipped Y
|
||||
)
|
||||
|
||||
func NewCanvas(w, h, size int) *Canvas {
|
||||
return &Canvas{
|
||||
enabled: true,
|
||||
|
|
@ -66,17 +102,17 @@ func (c *Canvas) Put(i *Frame) {
|
|||
func (c *Canvas) Clear() { c.wg = sync.WaitGroup{} }
|
||||
func (c *Canvas) SetEnabled(enabled bool) { c.enabled = enabled }
|
||||
|
||||
func (c *Canvas) Draw(encoding uint32, rot *Rotate, w, h, packedW, bpp int, data []byte, th int) *Frame {
|
||||
func (c *Canvas) Draw(encoding uint32, rot Rotation, w, h, packedW, bpp int, data []byte, th int) *Frame {
|
||||
dst := c.Get(w, h)
|
||||
if th == 0 {
|
||||
frame(encoding, dst, data, 0, h, h, w, packedW, bpp, rot)
|
||||
frame(encoding, dst, data, 0, h, w, h, packedW, bpp, rot)
|
||||
} else {
|
||||
hn := h / th
|
||||
c.wg.Add(th)
|
||||
for i := 0; i < th; i++ {
|
||||
xx := hn * i
|
||||
go func() {
|
||||
frame(encoding, dst, data, xx, hn, h, w, packedW, bpp, rot)
|
||||
frame(encoding, dst, data, xx, hn, w, h, packedW, bpp, rot)
|
||||
c.wg.Done()
|
||||
}()
|
||||
}
|
||||
|
|
@ -100,77 +136,23 @@ func (c *Canvas) Draw(encoding uint32, rot *Rotate, w, h, packedW, bpp int, data
|
|||
return dst
|
||||
}
|
||||
|
||||
func frame(encoding uint32, dst *Frame, data []byte, yy int, hn int, h int, w int, pwb int, bpp int, rot *Rotate) {
|
||||
func frame(encoding uint32, dst *Frame, data []byte, yy int, hn int, w int, h int, pwb int, bpp int, rot Rotation) {
|
||||
sPtr := unsafe.Pointer(&data[yy*pwb])
|
||||
dPtr := unsafe.Pointer(&dst.Pix[yy*dst.Stride])
|
||||
// some cores can zero-right-pad rows to the packed width value
|
||||
pad := pwb - w*bpp
|
||||
yn := yy + hn
|
||||
|
||||
if rot == nil {
|
||||
// LE, BE might not work
|
||||
switch encoding {
|
||||
case BitFormatShort565:
|
||||
for y := yy; y < yn; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
i565((*uint32)(dPtr), uint32(*(*uint16)(sPtr)))
|
||||
sPtr = unsafe.Add(sPtr, uintptr(bpp))
|
||||
dPtr = unsafe.Add(dPtr, uintptr(4))
|
||||
}
|
||||
if pad > 0 {
|
||||
sPtr = unsafe.Add(sPtr, uintptr(pad))
|
||||
}
|
||||
}
|
||||
case BitFormatInt8888Rev:
|
||||
for y := yy; y < yn; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
ix8888((*uint32)(dPtr), *(*uint32)(sPtr))
|
||||
sPtr = unsafe.Add(sPtr, uintptr(bpp))
|
||||
dPtr = unsafe.Add(dPtr, uintptr(4))
|
||||
}
|
||||
if pad > 0 {
|
||||
sPtr = unsafe.Add(sPtr, uintptr(pad))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch encoding {
|
||||
case BitFormatShort565:
|
||||
for y := yy; y < yn; y++ {
|
||||
for x, k := 0, 0; x < w; x++ {
|
||||
dx, dy := rot.Call(x, y, w, h)
|
||||
k = dx<<2 + dy*dst.Stride
|
||||
dPtr = unsafe.Pointer(&dst.Pix[k])
|
||||
i565((*uint32)(dPtr), uint32(*(*uint16)(sPtr)))
|
||||
sPtr = unsafe.Add(sPtr, uintptr(bpp))
|
||||
}
|
||||
if pad > 0 {
|
||||
sPtr = unsafe.Add(sPtr, uintptr(pad))
|
||||
}
|
||||
}
|
||||
case BitFormatInt8888Rev:
|
||||
for y := yy; y < yn; y++ {
|
||||
for x, k := 0, 0; x < w; x++ {
|
||||
dx, dy := rot.Call(x, y, w, h)
|
||||
k = dx<<2 + dy*dst.Stride
|
||||
dPtr = unsafe.Pointer(&dst.Pix[k])
|
||||
ix8888((*uint32)(dPtr), *(*uint32)(sPtr))
|
||||
sPtr = unsafe.Add(sPtr, uintptr(bpp))
|
||||
}
|
||||
if pad > 0 {
|
||||
sPtr = unsafe.Add(sPtr, uintptr(pad))
|
||||
}
|
||||
}
|
||||
}
|
||||
if pad < 0 {
|
||||
pad = 0
|
||||
}
|
||||
if rot != 0 {
|
||||
dPtr = unsafe.Pointer(&dst.Pix[0])
|
||||
}
|
||||
C.RGBA(C.int(encoding), dPtr, sPtr, C.int(yy), C.int(yy+hn), C.int(w), C.int(h), C.int(dst.Stride>>2), C.int(pad), C.int(rot))
|
||||
}
|
||||
|
||||
func i565(dst *uint32, px uint32) {
|
||||
*dst = (px >> 8 & 0xf8) | ((px >> 3 & 0xfc) << 8) | ((px << 3 & 0xfc) << 16) // | 0xff000000
|
||||
// setting the last byte to 255 allows saving RGBA images to PNG not as black squares
|
||||
}
|
||||
func _8888rev(px uint32) uint32 { return uint32(C.px8888rev(C.uint32_t(px))) }
|
||||
|
||||
func ix8888(dst *uint32, px uint32) {
|
||||
//*dst = ((px >> 16) & 0xff) | (px & 0xff00) | ((px << 16) & 0xff0000) + 0xff000000
|
||||
*dst = bits.ReverseBytes32(px << 8) //| 0xff000000
|
||||
func rotate(t int, x int, y int, w int, h int) (int, int) {
|
||||
return int(C.rot_x(C.int(t), C.int(x), C.int(y), C.int(w), C.int(h))),
|
||||
int(C.rot_y(C.int(t), C.int(x), C.int(y), C.int(w), C.int(h)))
|
||||
}
|
||||
|
|
|
|||
42
pkg/worker/caged/libretro/image/canvas.h
Normal file
42
pkg/worker/caged/libretro/image/canvas.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#ifndef CANVAS_H__
|
||||
#define CANVAS_H__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define BIT_SHORT5551 0
|
||||
#define BIT_INT_8888REV 1
|
||||
#define BIT_SHORT565 2
|
||||
|
||||
// Rotate90 is 90° CCW or 270° CW.
|
||||
#define r90_x(x, y, w, h) ( y )
|
||||
#define r90_y(x, y, w, h) ( (w - 1) - x )
|
||||
|
||||
// Rotate180 is 180° CCW.
|
||||
#define r180_x(x, y, w, h) ( (w - 1) - x )
|
||||
#define r180_y(x, y, w, h) ( (h - 1) - y )
|
||||
|
||||
// Rotate270 is 270° CCW or 90° CW.
|
||||
#define r270_x(x, y, w, h) ( (h - 1) - y )
|
||||
#define r270_y(x, y, w, h) ( x )
|
||||
|
||||
// Flip Y
|
||||
#define fy180_x(x, y, w, h) ( x )
|
||||
#define fy180_y(x, y, w, h) ( (h - 1) - y )
|
||||
|
||||
int rot_x(int t, int x, int y, int w, int h);
|
||||
int rot_y(int t, int x, int y, int w, int h);
|
||||
|
||||
#define _565(x) ((x >> 8 & 0xf8) | ((x >> 3 & 0xfc) << 8) | ((x << 3 & 0xfc) << 16)); // | 0xff000000
|
||||
#define _8888rev(px) (((px >> 16) & 0xff) | (px & 0xff00) | ((px << 16) & 0xff0000)); // | 0xff000000)
|
||||
|
||||
|
||||
void RGBA(int pix, void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot);
|
||||
|
||||
void i565(void *destination, void *source, int yy, int yn, int xw, int pad);
|
||||
void i8888(void *destination, void *source, int yy, int yn, int xw, int pad);
|
||||
void i565r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot);
|
||||
void i8888r(void *destination, void *source, int yy, int yn, int xw, int xh, int dw, int pad, int rot);
|
||||
|
||||
uint32_t px8888rev(uint32_t px);
|
||||
|
||||
#endif
|
||||
|
|
@ -1,14 +1,18 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkDraw(b *testing.B) {
|
||||
w1, h1 := 256, 240
|
||||
w2, h2 := 640, 480
|
||||
|
||||
type args struct {
|
||||
encoding uint32
|
||||
rot *Rotate
|
||||
rot Rotation
|
||||
scaleType int
|
||||
w int
|
||||
h int
|
||||
|
|
@ -24,35 +28,52 @@ func BenchmarkDraw(b *testing.B) {
|
|||
args args
|
||||
}{
|
||||
{
|
||||
name: "0th",
|
||||
name: "565_0th",
|
||||
args: args{
|
||||
encoding: BitFormatInt8888Rev,
|
||||
rot: nil,
|
||||
scaleType: ScaleNearestNeighbour,
|
||||
w: 256,
|
||||
h: 240,
|
||||
packedW: 256,
|
||||
bpp: 4,
|
||||
data: make([]uint8, 256*240*4),
|
||||
dw: 256,
|
||||
dh: 240,
|
||||
th: 0,
|
||||
encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour,
|
||||
w: w1, h: h1, packedW: w1, bpp: 2, data: make([]uint8, w1*h1*2), dw: w1, dh: h1, th: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "4th",
|
||||
name: "565_0th_90",
|
||||
args: args{
|
||||
encoding: BitFormatInt8888Rev,
|
||||
rot: nil,
|
||||
scaleType: ScaleNearestNeighbour,
|
||||
w: 256,
|
||||
h: 240,
|
||||
packedW: 256,
|
||||
bpp: 4,
|
||||
data: make([]uint8, 256*240*4),
|
||||
dw: 256,
|
||||
dh: 240,
|
||||
th: 4,
|
||||
encoding: BitFormatShort565, rot: A90, scaleType: ScaleNearestNeighbour,
|
||||
w: h1, h: w1, packedW: h1, bpp: 2, data: make([]uint8, w1*h1*2), dw: w1, dh: h1, th: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "565_0th",
|
||||
args: args{
|
||||
encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour,
|
||||
w: w2, h: h2, packedW: w1, bpp: 2, data: make([]uint8, w2*h2*2), dw: w2, dh: h2, th: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "565_4th",
|
||||
args: args{
|
||||
encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour,
|
||||
w: w1, h: h1, packedW: w1, bpp: 2, data: make([]uint8, w1*h1*2), dw: w1, dh: h1, th: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "565_4th",
|
||||
args: args{
|
||||
encoding: BitFormatShort565, scaleType: ScaleNearestNeighbour,
|
||||
w: w2, h: h2, packedW: w2, bpp: 2, data: make([]uint8, w2*h2*2), dw: w2, dh: h2, th: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "8888 - 0th",
|
||||
args: args{
|
||||
encoding: BitFormatInt8888Rev, scaleType: ScaleNearestNeighbour,
|
||||
w: w1, h: h1, packedW: w1, bpp: 4, data: make([]uint8, w1*h1*4), dw: w1, dh: h1, th: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "8888 - 4th",
|
||||
args: args{
|
||||
encoding: BitFormatInt8888Rev, scaleType: ScaleNearestNeighbour,
|
||||
w: w1, h: h1, packedW: w1, bpp: 4, data: make([]uint8, w1*h1*4), dw: w1, dh: h1, th: 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -64,7 +85,7 @@ func BenchmarkDraw(b *testing.B) {
|
|||
img2 := c.Get(bn.args.dw, bn.args.dh)
|
||||
c.Put(img2)
|
||||
b.ResetTimer()
|
||||
b.Run(fmt.Sprintf("%v", bn.name), func(b *testing.B) {
|
||||
b.Run(fmt.Sprintf("%vx%v_%v", bn.args.w, bn.args.h, bn.name), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
p := c.Draw(bn.args.encoding, bn.args.rot, bn.args.w, bn.args.h, bn.args.packedW, bn.args.bpp, bn.args.data, bn.args.th)
|
||||
c.Put(p)
|
||||
|
|
@ -95,10 +116,225 @@ func Test_ix8888(t *testing.T) {
|
|||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ix8888(tt.args.dst, tt.args.px)
|
||||
*tt.args.dst = _8888rev(tt.args.px)
|
||||
if *tt.args.dst != tt.args.expect {
|
||||
t.Errorf("nope, %x %x", *tt.args.dst, tt.args.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type dimensions struct {
|
||||
w int
|
||||
h int
|
||||
}
|
||||
|
||||
func TestRotate(t *testing.T) {
|
||||
tests := []struct {
|
||||
// packed bytes from a 2D matrix
|
||||
input []byte
|
||||
// original matrix's width
|
||||
w int
|
||||
// original matrix's height
|
||||
h int
|
||||
// rotation algorithm
|
||||
rotateHow []Rotation
|
||||
expected [][]byte
|
||||
}{
|
||||
{
|
||||
// a cross
|
||||
[]byte{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
3, 3, []Rotation{0, A90, A180, A270},
|
||||
[][]byte{
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]byte{
|
||||
1, 2,
|
||||
3, 4,
|
||||
5, 6,
|
||||
7, 8,
|
||||
},
|
||||
2, 4, []Rotation{0, A90, A180, A270},
|
||||
[][]byte{
|
||||
{
|
||||
1, 2,
|
||||
3, 4,
|
||||
5, 6,
|
||||
7, 8,
|
||||
},
|
||||
{
|
||||
2, 4, 6, 8,
|
||||
1, 3, 5, 7,
|
||||
},
|
||||
{
|
||||
8, 7,
|
||||
6, 5,
|
||||
4, 3,
|
||||
2, 1,
|
||||
},
|
||||
{
|
||||
7, 5, 3, 1,
|
||||
8, 6, 4, 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// a square
|
||||
[]byte{
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
},
|
||||
8, 6, []Rotation{0, A90, A180, A270},
|
||||
[][]byte{
|
||||
{
|
||||
// L // R
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
},
|
||||
{
|
||||
0, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
},
|
||||
|
||||
{
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
},
|
||||
{
|
||||
0, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for i, rot := range test.rotateHow {
|
||||
if output := exampleRotate(test.input, test.w, test.h, rot); !bytes.Equal(output, test.expected[i]) {
|
||||
t.Errorf(
|
||||
"Test fail for angle %v with %v that should be \n%v but it's \n%v",
|
||||
rot, test.input, test.expected[i], output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundsAfterRotation(t *testing.T) {
|
||||
tests := []struct {
|
||||
dim []dimensions
|
||||
rotateHow []Rotation
|
||||
}{
|
||||
{
|
||||
// a combinatorics lib would be nice instead
|
||||
[]dimensions{
|
||||
// square
|
||||
{w: 100, h: 100},
|
||||
// even w/h
|
||||
{w: 100, h: 50},
|
||||
// even h/w
|
||||
{w: 50, h: 100},
|
||||
// odd even w/h
|
||||
{w: 77, h: 32},
|
||||
// even odd h/w
|
||||
{w: 32, h: 77},
|
||||
// just odd
|
||||
{w: 13, h: 19},
|
||||
},
|
||||
[]Rotation{0, A90, A180, A270},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, rot := range test.rotateHow {
|
||||
for _, dim := range test.dim {
|
||||
|
||||
for y := 0; y < dim.h; y++ {
|
||||
for x := 0; x < dim.w; x++ {
|
||||
|
||||
xx, yy := rotate(int(rot), x, y, dim.w, dim.h)
|
||||
|
||||
if rot == A90 || rot == A270 { // is even
|
||||
yy, xx = xx, yy
|
||||
}
|
||||
|
||||
if xx < 0 || xx > dim.w {
|
||||
t.Errorf("Rot %v, coordinate x should be in range [0; %v]: %v", rot, dim.w-1, xx)
|
||||
}
|
||||
|
||||
if yy < 0 || yy > dim.h {
|
||||
t.Errorf("Rot %v, coordinate y should be in range [0; %v]: %v", rot, dim.h-1, yy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// exampleRotate is an example of rotation usage.
|
||||
//
|
||||
// [1 2 3 4 5 6 7 8 9]
|
||||
// [7 4 1 8 5 2 9 6 3]
|
||||
func exampleRotate(data []uint8, w int, h int, rot Rotation) []uint8 {
|
||||
dest := make([]uint8, len(data))
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
nx, ny := rotate(int(rot), x, y, w, h)
|
||||
stride := w
|
||||
if rot == A90 || rot == A270 { // is even
|
||||
stride = h
|
||||
}
|
||||
dest[nx+ny*stride] = data[x+y*w]
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,87 +0,0 @@
|
|||
// Package image contains functions for rotations of points in a 2-dimensional space.
|
||||
package image
|
||||
|
||||
type Angle uint
|
||||
|
||||
const (
|
||||
Angle0 Angle = iota
|
||||
Angle90
|
||||
Angle180
|
||||
Angle270
|
||||
Flip180
|
||||
)
|
||||
|
||||
// Angles is a helper to choose appropriate rotation based on its angle.
|
||||
var Angles = [5]Rotate{
|
||||
Angle0: {Angle: Angle0, Call: Rotate0},
|
||||
Angle90: {Angle: Angle90, Call: Rotate90, IsEven: true},
|
||||
Angle180: {Angle: Angle180, Call: Rotate180},
|
||||
Angle270: {Angle: Angle270, Call: Rotate270, IsEven: true},
|
||||
Flip180: {Angle: Flip180, Call: Invert180},
|
||||
}
|
||||
|
||||
func GetRotation(angle Angle) Rotate { return Angles[angle] }
|
||||
|
||||
// Rotate is an interface for rotation of a given point.
|
||||
//
|
||||
// With the coordinates x, y in the matrix of w x h.
|
||||
// Returns a pair of new coordinates x, y in the resulting matrix.
|
||||
// Be aware that w / h values are 0 index-based,
|
||||
// and it's meant to be used with h corresponded
|
||||
// to matrix height and y coordinate, and with w to x coordinate.
|
||||
type Rotate struct {
|
||||
Angle Angle
|
||||
Call func(x, y, w, h int) (int, int)
|
||||
IsEven bool
|
||||
}
|
||||
|
||||
// Rotate0 is 0° or the original orientation.
|
||||
//
|
||||
// 1 2 3 1 2 3
|
||||
// 4 5 6 -> 4 5 6
|
||||
// 7 8 9 7 8 9
|
||||
func Rotate0(x, y, _, _ int) (int, int) { return x, y }
|
||||
|
||||
// Rotate90 is 90° CCW or 270° CW.
|
||||
//
|
||||
// 1 2 3 3 6 9
|
||||
// 4 5 6 -> 2 5 8
|
||||
// 7 8 9 1 4 7
|
||||
func Rotate90(x, y, w, _ int) (int, int) { return y, (w - 1) - x }
|
||||
|
||||
// Rotate180 is 180° CCW.
|
||||
//
|
||||
// 1 2 3 9 8 7
|
||||
// 4 5 6 -> 6 5 4
|
||||
// 7 8 9 3 2 1
|
||||
func Rotate180(x, y, w, h int) (int, int) { return (w - 1) - x, (h - 1) - y }
|
||||
|
||||
// Rotate270 is 270° CCW or 90° CW.
|
||||
//
|
||||
// 1 2 3 7 4 1
|
||||
// 4 5 6 -> 8 5 2
|
||||
// 7 8 9 9 6 3
|
||||
func Rotate270(x, y, _, h int) (int, int) { return (h - 1) - y, x }
|
||||
|
||||
func Invert180(x, y, _, h int) (int, int) { return x, (h - 1) - y }
|
||||
|
||||
// ExampleRotate is an example of rotation usage.
|
||||
//
|
||||
// [1 2 3 4 5 6 7 8 9]
|
||||
// [7 4 1 8 5 2 9 6 3]
|
||||
func ExampleRotate(data []uint8, w int, h int, angle Angle) []uint8 {
|
||||
dest := make([]uint8, len(data))
|
||||
rotationFn := Angles[angle]
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
nx, ny := rotationFn.Call(x, y, w, h)
|
||||
stride := w
|
||||
if rotationFn.IsEven {
|
||||
stride = h
|
||||
}
|
||||
//fmt.Printf("%v:%v (%v) -> %v:%v (%v)\n", x, y, n1, nx, ny, n2)
|
||||
dest[nx+ny*stride] = data[x+y*w]
|
||||
}
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type dimensions struct {
|
||||
w int
|
||||
h int
|
||||
}
|
||||
|
||||
func TestRotate(t *testing.T) {
|
||||
tests := []struct {
|
||||
// packed bytes from a 2D matrix
|
||||
input []byte
|
||||
// original matrix's width
|
||||
w int
|
||||
// original matrix's height
|
||||
h int
|
||||
// rotation algorithm
|
||||
rotateHow []Angle
|
||||
expected [][]byte
|
||||
}{
|
||||
{
|
||||
// a cross
|
||||
[]byte{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
3, 3, []Angle{Angle0, Angle90, Angle180, Angle270},
|
||||
[][]byte{
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
{
|
||||
0, 1, 0,
|
||||
1, 1, 1,
|
||||
0, 1, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]byte{
|
||||
1, 2,
|
||||
3, 4,
|
||||
5, 6,
|
||||
7, 8,
|
||||
},
|
||||
2, 4, []Angle{Angle0, Angle90, Angle180, Angle270},
|
||||
[][]byte{
|
||||
{
|
||||
1, 2,
|
||||
3, 4,
|
||||
5, 6,
|
||||
7, 8,
|
||||
},
|
||||
{
|
||||
2, 4, 6, 8,
|
||||
1, 3, 5, 7,
|
||||
},
|
||||
{
|
||||
8, 7,
|
||||
6, 5,
|
||||
4, 3,
|
||||
2, 1,
|
||||
},
|
||||
{
|
||||
7, 5, 3, 1,
|
||||
8, 6, 4, 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// a square
|
||||
[]byte{
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
},
|
||||
8, 6, []Angle{Angle0, Angle90, Angle180, Angle270},
|
||||
[][]byte{
|
||||
{
|
||||
// L // R
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
},
|
||||
{
|
||||
0, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
},
|
||||
|
||||
{
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 0, 0, 0, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 1, 1, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 1,
|
||||
},
|
||||
{
|
||||
0, 0, 0, 0, 0, 1,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 0, 1, 1, 0,
|
||||
0, 1, 1, 1, 1, 0,
|
||||
1, 0, 0, 0, 0, 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for i, rot := range test.rotateHow {
|
||||
if output := ExampleRotate(test.input, test.w, test.h, rot); !bytes.Equal(output, test.expected[i]) {
|
||||
t.Errorf(
|
||||
"Test fail for angle %v with %v that should be \n%v but it's \n%v",
|
||||
rot, test.input, test.expected[i], output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundsAfterRotation(t *testing.T) {
|
||||
tests := []struct {
|
||||
dim []dimensions
|
||||
rotateHow []Angle
|
||||
}{
|
||||
{
|
||||
// a combinatorics lib would be nice instead
|
||||
[]dimensions{
|
||||
// square
|
||||
{w: 100, h: 100},
|
||||
// even w/h
|
||||
{w: 100, h: 50},
|
||||
// even h/w
|
||||
{w: 50, h: 100},
|
||||
// odd even w/h
|
||||
{w: 77, h: 32},
|
||||
// even odd h/w
|
||||
{w: 32, h: 77},
|
||||
// just odd
|
||||
{w: 13, h: 19},
|
||||
},
|
||||
[]Angle{Angle0, Angle90, Angle180, Angle270},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
for _, rot := range test.rotateHow {
|
||||
rotationFn := Angles[rot]
|
||||
for _, dim := range test.dim {
|
||||
|
||||
for y := 0; y < dim.h; y++ {
|
||||
for x := 0; x < dim.w; x++ {
|
||||
|
||||
xx, yy := rotationFn.Call(x, y, dim.w, dim.h)
|
||||
|
||||
if rotationFn.IsEven {
|
||||
yy, xx = xx, yy
|
||||
}
|
||||
|
||||
if xx < 0 || xx > dim.w {
|
||||
t.Errorf("Rot %v, coordinate x should be in range [0; %v]: %v", rot, dim.w-1, xx)
|
||||
}
|
||||
|
||||
if yy < 0 || yy > dim.h {
|
||||
t.Errorf("Rot %v, coordinate y should be in range [0; %v]: %v", rot, dim.h-1, yy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkDirect(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = Rotate90(1, 1, 2, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkLiteral(b *testing.B) {
|
||||
fn := Rotate90
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = fn(1, 1, 2, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAssign(b *testing.B) {
|
||||
fn := Angles[Angle90].Call
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = fn(1, 1, 2, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapReassign(b *testing.B) {
|
||||
fn := Angles[Angle90].Call
|
||||
for i := 0; i < b.N; i++ {
|
||||
fn2 := fn
|
||||
_, _ = fn2(1, 1, 2, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMapDirect(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = Angles[Angle90].Call(1, 1, 2, 2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewMapDirect(b *testing.B) {
|
||||
fns := map[Angle]func(x, y, w, h int) (int, int){
|
||||
Angle90: Rotate90,
|
||||
}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = fns[Angle90](1, 1, 2, 2)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
const (
|
||||
ScaleNot = iota // skips image interpolation
|
||||
ScaleNearestNeighbour // nearest neighbour interpolation
|
||||
ScaleBilinear // bilinear interpolation
|
||||
)
|
||||
|
||||
func Resize(scaleType int, src *image.RGBA, out *image.RGBA) {
|
||||
// !to do set it once instead switching on each iteration
|
||||
switch scaleType {
|
||||
case ScaleBilinear:
|
||||
draw.ApproxBiLinear.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
|
||||
case ScaleNot:
|
||||
fallthrough
|
||||
case ScaleNearestNeighbour:
|
||||
fallthrough
|
||||
default:
|
||||
draw.NearestNeighbor.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
|
||||
}
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ type Nanoarch struct {
|
|||
}
|
||||
options *map[string]string
|
||||
reserved chan struct{} // limits concurrent use
|
||||
Rot *image.Rotate
|
||||
Rot image.Rotation
|
||||
serializeSize C.size_t
|
||||
stopped atomic.Bool
|
||||
sysAvInfo C.struct_retro_system_av_info
|
||||
|
|
@ -82,19 +82,14 @@ type FrameInfo struct {
|
|||
}
|
||||
|
||||
type Metadata struct {
|
||||
LibPath string // the full path to some emulator lib
|
||||
AudioSampleRate int
|
||||
Fps float64
|
||||
BaseWidth int
|
||||
BaseHeight int
|
||||
Rotation image.Rotate
|
||||
IsGlAllowed bool
|
||||
UsesLibCo bool
|
||||
AutoGlContext bool
|
||||
HasMultitap bool
|
||||
HasVFR bool
|
||||
Options map[string]string
|
||||
Hacks []string
|
||||
LibPath string // the full path to some emulator lib
|
||||
IsGlAllowed bool
|
||||
UsesLibCo bool
|
||||
AutoGlContext bool
|
||||
HasMultitap bool
|
||||
HasVFR bool
|
||||
Options map[string]string
|
||||
Hacks []string
|
||||
}
|
||||
|
||||
// Nan0 is a global link for C callbacks to Go
|
||||
|
|
@ -123,7 +118,7 @@ func NewNano(localPath string) *Nanoarch {
|
|||
|
||||
func (n *Nanoarch) AudioSampleRate() int { return int(n.sysAvInfo.timing.sample_rate) }
|
||||
func (n *Nanoarch) VideoFramerate() int { return int(n.sysAvInfo.timing.fps) }
|
||||
func (n *Nanoarch) IsPortrait() bool { return n.Rot != nil && n.Rot.IsEven }
|
||||
func (n *Nanoarch) IsPortrait() bool { return n.Rot == image.A90 || n.Rot == image.A270 }
|
||||
func (n *Nanoarch) GeometryBase() (int, int) {
|
||||
return int(n.sysAvInfo.geometry.base_width), int(n.sysAvInfo.geometry.base_height)
|
||||
}
|
||||
|
|
@ -256,8 +251,7 @@ func (n *Nanoarch) LoadGame(path string) error {
|
|||
n.stopped.Store(false)
|
||||
|
||||
if n.Video.gl.enabled {
|
||||
// flip Y coordinates of OpenGL
|
||||
setRotation(uint(image.Flip180))
|
||||
setRotation(image.F180) // flip Y coordinates of OpenGL
|
||||
bufS := uint(n.sysAvInfo.geometry.max_width*n.sysAvInfo.geometry.max_height) * n.Video.BPP
|
||||
graphics.SetBuffer(int(bufS))
|
||||
n.log.Info().Msgf("Set buffer: %v", byteCountBinary(int64(bufS)))
|
||||
|
|
@ -387,17 +381,9 @@ func videoSetPixelFormat(format uint32) (C.bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func setRotation(rotation uint) {
|
||||
if Nan0.Rot != nil && rotation == uint(Nan0.Rot.Angle) {
|
||||
return
|
||||
}
|
||||
if rotation > 0 {
|
||||
r := image.GetRotation(image.Angle(rotation))
|
||||
Nan0.Rot = &r
|
||||
} else {
|
||||
Nan0.Rot = nil
|
||||
}
|
||||
Nan0.log.Debug().Msgf("Image rotated %v°", map[uint]uint{0: 0, 1: 90, 2: 180, 3: 270}[rotation])
|
||||
func setRotation(rotation image.Rotation) {
|
||||
Nan0.Rot = rotation
|
||||
Nan0.log.Debug().Msgf("Image rotated %v°", map[uint]uint{0: 0, 1: 90, 2: 180, 3: 270}[uint(rotation)])
|
||||
}
|
||||
|
||||
func printOpenGLDriverInfo() {
|
||||
|
|
@ -679,7 +665,7 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
|
|||
|
||||
switch cmd {
|
||||
case C.RETRO_ENVIRONMENT_SET_ROTATION:
|
||||
setRotation(*(*uint)(data) % 4)
|
||||
setRotation(image.Rotation(*(*uint)(data) % 4))
|
||||
return true
|
||||
case C.RETRO_ENVIRONMENT_GET_CAN_DUPE:
|
||||
*(*C.bool)(data) = C.bool(true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue