1

In PHP, a simple read and write file can be done by using fread() and fwrite(). The unpack() and pack() operator are used to extract binary information.

The question is, how can I read and write PGM (P5) image in PHP without using any additional PHP extension / library?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
rhzs
  • 516
  • 7
  • 24
  • 1
    You can read [the specs](http://netpbm.sourceforge.net/doc/pgm.html) for the PGM format and use the functions you listed. This is a Q&A site, not a job site. If you have a _specific_ problem, we'd be glad to help. – Herbert Dec 13 '11 at 12:16
  • https://docs.google.com/leaf?id=0B1jK9RMcqhcvNmQyNjk5MGItMjcyNS00MmY1LWJiZjEtYjE5NjNkY2E0MDlk&hl=en_US – rhzs Dec 13 '11 at 23:16
  • May be you should know the real file first, before you judge. https://docs.google.com/leaf?id=0B1jK9RMcqhcvNmQyNjk5MGItMjcyNS00MmY1LWJiZjEtYjE5NjNkY2E0MDlk&hl=en_US The problem was, how to use the those functions properly. – rhzs Dec 13 '11 at 23:23

3 Answers3

6

Depending on maximum value for gray you will have to use C* for values up to 255 and n* for greater values.

Sample class to read / write all pixel from array:

class PGM{
    public
        $magicNumber = '',
        $pixelArray = array(),
        $width = 0,
        $height = 0,
        $grayMax = 0,
        $createdBy = '';

    public function loadPGM($filename){
        $this->grayMax = $this->height = $this->width = $this->magicNumber = 0;
        $this->pixelArray = array();
        $fh = fopen($filename, 'rb');
        while($line = fgets($fh)){
            if(strpos($line, '#') === 0){
                continue;
            }
            if(!$this->grayMax){
                $arr = preg_split('/\s+/', trim($line));
                if($arr && !$this->magicNumber){
                    $this->magicNumber = array_shift($arr);
                    if($this->magicNumber != 'P5'){
                        throw new Exception("Unsupported PGM version");
                    }
                }
                if($arr && !$this->width)
                    $this->width = array_shift($arr);
                if($arr && !$this->height)
                    $this->height = array_shift($arr);
                if($arr && !$this->grayMax)
                    $this->grayMax = array_shift($arr);
            }else{
                $unpackMethod = ($this->grayMax > 255)?'n*':'C*';
                foreach(unpack($unpackMethod, $line) as $pixel){
                    $this->pixelArray[] = $pixel;
                }
            }
        }
        fclose($fh);
    }

    public function savePGM($filename){
        $fh = fopen($filename, 'wb');
        $this->magicNumber = 'P5';
        fwrite($fh, "{$this->magicNumber}\n");
        if($this->createdBy){
            fwrite($fh, "# {$this->createdBy}\n");
        }
        fwrite($fh, "{$this->width} {$this->height}\n");
        fwrite($fh, "{$this->grayMax}\n");
        $packMethod = ($this->grayMax > 255)?'n*':'C*';
        fwrite($fh, call_user_func_array('pack', array_merge(array($packMethod), $this->pixelArray)));  
        fclose($fh);
    }
}

Test case for your file:

$pgm = new PGM;
$pgm->loadPGM('a.pgm');
$pgm->createdBy = 'Created by IrfanView';
$pgm->savePGM('b.pgm');
echo (md5_file('a.pgm') == md5_file('b.pgm'))?'Images are identical':'Images are different';
dev-null-dweller
  • 29,274
  • 3
  • 65
  • 85
  • Hi dev, thanks for the passions resolving my problem. I would like to notice some lines. (1) Line 22, the logic operator will always throw the exception. Since the $this->pixelArray would probably be resulting more than 4 not exactly 4. (2) The function savePGM() does not seem correct for me. Perhaps the problem is on line 38, when trying to write the 'int' pixel value to 'unsigned char' / binary in file by using call_user_func_array and pack function. Could you fix the second issue? – rhzs Dec 13 '11 at 07:43
  • According to format specification, there should not be any whitespace between gray pixels so the array should contain no more than 5 elements. But if you have some images that are decoded properly and occupy more array entries after parsing, feel free to remove this check. As for saving, I have no idea what is wrong, but you may try alternate version that packs and writes every pixel separately. – dev-null-dweller Dec 13 '11 at 17:17
  • Both of them (alternate and original) give the wrong result. You may retest your function with my file https://docs.google.com/leaf?id=0B1jK9RMcqhcvNmQyNjk5MGItMjcyNS00MmY1LWJiZjEtYjE5NjNkY2E0MDlk&hl=en_US – rhzs Dec 13 '11 at 23:48
  • OK, I failed to understand why whitespaces are not allowed in gray section, and since I tested it on small images with low variety of shades of gray so that's why everything worked for me. – dev-null-dweller Dec 14 '11 at 20:24
  • Ok, it works. I also accepted your answer. Thanks dev. – rhzs Dec 15 '11 at 09:46
0

According to the spec: Portable Gray Map

This looks pretty simple.

You don't even need pack/unpack as sscanf() should cope with most of the ASCII to integer translation.

This C program which converts PGM to BMP may give you a few pointers.

James Anderson
  • 27,109
  • 7
  • 50
  • 78
  • The P5 format is not purely ASCII. Your understanding about PGM is about P2 not P5. – rhzs Dec 13 '11 at 13:07
0

I wrote a simple function to convert an image resource to PGM (portable graymap) in order to feed it to an OCR program. It works just like the rest of the image output functions, and will convert to grayscale for you:

<?php
     function imagepgm($image, $filename = null)
    {
        $pgm = "P5 ".imagesx($image)." ".imagesy($image)." 255\n";
        for($y = 0; $y < imagesy($image); $y++)
        {
            for($x = 0; $x < imagesx($image); $x++)
            {
                $colors = imagecolorsforindex($image, imagecolorat($image, $x, $y));
                $pgm .= chr(0.3 * $colors["red"] + 0.59 * $colors["green"] + 0.11 * $colors["blue"]);
            }
        }
        if($filename != null)
        {
            $fp = fopen($filename, "w");
            fwrite($fp, $pgm);
            fclose($fp);
        }
        else
        {
            return $pgm;
        }
    }
?>
Hardik Raval
  • 1,948
  • 16
  • 29
  • Your answer (or your purposed function) does not clearly answer the question. – rhzs Dec 13 '11 at 11:19
  • Also notice that my question does not want any additional php library such as GD, Cairo, ImageMagick. – rhzs Dec 13 '11 at 11:28