From f6eaee9f1f4fde0503081748928741cace0e43f3 Mon Sep 17 00:00:00 2001 From: Alex Eidt Date: Mon, 19 Sep 2022 20:41:07 -0700 Subject: [PATCH] rgb to rgba --- README.md | 6 ++--- camera.go | 4 ++-- imageio.go | 13 ++++++----- video.go | 6 ++--- videowriter.go | 18 ++++----------- vidio_test.go | 63 ++++++++++++++++++++++++++------------------------ 6 files changed, 51 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 1e00ab9..e108515 100644 --- a/README.md +++ b/README.md @@ -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. } ``` diff --git a/camera.go b/camera.go index 0163e83..400ab02 100644 --- a/camera.go +++ b/camera.go @@ -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", "-", ) diff --git a/imageio.go b/imageio.go index 5e1d553..03ecc4a 100644 --- a/imageio.go +++ b/imageio.go @@ -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 } } diff --git a/video.go b/video.go index 97fa739..546e2fb 100644 --- a/video.go +++ b/video.go @@ -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), "-", diff --git a/videowriter.go b/videowriter.go index 5b15f17..54c7b9a 100644 --- a/videowriter.go +++ b/videowriter.go @@ -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), diff --git a/vidio_test.go b/vidio_test.go index 3fb5876..03998d3 100644 --- a/vidio_test.go +++ b/vidio_test.go @@ -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") }