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](http://static.miraheze.org/rosettacodewiki/thumb/b/ba/Rcode-button-task-crushed.png/64px-Rcode-button-task-crushed.png)
You are encouraged to solve this task according to the task description, using any language you may know.
Task: Use write ppm file solution and grayscale image solution with this one in order to convert a color image to grayscale one.
Ada
<lang ada>with Ada.Characters.Latin_1; use Ada.Characters.Latin_1; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO;
function Get_PPM (File : File_Type) return Image is
use Ada.Characters.Latin_1; use Ada.Integer_Text_IO;
function Get_Line return String is -- Skips comments Byte : Character; Buffer : String (1..80); begin loop for I in Buffer'Range loop Character'Read (Stream (File), Byte); if Byte = LF then exit when Buffer (1) = '#'; return Buffer (1..I - 1); end if; Buffer (I) := Byte; end loop; if Buffer (1) /= '#' then raise Data_Error; end if; end loop; end Get_Line;
Height : Integer; Width : Integer;
begin
if Get_Line /= "P6" then raise Data_Error; end if; declare Line : String := Get_Line; Start : Integer := Line'First; Last : Positive; begin Get (Line, Width, Last); Start := Start + Last; Get (Line (Start..Line'Last), Height, Last); Start := Start + Last; if Start <= Line'Last then raise Data_Error; end if; if Width < 1 or else Height < 1 then raise Data_Error; end if; end; if Get_Line /= "255" then raise Data_Error; end if; declare Result : Image (1..Height, 1..Width); Buffer : String (1..Width * 3); Index : Positive; begin for I in Result'Range (1) loop String'Read (Stream (File), Buffer); Index := Buffer'First; for J in Result'Range (2) loop Result (I, J) := ( R => Luminance (Character'Pos (Buffer (Index))), G => Luminance (Character'Pos (Buffer (Index + 1))), B => Luminance (Character'Pos (Buffer (Index + 2))) ); Index := Index + 3; end loop; end loop; return Result; end;
end Get_PPM;</lang> The implementation propagates Data_Error when the file format is incorrect. End_Error is propagated when the file end is prematurely met. The following example illustrates conversion of a color file to grayscale. <lang ada>declare
F1, F2 : File_Type;
begin
Open (F1, In_File, "city.ppm"); Create (F2, Out_File, "city_grayscale.ppm"); Put_PPM (F2, Color (Grayscale (Get_PPM (F1)))); Close (F1); Close (F2);
end;</lang>
AutoHotkey
Only ppm6 files supported.
<lang AutoHotkey>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</lang>
BBC BASIC
<lang bbcbasic> 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</lang>
C
It is up to the caller to open the file and pass the handler to the function. So this code can be used in Read image file through a pipe without modification. It only understands the P6 file format.
Interface:
<lang c>image get_ppm(FILE *pf);</lang>
Implementation:
<lang c>#include "imglib.h"
- 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; }
}</lang>
The following acts as a filter to convert a PPM file read from standard input into a PPM gray image, and it outputs the converted image to standard output (see Grayscale image, Write ppm file, and Raster graphics operations in general):
<lang c>#include <stdio.h>
- include "imglib.h"
int main() {
image source; grayimage idest; source = get_ppm(stdin); idest = tograyscale(source); free_img(source); source = tocolor(idest); output_ppm(stdout, source); free_img(source); free_img((image)idest); return 0;
}</lang>
C#
Tested with this solution.
<lang csharp>using System.IO; class PPMReader {
public static Bitmap ReadBitmapFromPPM(string file) { var reader = new BinaryReader(new FileStream(file, FileMode.Open)); if (reader.ReadChar() != 'P' || reader.ReadChar() != '6') return null; reader.ReadChar(); //Eat newline string widths = "", heights = ""; char temp; while ((temp = reader.ReadChar()) != ' ') widths += temp; while ((temp = reader.ReadChar()) >= '0' && temp <= '9') heights += temp; if (reader.ReadChar() != '2' || reader.ReadChar() != '5' || reader.ReadChar() != '5') return null; reader.ReadChar(); //Eat the last newline int width = int.Parse(widths), height = int.Parse(heights); Bitmap bitmap = new Bitmap(width, height); //Read in the pixels for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) bitmap.SetPixel(x, y, new Bitmap.Color() { Red = reader.ReadByte(), Green = reader.ReadByte(), Blue = reader.ReadByte() }); return bitmap; }
}</lang>
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.
<lang lisp> (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) </lang> To read the feep.ppm file as shown on the description page for the ppm format use: <lang lisp> (read-ppm-image "feep.ppm") </lang>
D
The Image module contains a loadPPM6 function to load binary PPM images.
E
<lang e>def chr := <import:java.lang.makeCharacter>.asChar
def readPPM(inputStream) {
# Proper native-to-E stream IO facilities have not been designed and # implemented yet, so we are borrowing Java's. Poorly. This *will* be # improved eventually. # Reads one header token, skipping comments and whitespace, and exactly # one trailing whitespace character def readToken() { var token := "" var c := chr(inputStream.read()) while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#') { if (c == '#') { while (c != '\n') { c := chr(inputStream.read()) } } # skip over initial whitespace c := chr(inputStream.read()) } while (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { if (c == '#') { while (c != '\n') { c := chr(inputStream.read()) } } else { token += E.toString(c) c := chr(inputStream.read()) } } return token }
# Header require(readToken() == "P6") def width := __makeInt(readToken()) def height := __makeInt(readToken()) def maxval := __makeInt(readToken())
def size := width * height * 3
# Body # See Basic bitmap storage for the definition and origin of sign() def data := <elib:tables.makeFlexList>.fromType(<type:java.lang.Byte>, size) if (maxval >= 256) { for _ in 1..size { data.push(sign((inputStream.read() * 256 + inputStream.read()) * 255 // maxval)) } } else { for _ in 1..size { data.push(sign(inputStream.read() * 255 // maxval)) } }
def image := makeImage(width, height) image.replace(data.snapshot()) return image
}</lang>Note: As of this writing the grayscale image task has not been implemented, so the task code (below) won't actually run yet. But readPPM above has been tested separately.
<lang e>def readPPMTask(inputFile, outputFile) {
makeGrayscale \ .fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \ .toColor() \ .writePPM(<import:java.io.makeFileOutputStream>(outputFile))
}</lang>
Euphoria
<lang 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</lang>
Converting an image to grayscale: <lang euphoria>sequence image image = read_ppm("image.ppm") image = to_gray(image) image = to_color(image) write_ppm("image_gray.ppm",image)</lang>
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:
<lang qbasic>#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</lang>
Forth
<lang forth>: read-ppm { fid -- bmp }
pad dup 80 fid read-line throw 0= abort" Partial line" s" P6" compare abort" Only P6 supported." pad dup 80 fid read-line throw 0= abort" Partial line" 0. 2swap >number 1 /string \ skip space 0. 2swap >number 2drop drop nip ( w h ) bitmap { bmp } pad dup 80 fid read-line throw 0= abort" Partial line" s" 255" compare abort" Only 8-bits per color channel supported" 0 pad ! bmp bdim 0 do dup 0 do pad 3 fid read-file throw 3 - abort" Not enough pixel data in file" pad @ i j bmp b! loop loop drop bmp ;
\ testing round-trip 4 3 bitmap value test red test bfill green 1 2 test b!
s" red.ppm" w/o create-file throw test over write-ppm close-file throw
s" red.ppm" r/o open-file throw dup read-ppm value test2 close-file throw
- bsize ( bmp -- len ) bdim * pixels bdata ;
test dup bsize test2 dup bsize compare . \ 0 if identical</lang>
Fortran
(This function is part of module RCImageIO, see Write ppm file)
<lang fortran>subroutine read_ppm(u, img)
integer, intent(in) :: u type(rgbimage), intent(out) :: img integer :: i, j, ncol, cc character(2) :: sign character :: ccode img%width = 0 img%height = 0 nullify(img%red) nullify(img%green) nullify(img%blue)
read(u, '(A2)') sign read(u, *) img%width, img%height read(u, *) ncol
write(0,*) sign write(0,*) img%width, img%height write(0,*) ncol
if ( ncol /= 255 ) return
call alloc_img(img, img%width, img%height)
if ( valid_image(img) ) then do j=1, img%height do i=1, img%width read(u, '(A1)', advance='no', iostat=status) ccode cc = iachar(ccode) img%red(i,j) = cc read(u, '(A1)', advance='no', iostat=status) ccode cc = iachar(ccode) img%green(i,j) = cc read(u, '(A1)', advance='no', iostat=status) ccode cc = iachar(ccode) img%blue(i,j) = cc end do end do end if
end subroutine read_ppm</lang>
Notes:
- doing formatted I/O with Fortran is a pain... And unformatted does not mean free; Fortran2003 has streams, but they are not implemented (yet) in GNU Fortran compiler. Here (as in the write part) I've tried to handle the PPM format through formatted I/O. The tests worked but I have not tried still everything.
- comments after the first line are not handled
Go
<lang 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()
}</lang> Demonstration program, also demonstrating functions from task Grayscale image: <lang go>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) }
}</lang>
Haskell
The definition of Bitmap.Netpbm.readNetpbm is given here. <lang haskell>import Bitmap import Bitmap.RGB import Bitmap.Gray import Bitmap.Netpbm
import Control.Monad import Control.Monad.ST
main =
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>= stToIO . toGrayImage >>= writeNetpbm "new.pgm"</lang>
The above writes a PGM, not a PPM, since the image being output is in grayscale. If you actually want a gray PPM, convert the Image RealWorld Gray back to an Image RealWorld RGB first: <lang haskell>main =
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>= stToIO . (toRGBImage <=< toGrayImage) >>= writeNetpbm "new.ppm"</lang>
J
Solution:
Uses makeRGB from Basic bitmap storage.
<lang j>require 'files'
readppm=: monad define
dat=. fread y NB. read from file 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
)</lang>
Example:
Using utilities and file from Grayscale image and Write ppm file.
Writes a gray PPM file (a color format) which is bigger than necessary. A PGM file would be more appropriate.
<lang j>myimg=: readppm jpath '~temp/myimg.ppm'
myimgGray=: toColor toGray myimg
myimgGray writeppm jpath '~temp/myimgGray.ppm'</lang>
Julia
<lang Julia> using Color, Images, FixedPointNumbers
const M_RGB_Y = reshape(Color.M_RGB_XYZ[2,:], 3)
function rgb2gray(img::Image)
g = red(img)*M_RGB_Y[1] + green(img)*M_RGB_Y[2] + blue(img)*M_RGB_Y[3] g = clamp(g, 0.0, 1.0) return grayim(g)
end
ima = imread("bitmap_read_ppm_in.ppm") imb = convert(Image{Gray{Ufixed8}}, ima) imwrite(imb, "bitmap_read_ppm_out.png") </lang>
- Output:
Input and output images in PNG format. The PPM input file was created using a variant of the Julia PPM/Write solution to provide something reasonably colorful.
Mathematica/ Wolfram Language
<lang Mathematica>Import["file.ppm","PPM"] </lang>
Lua
<lang 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</lang>
Nim
<lang nim>import strutils
proc readPPM(f: TFile): Image =
if f.readLine != "P6": raise newException(E_base, "Invalid file format")
var line = "" while f.readLine(line): if line[0] != '#': break
var parts = line.split(" ") result = img(parseInt parts[0], parseInt parts[1])
if f.readLine != "255": raise newException(E_base, "Invalid file format")
var arr: array[256, int8] read = f.readBytes(arr, 0, 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.readBytes(arr, 0, 256)</lang>
OCaml
<lang ocaml>let read_ppm ~filename =
let ic = open_in filename in let line = input_line ic in if line <> "P6" then invalid_arg "not a P6 ppm file"; let line = input_line ic in let line = try if line.[0] = '#' (* skip comments *) then input_line ic else line with _ -> line in let width, height = Scanf.sscanf line "%d %d" (fun w h -> (w, h)) in let line = input_line ic in if line <> "255" then invalid_arg "not a 8 bit depth image"; let all_channels = let kind = Bigarray.int8_unsigned and layout = Bigarray.c_layout in Bigarray.Array3.create kind layout 3 width height in let r_channel = Bigarray.Array3.slice_left_2 all_channels 0 and g_channel = Bigarray.Array3.slice_left_2 all_channels 1 and b_channel = Bigarray.Array3.slice_left_2 all_channels 2 in for y = 0 to pred height do for x = 0 to pred width do r_channel.{x,y} <- (input_byte ic); g_channel.{x,y} <- (input_byte ic); b_channel.{x,y} <- (input_byte ic); done; done; close_in ic; (all_channels, r_channel, g_channel, b_channel)</lang>
and converting a given color file to grayscale: <lang ocaml>let () =
let img = read_ppm ~filename:"logo.ppm" in let img = to_color(to_grayscale ~img) in output_ppm ~oc:stdout ~img;
- </lang>
sending the result to stdout allows to see the result without creating a temporary file sending it through a pipe to the display utility of ImageMagick:
ocaml script.ml | display -
Oz
The read function in module "BitmapIO.oz"
:
<lang 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</lang>
The actual task: <lang oz>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"}</lang>
Perl
<lang perl>#! /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;</lang>
Phix
Based on Euphoria, requires write_ppm() from Write_a_PPM_file, to_gray from Grayscale_image <lang Phix>function read_ppm(sequence 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) Template:Dimx,dimy = scanf(line,"%d %d%s") line = gets(fn) Template: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
sequence img = read_ppm("Lena.ppm")
img = to_gray(img) write_ppm("LenaGray.ppm",img)</lang>
PicoLisp
<lang 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 ) ) ) ) ) )</lang>
Read a color image "img.ppm", convert and write to "img.pgm": <lang PicoLisp>(pgmWrite (ppm->pgm (ppmRead "img.ppm")) "img.pgm")</lang>
PL/I
<lang 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;</lang>
PureBasic
<lang 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</lang>
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 <lang PureBasic>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</lang>
Python
Extending the example given here <lang python># 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
</lang>
Racket
<lang 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))
</lang>
Ruby
Extending Basic_bitmap_storage#Ruby <lang 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')</lang>
Scala
Uses the Basic Bitmap Storage and Grayscale Bitmap classes.
See also Task Write a PPM File for save code.
<lang scala>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 }
}</lang>
Usage: <lang scala>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) } } }
}</lang>
Seed7
<lang 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.red_part := ord(getc(ppmFile)); pixColor.green_part := ord(getc(ppmFile)); pixColor.blue_part := ord(getc(ppmFile)); end for; end for; end if; close(ppmFile); end if; end func;</lang>
Tcl
The actual PPM reader is built into the photo image engine: <lang tcl>package require Tk
proc readPPM {image file} {
$image read $file -format ppm
}</lang>
Thus, to read a PPM, convert it to grayscale, and write it back out again becomes this (which requires Tcl 8.6 for try
/finally
); the PPM reader and writer are inlined because they are trivial at the script level:
<lang tcl>package require Tk
proc grayscaleFile {filename {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 }
}</lang>
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: <lang tcl>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 }
}</lang>
UNIX Shell
Ref: Bitmap#UNIX Shell
Add the following functions to the RGBColor_t type <lang bash> 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 }</lang>
Add the following function to the Bitmap_t type <lang bash> 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<&- }</lang>
Now we can: <lang bash>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"</lang>
Vedit macro language
<lang vedit>// 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</lang>
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. <lang vedit>// 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</lang>
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.
<lang XPL0>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 ]</lang>
zkl
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.
<lang zkl>//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);</lang>