分享
  1. 首页
  2. 文章

可移植像素图格式 PPM,灰度图格式 PGM,位图格式 PBM 的介绍 -- 视频和图像编程基础之一

harriszh · · 1833 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

可移植像素图格式 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

上面的图像是
220px-Feep_netbpm_p2_pgm_example

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

把上面六个像素放大后显示如下
Tiny6pixel

PAM

P7
WIDTH 4
HEIGHT 2
DEPTH 4
MAXVAL 255
TUPLTYPE RGB_ALPHA
ENDHDR
0000FFFF 00FF00FF FF0000FF FFFFFFFF
0000FF7F 00FF007F FF00007F FFFFFF7F

上面数据放大显示如下:
firefox_2018年09月15日_00-50-56

用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可以看到下面的播放视频

ppm_disp

它交替显示红绿蓝

总结

RGB比较直观的图像显示方法,但相对YUV来说比较占空间,每个像素占用3个byte, 像1080P的图像就要占用192010803=6,220,800byte, 但它用于作为图像和显示入门是非常好的。能够让你很快就常会编程显示图像。


有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
1833 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