Convert colors in C

This commit is contained in:
Sergey Stepanov 2023-09-22 17:46:00 +03:00 committed by sergystepanov
parent 8dd9e9c9be
commit 85cef0dfec
8 changed files with 513 additions and 486 deletions

View 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);
}

View file

@ -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)))
}

View 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

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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)
}
}

View file

@ -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)