rgb to rgba

This commit is contained in:
Alex Eidt 2022-09-19 20:41:07 -07:00
parent 2f07344cb8
commit f6eaee9f1f
6 changed files with 51 additions and 59 deletions

View file

@ -2,7 +2,7 @@
A simple Video I/O library written in Go. This library relies on [FFmpeg](https://www.ffmpeg.org/), and [FFProbe](https://www.ffmpeg.org/) which must be downloaded before usage and added to the system path.
All frames are encoded and decoded in 8-bit RGB format.
All frames are encoded and decoded in 8-bit RGBA format.
For Audio I/O using FFmpeg, see the [`aio`](https://github.com/AlexEidt/aio) project.
@ -16,7 +16,7 @@ go get github.com/AlexEidt/Vidio
The `Video` struct stores data about a video file you give it. The code below shows an example of sequentially reading the frames of the given video.
Calling the `Read()` function will fill in the `Video` struct `framebuffer` with the next frame data as 8-bit RGB data, stored in a flattened byte array in row-major order where each pixel is represented by three consecutive bytes representing the R, G and B component of that pixel.
Calling the `Read()` function will fill in the `Video` struct `framebuffer` with the next frame data as 8-bit RGBA data, stored in a flattened byte array in row-major order where each pixel is represented by four consecutive bytes representing the R, G, B and A components of that pixel. Note that the A (alpha) component will always be 255.
```go
vidio.NewVideo(filename string) (*vidio.Video, error)
@ -81,7 +81,6 @@ Macro() int
FPS() float64
Quality() float64
Codec() string
Format() string
Write(frame []byte) error
Close()
@ -96,7 +95,6 @@ type Options struct {
FPS float64 // Frames per second for output video.
Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
Codec string // Codec for video.
Format string // Pixel Format for video. Default "rgb24".
StreamFile string // File path for extra stream data.
}
```

View file

@ -90,7 +90,7 @@ func NewCamera(stream int) (*Camera, error) {
return nil, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
}
camera := &Camera{name: device, depth: 3}
camera := &Camera{name: device, depth: 4}
if err := camera.getCameraData(device); err != nil {
return nil, err
}
@ -205,7 +205,7 @@ func (camera *Camera) init() error {
"-f", webcamDeviceName,
"-i", camera.name,
"-f", "image2pipe",
"-pix_fmt", "rgb24",
"-pix_fmt", "rgba",
"-vcodec", "rawvideo",
"-",
)

View file

@ -11,7 +11,7 @@ import (
"image/png"
)
// Reads an image from a file. Currently only supports png and jpeg.
// Reads an image into an rgba byte buffer from a file. Currently only supports png and jpeg.
func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
f, err := os.Open(filename)
if err != nil {
@ -25,7 +25,7 @@ func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
}
bounds := image.Bounds().Max
size := bounds.X * bounds.Y * 3
size := bounds.X * bounds.Y * 4
var data []byte
if len(buffer) > 0 {
@ -45,13 +45,14 @@ func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
data[index+0] = byte(r)
data[index+1] = byte(g)
data[index+2] = byte(b)
index += 3
data[index+3] = 255
index += 4
}
}
return bounds.X, bounds.Y, data, nil
}
// Writes an image to a file. Currently only supports png and jpeg.
// Writes a rgba byte buffer to a file. Currently only supports png and jpeg.
func Write(filename string, width, height int, buffer []byte) error {
f, err := os.Create(filename)
if err != nil {
@ -63,9 +64,9 @@ func Write(filename string, width, height int, buffer []byte) error {
index := 0
for h := 0; h < height; h++ {
for w := 0; w < width; w++ {
r, g, b := buffer[index], buffer[index+1], buffer[index+2]
r, g, b := buffer[index+0], buffer[index+1], buffer[index+2]
image.Set(w, h, color.RGBA{r, g, b, 255})
index += 3
index += 4
}
}

View file

@ -143,7 +143,7 @@ func NewVideoStreams(filename string) ([]*Video, error) {
for i, data := range videoData {
video := &Video{
filename: filename,
depth: 3,
depth: 4,
stream: i,
hasstreams: hasstream,
metadata: data,
@ -190,13 +190,13 @@ func (video *Video) addVideoData(data map[string]string) {
func (video *Video) init() error {
// If user exits with Ctrl+C, stop ffmpeg process.
video.cleanup()
// ffmpeg command to pipe video data to stdout in 8-bit RGB format.
// ffmpeg command to pipe video data to stdout in 8-bit RGBA format.
cmd := exec.Command(
"ffmpeg",
"-i", video.filename,
"-f", "image2pipe",
"-loglevel", "quiet",
"-pix_fmt", "rgb24",
"-pix_fmt", "rgba",
"-vcodec", "rawvideo",
"-map", fmt.Sprintf("0:v:%d", video.stream),
"-",

View file

@ -23,7 +23,6 @@ type VideoWriter struct {
fps float64 // Frames per second for output video. Default 25.
quality float64 // Used if bitrate not given. Default 0.5.
codec string // Codec to encode video with. Default libx264.
format string // Output format. Default rgb24.
pipe *io.WriteCloser // Stdout pipe of ffmpeg process.
cmd *exec.Cmd // ffmpeg command.
}
@ -37,7 +36,6 @@ type Options struct {
FPS float64 // Frames per second for output video.
Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
Codec string // Codec for video.
Format string // Pixel Format for video. Default "rgb24".
StreamFile string // File path for extra stream data.
}
@ -86,10 +84,6 @@ func (writer *VideoWriter) Codec() string {
return writer.codec
}
func (writer *VideoWriter) Format() string {
return writer.format
}
// Creates a new VideoWriter struct with default values from the Options struct.
func NewVideoWriter(filename string, width, height int, options *Options) (*VideoWriter, error) {
// Check if ffmpeg is installed on the users machine.
@ -149,12 +143,6 @@ func NewVideoWriter(filename string, width, height int, options *Options) (*Vide
writer.codec = options.Codec
}
if options.Format == "" {
writer.format = "rgb24"
} else {
writer.format = options.Format
}
if options.StreamFile != "" {
if !exists(options.StreamFile) {
return nil, fmt.Errorf("file %s does not exist", options.StreamFile)
@ -177,7 +165,7 @@ func (writer *VideoWriter) init() error {
"-f", "rawvideo",
"-vcodec", "rawvideo",
"-s", fmt.Sprintf("%dx%d", writer.width, writer.height), // frame w x h.
"-pix_fmt", writer.format,
"-pix_fmt", "rgba",
"-r", fmt.Sprintf("%.02f", writer.fps), // frames per second.
"-i", "-", // The input comes from stdin.
}
@ -206,7 +194,7 @@ func (writer *VideoWriter) init() error {
command = append(
command,
"-vcodec", writer.codec,
"-pix_fmt", "yuv420p", // Output is 8-bit RGB, no alpha.
"-pix_fmt", "yuv420p", // Output is 8-bit RGB, ignore alpha.
)
// Code from the imageio-ffmpeg project.
@ -246,6 +234,8 @@ func (writer *VideoWriter) init() error {
if writer.height%writer.macro > 0 {
height += writer.macro - (writer.height % writer.macro)
}
writer.width = width
writer.height = height
command = append(
command,
"-vf", fmt.Sprintf("scale=%d:%d", width, height),

View file

@ -39,7 +39,7 @@ func TestVideoMetaData(t *testing.T) {
assertEquals(video.filename, "test/koala.mp4")
assertEquals(video.width, 480)
assertEquals(video.height, 270)
assertEquals(video.depth, 3)
assertEquals(video.depth, 4)
assertEquals(video.bitrate, 170549)
assertEquals(video.frames, 101)
assertEquals(video.duration, 3.366667)
@ -67,17 +67,23 @@ func TestVideoFrame(t *testing.T) {
defer video.Close()
video.Read()
// [203 222 134 203 222 134 203 222 134 203]
// [203 222 134 255 203 222 134 255 203 222 134 255 203]
assertEquals(video.framebuffer[0], uint8(203))
assertEquals(video.framebuffer[1], uint8(222))
assertEquals(video.framebuffer[2], uint8(134))
assertEquals(video.framebuffer[3], uint8(203))
assertEquals(video.framebuffer[4], uint8(222))
assertEquals(video.framebuffer[5], uint8(134))
assertEquals(video.framebuffer[6], uint8(203))
assertEquals(video.framebuffer[7], uint8(222))
assertEquals(video.framebuffer[8], uint8(134))
assertEquals(video.framebuffer[9], uint8(203))
assertEquals(video.framebuffer[3], uint8(255))
assertEquals(video.framebuffer[4], uint8(203))
assertEquals(video.framebuffer[5], uint8(222))
assertEquals(video.framebuffer[6], uint8(134))
assertEquals(video.framebuffer[7], uint8(255))
assertEquals(video.framebuffer[8], uint8(203))
assertEquals(video.framebuffer[9], uint8(222))
assertEquals(video.framebuffer[10], uint8(134))
assertEquals(video.framebuffer[11], uint8(255))
assertEquals(video.framebuffer[12], uint8(203))
fmt.Println("Video Frame Test Passed")
}
@ -90,8 +96,8 @@ func TestVideoWriting(t *testing.T) {
}
options := Options{
FPS: video.FPS(),
Bitrate: video.Bitrate(),
Codec: video.Codec(),
Bitrate: video.Bitrate(),
}
if video.HasStreams() {
options.StreamFile = video.FileName()
@ -101,12 +107,11 @@ func TestVideoWriting(t *testing.T) {
if err != nil {
panic(err)
}
for video.Read() {
err := writer.Write(video.FrameBuffer())
if err != nil {
panic(err)
}
writer.Write(video.FrameBuffer())
}
writer.Close()
os.Remove(output)
@ -134,14 +139,6 @@ func TestCameraIO(t *testing.T) {
count := 0
for webcam.Read() {
frame := webcam.FrameBuffer()
for i := 0; i < len(frame); i += 3 {
rgb := frame[i : i+3]
r, g, b := int(rgb[0]), int(rgb[1]), int(rgb[2])
gray := uint8((3*r + 4*g + b) / 8)
frame[i] = gray
frame[i+1] = gray
frame[i+2] = gray
}
err := writer.Write(frame)
if err != nil {
panic(err)
@ -250,18 +247,24 @@ func TestImageRead(t *testing.T) {
assertEquals(w, 200)
assertEquals(h, 133)
assertEquals(len(img), 200*133*3)
// [255 221 189 255 221 189 255 222 186 255]
assertEquals(len(img), 200*133*4)
// [255 221 189 255 255 221 189 255 255 222 186 255 255]
assertEquals(img[0], uint8(255))
assertEquals(img[1], uint8(221))
assertEquals(img[2], uint8(189))
assertEquals(img[3], uint8(255))
assertEquals(img[4], uint8(221))
assertEquals(img[5], uint8(189))
assertEquals(img[6], uint8(255))
assertEquals(img[7], uint8(222))
assertEquals(img[8], uint8(186))
assertEquals(img[9], uint8(255))
assertEquals(img[4], uint8(255))
assertEquals(img[5], uint8(221))
assertEquals(img[6], uint8(189))
assertEquals(img[7], uint8(255))
assertEquals(img[8], uint8(255))
assertEquals(img[9], uint8(222))
assertEquals(img[10], uint8(186))
assertEquals(img[11], uint8(255))
assertEquals(img[12], uint8(255))
fmt.Println("Image Reading Test Passed")
}