Bitmap/Read a PPM file

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.

11l

Translation of: Python
T Colour
   Byte r, g, b

   F (r, g, b)
      .r = r
      .g = g
      .b = b

   F ==(other)
      R .r == other.r & .g == other.g & .b == other.b

V black = Colour(0, 0, 0)
V white = Colour(255, 255, 255)

T Bitmap
   Int width, height
   Colour background
   [[Colour]] map

   F (width = 40, height = 40, background = white)
      assert(width > 0 & height > 0)
      .width = width
      .height = height
      .background = background
      .map = (0 .< height).map(h -> (0 .< @width).map(w -> @@background))

   F fillrect(x, y, width, height, colour = black)
      assert(x >= 0 & y >= 0 & width > 0 & height > 0)
      L(h) 0 .< height
         L(w) 0 .< width
            .map[y + h][x + w] = colour

   F set(x, y, colour = black)
      .map[y][x] = colour

   F get(x, y)
      R .map[y][x]

   F togreyscale()
      L(h) 0 .< .height
         L(w) 0 .< .width
            V (r, g, b) = .get(w, h)
            V l = Int(0.2126 * r + 0.7152 * g + 0.0722 * b)
            .set(w, h, Colour(l, l, l))

   F writeppmp3()
      V magic = "P3\n"
      V comment = "# generated from Bitmap.writeppmp3\n"
      V s = magic‘’comment‘’("#. #.\n#.\n".format(.width, .height, 255))
      L(h) (.height - 1 .< -1).step(-1)
         L(w) 0 .< .width
            V (r, g, b) = .get(w, h)
            s ‘’= ‘   #3 #3 #3’.format(r, g, b)
         s ‘’= "\n"
      R s

F tokenize(fstr)
   [String] tokens
   L(line) fstr.split("\n")
      I !line.starts_with(‘#’)
         L(t) line.split(‘ ’, group_delimiters' 1B)
            tokens.append(t)
   R tokens

F ppmp3tobitmap(fstr)
   V tokens = tokenize(fstr)
   V tokeni = -1
   F nexttoken()
      @tokeni++
      R @tokens[@tokeni]
   assert(‘P3’ == nexttoken(), ‘Wrong filetype’)
   V width  = Int(nexttoken())
   V height = Int(nexttoken())
   V maxval = Int(nexttoken())
   V bitmap = Bitmap(width, height, Colour(0, 0, 0))
   L(h) (height - 1 .< -1).step(-1)
      L(w) 0 .< width
         V r = Int(nexttoken())
         V g = Int(nexttoken())
         V b = Int(nexttoken())
         bitmap.set(w, h, Colour(r, g, b))

   R bitmap

V ppmtxt = |‘P3
             # feep.ppm
             4 4
             15
              0  0  0    0  0  0    0  0  0   15  0 15
              0  0  0    0 15  7    0  0  0    0  0  0
              0  0  0    0  0  0    0 15  7    0  0  0
             15  0 15    0  0  0    0  0  0    0  0  0
             ’

V bitmap = ppmp3tobitmap(ppmtxt)
print(‘Grey PPM:’)
bitmap.togreyscale()
print(bitmap.writeppmp3())
Output:
Grey PPM:
P3
# generated from Bitmap.writeppmp3
4 4
255
     0   0   0     0   0   0     0   0   0     4   4   4
     0   0   0    11  11  11     0   0   0     0   0   0
     0   0   0     0   0   0    11  11  11     0   0   0
     4   4   4     0   0   0     0   0   0     0   0   0

Action!

Part of the task responsible for conversion from RGB color image into a grayscale image can be found in the module RGB2GRAY.ACT. File D:PPM6.PPM can be generated by task Bitmap/Write a PPM file.

INCLUDE "H6:RGB2GRAY.ACT" ;from task Grayscale image

PROC DecodeSize(CHAR ARRAY s BYTE POINTER width,height)
  BYTE i

  width^=ValB(s)
  i=1
  WHILE i<=s(0) AND s(i)#32
  DO
    s(i)=32
    i==+1
  OD
  height^=ValB(s)
RETURN

PROC LoadHeader(RgbImage POINTER img
  CHAR ARRAY format BYTE dev)
  CHAR ARRAY line(255)
  BYTE header,size,max,width,height

  header=0 size=0 max=0
  WHILE max=0
  DO
    InputSD(dev,line)
    IF line(0)>0 AND line(1)#'# THEN
      IF header=0 THEN
        IF SCompare(format,format)#0 THEN
          Break()
        FI
        header=1
      ELSEIF size=0 THEN
        DecodeSize(line,@width,@height)
        IF width=0 OR height=0 THEN
          Break()
        FI
        img.w=width img.h=height
        size=1
      ELSEIF max=0 THEN
        max=ValB(line)
        IF max#255 THEN
          Break()
        FI
      FI
    FI
  OD
RETURN

PROC LoadPPM6(RgbImage POINTER img CHAR ARRAY path)
  BYTE dev=[1],x,y
  RGB c

  Close(dev)
  Open(dev,path,4)
  LoadHeader(img,"P6",dev)
  FOR y=0 TO img.h-1
  DO
    FOR x=0 TO img.w-1
    DO
      c.r=GetD(dev)
      c.g=GetD(dev)
      c.b=GetD(dev)
      SetRgbPixel(img,x,y,c)
    OD
  OD
  Close(dev)
RETURN

PROC SaveHeader(GrayImage POINTER img
  CHAR ARRAY format BYTE dev)

  PrintDE(dev,format)
  PrintBD(dev,img.w)
  PutD(dev,32)
  PrintBDE(dev,img.h)
  PrintBDE(dev,255)
RETURN

PROC SavePPM2(RgbImage POINTER img CHAR ARRAY path)
  BYTE dev=[1],x,y,c

  Close(dev)
  Open(dev,path,8)
  SaveHeader(img,"P2",dev)
  FOR y=0 TO img.h-1
  DO
    FOR x=0 TO img.w-1
    DO
      c=GetGrayPixel(img,x,y)
      PrintBD(dev,c)
      IF x=img.w-1 THEN
        PutDE(dev)
      ELSE
        PutD(dev,32)
      FI
    OD
  OD
  Close(dev)
RETURN

PROC Load(CHAR ARRAY path)
  CHAR ARRAY line(255)
  BYTE dev=[1]

  Close(dev)
  Open(dev,path,4)
  WHILE Eof(dev)=0
  DO
    InputSD(dev,line)
    PrintE(line)
  OD
  Close(dev)
RETURN

PROC Main()
  BYTE ARRAY rgbdata(300),graydata(100)
  RgbImage rgbimg
  GrayImage grayimg
  CHAR ARRAY path2="D:PPM2.PPM"
  CHAR ARRAY path6="D:PPM6.PPM"

  Put(125) PutE() ;clear the screen
  InitRgbImage(rgbimg,0,0,rgbdata)
  InitRgbToGray()
  
  PrintF("Loading %S...%E%E",path6)
  LoadPPM6(rgbimg,path6)

  PrintF("Converting RGB to grayscale...%E%E")
  InitGrayImage(grayimg,rgbimg.w,rgbimg.h,graydata)
  RgbToGray(rgbimg,grayimg)

  PrintF("Saving %S...%E%E",path2)
  SavePPM2(grayimg,path2)
  PrintF("Loading %S...%E%E",path2)
  Load(path2)
RETURN
Output:

Screenshot from Atari 8-bit computer

Loading D:PPM6.PPM...

Converting RGB to grayscale...

Saving D:PPM2.PPM...

Loading D:PPM2.PPM...

P2
3 4
255
0 18 182
54 201 73
237 255 61
45 54 74

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;

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.

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;

ATS

For this you will need the static and dynamic ATS source files of Bitmap#ATS, Grayscale_image#ATS, and Bitmap/Write_a_PPM_file#ATS. (You do not need libnetpbm, although one could easily use it with ATS.)

There are three files here: a static file for the interface to pixmap_read_ppm, a dynamic file for the implementation of pixmap_read_ppm, and a file for the program that converts an image to grayscale. (The last is a dynamic file, but we will call it the program file.)

With pixmap_read_ppm<rgb24> you should be able to read any valid PPM, whether raw or plain, and with any valid Maxval. The result is a pixmap1(rgb24) with implicit Maxval of 255. The reader tries to be very permissive, although there seems not much I can do about the strange way comments work in PPM.

The ATS static file

This file should be called bitmap_read_ppm_task.sats.

#define ATS_PACKNAME "Rosetta_Code.bitmap_read_ppm_task"

staload "bitmap_task.sats"

fn {a : t@ype}
pixmap_read_ppm :
  (* On failure to read, the return is None_vt(). I do not currently
     provide any indication of why the attempt failed, although in
     practice you probably would wish to add that. *)
  FILEref ->
    Option_vt ([w, h : pos] [p : addr | null < p]
               @(mfree_gc_v p | pixmap (a, w, h, p)))

The ATS dynamic file

This file should be called bitmap_read_ppm_task.dats.

(*------------------------------------------------------------------*)

#define ATS_DYNLOADFLAG 0
#define ATS_PACKNAME "Rosetta_Code.bitmap_read_ppm_task"

#include "share/atspre_staload.hats"

staload "bitmap_task.sats"

(* You need to staload bitmap_task.dats, so the ATS compiler will have
   access to its implementations of templates. But we staload it
   anonymously, so the programmer will not have access. *)
staload _ = "bitmap_task.dats"

staload "bitmap_read_ppm_task.sats"

(*------------------------------------------------------------------*)

datavtype magic_number_vt =
| Netpbm_magic_number of int
| Unknown_magic_number of ()

fn {}
read_magic_number (inpf : FILEref) : magic_number_vt =
  let
    val i = fileref_getc inpf
  in
    if i <> char2int0 'P' then
      Unknown_magic_number ()
    else
      let
        val i = fileref_getc inpf
      in
        if i < char2int0 '1' && char2int0 '7' < i then
          Unknown_magic_number ()
        else
          Netpbm_magic_number (i - char2int0 '0')
      end
  end

fn {}
get_next_char (inpf : FILEref) : int =
  let
    fnx
    get_next () : int =
      let
        val i = fileref_getc inpf
      in
        if i = char2int0 '#' then
          skip_through_newline ()
        else
          i
      end
    and
    skip_through_newline () : int =
      let
        val i = fileref_getc inpf
      in
        if i < 0 then
          i
        else if i = char2int0 '\n' then
          get_next ()
        else
          skip_through_newline ()
      end
  in
    get_next ()
  end

(* The only tokens we need to scan for, in P1 through P6, are unsigned
   integers. P7 headers (Portable Arbitrary Map) have a completely
   different arrangement, but we are not handling that. *)
fn {}
get_next_integer (inpf : FILEref)
    (* A negative return value means we have reached the end. We do
       not distinguish whitespace characters from anything else that
       is not a digit or '#'. (Really I want to use intmax_t here,
       rather than llint, but there is no intmax_t support in the
       prelude. The ats2-xprelude package has support, but I am
       avoiding the dependency. *)
    : llint =
  let
    fnx
    look_for_digit () : llint =
      let
        val i = get_next_char inpf
      in
        if i < char2int0 '0' || char2int0 '9' < i then
          look_for_digit ()
        else
          read_digits (g0i2i (i - char2int0 '0'))
      end
    and
    read_digits (x : llint) : llint =
      let
        val i = get_next_char inpf
      in
        if i < char2int0 '0' || char2int0 '9' < i then
          (* I cannot find an "ungetc" in prelude/SATS/filebas.sats,
             so I will use the foreign function interface directly. *)
          let
            typedef FILEstar = $extype"FILE *"
            extern castfn FILEref2star : FILEref -<> FILEstar
          in
            ignoret ($extfcall (int, "ungetc", i, FILEref2star inpf));
            x
          end
        else
          let
            val digit : llint = g0i2i (i - char2int0 '0')
          in
            read_digits ((10LL * x) + digit)
          end
      end
  in
    look_for_digit ()
  end

fn {}
read_ppm_header (inpf : FILEref)
    : Option_vt @(ullint, ullint, ullint) =
  let
    val width = get_next_integer inpf
  in
    if width < 0LL then
      None_vt ()
    else
      let
        val height = get_next_integer inpf
      in
        if height < 0LL then
          None_vt ()
        else
          let
            val maxval = get_next_integer inpf
          in
            if maxval < 0LL then
              None_vt ()
            else
              begin
                (* There is supposed to be a whitespace character (or
                   comments and whitespace character) after the
                   MAXVAL. We will accept anything, whitespace or
                   not. *)
                ignoret (fileref_getc inpf);

                Some_vt @(g0i2u width, g0i2u height, g0i2u maxval)
              end
          end
      end
  end

fn {}
get_next_single_byte (inpf : FILEref) : llint =
  let
    val i = fileref_getc inpf
  in
    if i < 0 then
      ~1LL
    else
      g0i2i i
  end

fn {}
get_next_double_byte (inpf : FILEref) : llint =
  let
    val i1 = fileref_getc inpf
  in
    if i1 < 0 then
      ~1LL
    else
      let
        val i0 = fileref_getc inpf
      in
        if i0 < 0 then
          ~1LL
        else
          let
            val i1 : llint = g0i2i i1
            and i0 : llint = g0i2i i0
          in
            (i1 * 256LL) + i0
          end
      end
  end

(*------------------------------------------------------------------*)
(* Implementation is provided only for rgb24. *)

extern castfn ull2sz : {i : int} ullint i -<> size_t i
extern castfn ull2u : {i : int} ullint i -<> uint i
extern castfn ull2u8 : ullint -<> uint8

extern fn {}
read_raw_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>)

extern fn {}
read_plain_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>)

extern fn {}
read_general_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>)

