图像格式PPMPGMPBM剖析及代码实现
可移植像素图格式 PPM,灰度图格式 PGM,位图格式 PBM 的介绍 简介
可移植像素图格式(PPM),可移植灰度图格式(PGM)和可移植位图格式(PBM)是便于跨平台的图像格式。有时候也被统称为 PNM 格式
文件格式描述
这三种格式其实是一样的描述方法,只不过 PBM 是单色,PGM 是灰度图,PPM 使用 RGB 颜色。
每个文件的开头两个字节(ASCII 码)作为文件描述符,指出具体格式和编码形式。
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) |
格式例子 PBM
注意每行结束有换行符
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
上面的图像是一个J
PGM
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
上面的图像是
PPM
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
把上面六个像素放大后显示如下
PAM
P7 WIDTH 4 HEIGHT 2 DEPTH 4 MAXVAL 255 TUPLTYPE RGB_ALPHA ENDHDR 0000FFFF 00FF00FF FF0000FF FFFFFFFF 0000FF7F 00FF007F FF00007F FFFFFF7F
上面数据放大显示如下:
用go生成PPM文件
下面是简单的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的程序
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) } }
用C生成PPM文件
#include <stdio.h> int main() { const char *filename = "n.pgm"; int x, y; const int x_max = 100; const int y_max = 100; unsigned char data[y_max][x_max]; const int MaxColorComponentValue = 255; FILE * fp; const char *comment = "# this is my new binary pgm file"; for (y = 0; y < y_max; ++y) { for (x = 0; x < x_max; ++x) { data[y][x] = (x + y) & 255; } } fp = fopen(filename, "wb"); fprintf(fp, "P5\n %s\n %d\n %d\n %d\n", comment, x_max, y_max, MaxColorComponentValue); fwrite(data, sizeof(data), 1, fp); fclose(fp); printf("OK - file %s saved\n", filename); return 0; }
或者
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格式
其实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$, 但它用于作为图像和显示入门是非常好的。能够让你很快就常会编程显示图像。