Task 518: .PBM File Format
Task 518: .PBM File Format
File Format Specifications for .PBM
The .PBM (Portable BitMap) file format is a simple monochrome (black and white) image format part of the Netpbm family. It supports two variants: plain (ASCII, magic number "P1") and raw (binary, magic number "P4"). The file starts with the magic number in ASCII, followed by optional comments (lines starting with "#"), then the width and height as ASCII decimal numbers separated by whitespace, and finally the pixel data. Pixels represent white (0) or black (1). In plain format, data is ASCII '0's and '1's separated by whitespace. In raw format, data is packed binary bits (MSB first, padded to bytes per row). Multiple images can be concatenated, but most tools handle only the first.
- List of Properties Intrinsic to the .PBM File Format:
- Magic number: "P1" (plain/ASCII) or "P4" (raw/binary).
- Comments: Optional list of comment strings (each starting with "#", can be multiple lines).
- Width: Integer representing the number of pixels per row.
- Height: Integer representing the number of rows.
These are the core metadata properties extractable from the header; pixel data is the image content itself and not considered a "property" here.
Two Direct Download Links for .PBM Files:
- https://filesamples.com/samples/image/pbm/sample_640×426.pbm
- https://filesamples.com/samples/image/pbm/sample_1280×853.pbm
HTML/JavaScript for Drag-and-Drop .PBM Property Dumper:
This is a self-contained HTML page with embedded JavaScript. Users can drag and drop a .PBM file onto the designated area, and it will parse the file (handling both plain and raw variants) and display the properties on screen.
Python Class for .PBM Handling:
import struct
import re
class PBMFile:
def __init__(self, filepath=None):
self.magic = None
self.comments = []
self.width = None
self.height = None
self.data = None # Pixel data (list of bytes for raw, string for plain)
if filepath:
self.read(filepath)
def read(self, filepath):
with open(filepath, 'rb') as f:
content = f.read()
# Parse header as text
text = content.decode('ascii', errors='ignore')
lines = text.splitlines()
idx = 0
self.magic = lines[idx].strip()
idx += 1
while idx < len(lines) and lines[idx].startswith('#'):
self.comments.append(lines[idx][1:].strip())
idx += 1
dimensions = re.split(r'\s+', lines[idx].strip())
self.width = int(dimensions[0])
self.height = int(dimensions[1])
# Data starts after header
header_end = content.find(b'\n', content.find(bytes(str(self.height), 'ascii'))) + 1
self.data = content[header_end:]
if self.magic == 'P1':
# Plain: keep as string
self.data = self.data.decode('ascii')
# For raw P4, data is binary
def print_properties(self):
print(f"Magic Number: {self.magic}")
print("Comments:", "\n".join(self.comments) if self.comments else "None")
print(f"Width: {self.width}")
print(f"Height: {self.height}")
def write(self, filepath):
if not all([self.magic, self.width, self.height, self.data]):
raise ValueError("Missing properties or data")
header = f"{self.magic}\n"
for comment in self.comments:
header += f"#{comment}\n"
header += f"{self.width} {self.height}\n"
with open(filepath, 'wb') as f:
f.write(header.encode('ascii'))
if self.magic == 'P1':
f.write(self.data.encode('ascii'))
else:
f.write(self.data)
# Example usage:
# pbm = PBMFile('sample.pbm')
# pbm.print_properties()
# pbm.write('output.pbm')
Java Class for .PBM Handling:
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class PBMFile {
private String magic;
private List<String> comments;
private int width;
private int height;
private byte[] data; // For binary, or byte[] representation for plain
public PBMFile() {
comments = new ArrayList<>();
}
public PBMFile(String filepath) throws IOException {
this();
read(filepath);
}
public void read(String filepath) throws IOException {
try (FileInputStream fis = new FileInputStream(filepath);
BufferedInputStream bis = new BufferedInputStream(fis)) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
while ((b = bis.read()) != -1) {
baos.write(b);
}
byte[] content = baos.toByteArray();
String text = new String(content, "ASCII");
Scanner scanner = new Scanner(text);
magic = scanner.next();
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.startsWith("#")) {
comments.add(line.substring(1).trim());
} else if (!line.isEmpty()) {
String[] dims = line.split("\\s+");
width = Integer.parseInt(dims[0]);
if (dims.length > 1) {
height = Integer.parseInt(dims[1]);
break;
}
}
}
// Find header end
int headerEnd = text.indexOf("\n", text.indexOf(String.valueOf(height))) + 1;
data = new byte[content.length - headerEnd];
System.arraycopy(content, headerEnd, data, 0, data.length);
}
}
public void printProperties() {
System.out.println("Magic Number: " + magic);
System.out.print("Comments: ");
if (comments.isEmpty()) {
System.out.println("None");
} else {
for (String comment : comments) {
System.out.println(comment);
}
}
System.out.println("Width: " + width);
System.out.println("Height: " + height);
}
public void write(String filepath) throws IOException {
if (magic == null || width == 0 || height == 0 || data == null) {
throw new IllegalStateException("Missing properties or data");
}
try (FileOutputStream fos = new FileOutputStream(filepath)) {
StringBuilder header = new StringBuilder();
header.append(magic).append("\n");
for (String comment : comments) {
header.append("#").append(comment).append("\n");
}
header.append(width).append(" ").append(height).append("\n");
fos.write(header.toString().getBytes("ASCII"));
fos.write(data);
}
}
// Example usage:
// public static void main(String[] args) throws IOException {
// PBMFile pbm = new PBMFile("sample.pbm");
// pbm.printProperties();
// pbm.write("output.pbm");
// }
}
JavaScript Class for .PBM Handling (Node.js):
const fs = require('fs');
class PBMFile {
constructor(filepath = null) {
this.magic = null;
this.comments = [];
this.width = null;
this.height = null;
this.data = null; // Buffer for data
if (filepath) {
this.read(filepath);
}
}
read(filepath) {
const content = fs.readFileSync(filepath);
const text = content.toString('ascii');
const lines = text.split(/\r?\n/);
let idx = 0;
this.magic = lines[idx++].trim();
while (idx < lines.length && lines[idx].startsWith('#')) {
this.comments.push(lines[idx++].substring(1).trim());
}
const dims = lines[idx].trim().split(/\s+/);
this.width = parseInt(dims[0]);
this.height = parseInt(dims[1]);
// Data starts after header
const headerEnd = text.indexOf('\n', text.indexOf(this.height.toString())) + 1;
this.data = content.slice(headerEnd);
}
printProperties() {
console.log(`Magic Number: ${this.magic}`);
console.log('Comments:', this.comments.length ? this.comments.join('\n') : 'None');
console.log(`Width: ${this.width}`);
console.log(`Height: ${this.height}`);
}
write(filepath) {
if (!this.magic || !this.width || !this.height || !this.data) {
throw new Error('Missing properties or data');
}
let header = `${this.magic}\n`;
this.comments.forEach(comment => {
header += `#${comment}\n`;
});
header += `${this.width} ${this.height}\n`;
const buffer = Buffer.concat([Buffer.from(header, 'ascii'), this.data]);
fs.writeFileSync(filepath, buffer);
}
}
// Example usage:
// const pbm = new PBMFile('sample.pbm');
// pbm.printProperties();
// pbm.write('output.pbm');
C++ Class for .PBM Handling:
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
class PBMFile {
private:
std::string magic;
std::vector<std::string> comments;
int width;
int height;
std::vector<char> data; // For pixel data
public:
PBMFile() : width(0), height(0) {}
PBMFile(const std::string& filepath) : PBMFile() { read(filepath); }
void read(const std::string& filepath) {
std::ifstream file(filepath, std::ios::binary);
if (!file) throw std::runtime_error("Cannot open file");
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
std::istringstream iss(content);
std::string line;
std::getline(iss, line);
magic = line;
while (std::getline(iss, line)) {
if (line[0] == '#') {
comments.push_back(line.substr(1));
} else {
std::istringstream dimiss(line);
dimiss >> width >> height;
break;
}
}
// Data is remaining content
auto pos = iss.tellg();
data.assign(content.begin() + pos, content.end());
}
void printProperties() const {
std::cout << "Magic Number: " << magic << std::endl;
std::cout << "Comments: ";
if (comments.empty()) {
std::cout << "None" << std::endl;
} else {
for (const auto& comment : comments) {
std::cout << comment << std::endl;
}
}
std::cout << "Width: " << width << std::endl;
std::cout << "Height: " << height << std::endl;
}
void write(const std::string& filepath) const {
if (magic.empty() || width == 0 || height == 0 || data.empty()) {
throw std::runtime_error("Missing properties or data");
}
std::ofstream file(filepath, std::ios::binary);
file << magic << "\n";
for (const auto& comment : comments) {
file << "#" << comment << "\n";
}
file << width << " " << height << "\n";
file.write(data.data(), data.size());
}
};
// Example usage:
// int main() {
// PBMFile pbm("sample.pbm");
// pbm.printProperties();
// pbm.write("output.pbm");
// return 0;
// }