extern fn {}
read_general$width () : [i : pos] size_t i

extern fn {}
read_general$height () : [i : pos] size_t i

extern fn {}
read_general$maxval () : [i : pos | i <= 65535] uint i

extern fn {}
read_general$next_value : FILEref -> llint

implement
pixmap_read_ppm<rgb24> inpf =
  case+ read_magic_number inpf of
  | ~ Unknown_magic_number () => None_vt ()
  | ~ Netpbm_magic_number num =>
    begin
      case+ num of
      | 6 => read_raw_ppm_rgb24 inpf
      | 3 => read_plain_ppm_rgb24 inpf
      | _ => None_vt
    end

implement {}
read_raw_ppm_rgb24 inpf =
  case+ read_ppm_header inpf of
  | ~ None_vt () => None_vt ()
  | ~ Some_vt @(width, height, maxval) =>
    let
      val width = g1ofg0 width
      and height = g1ofg0 height
      and maxval = g1ofg0 maxval
    in
      if (width < 1LLU) + (height < 1LLU) +
            (maxval < 1LLU) + (65535LLU < maxval) then
        None_vt ()
      else
        let
          val w : Size_t = ull2sz width
          val h : Size_t = ull2sz height
          val maxval : uInt = ull2u maxval
        in
          if maxval = 255u then
            let
              val @(pfgc | pix) = pixmap_make<rgb24> (w, h)
              val success =
                load<rgb24> (inpf, pix, rgb24_make (255, 0, 0))
            in
              if ~success then
                begin
                  free (pfgc | pix);
                  None_vt ()
                end
              else
                Some_vt @(pfgc | pix)
            end
          else if maxval < 256u then
            let
              implement read_general$width<> () = w
              implement read_general$height<> () = h
              implement read_general$maxval<> () = maxval
              implement
              read_general$next_value<> inpf =
                get_next_single_byte inpf
            in
              read_general_ppm_rgb24<> inpf
            end
          else
            let
              implement read_general$width<> () = w
              implement read_general$height<> () = h
              implement read_general$maxval<> () = maxval
              implement
              read_general$next_value<> inpf =
                get_next_double_byte inpf
            in
              read_general_ppm_rgb24<> inpf
            end
        end
    end

implement {}
read_plain_ppm_rgb24 inpf =
  case+ read_ppm_header inpf of
  | ~ None_vt () => None_vt ()
  | ~ Some_vt @(width, height, maxval) =>
    let
      val width = g1ofg0 width
      and height = g1ofg0 height
      and maxval = g1ofg0 maxval
    in
      if (width < 1LLU) + (height < 1LLU) +
            (maxval < 1LLU) + (65535LLU < maxval) then
        None_vt ()
      else
        let
          val w : Size_t = ull2sz width
          val h : Size_t = ull2sz height
          val maxval : uInt = ull2u maxval
          implement read_general$width<> () = w
          implement read_general$height<> () = h
          implement read_general$maxval<> () = maxval
          implement
            read_general$next_value<> inpf =
              get_next_integer inpf
        in
          read_general_ppm_rgb24<> inpf
        end
    end

implement {}
read_general_ppm_rgb24 inpf =
  let
    val [w : int] w = read_general$width<> ()
    and [h : int] h = read_general$height<> ()
    and maxval = read_general$maxval<> ()

    fn
    scale_value (v : ullint) : uint8 =
      if maxval = 255u then
        ull2u8 v
      else
        let
          val maxval : ullint = g0u2u maxval
          val v = 255LLU * v
          val v1 = v / maxval
          and v0 = v mod maxval
        in
          if v0 + v0 < maxval then
            ull2u8 v1
          else if maxval < v0 + v0 then
            ull2u8 (succ v1)
          else if v1 mod 2LLU = 0LLU then
            ull2u8 v1
          else
            ull2u8 (succ v1)
        end

    (* For easier programming, start with a fully initialized
       pixmap. The routine probably is I/O-bound, anyway. *)
    val @(pfgc | pix) =
      pixmap_make<rgb24> (w, h, rgb24_make (255, 0, 0))

    macdef between (i, j, v) =
      let
        val v = ,(v)
      in
        (,(i) <= v) * (v <= ,(j))
      end

    fun
    loop {x, y : nat | x <= w; y <= h}
         .<h - y, w - x>.
         (pix : !pixmap (rgb24, w, h),
          x   : size_t x,
          y   : size_t y)
        : bool (* success *) =
      if y = h then
        true
      else if x = w then
        loop (pix, i2sz 0, succ y)
      else
        let
          val maxv : llint = g0u2i maxval
          val vr = read_general$next_value<> inpf
        in
          if ~between (0LL, maxv, vr) then
            false
          else
            let
              val vg = read_general$next_value<> inpf
            in
              if ~between (0LL, maxv, vg) then
                false
              else
                let
                  val vb = read_general$next_value<> inpf
                in
                  if ~between (0LL, maxv, vb) then
                    false
                  else
                    let
                      val r = scale_value (g0i2u vr)
                      and g = scale_value (g0i2u vg)
                      and b = scale_value (g0i2u vb)
                    in
                      pix[x, y] := rgb24_make @(r, g, b);
                      loop (pix, succ x, y)
                    end
                end
            end
        end

    val success = loop (pix, i2sz 0, i2sz 0)
  in
    if ~success then
      begin
        free (pfgc | pix);
        None_vt ()
      end
    else
      Some_vt @(pfgc | pix)
  end

(*------------------------------------------------------------------*)

#ifdef BITMAP_READ_PPM_TASK_TEST #then

staload "bitmap_write_ppm_task.sats"
staload _ = "bitmap_write_ppm_task.dats"

(* The test program converts a PPM at standard input to a raw PPM with
   MAXVAL 255. *)
implement
main0 () =
  let
    val pix_opt = pixmap_read_ppm<rgb24> stdin_ref
  in
    case+ pix_opt of
    | ~ None_vt () => ()
    | ~ Some_vt @(pfgc | pix) =>
      begin
        ignoret (pixmap_write_ppm (stdout_ref, pix));
        free (pfgc | pix)
      end
  end

#endif

(*------------------------------------------------------------------*)

The ATS program file

This file should be called bitmap_read_ppm_task_program.dats (though it actually could be called by another name).

 
 
(* The program should be able to read a PPM in raw or plain format,
   with any valid Maxval. The output will be a grayscale raw PPM with
   Maxval=255.

   Compile with "myatscc bitmap_read_ppm_task_program.dats", which
   should give you a program named "bitmap_read_ppm_task_program". *)

(*

##myatsccdef=\
patscc -std=gnu2x -g -O2 -DATS_MEMALLOC_LIBC \
  -o $fname($1) $1 \
  bitmap{,_{{read,write}_ppm,grayscale}}_task.{s,d}ats

*)

#include "share/atspre_staload.hats"

staload "bitmap_task.sats"
staload "bitmap_read_ppm_task.sats"
staload "bitmap_write_ppm_task.sats"
staload "bitmap_grayscale_task.sats"

staload _ = "bitmap_task.dats"
staload _ = "bitmap_read_ppm_task.dats"
staload _ = "bitmap_write_ppm_task.dats"
staload _ = "bitmap_grayscale_task.dats"

implement
main0 (argc, argv) =
  let
    val args = listize_argc_argv (argc, argv)
    val nargs = length args

    val inpf =
      if nargs < 2 then
        stdin_ref
      else if args[1] = "-" then
        stdin_ref
      else
        fileref_open_exn (args[1], file_mode_r)
    val pix_opt = pixmap_read_ppm<rgb24> inpf
    val () = fileref_close inpf
  in
    case+ pix_opt of
    | ~ None_vt () =>
      begin
        free args;
        println! ("For some reason, I failed to read the image.");
        exit 1
      end
    | ~ Some_vt @(pfgc1 | pix1) =>
      let
        val @(pfgc2 | pix2) = pixmap_convert<rgb24,gray8> pix1
        val () = free (pfgc1 | pix1)
        val @(pfgc3 | pix3) = pixmap_convert<gray8,rgb24> pix2
        val () = free (pfgc2 | pix2)

        val outf =
          if nargs < 3 then
            stdout_ref
          else if args[2] = "-" then
            stdout_ref
          else
            fileref_open_exn (args[2], file_mode_w)
        val success = pixmap_write_ppm<rgb24> (outf, pix3)
        val () = fileref_close outf

        val () = free (pfgc3 | pix3)
      in
        free args;
        if ~success then
          begin
            println! ("For some reason, ",
                      "I failed to write a new image.");
            exit 2
          end
      end
  end

You can compile the program with the shell command

myatscc bitmap_read_ppm_task_program.dats

If compilation is successful, the program will be called bitmap_read_ppm_task_program. You can give up to two arguments (any others will be ignored). The first argument is a file name for the input file, the second is the file name for the output file. Either argument can be "-", meaning to use the respective standard input or output. An argument omitted is equivalent to "-".

Shown in the margin are before and after for SIPI test image 4.1.06 (not counting that I have converted the PPM files to JPEGs).

AutoHotkey

Works with: AutoHotkey_L

Only ppm6 files supported.

img := ppm_read("lena50.ppm") ;   
x := img[4,4] ; get pixel(4,4)
y := img[24,24] ; get pixel(24,24)
msgbox % x.rgb() " " y.rgb()
img.write("lena50copy.ppm")
return
 
ppm_read(filename, ppmo=0) ; only ppm6 files supported 
{
if !ppmo  ; if image not already in memory, read from filename
  fileread, ppmo, % filename 

  index := 1  
  pos := 1

  loop, parse, ppmo, `n, `r
  {
    if (substr(A_LoopField, 1, 1) == "#")
      continue
loop, 
{ 
 if !pos := regexmatch(ppmo, "\d+", pixel, pos)
break
    bitmap%A_Index% := pixel
    if (index == 4)
      Break
    pos := regexmatch(ppmo, "\s", x, pos)
    index ++
}
  }
 
  type := bitmap1
  width := bitmap2
  height := bitmap3
  maxcolor := bitmap4
  bitmap := Bitmap(width, height, color(0,0,0))
  index := 1
  i := 1
  j := 1
 bits := pos 
loop % width * height 
  {
      bitmap[i, j, "r"]  := numget(ppmo, 3 * A_Index + bits, "uchar")
      bitmap[i, j, "g"]  := numget(ppmo, 3 * A_Index + bits + 1, "uchar")
      bitmap[i, j, "b"]  := numget(ppmo, 3 * A_Index + bits + 2, "uchar")

      if (j == width)
{
	j := 1
	i += 1
}
      else
	j++
}
 return bitmap  
  }
#include bitmap_storage.ahk ; from http://rosettacode.org/wiki/Basic_bitmap_storage/AutoHotkey

