可移植像素图格式 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;
/* 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;
}
或者
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的图像就要占用192010803=6,220,800byte, 但它用于作为图像和显示入门是非常好的。能够让你很快就常会编程显示图像。
有疑问加站长微信联系(非本文作者)