# Bitmap/Read a PPM file

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Bitmap/Read a PPM file
You are encouraged to solve this task according to the task description, using any language you may know.

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: 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 = BVec3

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

CHAR ARRAY format BYTE dev)
CHAR ARRAY line(255)

WHILE max=0
DO
InputSD(dev,line)
IF line(0)>0 AND line(1)#'# THEN
IF SCompare(format,format)#0 THEN
Break()
FI
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)
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)
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("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)
RETURN```
Output:
```Loading D:PPM6.PPM...

Converting RGB to grayscale...

Saving D:PPM2.PPM...

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

```with Ada.Characters.Latin_1;  use Ada.Characters.Latin_1;

function Get_PPM (File : File_Type) return Image is

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"

fn {a : t@ype}
(* 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`.

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

(* 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. *)

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

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 {}
: 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 {}

extern fn {}

extern fn {}

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
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 {}
| ~ 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
get_next_single_byte inpf
in
end
else
let
implement read_general\$width<> () = w
implement read_general\$height<> () = h
implement read_general\$maxval<> () = maxval
implement
get_next_double_byte inpf
in
end
end
end

implement {}
| ~ 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
get_next_integer inpf
in
end
end

implement {}
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

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

(* 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.

should give you a program named "bitmap_read_ppm_task_program". *)

(*

##myatsccdef=\
patscc -std=gnu2x -g -O2 -DATS_MEMALLOC_LIBC \
-o \$fname(\$1) \$1 \

*)

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"

image get_ppm(FILE *pf)
{
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;
{
public static Bitmap ReadBitmapFromPPM(string file)
{
var reader = new BinaryReader(new FileStream(file, FileMode.Open));
return null;
string widths = "", heights = "";
char temp;
while ((temp = reader.ReadChar()) != ' ')
widths += temp;
while ((temp = reader.ReadChar()) >= '0' && temp <= '9')
heights += temp;
return null;
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()
{
});
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))

(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

(with-open-file (s file :direction :input)
(do ((failure-count 0 (1+ failure-count))
(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))))))))

(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))))))
(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))
file-pos
width
height
#'(lambda (w h val)
(setf (rgb-pixel image w h) val))
#'(lambda (stream)
image))
((string= "P3" image-type)
(with-open-file (stream file :direction :input)
file-pos
width
height
#'(lambda (w h val)
(setf (rgb-pixel image w h) val))
#'(lambda (stream)
image))
(t 'unsupported))
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;
ppm: TMemoryStream;
begin
ppm := TMemoryStream.Create;
try
Header := Format('P6'#10'%d %d'#10'255'#10, [Self.Width, Self.Height]);

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;

begin
end;

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

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

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

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

exit;

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
// Save as ppm
SaveAsPPM('Output.ppm');

// Load as ppm and convert in grayscale

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

Free;
end;
end.
```

## E

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

# 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
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
}
while (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
if (c == '#') {
while (c != '\n') { c := chr(inputStream.read()) }
} else {
token += E.toString(c)
}
}
}

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 \
.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).

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

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

% 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
{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)},
#bitmap{pixels=Pixels, shape=Shape}.

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

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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
[<<"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

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 = 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"
bmp bdim
0 do
dup 0 do
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, *) img%width, img%height

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
cc = iachar(ccode)
img%red(i,j) = cc
cc = iachar(ccode)
img%green(i,j) = cc
cc = iachar(ccode)
img%blue(i,j) = cc
end do
end do
end if

```

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

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
}
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])"
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)
}
}
```

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

```import Bitmap
import Bitmap.RGB
import Bitmap.Gray
import Bitmap.Netpbm

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'

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) );
if ( header1 != 'P' || header2 != '6' || header3 != END_OF_LINE) {
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 ) {
throw new IllegalArgumentException("Maximum color value is outside the range 0..255");
}

// Remove any comments before reading data
while ( reader.read() == START_OF_COMMENT ) {
}

BasicBitmapStorage bitmap = new BasicBitmapStorage(width, height);

byte[] buffer = new byte[width * 3];
for ( int y = 0; y < height; y++ ) {
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);
}
}

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

## Julia

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

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) {
if (buffer[0].toChar() == '#') {
skipComment(buffer)
}
else {
break
}
}
val bbs = BasicBitmapStorage(width, height)
buffer = ByteArray(width * 3)
var y = 0
while (y < height) {
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?
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 {
{
}
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.

var
arr: array[256, int8]
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
inc pos

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)
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
%% Write
define
F = {New Open.file init(name:Filename)}

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

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
B
finally
{F close}
end
end

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```

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

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

; Author Roger Rösch (Nickname Macros)
IDFile = ReadFile(#PB_Any, file\$)
If IDFile
If CreateImage(Image, 1, 1)
ReadString(IDFile) ; skip comment
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)
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)
; 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"
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)

(parameterize ([current-input-port port])
(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 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,
(
),
)
},
)
}

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 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 {
FileNotFound,
#[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();

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

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)

implicit val in=new BufferedInputStream(new FileInputStream(filename))
{
for(y <- 0 until bm.height; x <- 0 until bm.width; c=readColor)
bm.setPixel(x, y, c)
return Some(bm)
}
None
}

val parts=line.split("\\s")
val width=parts(0).toInt
val height=parts(1).toInt

new PpmHeader(format, width, height, maxColor)
}

private def readLine(implicit in:InputStream)={
var out=""
out
}
}
```

Usage:

```object PixmapTest {
def main(args: Array[String]): Unit = {
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] <> '#';
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

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
}

exec 4<"\$1"
typeset filetype
if [[ \$filetype != "P3" ]]; then
print -u2 "error: I can only read P3 type PPM files"
else
read -u4 _.width _.height
integer 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.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.

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

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

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

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: