Bitmap/Read a PPM file

Revision as of 00:48, 4 September 2009 by Tikkanz (talk | contribs) (→‎{{header|J}}: fix bug in example)

Using the data storage type defined on this page for raster images, read an image from a PPM file (binary P6 prefered). (Read the definition of PPM file on Wikipedia.)

Task
Bitmap/Read a PPM file
You are encouraged to solve this task according to the task description, using any language you may know.

Task: Use write ppm file solution and grayscale image solution with this one in order to convert a color image to grayscale one.

Ada

<lang ada> with Ada.Characters.Latin_1; use Ada.Characters.Latin_1; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO;

function Get_PPM (File : File_Type) return Image is

  use Ada.Characters.Latin_1;
  use Ada.Integer_Text_IO;
  function Get_Line return String is -- Skips comments
     Byte   : Character;
     Buffer : String (1..80);
  begin
     loop
        for I in Buffer'Range loop
           Character'Read (Stream (File), Byte);
           if Byte = LF then
              exit when Buffer (1) = '#';
              return Buffer (1..I - 1);
           end if;
           Buffer (I) := Byte;
        end loop;
        if Buffer (1) /= '#' then
           raise Data_Error;
        end if;
     end loop;
  end Get_Line;
  Height : Integer;
  Width  : Integer;