BBC BASIC

      f% = OPENIN("c:\lena.ppm")
      IF f%=0 ERROR 100, "Failed to open input file"
      
      IF GET$#f% <> "P6" ERROR 101, "File is not in P6 format"
      REPEAT
        in$ = GET$#f%
      UNTIL LEFT$(in$,1) <> "#"
      size$ = in$
      max$ = GET$#f%
      
      Width% = VAL(size$)
      space% = INSTR(size$, " ")
      Height% = VALMID$(size$, space%)
      
      VDU 23,22,Width%;Height%;8,16,16,128
      
      FOR y% = Height%-1 TO 0 STEP -1
        FOR x% = 0 TO Width%-1
          r% = BGET#f% : g% = BGET#f% : b% = BGET#f%
          l% = INT(0.3*r% + 0.59*g% + 0.11*b% + 0.5)
          PROCsetpixel(x%,y%,l%,l%,l%)
        NEXT
      NEXT y%
      END
      
      DEF PROCsetpixel(x%,y%,r%,g%,b%)
      COLOUR 1,r%,g%,b%
      GCOL 1
      LINE x%*2,y%*2,x%*2,y%*2
      ENDPROC

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:

image get_ppm(FILE *pf);

Implementation:

#include "imglib.h"

#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);
        /* the code fails if the white space following "P6" is not '\n' */
        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;

        r = fscanf(pf, "%u", &d);
        if ( (r < 1) || ( d != 255 ) ) return NULL;
        fseek(pf, 1, SEEK_CUR); /* skip one byte, should be whitespace */

        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;
        }
}

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):

#include <stdio.h>
#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;
}

C#

Tested with this solution.

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;
    }
}

Common Lisp

The function read-ppm-image reads either a P6 or P3 file depending on the file contents. The package description assumes that you have the Basic bitmap storage#Common Lisp package.

