Bitmap/Read a PPM file: Difference between revisions
(Add Seed7 example) |
m (omissions) |
||
Line 1,337:
return</lang>
{{omit from|AWK}}
{{omit from|Lotus 123 Macro Scripting}}
{{omit from|PARI/GP}}
|
Revision as of 20:37, 17 September 2011
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.
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>
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, shoule 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>
D
This example uses storage defined on Basic bitmap storage problem page.
This is wrap-around defined storage, P6 binary mode.
<lang D>import tango.core.Exception; import tango.io.FileConduit; import tango.io.MappedBuffer; import tango.io.stream.LineStream; import tango.io.protocol.Reader; import tango.text.convert.Integer;
class P6Image {
class BadInputException : Exception { this() { super("Bad file format"); } } class NoImageException : Exception { this() { super("No image data"); } } static const char[] type = "P6"; MappedBuffer fileBuf; ubyte _maxVal, gotImg;
public:
RgbBitmap bitmap;
this (FileConduit input) { fileBuf = new MappedBuffer(input); if (processHeader(new LineInput(fileBuf))) throw new BadInputException;
if (processData(fileBuf)) throw new BadInputException; }
ubyte maxVal() { return _maxVal; }
int processHeader(LineInput li) { char[] line; uint eaten;
li.readln(line); if (line != type) return 1;
li.readln(line); // skip comment lines while (line.length && line[0] == '#') li.readln(line); auto width = parse(line, 0, &eaten); auto height = parse(line[eaten..$], 0, &eaten); if (!eaten || width > 0xffff_ffff || height > 0xffff_ffff) return 1;
li.readln(line); auto temp = parse(line, 0, &eaten); if (!eaten || temp > 255) return 1; _maxVal = temp;
bitmap = RgbBitmap(width, height);
gotImg = 1; return 0; }
int processData(MappedBuffer mb) { if (! gotImg) throw new NoImageException; mb.fill(bitmap.data); return 0; }
}</lang>
Reading from file: <lang D>auto p6 = new P6Image(new FileConduit("image.ppm"));</lang>
E
<lang e>def chr := <import:java.lang.makeCharacter>.asChar
def readPPM(inputStream) {
# Proper native-to-E stream IO facilities have not been designed and # implemented yet, so we are borrowing Java's. Poorly. This *will* be # improved eventually. # Reads one header token, skipping comments and whitespace, and exactly # one trailing whitespace character def readToken() { var token := "" var c := chr(inputStream.read()) while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#') { if (c == '#') { while (c != '\n') { c := chr(inputStream.read()) } } # skip over initial whitespace c := chr(inputStream.read()) } while (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { if (c == '#') { while (c != '\n') { c := chr(inputStream.read()) } } else { token += E.toString(c) c := chr(inputStream.read()) } } return token }
# Header require(readToken() == "P6") def width := __makeInt(readToken()) def height := __makeInt(readToken()) def maxval := __makeInt(readToken())
def size := width * height * 3
# Body # See Basic bitmap storage for the definition and origin of sign() def data := <elib:tables.makeFlexList>.fromType(<type:java.lang.Byte>, size) if (maxval >= 256) { for _ in 1..size { data.push(sign((inputStream.read() * 256 + inputStream.read()) * 255 // maxval)) } } else { for _ in 1..size { data.push(sign(inputStream.read() * 255 // maxval)) } }
def image := makeImage(width, height) image.replace(data.snapshot()) return image
}</lang>Note: As of this writing the grayscale image task has not been implemented, so the task code (below) won't actually run yet. But readPPM above has been tested separately.
<lang e>def readPPMTask(inputFile, outputFile) {
makeGrayscale \ .fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \ .toColor() \ .writePPM(<import:java.io.makeFileOutputStream>(outputFile))
}</lang>
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>
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 (
"io" "io/ioutil" "os" "regexp" "strconv"
)
// ReadFrom constructs a Bitmap object from an io.Reader. func ReadPpmFrom(r io.Reader) (b *Bitmap, err os.Error) {
var all []byte all, err = ioutil.ReadAll(r) if err != nil { return } bss := rxHeader.FindSubmatch(all) if bss == nil { return nil, os.NewError("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, os.NewError("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 := bss[12] 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 os.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>
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>
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>
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>
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>
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>
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>
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>