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. 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. 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. 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 ```go
vidio.NewVideo(filename string) (*vidio.Video, error) vidio.NewVideo(filename string) (*vidio.Video, error)
@ -81,7 +81,6 @@ Macro() int
FPS() float64 FPS() float64
Quality() float64 Quality() float64
Codec() string Codec() string
Format() string
Write(frame []byte) error Write(frame []byte) error
Close() Close()
@ -96,7 +95,6 @@ type Options struct {
FPS float64 // Frames per second for output video. 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. Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
Codec string // Codec for video. Codec string // Codec for video.
Format string // Pixel Format for video. Default "rgb24".
StreamFile string // File path for extra stream data. 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) 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 { if err := camera.getCameraData(device); err != nil {
return nil, err return nil, err
} }
@ -205,7 +205,7 @@ func (camera *Camera) init() error {
"-f", webcamDeviceName, "-f", webcamDeviceName,
"-i", camera.name, "-i", camera.name,
"-f", "image2pipe", "-f", "image2pipe",
"-pix_fmt", "rgb24", "-pix_fmt", "rgba",
"-vcodec", "rawvideo", "-vcodec", "rawvideo",
"-", "-",
) )

View file

@ -11,7 +11,7 @@ import (
"image/png" "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) { func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
f, err := os.Open(filename) f, err := os.Open(filename)
if err != nil { if err != nil {
@ -25,7 +25,7 @@ func Read(filename string, buffer ...[]byte) (int, int, []byte, error) {
} }
bounds := image.Bounds().Max bounds := image.Bounds().Max
size := bounds.X * bounds.Y * 3 size := bounds.X * bounds.Y * 4
var data []byte var data []byte
if len(buffer) > 0 { 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+0] = byte(r)
data[index+1] = byte(g) data[index+1] = byte(g)
data[index+2] = byte(b) data[index+2] = byte(b)
index += 3 data[index+3] = 255
index += 4
} }
} }
return bounds.X, bounds.Y, data, nil 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 { func Write(filename string, width, height int, buffer []byte) error {
f, err := os.Create(filename) f, err := os.Create(filename)
if err != nil { if err != nil {
@ -63,9 +64,9 @@ func Write(filename string, width, height int, buffer []byte) error {
index := 0 index := 0
for h := 0; h < height; h++ { for h := 0; h < height; h++ {
for w := 0; w < width; w++ { 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}) 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 { for i, data := range videoData {
video := &Video{ video := &Video{
filename: filename, filename: filename,
depth: 3, depth: 4,
stream: i, stream: i,
hasstreams: hasstream, hasstreams: hasstream,
metadata: data, metadata: data,
@ -190,13 +190,13 @@ func (video *Video) addVideoData(data map[string]string) {
func (video *Video) init() error { func (video *Video) init() error {
// If user exits with Ctrl+C, stop ffmpeg process. // If user exits with Ctrl+C, stop ffmpeg process.
video.cleanup() 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( cmd := exec.Command(
"ffmpeg", "ffmpeg",
"-i", video.filename, "-i", video.filename,
"-f", "image2pipe", "-f", "image2pipe",
"-loglevel", "quiet", "-loglevel", "quiet",
"-pix_fmt", "rgb24", "-pix_fmt", "rgba",
"-vcodec", "rawvideo", "-vcodec", "rawvideo",
"-map", fmt.Sprintf("0:v:%d", video.stream), "-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. fps float64 // Frames per second for output video. Default 25.
quality float64 // Used if bitrate not given. Default 0.5. quality float64 // Used if bitrate not given. Default 0.5.
codec string // Codec to encode video with. Default libx264. codec string // Codec to encode video with. Default libx264.
format string // Output format. Default rgb24.
pipe *io.WriteCloser // Stdout pipe of ffmpeg process. pipe *io.WriteCloser // Stdout pipe of ffmpeg process.
cmd *exec.Cmd // ffmpeg command. cmd *exec.Cmd // ffmpeg command.
} }
@ -37,7 +36,6 @@ type Options struct {
FPS float64 // Frames per second for output video. 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. Quality float64 // If bitrate not given, use quality instead. Must be between 0 and 1. 0:best, 1:worst.
Codec string // Codec for video. Codec string // Codec for video.
Format string // Pixel Format for video. Default "rgb24".
StreamFile string // File path for extra stream data. StreamFile string // File path for extra stream data.
} }
@ -86,10 +84,6 @@ func (writer *VideoWriter) Codec() string {
return writer.codec return writer.codec
} }
func (writer *VideoWriter) Format() string {
return writer.format
}
// Creates a new VideoWriter struct with default values from the Options struct. // Creates a new VideoWriter struct with default values from the Options struct.
func NewVideoWriter(filename string, width, height int, options *Options) (*VideoWriter, error) { func NewVideoWriter(filename string, width, height int, options *Options) (*VideoWriter, error) {
// Check if ffmpeg is installed on the users machine. // 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 writer.codec = options.Codec
} }
if options.Format == "" {
writer.format = "rgb24"
} else {
writer.format = options.Format
}
if options.StreamFile != "" { if options.StreamFile != "" {
if !exists(options.StreamFile) { if !exists(options.StreamFile) {
return nil, fmt.Errorf("file %s does not exist", options.StreamFile) return nil, fmt.Errorf("file %s does not exist", options.StreamFile)
@ -177,7 +165,7 @@ func (writer *VideoWriter) init() error {
"-f", "rawvideo", "-f", "rawvideo",
"-vcodec", "rawvideo", "-vcodec", "rawvideo",
"-s", fmt.Sprintf("%dx%d", writer.width, writer.height), // frame w x h. "-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. "-r", fmt.Sprintf("%.02f", writer.fps), // frames per second.
"-i", "-", // The input comes from stdin. "-i", "-", // The input comes from stdin.
} }
@ -206,7 +194,7 @@ func (writer *VideoWriter) init() error {
command = append( command = append(
command, command,
"-vcodec", writer.codec, "-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. // Code from the imageio-ffmpeg project.
@ -246,6 +234,8 @@ func (writer *VideoWriter) init() error {
if writer.height%writer.macro > 0 { if writer.height%writer.macro > 0 {
height += writer.macro - (writer.height % writer.macro) height += writer.macro - (writer.height % writer.macro)
} }
writer.width = width
writer.height = height
command = append( command = append(
command, command,
"-vf", fmt.Sprintf("scale=%d:%d", width, height), "-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.filename, "test/koala.mp4")
assertEquals(video.width, 480) assertEquals(video.width, 480)
assertEquals(video.height, 270) assertEquals(video.height, 270)
assertEquals(video.depth, 3) assertEquals(video.depth, 4)
assertEquals(video.bitrate, 170549) assertEquals(video.bitrate, 170549)
assertEquals(video.frames, 101) assertEquals(video.frames, 101)
assertEquals(video.duration, 3.366667) assertEquals(video.duration, 3.366667)
@ -67,17 +67,23 @@ func TestVideoFrame(t *testing.T) {
defer video.Close() defer video.Close()
video.Read() 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[0], uint8(203))
assertEquals(video.framebuffer[1], uint8(222)) assertEquals(video.framebuffer[1], uint8(222))
assertEquals(video.framebuffer[2], uint8(134)) assertEquals(video.framebuffer[2], uint8(134))
assertEquals(video.framebuffer[3], uint8(203)) assertEquals(video.framebuffer[3], uint8(255))
assertEquals(video.framebuffer[4], uint8(222))
assertEquals(video.framebuffer[5], uint8(134)) assertEquals(video.framebuffer[4], uint8(203))
assertEquals(video.framebuffer[6], uint8(203)) assertEquals(video.framebuffer[5], uint8(222))
assertEquals(video.framebuffer[7], uint8(222)) assertEquals(video.framebuffer[6], uint8(134))
assertEquals(video.framebuffer[8], uint8(134)) assertEquals(video.framebuffer[7], uint8(255))
assertEquals(video.framebuffer[9], uint8(203))
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") fmt.Println("Video Frame Test Passed")
} }
@ -90,8 +96,8 @@ func TestVideoWriting(t *testing.T) {
} }
options := Options{ options := Options{
FPS: video.FPS(), FPS: video.FPS(),
Bitrate: video.Bitrate(),
Codec: video.Codec(), Codec: video.Codec(),
Bitrate: video.Bitrate(),
} }
if video.HasStreams() { if video.HasStreams() {
options.StreamFile = video.FileName() options.StreamFile = video.FileName()
@ -101,12 +107,11 @@ func TestVideoWriting(t *testing.T) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
for video.Read() { for video.Read() {
err := writer.Write(video.FrameBuffer()) writer.Write(video.FrameBuffer())
if err != nil {
panic(err)
}
} }
writer.Close() writer.Close()
os.Remove(output) os.Remove(output)
@ -134,14 +139,6 @@ func TestCameraIO(t *testing.T) {
count := 0 count := 0
for webcam.Read() { for webcam.Read() {
frame := webcam.FrameBuffer() 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) err := writer.Write(frame)
if err != nil { if err != nil {
panic(err) panic(err)
@ -250,18 +247,24 @@ func TestImageRead(t *testing.T) {
assertEquals(w, 200) assertEquals(w, 200)
assertEquals(h, 133) assertEquals(h, 133)
assertEquals(len(img), 200*133*3) assertEquals(len(img), 200*133*4)
// [255 221 189 255 221 189 255 222 186 255] // [255 221 189 255 255 221 189 255 255 222 186 255 255]
assertEquals(img[0], uint8(255)) assertEquals(img[0], uint8(255))
assertEquals(img[1], uint8(221)) assertEquals(img[1], uint8(221))
assertEquals(img[2], uint8(189)) assertEquals(img[2], uint8(189))
assertEquals(img[3], uint8(255)) assertEquals(img[3], uint8(255))
assertEquals(img[4], uint8(221))
assertEquals(img[5], uint8(189)) assertEquals(img[4], uint8(255))
assertEquals(img[6], uint8(255)) assertEquals(img[5], uint8(221))
assertEquals(img[7], uint8(222)) assertEquals(img[6], uint8(189))
assertEquals(img[8], uint8(186)) assertEquals(img[7], uint8(255))
assertEquals(img[9], 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") fmt.Println("Image Reading Test Passed")
} }