begin

  if Get_Line /= "P6" then
     raise Data_Error;
  end if;
  declare
     Line  : String  := Get_Line;
     Start : Integer := Line'First;
     Last  : Positive;
  begin
     Get (Line, Width, Last);                     Start := Start + Last;
     Get (Line (Start..Line'Last), Height, Last); Start := Start + Last;
     if Start <= Line'Last then
        raise Data_Error;
     end if;
     if Width < 1 or else Height < 1 then
        raise Data_Error;
     end if;
  end;
  if Get_Line /= "255" then
     raise Data_Error;
  end if;
  declare
     Result : Image (1..Height, 1..Width);
     Buffer : String (1..Width * 3);
     Index  : Positive;
  begin
     for I in Result'Range (1) loop
        String'Read (Stream (File), Buffer);
        Index := Buffer'First;
        for J in Result'Range (2) loop
           Result (I, J) :=
              (  R => Luminance (Character'Pos (Buffer (Index))),
                 G => Luminance (Character'Pos (Buffer (Index + 1))),
                 B => Luminance (Character'Pos (Buffer (Index + 2)))
              );
           Index := Index + 3;
        end loop;
     end loop;
     return Result;
  end;

end Get_PPM; </lang> The implementation propagates Data_Error when the file format is incorrect. End_Error is propagated when the file end is prematurely met. The following example illustrates conversion of a color file to grayscale. <lang ada> declare

  F1, F2 : File_Type;

begin

  Open (F1, In_File, "city.ppm");
  Create (F2, Out_File, "city_grayscale.ppm");
  Put_PPM (F2, Color (Grayscale (Get_PPM (F1))));
  Close (F1);
  Close (F2);

end; </lang>

AutoHotkey

<lang AutoHotkey> ppm = ( P6

  1. lasdjkf

87 09 255 color data... ) MsgBox % ppm_width(ppm) MsgBox % ppm_height(ppm) MsgBox % ppm_colors(ppm) MsgBox % ppm_data(ppm)

ppm_read(file) {

 FileRead, ppm, % file
 Return ppm

}

ppm_width(ppm) {

 RegExMatch(ppm, "\R(\d+)\s(\d+)", dim)
 Return dim1

}

ppm_height(ppm) {

 RegExMatch(ppm, "\R(\d+)\s(\d+)", dim)
 Return dim2

}

ppm_colors(ppm) {

 RegExMatch(ppm, "\R(\d+)\D*\R", colors)  ; \R stands for any
 Return colors1

}

ppm_data(ppm) {

 pos :=  RegExMatch(ppm, "\R(\d+)\D*\R", colors)  ; \R stands for any newline
 StringTrimLeft, data, ppm, pos + StrLen(colors1)
 Return data

} </lang>

C

It is up to the caller to open the file and pass the handler to the function. So this code can be used in Read image file through a pipe without modification. It only understands the P6 file format.

Interface:

<lang c>image get_ppm(FILE *pf);</lang>

Implementation:

<lang c>#include "imglib.h"

  1. define PPMREADBUFLEN 256

image get_ppm(FILE *pf) {

       char buf[PPMREADBUFLEN], *t;
       image img;
       unsigned int w, h, d;
       int r;
       
       if (pf == NULL) return NULL;
       t = fgets(buf, PPMREADBUFLEN, pf);
       if ( (t == NULL) || ( strncmp(buf, "P6\n", 3) != 0 ) ) return NULL;
       do
       { /* Px formats can have # comments after first line */
          t = fgets(buf, PPMREADBUFLEN, pf);
          if ( t == NULL ) return NULL;
       } while ( strncmp(buf, "#", 1) == 0 );
       r = sscanf(buf, "%u %u", &w, &h);
       if ( r < 2 ) return NULL;
       // The program fails if the first byte of the image is equal to 32. because
       // the fscanf eats the space and the image is read with some bit less
       r = fscanf(pf, "%u\n", &d);
       if ( (r < 1) || ( d != 255 ) ) return NULL;
       img = alloc_img(w, h);
       if ( img != NULL )
       {
           size_t rd = fread(img->buf, sizeof(pixel), w*h, pf);
           if ( rd < w*h )
           {
              free_img(img);
              return NULL;
           }
           return img;
       }

}</lang>

The following acts as a filter to convert a PPM file read from standard input into a PPM gray image, and it outputs the converted image to standard output (see Grayscale image, Write ppm file, and Raster graphics operations in general):

<lang c>#include <stdio.h>

  1. include "imglib.h"

int main() {

  image source;
  grayimage idest;
  
  source = get_ppm(stdin);
  idest = tograyscale(source);
  free_img(source);
  source = tocolor(idest);
  output_ppm(stdout, source);
  free_img(source); free_img((image)idest);
  return 0;

}</lang>

C#

Tested with this solution.

<lang csharp> using System.IO; class PPMReader {

   public static Bitmap ReadBitmapFromPPM(string file)
   {
       var reader = new BinaryReader(new FileStream(file, FileMode.Open));
       if (reader.ReadChar() != 'P' || reader.ReadChar() != '6')
           return null;
       reader.ReadChar(); //Eat newline
       string widths = "", heights = "";
       char temp;
       while ((temp = reader.ReadChar()) != ' ')
           widths += temp;
       while ((temp = reader.ReadChar()) >= '0' && temp <= '9')
           heights += temp;
       if (reader.ReadChar() != '2' || reader.ReadChar() != '5' || reader.ReadChar() != '5')
           return null;
       reader.ReadChar(); //Eat the last newline
       int width = int.Parse(widths),
           height = int.Parse(heights);
       Bitmap bitmap = new Bitmap(width, height);
       //Read in the pixels
       for (int y = 0; y < height; y++)
           for (int x = 0; x < width; x++)
               bitmap.SetPixel(x, y, new Bitmap.Color()
               {
                   Red = reader.ReadByte(),
                   Green = reader.ReadByte(),
                   Blue = reader.ReadByte()
               });
       return bitmap;
   }

} </lang>


D

This example uses storage defined on Basic bitmap storage problem page.

This is wrap-around defined storage, P6 binary mode.

Works with: tango

<lang D> import tango.core.Exception; import tango.io.FileConduit; import tango.io.MappedBuffer; import tango.io.stream.LineStream; import tango.io.protocol.Reader; import tango.text.convert.Integer;

class P6Image {

   class BadInputException : Exception { this() { super("Bad file format"); } }
   class NoImageException : Exception { this() { super("No image data"); } }
   static const char[] type = "P6";
   MappedBuffer fileBuf;
   ubyte _maxVal, gotImg;

public:

   RgbBitmap bitmap;
   this (FileConduit input) {
       fileBuf = new MappedBuffer(input);
       if (processHeader(new LineInput(fileBuf)))
           throw new BadInputException;
       if (processData(fileBuf))
           throw new BadInputException;
   }
   ubyte maxVal() { return _maxVal; }
   int processHeader(LineInput li) {
       char[] line;
       uint eaten;
       li.readln(line);
       if (line != type) return 1;
       li.readln(line);
       // skip comment lines
       while (line.length && line[0] == '#') li.readln(line);
       auto width = parse(line, 0, &eaten);
       auto height = parse(line[eaten..$], 0, &eaten);
       if (!eaten || width > 0xffff_ffff || height > 0xffff_ffff) return 1;
       li.readln(line);
       auto temp = parse(line, 0, &eaten);
       if (!eaten || temp > 255) return 1;
       _maxVal = temp;
       bitmap = RgbBitmap(width, height);
       gotImg = 1;
       return 0;
   }
   int processData(MappedBuffer mb) {
       if (! gotImg) throw new NoImageException;
       mb.fill(bitmap.data);
       return 0;
   }

} </lang>

Reading from file: <lang D> auto p6 = new P6Image(new FileConduit("image.ppm")); </lang>

E

<lang e>def chr := <import:java.lang.makeCharacter>.asChar

def readPPM(inputStream) {

 # Proper native-to-E stream IO facilities have not been designed and
 # implemented yet, so we are borrowing Java's. Poorly. This *will* be
 # improved eventually.
 
 # Reads one header token, skipping comments and whitespace, and exactly
 # one trailing whitespace character
 def readToken() {
   var token := ""
   var c := chr(inputStream.read())
   while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#') {
     if (c == '#') {
       while (c != '\n') { c := chr(inputStream.read()) }
     }
     # skip over initial whitespace
     c := chr(inputStream.read())
   }
   while (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
     if (c == '#') {
       while (c != '\n') { c := chr(inputStream.read()) }
     } else {
       token += E.toString(c)
       c := chr(inputStream.read())
     }
   }
   return token
 }
 # Header
 require(readToken() == "P6")
 def width := __makeInt(readToken())
 def height := __makeInt(readToken())
 def maxval := __makeInt(readToken())
 def size := width * height * 3
 # Body
 # See Basic bitmap storage for the definition and origin of sign()
 def data := <elib:tables.makeFlexList>.fromType(<type:java.lang.Byte>, size)
 if (maxval >= 256) {
   for _ in 1..size {
     data.push(sign((inputStream.read() * 256 + inputStream.read()) * 255 // maxval))
   }
 } else {
   for _ in 1..size {
     data.push(sign(inputStream.read() * 255 // maxval))
   }
 }
 def image := makeImage(width, height)
 image.replace(data.snapshot())
 return image

}</lang>Note: As of this writing the grayscale image task has not been implemented, so the task code (below) won't actually run yet. But readPPM above has been tested separately.

<lang e>def readPPMTask(inputFile, outputFile) {

 makeGrayscale \
   .fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \
   .toColor() \
   .writePPM(<import:java.io.makeFileOutputStream>(outputFile))

}</lang>

Forth

: read-ppm { fid -- bmp }
  pad dup 80 fid read-line throw 0= abort" Partial line"
  s" P6" compare abort" Only P6 supported."
  pad dup 80 fid read-line throw 0= abort" Partial line"
  0. 2swap >number
  1 /string		\ skip space
  0. 2swap >number
  2drop drop nip    ( w h )
  bitmap { bmp }
  pad dup 80 fid read-line throw 0= abort" Partial line"
  s" 255" compare abort" Only 8-bits per color channel supported"
  0 pad !
  bmp bdim
  0 do
    dup 0 do
      pad 3 fid read-file throw
      3 - abort" Not enough pixel data in file"
      pad @ i j bmp b!
    loop
  loop drop
  bmp ;
\ testing round-trip
4 3 bitmap value test
red test bfill
green 1 2 test b!

s" red.ppm" w/o create-file throw
test over write-ppm
close-file throw

s" red.ppm" r/o open-file throw
dup read-ppm value test2
close-file throw

: bsize ( bmp -- len ) bdim * pixels bdata ;

test dup bsize  test2 dup bsize  compare .    \ 0 if identical

Fortran

Works with: Fortran version 90 and later

(This function is part of module RCImageIO, see Write ppm file)

<lang fortran> subroutine read_ppm(u, img)

   integer, intent(in) :: u
   type(rgbimage), intent(out) :: img
   integer :: i, j, ncol, cc
   character(2) :: sign
   character :: ccode
   
   img%width = 0
   img%height = 0
   nullify(img%red)
   nullify(img%green)
   nullify(img%blue)
   read(u, '(A2)') sign
   read(u, *) img%width, img%height
   read(u, *) ncol
   write(0,*) sign
   write(0,*) img%width, img%height
   write(0,*) ncol
   if ( ncol /= 255 ) return
   call alloc_img(img, img%width, img%height)
   if ( valid_image(img) ) then
      do j=1, img%height
         do i=1, img%width
            read(u, '(A1)', advance='no', iostat=status) ccode
            cc = iachar(ccode)
            img%red(i,j) = cc
            read(u, '(A1)', advance='no', iostat=status) ccode
            cc = iachar(ccode)
            img%green(i,j) = cc
            read(u, '(A1)', advance='no', iostat=status) ccode
            cc = iachar(ccode)
            img%blue(i,j) = cc
         end do
      end do
   end if
 end subroutine read_ppm</lang>

Notes:

  • doing formatted I/O with Fortran is a pain... And unformatted does not mean free; Fortran2003 has streams, but they are not implemented (yet) in GNU Fortran compiler. Here (as in the write part) I've tried to handle the PPM format through formatted I/O. The tests worked but I have not tried still everything.
  • comments after the first line are not handled

Haskell

The definition of Bitmap.Netpbm.readNetpbm is given here. <lang haskell>import Bitmap import Bitmap.RGB import Bitmap.Gray import Bitmap.Netpbm

import Control.Monad import Control.Monad.ST

main =

   (readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>=
   stToIO . toGrayImage >>=
   writeNetpbm "new.pgm"</lang>

The above writes a PGM, not a PPM, since the image being output is in grayscale. If you actually want a gray PPM, convert the Image RealWorld Gray back to an Image RealWorld RGB first: <lang haskell>main =

   (readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>=
   stToIO . (toRGBImage <=< toGrayImage) >>=
   writeNetpbm "new.ppm"</lang>

J

Solution: Uses makeRGB from Basic bitmap storage. <lang j> require 'files'

readppm=: monad define

 dat=. fread y                                      NB. read from file
 't wbyh maxval dat'=. 4{. <;._2 (, LF -. {:) dat   NB. parse
 'wbyh maxval'=.  _99&". &.> wbyh;maxval            NB. convert to numeric
 if. (_99 e. wbyh,maxval) +. t -.@-: 'P6' do. _1 return. end.
 (a. i. dat) makeRGB |.wbyh                         NB. convert to basic bitmap format

) </lang>

Example:
Using utilities and file from Grayscale image and Write ppm file.
Writes a gray PPM file (a color format) which is bigger than necessary. A PGM file would be more appropriate. <lang j>

  myimg=: readppm jpath '~temp/myimg.ppm'
  myimgGray=: toColor toGray myimg
  myimgGray writeppm jpath '~temp/myimgGray.ppm'

</lang>

OCaml

<lang ocaml>let read_ppm ~filename =

 let ic = open_in filename in
 let line = input_line ic in
 if line <> "P6" then invalid_arg "not a P6 ppm file";
 let line = input_line ic in
 let line =
   try if line.[0] = '#'  (* skip comments *)
   then input_line ic
   else line
   with _ -> line
 in
 let width, height =
   Scanf.sscanf line "%d %d" (fun w h -> (w, h))
 in
 let line = input_line ic in
 if line <> "255" then invalid_arg "not a 8 bit depth image";
 let all_channels =
   let kind = Bigarray.int8_unsigned
   and layout = Bigarray.c_layout
   in
   Bigarray.Array3.create kind layout 3 width height
 in
 let r_channel = Bigarray.Array3.slice_left_2 all_channels 0
 and g_channel = Bigarray.Array3.slice_left_2 all_channels 1
 and b_channel = Bigarray.Array3.slice_left_2 all_channels 2
 in
 for y = 0 to pred height do
   for x = 0 to pred width do
     r_channel.{x,y} <- (input_byte ic);
     g_channel.{x,y} <- (input_byte ic);
     b_channel.{x,y} <- (input_byte ic);
   done;
 done;
 close_in ic;
 (all_channels,
  r_channel,
  g_channel,
  b_channel)</lang>

and converting a given color file to grayscale: <lang ocaml>let () =

 let img = read_ppm ~filename:"logo.ppm" in
 let img = to_color(to_grayscale ~img) in
 output_ppm ~oc:stdout ~img;
</lang>

sending the result to stdout allows to see the result without creating a temporary file sending it through a pipe to the display utility of ImageMagick:

ocaml script.ml | display -

Perl

Library: Imlib2

<lang perl>#! /usr/bin/perl

use strict; use Image::Imlib2;

my $img = Image::Imlib2->load("out0.ppm");

  1. let's do something with it now

$img->set_color(255, 255, 255, 255); $img->draw_line(0,0, $img->width,$img->height); $img->image_set_format("png"); $img->save("out1.png");

exit 0;</lang>

Ruby

Extending Basic_bitmap_storage#Ruby <lang ruby>class Bitmap

 # 'open' is a class method
 def self.open(filename)
   bitmap = nil
   File.open(filename, 'r') do |f|
     header = [f.gets.chomp, f.gets.chomp, f.gets.chomp]
     width, height = header[1].split.map {|n| n.to_i }
     if header[0] != 'P6' or header[2] != '255' or width < 1 or height < 1
       raise StandardError, "file '#{filename}' does not start with the expected header"
     end
     f.binmode
     bitmap = self.new(width, height)
     height.times do |y|
       width.times do |x|
         # read 3 bytes
         red, green, blue = f.read(3).unpack('C3')
         bitmap[x,y] = RGBColour.new(red, green, blue)
       end
     end
   end
   bitmap
 end

end

  1. create an image: a green cross on a blue background

colour_bitmap = Bitmap.new(20, 30) colour_bitmap.fill(RGBColour::BLUE) colour_bitmap.height.times {|y| [9,10,11].each {|x| colour_bitmap[x,y]=RGBColour::GREEN}} colour_bitmap.width.times {|x| [14,15,16].each {|y| colour_bitmap[x,y]=RGBColour::GREEN}} colour_bitmap.save('testcross.ppm')

  1. then, convert to grayscale

Bitmap.open('testcross.ppm').to_grayscale!.save('testgray.ppm')</lang>

Tcl

Library: Tk

The actual PPM reader is built into the photo image engine: <lang tcl>package require Tk

proc readPPM {image file} {

   $image read $file -format ppm

}</lang> Thus, to read a PPM, convert it to grayscale, and write it back out again becomes this (which requires Tcl 8.6 for try/finally); the PPM reader and writer are inlined because they are trivial at the script level: <lang tcl>package require Tk

proc grayscaleFile {filename} {

   set buffer [image create photo]
   try {
       $buffer read $filename -format ppm
       set w [image width $buffer]
       set h [image height $buffer]
       for {set x 0} {$x<$w} {incr x} {
           for {set y 0} {$y<$h} {incr y} {
               lassign [$buffer get $x $y] r g b
               set l [expr {int(0.2126*$r + 0.7152*$g + 0.0722*$b)}]
               $buffer put [format "#%02x%02x%02x" $l $l $l] -to $x $y
           }
       }
       $buffer write $filename -format ppm
   } finally {
       image delete $buffer
   }

}</lang>

Vedit macro language

//   Load a PPM file
//     @10 = filename
//   On return:
//     #10 points to buffer containing pixel data,
//     #11 = width,  #12 = height.

:LOAD_PPM:
File_Open(@10)
BOF
Search("|X", ADVANCE)		// skip "P6"
#11 = Num_Eval(ADVANCE)		// #11 = width
Match("|X", ADVANCE)		// skip separator
#12 = Num_Eval(ADVANCE)		// #12 = height
Match("|X", ADVANCE)
Search("|X", ADVANCE)		// skip maxval (assume 255)
Del_Block(0,CP)			// remove the header
Return

Example of usage. In addition to LOAD_PPM routine above, you need routine RGB_TO_GRAYSCALE from Grayscale image and routine SAVE_PPM from Write ppm file.

// Load RGB image
Reg_Set(10, "|(USER_MACRO)\example.ppm")
Call("LOAD_PPM")

// Convert to grayscale
#10 = Buf_Num
Call("RGB_TO_GRAYSCALE")
Buf_Switch(#10) Buf_Quit(OK)

// Convert to RGB
Call("GRAYSCALE_TO_RGB")

// Save the image
Reg_Set(10, "|(USER_MACRO)\example_gray.ppm")
Call("SAVE_PPM")

// Cleanup and exit
Buf_Switch(#20) Buf_Quit(OK)
return