(in-package #:rgb-pixel-buffer)

(defparameter *whitespaces-chars* '(#\SPACE #\RETURN #\TAB #\NEWLINE #\LINEFEED))

(defun read-header-chars (stream &optional (delimiter-list *whitespaces-chars*))
  (do ((c (read-char stream nil :eof)
          (read-char stream nil :eof))
       (vals nil (if (or (null c) (char= c  #\#)) vals (cons c vals))))   ;;don't collect comment chars
       ((or (eql c :eof) (member c delimiter-list)) (map 'string #'identity (nreverse vals)))   ;;return strings
    (when (char= c #\#)   ;;skip comments
      (read-line stream))))

(defun read-ppm-file-header (file)
  (with-open-file (s file :direction :input)
    (do ((failure-count 0 (1+ failure-count))
	 (tokens nil (let ((t1 (read-header-chars s)))
		       (if (> (length t1) 0)
			   (cons t1 tokens)
			   tokens))))
	((>= (length tokens) 4) (values (nreverse tokens)
			      (file-position s)))
      (when (>= failure-count 10)
	(error (format nil "File ~a does not seem to be a proper ppm file - maybe too many comment lines" file)))
      (when (= (length tokens) 1)
	(when (not (or (string= (first tokens) "P6") (string= (first tokens) "P3")))
	  (error (format nil "File ~a is not a ppm file - wrong magic-number. Read ~a instead of P6 or P3 " file (first tokens))))))))

(defun read-ppm-image (file)
  (flet ((image-data-reader (stream start-position width height image-build-function read-function)
	   (file-position stream start-position)
	   (dotimes (row height)
	     (dotimes (col width)
	       (funcall image-build-function row col (funcall read-function stream))))))
    (multiple-value-bind (header file-pos) (read-ppm-file-header file)
      (let* ((image-type (first header))
	     (width (parse-integer (second header) :junk-allowed t))
	     (height (parse-integer (third header) :junk-allowed t))
	     (max-value (parse-integer (fourth header) :junk-allowed t))
	     (image (make-rgb-pixel-buffer width height)))
	(when (> max-value 255)
	  (error "unsupported depth - convert to 1byte depth with pamdepth"))
	(cond ((string= "P6" image-type)
	       (with-open-file (stream file :direction :input :element-type '(unsigned-byte 8))
		 (image-data-reader stream
				    file-pos
				    width
				    height
				    #'(lambda (w h val)
					(setf (rgb-pixel image w h) val))
				    #'(lambda (stream)
					(make-rgb-pixel (read-byte stream)
							(read-byte stream)
							(read-byte stream))))
		 image))
	      ((string= "P3" image-type)
	       (with-open-file (stream file :direction :input)
		 (image-data-reader stream
				    file-pos
				    width
				    height
				    #'(lambda (w h val)
					(setf (rgb-pixel image w h) val))
				    #'(lambda (stream)
					(make-rgb-pixel (read stream)
							(read stream)
							(read stream))))
		 image))
	      (t 'unsupported))
      image))))

(export 'read-ppm-image)

To read the feep.ppm file as shown on the description page for the ppm format use:

(read-ppm-image "feep.ppm")

D

The Image module contains a loadPPM6 function to load binary PPM images.

Delphi

Class helper for read and write Bitmap's and Ppm's

Translation of: C#
program BtmAndPpm;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  System.Classes,
  Winapi.Windows,
  Vcl.Graphics;

type
  TBitmapHelper = class helper for TBitmap
  private
  public
    procedure SaveAsPPM(FileName: TFileName; useGrayScale: Boolean = False);
    procedure LoadFromPPM(FileName: TFileName; useGrayScale: Boolean = False);
  end;

function ColorToGray(Color: TColor): TColor;
var
  L: Byte;
begin
  L := round(0.2126 * GetRValue(Color) + 0.7152 * GetGValue(Color) + 0.0722 *
    GetBValue(Color));
  Result := RGB(L, L, L);
end;

{ TBitmapHelper }

procedure TBitmapHelper.SaveAsPPM(FileName: TFileName; useGrayScale: Boolean = False);
var
  i, j, color: Integer;
  Header: AnsiString;
  ppm: TMemoryStream;
begin
  ppm := TMemoryStream.Create;
  try
    Header := Format('P6'#10'%d %d'#10'255'#10, [Self.Width, Self.Height]);
    writeln(Header);
    ppm.Write(Tbytes(Header), Length(Header));

    for i := 0 to Self.Height - 1 do
      for j := 0 to Self.Width - 1 do
      begin
        if useGrayScale then
          color := ColorToGray(ColorToRGB(Self.Canvas.Pixels[i, j]))
        else
          color := ColorToRGB(Self.Canvas.Pixels[i, j]);
        ppm.Write(color, 3);
      end;
    ppm.SaveToFile(FileName);
  finally
    ppm.Free;
  end;
end;

procedure TBitmapHelper.LoadFromPPM(FileName: TFileName; useGrayScale: Boolean = False);
var
  p: Integer;
  ppm: TMemoryStream;
  sW, sH: string;
  temp: AnsiChar;
  W, H: Integer;
  Color: TColor;

  function ReadChar: AnsiChar;
  begin
    ppm.Read(Result, 1);
  end;

begin
  ppm := TMemoryStream.Create;
  ppm.LoadFromFile(FileName);
  if ReadChar + ReadChar <> 'P6' then
    exit;

  repeat
    temp := ReadChar;
    if temp in ['0'..'9'] then
      sW := sW + temp;
  until temp = ' ';

  repeat
    temp := ReadChar;
    if temp in ['0'..'9'] then
      sH := sH + temp;
  until temp = #10;

  W := StrToInt(sW);
  H := StrToInt(sH);

  if ReadChar + ReadChar + ReadChar <> '255' then
    exit;

  ReadChar(); //skip newLine

  SetSize(W, H);
  p := 0;
  while ppm.Read(Color, 3) > 0 do
  begin
    if useGrayScale then
      Color := ColorToGray(Color);
    Canvas.Pixels[p mod W, p div W] := Color;
    inc(p);
  end;
  ppm.Free;
end;

begin
  with TBitmap.Create do
  begin
    // Load bmp
    LoadFromFile('Input.bmp');
    // Save as ppm
    SaveAsPPM('Output.ppm');

    // Load as ppm and convert in grayscale
    LoadFromPPM('Output.ppm', True);

    // Save as bmp
    SaveToFile('Output.bmp');

    Free;
  end;
end.

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
}

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.

def readPPMTask(inputFile, outputFile) {
  makeGrayscale \
    .fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \
    .toColor() \
    .writePPM(<import:java.io.makeFileOutputStream>(outputFile))
}

Erlang

% This module provides basic operations on ppm files:
% Read from file, create ppm in memory (from generic bitmap) and save to file.
% Writing PPM files was introduced in roseta code task 'Bitmap/Write a PPM file'
% but the same code is included here to provide whole set of operations on ppm
% needed for purposes of this task.

-module(ppm).

-export([ppm/1, write/2, read/1]).

% constants for writing ppm file
-define(WHITESPACE, <<10>>).
-define(SPACE, <<32>>).

% constants for reading ppm file
-define(WHITESPACES, [9, 10, 13, 32]).
-define(PPM_HEADER, "P6").

% data structure introduced in task Bitmap (module ros_bitmap.erl)
-record(bitmap, {
    mode = rgb,
    pixels = nil,
    shape = {0, 0}
  }).

%%%%%%%%% API %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% read ppm file from file
read(Filename) ->
    {ok, File} = file:read_file(Filename),
    parse(File).

% create ppm image from bitmap record
ppm(Bitmap) ->
    {Width, Height} = Bitmap#bitmap.shape,
    Pixels = ppm_pixels(Bitmap),
    Maxval = 255,  % original ppm format maximum
    list_to_binary([
      header(), width_and_height(Width, Height), maxval(Maxval), Pixels]).

% write bitmap as ppm file
write(Bitmap, Filename) ->
    Ppm = ppm(Bitmap),
    {ok, File} = file:open(Filename, [binary, write]),
    file:write(File, Ppm),
    file:close(File).

%%%%%%%%% Reading PPM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parse(Binary) ->
    {?PPM_HEADER, Data} = get_next_token(Binary),
    {Width, HeightAndRest} = get_next_token(Data),
    {Height, MaxValAndRest} = get_next_token(HeightAndRest),
    {_MaxVal, RawPixels} = get_next_token(MaxValAndRest),
    Shape = {list_to_integer(Width), list_to_integer(Height)},
    Pixels = load_pixels(RawPixels),
    #bitmap{pixels=Pixels, shape=Shape}.

% load binary as a list of RGB triplets
load_pixels(Binary) when is_binary(Binary)->
    load_pixels([], Binary).
load_pixels(Acc, <<>>) ->
    array:from_list(lists:reverse(Acc));
load_pixels(Acc, <<R, G, B, Rest/binary>>) ->
    load_pixels([<<R,G,B>>|Acc], Rest).

is_whitespace(Byte) ->
    lists:member(Byte, ?WHITESPACES).

% get next part of PPM file, skip whitespaces, and return the rest of a binary
get_next_token(Binary) ->
    get_next_token("", true, Binary).
get_next_token(CurrentToken, false, <<Byte, Rest/binary>>) ->
    case is_whitespace(Byte) of
        true ->
            {lists:reverse(CurrentToken), Rest};
        false ->
            get_next_token([Byte | CurrentToken], false, Rest)
    end;
get_next_token(CurrentToken, true, <<Byte, Rest/binary>>) ->
    case is_whitespace(Byte) of
        true ->
            get_next_token(CurrentToken, true, Rest);
        false ->
            get_next_token([Byte | CurrentToken], false, Rest)
    end.

%%%%%%%%% Writing PPM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
header() ->
    [<<"P6">>, ?WHITESPACE].

width_and_height(Width, Height) ->
    [encode_decimal(Width), ?SPACE, encode_decimal(Height), ?WHITESPACE].

maxval(Maxval) ->
   [encode_decimal(Maxval), ?WHITESPACE].

ppm_pixels(Bitmap) ->
    % 24 bit color depth
    array:to_list(Bitmap#bitmap.pixels).

encode_decimal(Number) ->
    integer_to_list(Number).

Usage in accordance with Grayscale Task:

Colorful = ppm:read("colorful.ppm"),
Gray = ros_bitmap:convert(ros_bitmap:convert(Colorful, grey), rgb),
ppm:write(Gray, "gray.ppm"),

Euphoria

include get.e

function get2(integer fn)
    sequence temp
    temp = get(fn)
    return temp[2] - temp[1]*temp[1]
end function

function read_ppm(sequence filename)
    sequence image, line
    integer dimx, dimy, maxcolor
    atom fn
    fn = open(filename, "rb")
    if fn < 0 then
        return -1 -- unable to open
    end if
    line = gets(fn)
    if not equal(line,"P6\n") then
        return -1 -- only ppm6 files are supported
    end if
    dimx = get2(fn)
    if dimx < 0 then
        return -1
    end if
    dimy = get2(fn)
    if dimy < 0 then
        return -1
    end if
    maxcolor = get2(fn)
    if maxcolor != 255 then
        return -1 -- maxcolors other then 255 are not supported
    end if
    image = repeat(repeat(0,dimy),dimx)
    for y = 1 to dimy do
        for x = 1 to dimx do
            image[x][y] = getc(fn)*#10000 + getc(fn)*#100 + getc(fn)
        end for
    end for
    close(fn)
    return image
end function

Converting an image to grayscale:

sequence image
image = read_ppm("image.ppm")
image = to_gray(image)
image = to_color(image)
write_ppm("image_gray.ppm",image)

FBSL

Read a colored PPM file, convert it to grayscale and write back to disk under a different name. Sanity checks are omitted for brevity.

24-bpp P6 PPM solution:

 
#ESCAPECHARS ON

DIM colored = ".\\Lena.ppm", grayscale = ".\\LenaGry.ppm"
DIM head, tail, r, g, b, l, ptr, blobsize

FILEGET(FILEOPEN(colored, BINARY), FILELEN(colored)): FILECLOSE(FILEOPEN) ' Load buffer
blobsize = INSTR(FILEGET, "\n255\n") + 4 ' Get sizeof PPM header
head = @FILEGET + blobsize: tail = @FILEGET + FILELEN ' Set loop bounds

FOR ptr = head TO tail STEP 3 ' Transform color triplets
	r = PEEK(ptr + 0, 1) ' Read colors stored in RGB order
	g = PEEK(ptr + 1, 1)
	b = PEEK(ptr + 2, 1)
	l = 0.2126 * r + 0.7152 * g + 0.0722 * b ' Derive luminance
	POKE(ptr + 0, CHR(l))(ptr + 1, CHR)(ptr + 2, CHR) ' Write grayscale
NEXT

FILEPUT(FILEOPEN(grayscale, BINARY_NEW), FILEGET): FILECLOSE(FILEOPEN) ' Save buffer

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)

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

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

FreeBASIC

Translation of: Yabasic
Dim As String imagen = "Lena.ppm"

Sub readPPM (fs As String)
    Dim As Integer x, y, ancho, alto
    Dim As String t, kolor
    Dim As Ubyte r, g, b
    
    If Len(fs) = 0 Then Print "No PPM file name indicated.": Exit Sub
    
    Dim As Long ff = Freefile
    Open fs For Binary As #ff
    If Err Then Print "File "; fs; " not found.": Exit Sub
    
    Input #ff, t, ancho, alto, kolor
    
    If t = "P6" Then
        Screenres ancho, alto, 32
        For y = 0 To alto - 1
            For x = 0 To ancho - 1
                Get #ff, , r
                Get #ff, , g
                Get #ff, , b
                Pset (x, y), Rgb(r, g, b)
            Next x
        Next y
        Close #ff
    Else
        Print "File is NOT PPM P6 type."
    End If
End Sub

readPPM(imagen)
Sleep

Go

package raster

import (
    "errors"
    "io"
    "io/ioutil"
    "os"
    "regexp"
    "strconv"
)

// ReadFrom constructs a Bitmap object from an io.Reader.
func ReadPpmFrom(r io.Reader) (b *Bitmap, err error) {
    var all []byte
    all, err = ioutil.ReadAll(r)
    if err != nil {
        return
    }
    bss := rxHeader.FindSubmatch(all)
    if bss == nil {
        return nil, errors.New("unrecognized ppm header")
    }
    x, _ := strconv.Atoi(string(bss[3]))
    y, _ := strconv.Atoi(string(bss[6]))
    maxval, _ := strconv.Atoi(string(bss[9]))
    if maxval > 255 {
        return nil, errors.New("16 bit ppm not supported")
    }
    allCmts := append(append(append(bss[1], bss[4]...), bss[7]...), bss[10]...)
    b = NewBitmap(x, y)
    b.Comments = rxComment.FindAllString(string(allCmts), -1)
    b3 := all[len(bss[0]):]
    var n1 int
    for i := range b.px {
        b.px[i].R = byte(int(b3[n1]) * 255 / maxval)
        b.px[i].G = byte(int(b3[n1+1]) * 255 / maxval)
        b.px[i].B = byte(int(b3[n1+2]) * 255 / maxval)
        n1 += 3
    }
    return
}

const (
    // single whitespace character
    ws = "[ \n\r\t\v\f]"
    // isolated comment
    cmt = "#[^\n\r]*"
    // comment sub expression
    cmts = "(" + ws + "*" + cmt + "[\n\r])"
    // number with leading comments
    num = "(" + cmts + "+" + ws + "*|" + ws + "+)([0-9]+)"
)

var rxHeader = regexp.MustCompile("^P6" + num + num + num +
    "(" + cmts + "*" + ")" + ws)
var rxComment = regexp.MustCompile(cmt)

// ReadFile writes binary P6 format PPM from the specified filename.
func ReadPpmFile(fn string) (b *Bitmap, err error) {
    var f *os.File
    if f, err = os.Open(fn); err != nil {
        return
    }
    if b, err = ReadPpmFrom(f); err != nil {
        return
    }
    return b, f.Close()
}

Demonstration program, also demonstrating functions from task Grayscale image:

package main

// Files required to build supporting package raster are found in:
// * This task (immediately above)
// * Bitmap
// * Grayscale image
// * Write a PPM file

import (
    "raster"
    "fmt"
)

func main() {
    // (A file with this name is output by the Go solution to the task
    // "Bitmap/Read an image through a pipe," but of course any 8-bit
    //  P6 PPM file should work.)
    b, err := raster.ReadPpmFile("pipein.ppm")
    if err != nil {
        fmt.Println(err)
        return
    }
    b = b.Grmap().Bitmap()
    err = b.WritePpmFile("grayscale.ppm")
    if err != nil {
        fmt.Println(err)
    }
}

Haskell

The definition of Bitmap.Netpbm.readNetpbm is given here.

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"

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:

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

J

Solution:
Uses makeRGB from Basic bitmap storage.

require 'files'

readppm=: monad define
  dat=. fread y                                           NB. read from file
  msk=. 1 ,~ (*. 3 >: +/\) (LF&=@}: *. '#'&~:@}.) dat     NB. mark field ends
  't wbyh maxval dat'=. msk <;._2 dat                     NB. parse
  'wbyh maxval'=. 2 1([ {. [: _99&". (LF,' ')&charsub)&.> wbyh;maxval  NB. convert to numeric
  if. (_99 0 +./@e. wbyh,maxval) +. 'P6' -.@-: 2{.t do. _1 return. end.
  (a. i. dat) makeRGB |.wbyh                              NB. convert to basic bitmap format
)

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.

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

Java

For convenience, the code for the class used in the Bitmap task here and integrated with the code in the Grayscale image is included.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ReadPPMFile {

	public static void main(String[] aArgs) throws IOException {
        // Using the file created in the Bitmap task
		String filePath = "output.ppm";
		
		reader = new BufferedInputStream( new FileInputStream(filePath) );
		final char header1 = (char) reader.read();
        final char header2 = (char) reader.read();
        final char header3 = (char) reader.read();
        if ( header1 != 'P' || header2 != '6' || header3 != END_OF_LINE) {
        	reader.close();
        	throw new IllegalArgumentException("Not a valid P6 PPM file");
        }
        
        final int width = processCharacters(SPACE_CHARACTER);  
        final int height = processCharacters(END_OF_LINE);
        final int maxColorValue = processCharacters(END_OF_LINE);
        if ( maxColorValue < 0 || maxColorValue > 255 ) {
        	reader.close();
        	throw new IllegalArgumentException("Maximum color value is outside the range 0..255");
        }            
        
        // Remove any comments before reading data
        reader.mark(1);
        while ( reader.read() == START_OF_COMMENT ) {
        	 while ( reader.read() != END_OF_LINE );
        	 reader.mark(1);
        }
        reader.reset();
                
       // Read data
        BasicBitmapStorage bitmap = new BasicBitmapStorage(width, height);
        
        byte[] buffer = new byte[width * 3];
        for ( int y = 0; y < height; y++ ) {
            reader.read(buffer, 0, buffer.length);
            for ( int x = 0; x < width; x++ ) {                	
                Color color = new Color(Byte.toUnsignedInt(buffer[x * 3]),
                						Byte.toUnsignedInt(buffer[x * 3 + 1]),
                						Byte.toUnsignedInt(buffer[x * 3 + 2]));
                bitmap.setPixel(x, y, color);
            }
        }
        
        reader.close();
        
        // Convert to gray scale and save to a file
        bitmap.convertToGrayscale();
        File grayFile = new File("outputGray.jpg");
        ImageIO.write((RenderedImage) bitmap.getImage(), "jpg", grayFile);  	
	}
	
	private static int processCharacters(char aChar) throws IOException {
		StringBuilder characters = new StringBuilder();
		char ch;		
		while ( ( ch = (char) reader.read() ) != aChar ) {
        	if ( ch == START_OF_COMMENT ) {
        		while ( reader.read() != END_OF_LINE );
        		continue;
        	}
        	characters.append(ch);
        }
		return Integer.valueOf(characters.toString());
	}
	
	private static BufferedInputStream reader;
	
	private static final char START_OF_COMMENT = '#';
	private static final char SPACE_CHARACTER = ' ';
	private static final char END_OF_LINE = '\n';
   
}	
    
final class BasicBitmapStorage {

    public BasicBitmapStorage(int aWidth, int aHeight) {
        image = new BufferedImage(aWidth, aHeight, BufferedImage.TYPE_INT_RGB);
    }

    public void fill(Color aColor) {
        Graphics graphics = image.getGraphics();
        graphics.setColor(aColor);
        graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
    }    

    public Color getPixel(int aX, int aY) {
        return new Color(image.getRGB(aX, aY));
    }
    
    public void setPixel(int aX, int aY, Color aColor) {
        image.setRGB(aX, aY, aColor.getRGB());
    }
    
    public Image getImage() {
    	return image;
    }
    
    public void convertToGrayscale() {        
        for ( int y = 0; y < image.getHeight(); y++ ) {
           	for ( int x = 0; x < image.getWidth(); x++ ) {
                int color = image.getRGB(x, y);

                int alpha = ( color >> 24 ) & 255;
                int red = ( color >> 16 ) & 255;
                int green = ( color >> 8 ) & 255;
                int blue = color & 255;

                final int luminance = (int) ( 0.2126 * red + 0.7152 * green + 0.0722 * blue );

                alpha = alpha << 24;
                red = luminance << 16;
                green = luminance << 8;
                blue = luminance;

                color = alpha + red + green + blue;

                image.setRGB(x, y, color);
            }
        }
    }
    
    private final BufferedImage image;

}
Output:

Media:ColouredJava.png & Media:GrayscaleJava.png

Julia

Works with: Julia version 0.6
using Images, FileIO, Netpbm

rgbimg = load("data/bitmapInputTest.ppm")
greyimg = Gray.(rgbimg)
save("data/bitmapOutputTest.ppm", greyimg)

Kotlin

For convenience, we repeat the code for the class used in the Bitmap task here and integrate the code in the Grayscale image task within it.

// Version 1.2.40

import java.awt.Color
import java.awt.Graphics
import java.awt.image.BufferedImage
import java.io.FileInputStream
import java.io.PushbackInputStream
import java.io.File
import javax.imageio.ImageIO

class BasicBitmapStorage(width: Int, height: Int) {
    val image = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)

    fun fill(c: Color) {
        val g = image.graphics
        g.color = c
        g.fillRect(0, 0, image.width, image.height)
    }

    fun setPixel(x: Int, y: Int, c: Color) = image.setRGB(x, y, c.getRGB())

    fun getPixel(x: Int, y: Int) = Color(image.getRGB(x, y))

    fun toGrayScale() {
        for (x in 0 until image.width) {
            for (y in 0 until image.height) {
                var rgb  = image.getRGB(x, y)
                val red   = (rgb shr 16) and 0xFF
                val green = (rgb shr  8) and 0xFF
                val blue  =  rgb and 0xFF
                val lumin = (0.2126 * red + 0.7152 * green + 0.0722 * blue).toInt()
                rgb = (lumin shl 16) or (lumin shl 8) or lumin
                image.setRGB(x, y, rgb)
            }
        }
    }
}

fun PushbackInputStream.skipComment() {
    while (read().toChar() != '\n') {}
}

fun PushbackInputStream.skipComment(buffer: ByteArray) {
    var nl: Int
    while (true) {
        nl = buffer.indexOf(10) // look for newline at end of comment
        if (nl != -1) break
        read(buffer)  // read another buffer full if newline not yet found
    }
    val len = buffer.size
    if (nl < len - 1) unread(buffer, nl + 1, len - nl - 1)
}

fun Byte.toUInt() = if (this < 0) 256 + this else this.toInt()

fun main(args: Array<String>) {
    // use file, output.ppm, created in the Bitmap/Write a PPM file task
    val pbis = PushbackInputStream(FileInputStream("output.ppm"), 80)
    pbis.use {
        with (it) {
            val h1 = read().toChar()
            val h2 = read().toChar()
            val h3 = read().toChar()
            if (h1 != 'P' || h2 != '6' || h3 != '\n') {
                println("Not a P6 PPM file")
                System.exit(1)
            }
            val sb = StringBuilder()
            while (true) {
                val r = read().toChar()
                if (r == '#') { skipComment(); continue }
                if (r == ' ') break  // read until space reached
                sb.append(r.toChar())
            }
            val width = sb.toString().toInt()
            sb.setLength(0)
            while (true) {
                val r = read().toChar()
                if (r == '#') { skipComment(); continue }
                if (r == '\n') break  // read until new line reached
                sb.append(r.toChar())
            }
            val height = sb.toString().toInt()
            sb.setLength(0)
            while (true) {
                val r = read().toChar()
                if (r == '#') { skipComment(); continue }
                if (r == '\n') break  // read until new line reached
                sb.append(r.toChar())
            }
            val maxCol = sb.toString().toInt()
            if (maxCol !in 0..255) {
                println("Maximum color value is outside the range 0..255")
                System.exit(1)
            }
            var buffer = ByteArray(80)
            // get rid of any more opening comments before reading data
            while (true) {
                read(buffer)
                if (buffer[0].toChar() == '#') {
                    skipComment(buffer)
                }
                else {
                    unread(buffer)
                    break
                }
            }
            // read data
            val bbs = BasicBitmapStorage(width, height)
            buffer = ByteArray(width * 3)
            var y = 0
            while (y < height) {
                read(buffer)
                for (x in 0 until width) {
                    val c = Color(
                        buffer[x * 3].toUInt(),
                        buffer[x * 3 + 1].toUInt(),
                        buffer[x * 3 + 2].toUInt()
                    )
                    bbs.setPixel(x, y, c)
                }
                y++
            }
            // convert to grayscale and save to a file
            bbs.toGrayScale()
            val grayFile = File("output_gray.jpg")
            ImageIO.write(bbs.image, "jpg", grayFile)
        }
    }
}

Lua

function Read_PPM( filename )
    local fp = io.open( filename, "rb" )    
    if fp == nil then return nil end

    local data = fp:read( "*line" )
    if data ~= "P6" then return nil end
 
    repeat
        data = fp:read( "*line" )
    until string.find( data, "#" ) == nil

    local image = {}
    local size_x, size_y
    
    size_x = string.match( data, "%d+" )
    size_y = string.match( data, "%s%d+" )

    data = fp:read( "*line" )
    if tonumber(data) ~= 255 then return nil end

    for i = 1, size_x do
        image[i] = {}
    end
 
    for j = 1, size_y do
        for i = 1, size_x do
            image[i][j] = { string.byte( fp:read(1) ), string.byte( fp:read(1) ), string.byte( fp:read(1) ) }
        end
    end
 
    fp:close()
 
    return image
end

M2000 Interpreter

Now function Bitmap has double signature. With two numbers make a bitmap,with all pixels white. With one number, expect that it is a file number and read file, and then return the bitmap.

Module Checkit {
      Function Bitmap  {
            If match("NN") then {
                 Read x as long, y as long
            } else.if Match("N") Then  {
                \\ is a file?
                  Read f
                  if not Eof(f) then {
                        Line Input #f,  p3$
                              If p3$="P3" Then {
                                    Line Input #f, Comment$
                                    if left$(Comment$,1)="#" then {
                                          Line Input #f, Dimension$
                                    } else  Dimension$=Comment$
                                    long x=Val(piece$(Dimension$," ")(0))
                                    long y=Val(piece$(Dimension$," ")(1))
                                    do {
                                          Line Input #f, P255$
                                    } until left$(P255$, 1)<>"#"
                                    If not P255$="255" then Error "Not proper ppm format"
                              }                  
                  }
            } else Error "No proper arguments"
            
            
            if x<1 or y<1 then  Error "Wrong dimensions"
            structure rgb {
                  red as byte
                  green as byte
                  blue as byte
            }
            m=len(rgb)*x mod 4
            if m>0 then m=4-m  ' add some bytes to raster line
            m+=len(rgb) *x
            Structure rasterline {
                  { 
                        pad as byte*m
                  }   
                  \\ union pad+hline
                  hline as rgb*x
            }
      
            Structure Raster {
                  magic as integer*4
                  w as integer*4
                  h as integer*4
                  lines as rasterline*y
            }
            Buffer Clear Image1 as Raster
            \\ 24 chars as header to be used from bitmap render build in functions
            Return Image1, 0!magic:="cDIB", 0!w:=Hex$(x,2), 0!h:=Hex$(y, 2)
            \\ fill white (all 255)
            \\ Str$(string) convert to ascii, so we get all characters from words  width to byte width
            if not valid(f) then  Return Image1, 0!lines:=Str$(String$(chrcode$(255), Len(rasterline)*y))
            Buffer Clear Pad as Byte*4
            SetPixel=Lambda Image1, Pad,aLines=Len(Raster)-Len(Rasterline), blines=-Len(Rasterline) (x, y, c) ->{
                  where=alines+3*x+blines*y
                  if c>0 then c=color(c)
                  c-!
                  Return Pad, 0:=c as long
                  Return Image1, 0!where:=Eval(Pad, 2) as byte, 0!where+1:=Eval(Pad, 1) as byte, 0!where+2:=Eval(Pad, 0) as byte
            
            }
            GetPixel=Lambda Image1,aLines=Len(Raster)-Len(Rasterline), blines=-Len(Rasterline) (x,y) ->{
                  where=alines+3*x+blines*y
                  =color(Eval(image1, where+2 as byte), Eval(image1, where+1 as byte), Eval(image1, where as byte))
            }
            StrDib$=Lambda$ Image1, Raster -> {
                  =Eval$(Image1, 0, Len(Raster))
            }
            CopyImage=Lambda Image1 (image$) -> {
                  if left$(image$,12)=Eval$(Image1, 0, 24 ) Then  {
                         Return Image1, 0:=Image$
                  } Else Error "Can't Copy Image"
            }
            Export2File=Lambda Image1, x, y (f) -> {
                  \\ use this between open and close 
                  Print #f, "P3"
                  Print #f,"# Created using M2000 Interpreter"
                  Print #f, x;" ";y
                  Print #f, 255
                  x2=x-1
                  where=24
                  For y1= 0 to y-1 {
                        a$=""
                        For x1=0 to x2 {
                              Print #f, a$;Eval(Image1, where+2 as byte);" ";
                              Print #f,  Eval(Image1, where+1 as byte);" ";
                              Print #f,  Eval(Image1, where as byte);
                              where+=3
                              a$=" "
                        }
                        Print #f
                        m=where mod 4
                        if m<>0 then where+=4-m
                  }            
            }
            if valid(F) then {
                  'load  RGB values form file
                  x0=x-1
                  where=24
                        For y1=y-1 to 0 {
                              do {
                                          Line Input #f, aline$
                              } until left$(aline$,1)<>"#"
                              flush   ' empty stack
                              Stack aline$  ' place all values to stack as FIFO         
                              For x1=0 to x0 {
                                    \\ now read from stack using Number
                                    Return Image1, 0!where+2:=Number as byte, 0!where+1:=Number as byte, 0!where:=Number as byte
                                    where+=3
                              }
                              m=where mod 4
                              if m<>0 then where+=4-m                                   
                        }
            }
            Group Bitmap {
                  SetPixel=SetPixel
                  GetPixel=GetPixel
                  Image$=StrDib$
                  Copy=CopyImage
                  ToFile=Export2File
            }
            =Bitmap
      }
      A=Bitmap(10, 10)
      Call A.SetPixel(5,5, color(128,0,255))
      Open "A.PPM" for Output as #F
            Call A.ToFile(F)
      Close #f
      Open "A.PPM" for Input as #F
      Try {
            C=Bitmap(f)     
            Copy 400*twipsx,200*twipsy use C.Image$()
      }
      Close #f
      ' is the same as this one
        Open "A.PPM" for Input as #F
            Line Input #f,  p3$
            If p3$="P3" Then {
                  Line Input #f, Comment$
                  if left$(Comment$,1)="#" then {
                        Line Input #f, Dimension$
                  } else  Dimension$=Comment$
                  Long x=Val(piece$(Dimension$," ")(0))
                  Long y=Val(piece$(Dimension$," ")(1))
                  do {
                        Line Input #f, P255$
                  } until left$(P255$, 1)<>"#"
                  If not P255$="255" then Error "Not proper ppm format"
                  B=Bitmap(x, y)
                  x0=x-1
                  For y1=y-1 to 0 {
                        do {
                                    Line Input #f, aline$
                        } until left$(aline$,1)<>"#"
                        flush   ' empty stack
                        Stack aline$  ' place all values to stack as FIFO         
                        For x1=0 to x0 {
                              \\ now read from stack
                              Read red, green, blue
                              Call B.setpixel(x1, y1, Color(red, green, blue))
                        }
                  }
            }
      Close #f
      If valid("B") then  Copy 200*twipsx,200*twipsy use B.Image$()
}
Checkit

Mathematica/ Wolfram Language

Import["file.ppm","PPM"]

Nim

import strutils
import bitmap
import streams

type FormatError = object of CatchableError

# States used to parse the header.
type State = enum waitingMagic, waitingWidth, waitingHeight, waitingColors

#---------------------------------------------------------------------------------------------------

iterator tokens(f: Stream): tuple[value: string, lastInLine: bool] =
  ## Yield the tokens in the header.
  for line in f.lines:
    if not line.startsWith('#'):
      let fields = line.splitWhitespace()
      for i, t in fields:
        yield (t, i == fields.high)

#---------------------------------------------------------------------------------------------------

proc getInt(s: string): int {.inline.} =
  ## Try to parse an int. Raise an exception if not an integer.
  try:
    result = s.parseInt()
  except ValueError:
    raise newException(FormatError, "Invalid value")

#---------------------------------------------------------------------------------------------------

proc header(f: Stream): tuple[width, height: Index] =
  ## Read the header and retrun the image width and height.
  var state = waitingMagic
  for (token, lastInLine) in f.tokens:
    case state
    of waitingMagic:
      if token != "P6":
        raise newException(FormatError, "Invalid file header")
    of waitingWidth:
      result.width = token.getInt()
    of waitingHeight:
      result.height = token.getInt()
    of waitingColors:
      if token.getInt() != 255:
        raise newException(FormatError, "Invalid number of colors")
      if not lastInLine:
        raise newException(FormatError, "Invalid data after number of colors")
      break
    state = succ(state)

#---------------------------------------------------------------------------------------------------

proc readPPM*(f: Stream): Image =
  ## Read a PPM file from a stream into an image.

  let header = f.header()
  result = newImage(header.width, header.height)

  var
    arr: array[256, int8]
    read = f.readData(addr(arr), 256)
    pos = 0

  while read != 0:
    for i in 0 ..< read:
      case pos mod 3
      of 0: result.pixels[pos div 3].r = arr[i].uint8
      of 1: result.pixels[pos div 3].g = arr[i].uint8
      of 2: result.pixels[pos div 3].b = arr[i].uint8
      else: discard
      inc pos

    read = f.readData(addr(arr), 256)

  if pos != 3 * result.w * result.h:
    raise newException(FormatError, "Truncated file")

#---------------------------------------------------------------------------------------------------

proc readPPM*(filename: string): Image =
  ## Load a PPM file into an image.

  var file = openFileStream(filename, fmRead)
  result = file.readPPM()
  file.close()

#———————————————————————————————————————————————————————————————————————————————————————————————————

when isMainModule:
  let image = readPPM("output.ppm")
  echo image.h, " ", image.w

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)

and converting a given color file to grayscale:

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

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 -

Oz

The read function in module "BitmapIO.oz":

functor
import
   Bitmap
   Open
export
   Read
   %% Write
define
   fun {Read Filename}
      F = {New Open.file init(name:Filename)}

      fun {ReadColor8 _}
	 Bytes = {F read(list:$ size:3)}
      in
	 {List.toTuple color Bytes}
      end

      fun {ReadColor16 _}
	 Bytes = {F read(list:$ size:6)}
      in
	 {List.toTuple color {Map {PairUp Bytes} FromBytes}}
      end
   in
      try
	 Magic = {F read(size:2 list:$)}
	 if Magic \= "P6" then raise bitmapIO(read unsupportedFormat(Magic)) end end
	 Width = {ReadNumber F}
	 Height = {ReadNumber F}
	 MaxVal = {ReadNumber F}
	 MaxVal =< 0xffff = true
	 Reader = if MaxVal =< 0xff then ReadColor8 else ReadColor16 end
	 B = {Bitmap.new Width Height}
      in
	 {Bitmap.transform B Reader}
	 B
      finally
	 {F close}
      end
   end

   fun {ReadNumber F}
      Ds
   in
      {SkipWS F}
      Ds = for collect:Collect break:Break do
	      [C] = {F read(list:$ size:1)}
	   in
	      if {Char.isDigit C} then {Collect C}
	      else {Break}
	      end
	   end
      {SkipWS F}
      {String.toInt Ds}
   end

   proc {SkipWS F}
      [C] = {F read(list:$ size:1)}
   in
      if {Char.isSpace C} then {SkipWS F}
      elseif C == &# then
	 {SkipLine F}
      else
	 {F seek(whence:current offset:~1)}
      end
   end

   proc {SkipLine F}
      [C] = {F read(list:$ size:1)}
   in
      if C \= &\n andthen  C \= &\r then {SkipLine F} end
   end
   
   fun {PairUp Xs}
      case Xs of X1|X2|Xr then [X1 X2]|{PairUp Xr}
      [] nil then nil
      end
   end

   fun {FromBytes [C1 C2]}
      C1 * 0x100 + C2
   end

   %% Omitted: Write
end

The actual task:

declare
  [BitmapIO Grayscale] = {Module.link ['BitmapIO.ozf' 'Grayscale.ozf']}

  B = {BitmapIO.read "image.ppm"}
  G = {Grayscale.toGraymap B}
in
  {BitmapIO.write {Grayscale.fromGraymap G} "greyimage.ppm"}

Perl

Library: Imlib2
#! /usr/bin/perl

use strict;
use Image::Imlib2;

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

# 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;

Phix

Based on Euphoria, requires write_ppm() from Write_a_PPM_file, to_grey from Grayscale_image
Note that demo\rosetta\Bitmap_read_ppm.exw is just the last 3 lines with the include ppm.e since that contains the read_ppm() (abeit with a few more options and other tweaks) also used by several other examples, and covers the above requirements. Results may be verified with demo\rosetta\viewppm.exw

-- demo\rosetta\Bitmap_read_ppm.exw (runnable version)

function read_ppm(string filename)
sequence image, line
integer dimx, dimy, maxcolor
atom fn = open(filename, "rb")
    if fn<0 then
        return -1 -- unable to open
    end if
    line = gets(fn)
    if line!="P6\n" then
        return -1 -- only ppm6 files are supported
    end if
    line = gets(fn)
    {{dimx,dimy}} = scanf(line,"%d %d%s")
    line = gets(fn)
    {{maxcolor}} = scanf(line,"%d%s")
    image = repeat(repeat(0,dimy),dimx)
    for y=1 to dimy do
        for x=1 to dimx do
            image[x][y] = getc(fn)*#10000 + getc(fn)*#100 + getc(fn)
        end for
    end for
    close(fn)
    return image
end function

--include ppm.e   -- read_ppm(), write_ppm(), to_grey()  (as distributed, instead of the above)
 
sequence img = read_ppm("Lena.ppm")
img = to_grey(img)
write_ppm("LenaGray.ppm",img)

PicoLisp

(de ppmRead (File)
   (in File
      (unless (and `(hex "5036") (rd 2))  # P6
         (quit "Wrong file format" File) )
      (rd 1)
      (let (DX 0  DY 0  Max 0  C)
         (while (>= 9 (setq C (- (rd 1) `(char "0"))) 0)
            (setq DX (+ (* 10 DX) C)) )
         (while (>= 9 (setq C (- (rd 1) `(char "0"))) 0)
            (setq DY (+ (* 10 DY) C)) )
         (while (>= 9 (setq C (- (rd 1) `(char "0"))) 0)
            (setq Max (+ (* 10 Max) C)) )
         (prog1
            (make (do DY (link (need DX))))
            (for Y @
               (map
                  '((X) (set X (list (rd 1) (rd 1) (rd 1))))
                  Y ) ) ) ) ) )

Read a color image "img.ppm", convert and write to "img.pgm":

(pgmWrite (ppm->pgm (ppmRead "img.ppm")) "img.pgm")

PL/I

/* BITMAP FILE: read in a file in PPM format, P6 (binary). 14/5/2010 */
test: procedure options (main);
   declare (m, n, max_color, i, j) fixed binary (31);
   declare ch character (1), ID character (2);
   declare 1 pixel union,
            2 color bit(24) aligned,
            2 primary_colors,
               3 R char (1),
               3 G char (1),
               3 B char (1);
   declare in file record;

   open file (in) title ('/IMAGE.PPM,TYPE(FIXED),RECSIZE(1)' ) input;

   call get_char;
   ID = ch;
   call get_char;
   substr(ID, 2,1) = ch;
   /* Read in the dimensions of the image */
   call get_integer (m);
   call get_integer (n);

   /* Read in the maximum color size used */
   call get_integer (max_color);
      /* The previous call reads in ONE line feed or CR or other terminator */
      /* character. */

begin;
   declare image (0:m-1,0:n-1) bit (24);

   do i = 0 to hbound(image, 1);
      do j = 0 to hbound(image,2);
         read file (in) into (R);
         read file (in) into (G);
         read file (in) into (B);
         image(i,j) = color;
      end;
   end;
end;

get_char: procedure;
   do until (ch ^= ' ');
      read file (in) into (ch);
   end;
end get_char;

get_integer: procedure (value);
   declare value fixed binary (31);

   do until (ch = ' ');
      read file (in) into (ch);
   end;
   value = 0;
   do until (is_digit(ch));
      value = value*10 + ch;
      read file (in) into (ch);
   end;
end get_integer;

is_digit: procedure (ch) returns (bit(1));
   declare ch character (1);
   return (index('0123456789', ch) > 0);
end is_digit;
end test;

PureBasic

Structure PPMColor
  r.c
  g.c
  b.c
EndStructure

Procedure LoadImagePPM(Image, file$)
  ; Author Roger Rösch (Nickname Macros)
  IDFile = ReadFile(#PB_Any, file$)
  If IDFile
    If CreateImage(Image, 1, 1)
      Format$ = ReadString(IDFile)
      ReadString(IDFile) ; skip comment
      Dimensions$ = ReadString(IDFile)
      w           = Val(StringField(Dimensions$, 1, " "))
      h           = Val(StringField(Dimensions$, 2, " "))
      ResizeImage(Image, w, h)
      StartDrawing(ImageOutput(Image))
      max = Val(ReadString(IDFile))           ; Maximal Value for a color
      Select Format$
        Case "P3" ; File in ASCII format
          ; Exract everey number remaining in th file into an array using an RegEx
          Stringlen = Lof(IDFile) - Loc(IDFile)
          content$  = Space(Stringlen)
          Dim color.s(0)
          ReadData(IDFile, @content$, Stringlen)
          CreateRegularExpression(1, "\d+")
          ExtractRegularExpression(1, content$, color())
          ; Plot color information on our empty Image
          For y = 0 To h - 1
            For x = 0 To w - 1
              pos = (y*w + x)*3
              r=Val(color(pos))*255 / max
              g=Val(color(pos+1))*255 / max
              b=Val(color(pos+2))*255 / max
              Plot(x, y, RGB(r,g,b))
            Next
          Next
        Case "P6" ;File In binary format
          ; Read whole bytes into a buffer because its faster than reading single ones
          Bufferlen = Lof(IDFile) - Loc(IDFile)
          *Buffer   = AllocateMemory(Bufferlen)
          ReadData(IDFile, *Buffer, Bufferlen)
          ; Plot color information on our empty Image
          For y = 0 To h - 1
            For x = 0 To w - 1
              *color.PPMColor = pos + *Buffer
              Plot(x, y, RGB(*color\r*255 / max, *color\g*255 / max, *color\b*255 / max))
              pos + 3
            Next
          Next
      EndSelect
      StopDrawing()
      ; Return 1 if successfully loaded to behave as other PureBasic functions
      ProcedureReturn 1 
    EndIf
  EndIf
EndProcedure

To complete the task, the following code should be added to the above fragment and to the PureBasic solutions for Grayscale image and Write a PPM file

Define file.s, file2.s, image = 3
file = OpenFileRequester("Select source image file", "", "PPM image (*.ppm)|*.ppm", 0)
If file And LCase(GetExtensionPart(file)) = "ppm"
  LoadImagePPM(image, file)
  ImageGrayout(image)
  file2 = Left(file, Len(file) - Len(GetExtensionPart(file))) + "_grayscale." + GetExtensionPart(file)
  SaveImageAsPPM(image, file2, 1)
EndIf

Python

Works with: Python version 3.1

Extending the example given here

# With help from http://netpbm.sourceforge.net/doc/ppm.html

# String masquerading as ppm file (version P3)
import io

ppmtxt = '''P3
# feep.ppm
4 4
15
 0  0  0    0  0  0    0  0  0   15  0 15
 0  0  0    0 15  7    0  0  0    0  0  0
 0  0  0    0  0  0    0 15  7    0  0  0
15  0 15    0  0  0    0  0  0    0  0  0
'''


def tokenize(f):
    for line in f:
        if line[0] != '#':
            for t in line.split():
                yield t

def ppmp3tobitmap(f):
    t = tokenize(f)
    nexttoken = lambda : next(t)
    assert 'P3' == nexttoken(), 'Wrong filetype'
    width, height, maxval = (int(nexttoken()) for i in range(3))
    bitmap = Bitmap(width, height, Colour(0, 0, 0))
    for h in range(height-1, -1, -1):
        for w in range(0, width):
            bitmap.set(w, h, Colour( *(int(nexttoken()) for i in range(3))))

    return bitmap
    
print('Original Colour PPM file')
print(ppmtxt)
ppmfile = io.StringIO(ppmtxt)
bitmap = ppmp3tobitmap(ppmfile)
print('Grey PPM:')
bitmap.togreyscale()
ppmfileout = io.StringIO('')
bitmap.writeppmp3(ppmfileout)
print(ppmfileout.getvalue())


'''
The print statements above produce the following output:

Original Colour PPM file
P3
# feep.ppm
4 4
15
 0  0  0    0  0  0    0  0  0   15  0 15
 0  0  0    0 15  7    0  0  0    0  0  0
 0  0  0    0  0  0    0 15  7    0  0  0
15  0 15    0  0  0    0  0  0    0  0  0

Grey PPM:
P3
# generated from Bitmap.writeppmp3
4 4
11
    0  0  0    0  0  0    0  0  0    4  4  4
    0  0  0   11 11 11    0  0  0    0  0  0
    0  0  0    0  0  0   11 11 11    0  0  0
    4  4  4    0  0  0    0  0  0    0  0  0

'''

Racket

#lang racket
(require racket/draw)

(define (read-ppm port)
  (parameterize ([current-input-port port])
    (define magic (read))
    (define width (read))
    (define height (read))
    (define maxcol (read))
    (define bm (make-object bitmap% width height))
    (define dc (new bitmap-dc% [bitmap bm]))
    (send dc set-smoothing 'unsmoothed)
    (define (adjust v) (* 255 (/ v maxcol)))
    (for/list ([x width])
      (for/list ([y height])
        (define red (read))
        (define green (read))
        (define blue (read))
        (define color (make-object color% (adjust red) (adjust green) (adjust blue)))
        (send dc set-pen color 1 'solid)
        (send dc draw-point x y)))
    bm))

Raku

(formerly Perl 6)

Works with: Rakudo version 2017.09

Uses pieces from Bitmap, Write a PPM file and Grayscale image tasks. Included here to make a complete, runnable program.

class Pixel { has UInt ($.R, $.G, $.B) }
class Bitmap {
    has UInt ($.width, $.height);
    has Pixel @.data;
}

role PGM {
    has @.GS;
    method P5 returns Blob {
	"P5\n{self.width} {self.height}\n255\n".encode('ascii')
	~ Blob.new: self.GS
    }
}

sub load-ppm ( $ppm ) {
    my $fh    = $ppm.IO.open( :enc('ISO-8859-1') );
    my $type  = $fh.get;
    my ($width, $height) = $fh.get.split: ' ';
    my $depth = $fh.get;
    Bitmap.new( width => $width.Int, height => $height.Int,
      data => ( $fh.slurp.ords.rotor(3).map:
        { Pixel.new(R => $_[0], G => $_[1], B => $_[2]) } )
    )
}

sub grayscale ( Bitmap $bmp ) {
    $bmp.GS = map { (.R*0.2126 + .G*0.7152 + .B*0.0722).round(1) min 255 }, $bmp.data;
}

my $filename = './camelia.ppm';

my Bitmap $b = load-ppm( $filename ) but PGM;

grayscale($b);

'./camelia-gs.pgm'.IO.open(:bin, :w).write: $b.P5;

See camelia, and camelia-gs images. (converted to .png as .ppm format is not widely supported).

REXX

The input file   Lenna50.ppm   is a PPM format of
the input file    Lenna50.jpg    used elsewhere on Rosetta Code.

This REXX program handles alternative delimiters as well as comments within the PPM header.

/*REXX program reads a PPM formatted image file,  and creates a gray─scale image of it. */
parse arg iFN oFN                                /*obtain optional argument from the CL.*/
if iFN=='' | iFN==","  then  iFN= 'Lenna50'      /*Not specified?  Then use the default.*/
if oFN=='' | oFN==","  then  oFN= 'greyscale'    /* "      "         "   "   "     "    */
iFID= iFN'.ppm';             oFID= oFN'.ppm'     /*complete the  input and output  FIDs.*/
call charin iFID, 1, 0                           /*set the position of the input file.  */
y=charin(iFID, , copies(9, digits() ) )          /*read the entire input file  ───►  X  */
parse var  y   id  3 c 4 3 width height # pixels /*extract header info from the PPM hdr.*/
      LF= 'a'x                                   /*define a comment separator  (in hdr).*/      /* ◄─── LF delimiters & comments*/
if c==LF  then do;  commentEND=pos(LF, y, 4)     /*point to the last char in the comment*/      /* ◄─── LF delimiters & comments*/
                    parse var  y   =(commentEND)  +1  width  height          #  pixels          /* ◄─── LF delimiters & comments*/
               end                                                                              /* ◄─── LF delimiters & comments*/
                                                 /* [↓]  has an alternative delimiter?  */      /* ◄─── LF delimiters & comments*/
z=pos(LF, height);  if z\==0  then parse var  height height    =(z)   +1     #  pixels          /* ◄─── LF delimiters & comments*/
z=pos(LF, #     );  if z\==0  then parse var  #      #         =(z)   +1        pixels          /* ◄─── LF delimiters & comments*/
chunk=4000                                       /*chunk size to be written at one time.*/
LenPixels= length(pixels)

  do j=0  for 256;  _=d2c(j);   @._=j;   @@.j=_  /*build two tables for fast conversions*/
  end   /*j*/

call charout oFID, ,  1                          /*set the position of the output file. */
call charout oFID, id || width height #' '       /*write the header followed by a blank.*/
!=1
    do until !>=LenPixels;            $=         /*$:      partial output string so far.*/
      do !=!  by 3  for chunk                    /*chunk:  # pixels converted at 1 time.*/
      parse var pixels  =(!)  r +1   g +1   b +1 /*obtain the next  RGB  of a PPM pixel.*/
      if r==''  then leave                       /*has the end─of─string been reached?  */
      _=(.2126*@.r  + .7152*@.g  + .0722*@.b )%1 /*an integer RGB greyscale of a pixel. */
      $=$  ||  @@._  ||  @@._  ||  @@._          /*lump (grey)  R G B  pixels together. */
      end   /*!*/                                /* [↑]  D2C  converts decimal ───► char*/
    call charout oFID, $                         /*write the next bunch of pixels.      */
    end     /*until*/

call charout oFID                                /*close the output file just to be safe*/
say 'File '       oFID       " was created."     /*stick a fork in it,  we're all done. */
output:
File  greyscale.ppm  was created.

Ruby

Extending Basic_bitmap_storage#Ruby

class Pixmap
  # '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

# create an image: a green cross on a blue background
colour_bitmap = Pixmap.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')

# then, convert to grayscale
Pixmap.open('testcross.ppm').to_grayscale!.save('testgray.ppm')

Rust

parser.rs:
use super::{Color, ImageFormat};
use std::str::from_utf8;
use std::str::FromStr;

pub fn parse_version(input: &[u8]) -> nom::IResult<&[u8], ImageFormat> {
    use nom::branch::alt;
    use nom::bytes::complete::tag;
    use nom::character::complete::line_ending;
    use nom::combinator::map;
    use nom::sequence::terminated;

    // starts with P3/P6 ends with a CR/LF
    terminated(
        alt((
            map(tag("P3".as_bytes()), |_| ImageFormat::P3),
            map(tag("P6".as_bytes()), |_| ImageFormat::P6),
        )),
        line_ending,
    )(input)
}

pub fn parse_image_attributes(input: &[u8]) -> nom::IResult<&[u8], (usize, usize, usize)> {
    use nom::character::complete::line_ending;
    use nom::character::complete::{digit1, space1};
    use nom::sequence::terminated;
    use nom::sequence::tuple;

    // 3 numbers separated by spaces ends with a CR/LF
    terminated(tuple((digit1, space1, digit1, space1, digit1)), line_ending)(input).map(
        |(next_input, result)| {
            (
                next_input,
                (
                    usize::from_str_radix(from_utf8(result.0).unwrap(), 10).unwrap(),
                    usize::from_str_radix(from_utf8(result.2).unwrap(), 10).unwrap(),
                    usize::from_str_radix(from_utf8(result.4).unwrap(), 10).unwrap(),
                ),
            )
        },
    )
}

pub fn parse_color_binary(input: &[u8]) -> nom::IResult<&[u8], Color> {
    use nom::number::complete::u8 as nom_u8;
    use nom::sequence::tuple;

    tuple((nom_u8, nom_u8, nom_u8))(input).map(|(next_input, res)| {
        (
            next_input,
            Color {
                red: res.0,
                green: res.1,
                blue: res.2,
            },
        )
    })
}

pub fn parse_data_binary(input: &[u8]) -> nom::IResult<&[u8], Vec<Color>> {
    use nom::multi::many0;
    many0(parse_color_binary)(input)
}

pub fn parse_color_ascii(input: &[u8]) -> nom::IResult<&[u8], Color> {
    use nom::character::complete::{digit1, space0, space1};
    use nom::sequence::tuple;

    tuple((digit1, space1, digit1, space1, digit1, space0))(input).map(|(next_input, res)| {
        (
            next_input,
            Color {
                red: u8::from_str(from_utf8(res.0).unwrap()).unwrap(),
                green: u8::from_str(from_utf8(res.2).unwrap()).unwrap(),
                blue: u8::from_str(from_utf8(res.4).unwrap()).unwrap(),
            },
        )
    })
}

pub fn parse_data_ascii(input: &[u8]) -> nom::IResult<&[u8], Vec<Color>> {
    use nom::multi::many0;
    many0(parse_color_ascii)(input)
}


lib.rs:
extern crate nom;
extern crate thiserror;
mod parser;

use std::default::Default;
use std::fmt;
use std::io::{BufWriter, Error, Write};
use std::ops::{Index, IndexMut};
use std::{fs::File, io::Read};
use thiserror::Error;

#[derive(Copy, Clone, Default, PartialEq, Debug)]
pub struct Color {
    pub red: u8,
    pub green: u8,
    pub blue: u8,
}

#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ImageFormat {
    P3,
    P6,
}

impl From<&str> for ImageFormat {
    fn from(i: &str) -> Self {
        match i.to_lowercase().as_str() {
            "p3" => ImageFormat::P3,
            "p6" => ImageFormat::P6,
            _ => unimplemented!("no other formats supported"),
        }
    }
}

impl fmt::Display for ImageFormat {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ImageFormat::P3 => {
                write!(f, "P3")
            }
            ImageFormat::P6 => {
                write!(f, "P6")
            }
        }
    }
}

#[derive(Error, Debug)]
pub enum ImageError {
    #[error("File not found")]
    FileNotFound,
    #[error("File not readable")]
    FileNotReadable,
    #[error("Invalid header information")]
    InvalidHeader,
    #[error("Invalid information in the data block")]
    InvalidData,
    #[error("Invalid max color information")]
    InvalidMaxColor,
    #[error("File is incomplete")]
    IncompleteFile,
    #[error("unknown data store error")]
    Unknown,
}
pub struct Image {
    pub format: ImageFormat,
    pub width: usize,
    pub height: usize,
    pub data: Vec<Color>,
}

impl Image {
    #[must_use]
    pub fn new(width: usize, height: usize) -> Self {
        Self {
            format: ImageFormat::P6,
            width,
            height,
            data: vec![Color::default(); width * height],
        }
    }

    pub fn fill(&mut self, color: Color) {
        for elem in &mut self.data {
            *elem = color;
        }
    }

    /// # Errors
    ///
    /// Will return `Error` if `filename` does not exist or the user does not have
    /// permission to write to it, or the write operation fails.
    pub fn write_ppm(&self, filename: &str) -> Result<(), Error> {
        let file = File::create(filename)?;
        let mut writer = BufWriter::new(file);
        writeln!(&mut writer, "{}", self.format.to_string())?;
        writeln!(&mut writer, "{} {} 255", self.width, self.height)?;
        match self.format {
            ImageFormat::P3 => {
                writer.write_all(
                    &self
                        .data
                        .iter()
                        .flat_map(|color| {
                            vec![
                                color.red.to_string(),
                                color.green.to_string(),
                                color.blue.to_string(),
                            ]
                        })
                        .collect::<Vec<String>>()
                        .join(" ")
                        .as_bytes(),
                )?;
            }
            ImageFormat::P6 => {
                writer.write_all(
                    &self
                        .data
                        .iter()
                        .flat_map(|color| vec![color.red, color.green, color.blue])
                        .collect::<Vec<u8>>(),
                )?;
            }
        }
        Ok(())
    }

    /// # Panics
    ///
    /// Panics if the format is not P6 or P3 PPM
    /// # Errors
    ///
    /// Will return `Error` if `filename` does not exist or the user does not have
    /// permission to read it or the read operation fails, or the file format does not
    /// match the specification
    pub fn read_ppm(filename: &str) -> Result<Image, ImageError> {
        let mut file = File::open(filename).map_err(|_| ImageError::FileNotFound)?;
        let mut data: Vec<u8> = Vec::new();
        file.read_to_end(&mut data)
            .map_err(|_| ImageError::FileNotReadable)?;

        let (i, format) = parser::parse_version(&data).map_err(|_| ImageError::InvalidHeader)?;
        let (i, (width, height, max_color)) =
            parser::parse_image_attributes(i).map_err(|_| ImageError::InvalidHeader)?;

        if max_color != 255 {
            return Err(ImageError::InvalidMaxColor);
        }

        let (_, data) = match format {
            ImageFormat::P3 => parser::parse_data_ascii(i).map_err(|_| ImageError::InvalidData)?,
            ImageFormat::P6 => parser::parse_data_binary(i).map_err(|_| ImageError::InvalidData)?,
        };

        if data.len() != height * width {
            return Err(ImageError::IncompleteFile);
        };

        Ok(Image {
            format,
            width,
            height,
            data,
        })
    }
}

impl Index<(usize, usize)> for Image {
    type Output = Color;

    fn index(&self, (x, y): (usize, usize)) -> &Color {
        &self.data[x + y * self.width]
    }
}

impl IndexMut<(usize, usize)> for Image {
    fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color {
        &mut self.data[x + y * self.width]
    }
}


use bitmap::Image;

// see read_ppm implementation in the bitmap library

pub fn main() {
    // read a PPM image, which was produced by the write-a-ppm-file task
    let image = Image::read_ppm("./test_image.ppm").unwrap();

    println!("Read using nom parsing:");
    println!("Format: {:?}", image.format);
    println!("Dimensions: {} x {}", image.height, image.width);
}

Scala

Uses the Basic Bitmap Storage and Grayscale Bitmap classes.

See also Task Write a PPM File for save code.

import scala.io._
import scala.swing._
import java.io._
import java.awt.Color
import javax.swing.ImageIcon

object Pixmap {
   private case class PpmHeader(format:String, width:Int, height:Int, maxColor:Int)
	
   def load(filename:String):Option[RgbBitmap]={
      implicit val in=new BufferedInputStream(new FileInputStream(filename))
      val header=readHeader
      if(header.format=="P6")
      {
         val bm=new RgbBitmap(header.width, header.height);
         for(y <- 0 until bm.height; x <- 0 until bm.width; c=readColor)
            bm.setPixel(x, y, c)
         return Some(bm)
      }
      None
   }

   private def readHeader(implicit in:InputStream)={
      var format=readLine
		
      var line=readLine
      while(line.startsWith("#"))   //skip comments
         line=readLine

      val parts=line.split("\\s")
      val width=parts(0).toInt
      val height=parts(1).toInt
      val maxColor=readLine.toInt
		
      new PpmHeader(format, width, height, maxColor)
   }

   private def readColor(implicit in:InputStream)=new Color(in.read, in.read, in.read)

   private def readLine(implicit in:InputStream)={
      var out=""
      var b=in.read
      while(b!=0xA){out+=b.toChar; b=in.read}
      out
   }
}

Usage:

object PixmapTest {
   def main(args: Array[String]): Unit = {
      val img=Pixmap.load("image.ppm").get
      val grayImg=BitmapOps.grayscale(img);
      Pixmap.save(grayImg, "image_gray.ppm")

      val mainframe=new MainFrame(){
         title="Test"
         visible=true
         contents=new Label(){
            icon=new ImageIcon(grayImg.image)
         }
      }
   }
}

Seed7

$ include "seed7_05.s7i";
  include "draw.s7i";
  include "color.s7i";

const func PRIMITIVE_WINDOW: getPPM (in string: fileName) is func
  result
    var PRIMITIVE_WINDOW: aWindow is PRIMITIVE_WINDOW.value;
  local
    var file: ppmFile is STD_NULL;
    var string: line is "";
    var integer: width is 0;
    var integer: height is 0;
    var integer: x is 0;
    var integer: y is 0;
    var color: pixColor is black;
  begin
    ppmFile := open(fileName, "r");
    if ppmFile <> STD_NULL then
      if getln(ppmFile) = "P6" then
        repeat
          line := getln(ppmFile);
        until line = "" or line[1] <> '#';
        read(ppmFile, width);
        readln(ppmFile, height);
        aWindow := newPixmap(width, height);
        for y range 0 to pred(height) do
          for x range 0 to pred(width) do
            pixColor.redLight   := ord(getc(ppmFile));
            pixColor.greenLight := ord(getc(ppmFile));
            pixColor.blueLight  := ord(getc(ppmFile));
          end for;
        end for;
      end if;
      close(ppmFile);
    end if;
  end func;

Tcl

Library: Tk

The actual PPM reader is built into the photo image engine:

package require Tk

proc readPPM {image file} {
    $image read $file -format ppm
}

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:

package require Tk

proc grayscaleFile {filename {newFilename ""}} {
    set buffer [image create photo]
    if {$newFilename eq ""} {set newFilename $filename}
    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 $newFilename -format ppm
    } finally {
        image delete $buffer
    }
}

However, the Tk library also has built-in the ability to convert code to grayscale directly during the saving of an image to a file, leading to this minimal solution:

package require Tk

proc grayscaleFile {filename {newFilename ""}} {
    set buffer [image create photo]
    if {$newFilename eq ""} {set newFilename $filename}
    try {
        $buffer read $filename -format ppm
        $buffer write $newFilename -format ppm -grayscale
    } finally {
        image delete $buffer
    }
}

UNIX Shell

Works with: ksh93

Ref: Bitmap#UNIX Shell

Add the following functions to the RGBColor_t type

    function setrgb {
        _.r=$1
        _.g=$2
        _.b=$3
    }
    function grayscale {
        integer x=$(( round( 0.2126*_.r + 0.7152*_.g + 0.0722*_.b ) ))
        _.r=$x
        _.g=$x
        _.b=$x
    }

Add the following function to the Bitmap_t type

    function grayscale {
        RGBColor_t c
        for ((y=0; y<_.height; y++)); do
            for ((x=0; x<_.width; x++)); do
                c.setrgb ${_.data[y][x]}
                c.grayscale
                _.data[y][x]=$(c.to_s)
            done
        done
    }

    function read {
        exec 4<"$1"
        typeset filetype
        read -u4 filetype
        if [[ $filetype != "P3" ]]; then
            print -u2 "error: I can only read P3 type PPM files"
        else
            read -u4 _.width _.height
            integer maxval
            read -u4 maxval
            integer x y r g b
            typeset -a bytes
            for ((y=0; y<_.height; y++)); do
                read -u4 -A bytes
                for ((x=0; x<_.width; x++)); do
                    r=${bytes[3*x+0]}
                    g=${bytes[3*x+1]}
                    b=${bytes[3*x+2]}
                    if (( r > maxval || g > maxval || b > maxval )); then
                        print -u2 "error: invalid color ($r $g $b), max=$maxval"
                        return 1
                    fi
                    _.data[y][x]="$r $g $b" 
                done 
            done
        fi
        exec 4<&-
    }

Now we can:

Bitmap_t c
c.read "$HOME/tmp/bitmap.ppm"
c.to_s

if [[ $(c.to_s) == $(cat "$HOME/tmp/bitmap.ppm") ]]; then
    echo looks OK
else
    echo something is wrong
fi

c.grayscale
c.to_s
c.write "$HOME/tmp/bitmap_g.ppm"

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

Wren

Library: DOME

This assumes that Lenna100.jpg, a 512 x 512 color image of the eponymous lady, has already been converted to Lenna100.ppm using a variation of the 'Write a PPM file' task.

import "graphics" for Canvas, ImageData, Color
import "dome" for Window, Process
import "io" for FileSystem

class Bitmap {
    construct new(fileName, fileName2, width, height) {
        Window.title = "Bitmap - read PPM file"
        Window.resize(width, height)
        Canvas.resize(width, height)
        _w = width
        _h = height
        _fn2 = fileName2
        loadPPMFile(fileName)
    }

    init() {
        toGrayScale()
        // display images side by side
        _bmp.draw(0, 0)
        _bmp2.draw(536, 0)
        // save gray scale image to file
        _bmp2.saveToFile(_fn2)
    }

    loadPPMFile(fileName) {
        var ppm = FileSystem.load(fileName)
        var count = ppm.count // ensure file is fully loaded before proceeding 
        if (ppm[0..1] != "P6") {
            System.print("The loaded file is not a P6 file.")
            Process.exit()
        }
        var lines = ppm.split("\n")
        if (Num.fromString(lines[2]) > 255) {
            System.print("The maximum color value can't exceed 255.")
            Process.exit()
        }
        var wh = lines[1].split(" ")
        var w = Num.fromString(wh[0])
        var h = Num.fromString(wh[1])
        _bmp = ImageData.create(fileName, w, h)
        var bytes = ppm.bytes
        var i = bytes.count - 3 * w * h
        for (y in 0...h) {
            for (x in 0...w) {
                var r = bytes[i]
                var g = bytes[i+1]
                var b = bytes[i+2]
                var c = Color.rgb(r, g, b)
                pset(x, y, c)
                i = i + 3
            }
        }
    }

    toGrayScale() {
        _bmp2 = ImageData.create("gray scale", _bmp.width, _bmp.height)
        for (x in 0..._bmp.width) {
            for (y in 0..._bmp.height) {
                var c1 = _bmp.pget(x, y)
                var lumin = (0.2126 * c1.r + 0.7152 * c1.g + 0.0722 * c1.b).floor
                var c2 = Color.rgb(lumin, lumin,lumin, c1.a)
                _bmp2.pset(x, y, c2)
            }
        }
    }

    pset(x, y, col) { _bmp.pset(x, y, col) }

    pget(x, y) { _bmp.pget(x, y) }

    update() {}

    draw(alpha) {}
}

var Game = Bitmap.new("Lenna100.ppm", "Lenna100_gs.jpg", 1048, 512)

XPL0

The simplicity of redirecting an input file on the command line doesn't work for files that contain binary data ($03 will abort a program). Image files larger than 1280x1024 are clipped to the screen dimensions.

include c:\cxpl\codes;          \intrinsic 'code' declarations

func OpenInFile;                \Open for input the file typed on command line
int  CpuReg, Handle;
char CmdTail($80);
[CpuReg:= GetReg;
Blit(CpuReg(11), $81, CpuReg(12), CmdTail, $7F);       \get copy of command line
Trap(false);                    \turn off error trapping
Handle:= FOpen(CmdTail, 0);     \open named file for input
FSet(Handle, ^I);               \assign file to input device 3
OpenI(3);                       \initialize input buffer pointers
if GetErr then return false;
Trap(true);
return true;
];

int  C, X, Y, Width, Height, Max, Lum;
real Red, Green, Blue;
[if not OpenInFile then [Text(0, "File not found");  exit];
if ChIn(3)#^P or ChIn(3)#^6 then [Text(0, "Not P6 PPM file");  exit];
repeat  loop    [C:= ChIn(3);
                if C # ^# then quit;
                repeat C:= ChIn(3) until C=$0A\EOL\;
                ];
until   C>=^0 & C<=^9;
Backup;                         \back up so IntIn re-reads first digit
Width:=  IntIn(3);              \(skips any whitespace)
Height:= IntIn(3);
Max:=    IntIn(3) + 1;          \(255/15=17; 256/16=16)
case of
  Width<= 640 & Height<=480: SetVid($112);
  Width<= 800 & Height<=600: SetVid($115);
  Width<=1024 & Height<=768: SetVid($118)
other SetVid($11B);             \1280x1024
for Y:= 0 to Height-1 do
    for X:= 0 to Width-1 do
        [Red  := float(ChIn(3)*256/Max) * 0.21; \convert color to grayscale
         Green:= float(ChIn(3)*256/Max) * 0.72;
         Blue := float(ChIn(3)*256/Max) * 0.07;
         Lum:= fix(Red) + fix(Green) + fix(Blue);
         Point(X, Y, Lum<<16 + Lum<<8 + Lum);
        ];
X:= ChIn(1);                    \wait for keystroke
SetVid(3);                      \restore normal text display
]

Yabasic

sub readPPM(f$)
	local ff, x, y, t$, dcol$, wid, hei

	if f$ = "" print "No PPM file name indicate." : return false

	ff = open (f$, "rb")
	if not ff print "File ", f$, " not found."  : return false

	input #ff t$, wid, hei, dcol$

	if t$ = "P6" then
		open window wid, hei
	
		for x = 0 to hei - 1
		 	for y = 0 to wid - 1
		 		color peek(#ff), peek(#ff), peek(#ff)
		 		dot y, x
		 	next y
		next x
		
		close #ff
	else
		print "File is NOT PPM P6 type." : return false
	end if
	
	return true
end sub

zkl

Translation of: FBSL

Read a colored PPM file, convert it (in place) to grayscale and write the new image back to disk under a different name. Sanity checks are omitted.

I used a slightly different image from what is shown, but the results are the same.

 
//24-bpp P6 PPM solution:
image:=File("lena.ppm","rb").read();
start:=image.find("\n255\n")+5;   // Get sizeof PPM header
 
foreach n in ([start..image.len()-1,3]){  //  Transform color triplets
   r,g,b:=image[n,3];  // Read colors stored in RGB order
   l:=(0.2126*r + 0.7152*g + 0.0722*b).toInt(); // Derive luminance
   image[n,3]=T(l,l,l);
}

File("lenaGrey.ppm","wb").write(image);