可移植像素圖格式(PPM),可移植灰度圖格式(PGM)和可移植位圖格式(PBM)是便於跨平臺的圖像格式。有時候也被統稱爲 PNM 格式git
這三種格式實際上是同樣的描述方法,只不過 PBM 是單色,PGM 是灰度圖,PPM 使用 RGB 顏色。
每一個文件的開頭兩個字節(ASCII 碼)做爲文件描述符,指出具體格式和編碼形式。github
Type | Magic number | Extension | Colors | |
---|---|---|---|---|
ASCII | Binary | |||
Portable BitMap | P1 | P4 | .pbm | 0–1 (white & black) |
Portable GrayMap | P2 | P5 | .pgm | 0–255 (gray scale) |
Portable PixMap | P3 | P6 | .ppm | 0–255 (RGB) |
Portable Arbitrary Map | P7 | .pam | 0–255 (RGB_ALPHA) |
注意每行結束有換行符編程
P1 # This is an example bitmap of the letter "J" 6 10 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
上面的圖像是一個Japp
P2 # Shows the word "FEEP" (example from Netpbm man page on PGM) 24 7 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
上面的圖像是ide
P3 3 2 255 # The part above is the header # "P3" means this is a RGB color image in ASCII # "3 2" is the width and height of the image in pixels # "255" is the maximum value for each color # The part below is image data: RGB triplets 255 0 0 0 255 0 0 0 255 255 255 0 255 255 255 0 0 0
把上面六個像素放大後顯示以下ui
P7 WIDTH 4 HEIGHT 2 DEPTH 4 MAXVAL 255 TUPLTYPE RGB_ALPHA ENDHDR 0000FFFF 00FF00FF FF0000FF FFFFFFFF 0000FF7F 00FF007F FF00007F FFFFFF7F
上面數據放大顯示以下:this
下面是簡單的ppm包編碼
package ppm import ( "fmt" "io" "os" ) // WriteTo outputs 8-bit P6 PPM format to an io.Writer. func (b *Bitmap) WritePpmTo(w io.Writer) (err error) { // magic number if _, err = fmt.Fprintln(w, "P6"); err != nil { return } // comments for _, c := range b.Comments { if _, err = fmt.Fprintln(w, c); err != nil { return } } // x, y, depth _, err = fmt.Fprintf(w, "%d %d\n255\n", b.cols, b.rows) if err != nil { return } // raster data in a single write b3 := make([]byte, 3*len(b.px)) n1 := 0 for _, px := range b.px { b3[n1] = px.R b3[n1+1] = px.G b3[n1+2] = px.B n1 += 3 } if _, err = w.Write(b3); err != nil { return } return } // WriteFile writes to the specified filename. func (b *Bitmap) WritePpmFile(fn string) (err error) { var f *os.File if f, err = os.Create(fn); err != nil { return } if err = b.WritePpmTo(f); err != nil { return } return f.Close() }
下面是生成ppm的程序spa
package main // Files required to build supporting package raster are found in: // * This task (immediately above) // * Bitmap task import ( "raster" "fmt" ) func main() { b := raster.NewBitmap(400, 300) b.FillRgb(0x240008) // a dark red err := b.WritePpmFile("write.ppm") if err != nil { fmt.Println(err) } }
#include <stdio.h> int main() { const char *filename = "n.pgm"; int x, y; /* size of the image */ const int x_max = 100; /* width */ const int y_max = 100; /* height */ /* 2D array for colors (shades of gray) */ unsigned char data[y_max][x_max]; /* color component is coded from 0 to 255 ; it is 8 bit color file */ const int MaxColorComponentValue = 255; FILE * fp; /* comment should start with # */ const char *comment = "# this is my new binary pgm file"; /* fill the data array */ for (y = 0; y < y_max; ++y) { for (x = 0; x < x_max; ++x) { data[y][x] = (x + y) & 255; } } /* write the whole data array to ppm file in one step */ /* create new file, give it a name and open it in binary mode */ fp = fopen(filename, "wb"); /* write header to the file */ fprintf(fp, "P5\n %s\n %d\n %d\n %d\n", comment, x_max, y_max, MaxColorComponentValue); /* write image data bytes to the file */ fwrite(data, sizeof(data), 1, fp); fclose(fp); printf("OK - file %s saved\n", filename); return 0; }
或者firefox
imglib.h
#ifndef _IMGLIB_0 #define _IMGLIB_0 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <math.h> #include <sys/queue.h> typedef unsigned char color_component; typedef color_component pixel[3]; typedef struct { unsigned int width; unsigned int height; pixel * buf; } image_t; typedef image_t * image; image alloc_img(unsigned int width, unsigned int height); void free_img(image); void fill_img(image img, color_component r, color_component g, color_component b ); void put_pixel_unsafe( image img, unsigned int x, unsigned int y, color_component r, color_component g, color_component b ); void put_pixel_clip( image img, unsigned int x, unsigned int y, color_component r, color_component g, color_component b ); #define GET_PIXEL(IMG, X, Y) (IMG->buf[ ((Y) * IMG->width + (X)) ]) #endif
imglib.c
image alloc_img(unsigned int width, unsigned int height) { image img; img = malloc(sizeof(image_t)); img->buf = malloc(width * height * sizeof(pixel)); img->width = width; img->height = height; return img; } void free_img(image img) { free(img->buf); free(img); } void fill_img( image img, color_component r, color_component g, color_component b ) { unsigned int i, n; n = img->width * img->height; for (i=0; i < n; ++i) { img->buf[i][0] = r; img->buf[i][1] = g; img->buf[i][2] = b; } } void put_pixel_unsafe( image img, unsigned int x, unsigned int y, color_component r, color_component g, color_component b ) { unsigned int ofs; ofs = (y * img->width) + x; img->buf[ofs][0] = r; img->buf[ofs][1] = g; img->buf[ofs][2] = b; } void put_pixel_clip( image img, unsigned int x, unsigned int y, color_component r, color_component g, color_component b ) { if (x < img->width && y < img->height) put_pixel_unsafe(img, x, y, r, g, b); }
output_ppm
#include "imglib.h" void output_ppm(FILE *fd, image img) { unsigned int n; (void) fprintf(fd, "P6\n%d %d\n255\n", img->width, img->height); n = img->width * img->height; (void) fwrite(img->buf, sizeof(pixel), n, fd); (void) fflush(fd); }
其實raw rgb和ppm數據是同樣的,咱們生成出300幀的ppm數據,而後送給ffplayer(ffmpeg)來播放
下面是個人go代碼:
// // Created by : Harris Zhu // Filename : genrgb.go // Author : Harris Zhu // Created On : 2018-09-14 02:13:11 // Last Modified : 2018-09-14 02:13:11 // Update Count : 1 // Tags : // Description : // Conclusion : // //======================================================================= package main import ( "bufio" "os" "strconv" "time" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "genrgb" app.Version = "1.0.0" app.Compiled = time.Now() app.Authors = []cli.Author{ cli.Author{ Name: "Harris Zhu", Email: "zhuzhzh@163.com", }, } app.Usage = "svpaser <sv file>" name := "rgb.data" w := 600 h := 480 f := 20 app.Action = func(c *cli.Context) error { name = c.Args().Get(0) w, _ = strconv.Atoi(c.Args().Get(1)) h, _ = strconv.Atoi(c.Args().Get(2)) f, _ = strconv.Atoi(c.Args().Get(3)) genrgb(name, w, h, f) return nil } app.Run(os.Args) } func genrgb(filepath string, w int, h int, f int) { fout, err := os.Create(filepath) defer fout.Close() if err != nil { panic(err) } bufout := bufio.NewWriter(fout) // bufout.WriteString("P6\n # this is my ppm file\n") // bufout.WriteString(strconv.Itoa(w)) // bufout.WriteString("\n") // bufout.WriteString(strconv.Itoa(h)) // bufout.WriteString("\n") // bufout.WriteString(strconv.Itoa(255)) // bufout.WriteString("\n") r := []byte{255, 0, 0} g := []byte{0, 255, 0} b := []byte{0, 0, 255} dcnt := 0 //k: frame //i: height column //j: width line for k := 0; k < f; k++ { for i := 0; i < h; i++ { //fmt.Println("hline: ", i) for j := 0; j < w; j++ { if k%3 == 0 { bufout.Write(r) dcnt++ } else if k%3 == 1 { bufout.Write(g) dcnt++ } else if k%3 == 2 { bufout.Write(b) dcnt++ } //fmt.Println("vline: ", j) } } bufout.Flush() //fmt.Printf("total pixels = %d\n", dcnt) } }
下面是makefile:
FILE = genrgb GENFILE = rgb.data b build: go build -gcflags "-N -l" $(FILE).go g gen: ./$(FILE) $(GENFILE) 60 40 300 p play: cat $(GENFILE) | ffplay -i pipe:0 -f rawvideo -pix_fmt rgb24 -video_size 60x40
依次執行make b; make g; make p
能夠看到下面的播放視頻
它交替顯示紅綠藍
RGB比較直觀的圖像顯示方法,但相對YUV來講比較佔空間,每一個像素佔用3個byte, 像1080P的圖像就要佔用$1920*1080*3=6,220,800byte$, 但它用於做爲圖像和顯示入門是很是好的。可以讓你很快就常會編程顯示圖像。