Bitmap/Read a PPM file: Difference between revisions
Alextretyak (talk | contribs) m (→{{header|11l}}: BVec3) |
|||
(90 intermediate revisions by 38 users not shown) | |||
Line 1: | Line 1: | ||
{{task|Raster graphics operations}} |
|||
[[Category:Input Output]] |
[[Category:Input Output]] |
||
[[Category:E examples needing attention]] |
|||
{{task|Raster graphics operations}} |
|||
Using the data storage type defined [[Basic_bitmap_storage|on this page]] for raster images, read an image from a PPM file (binary P6 prefered). |
Using the data storage type defined [[Basic_bitmap_storage|on this page]] for raster images, read an image from a PPM file (binary P6 prefered). |
||
Line 6: | Line 7: | ||
'''Task''': Use [[write ppm file]] solution and [[grayscale image]] solution with this one in order to convert a color image to grayscale one. |
'''Task''': Use [[write ppm file]] solution and [[grayscale image]] solution with this one in order to convert a color image to grayscale one. |
||
=={{header|11l}}== |
|||
{{trans|Python}} |
|||
<syntaxhighlight lang="11l">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())</syntaxhighlight> |
|||
{{out}} |
|||
<pre> |
|||
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 |
|||
</pre> |
|||
=={{header|Action!}}== |
|||
Part of the task responsible for conversion from RGB color image into a grayscale image can be found in the module [http://www.rosettacode.org/wiki/Category:Action!_Bitmap_tools#RGB2GRAY.ACT RGB2GRAY.ACT]. File D:PPM6.PPM can be generated by task [http://www.rosettacode.org/wiki/Bitmap/Write_a_PPM_file#Action.21 Bitmap/Write a PPM file]. |
|||
{{libheader|Action! Bitmap tools}} |
|||
{{libheader|Action! Tool Kit}} |
|||
<syntaxhighlight lang="action!">INCLUDE "H6:RGB2GRAY.ACT" ;from task Grayscale image |
|||
PROC DecodeSize(CHAR ARRAY s BYTE POINTER width,height) |
|||
BYTE i |
|||
width^=ValB(s) |
|||
i=1 |
|||
WHILE i<=s(0) AND s(i)#32 |
|||
DO |
|||
s(i)=32 |
|||
i==+1 |
|||
OD |
|||
height^=ValB(s) |
|||
RETURN |
|||
PROC LoadHeader(RgbImage POINTER img |
|||
CHAR ARRAY format BYTE dev) |
|||
CHAR ARRAY line(255) |
|||
BYTE header,size,max,width,height |
|||
header=0 size=0 max=0 |
|||
WHILE max=0 |
|||
DO |
|||
InputSD(dev,line) |
|||
IF line(0)>0 AND line(1)#'# THEN |
|||
IF header=0 THEN |
|||
IF SCompare(format,format)#0 THEN |
|||
Break() |
|||
FI |
|||
header=1 |
|||
ELSEIF size=0 THEN |
|||
DecodeSize(line,@width,@height) |
|||
IF width=0 OR height=0 THEN |
|||
Break() |
|||
FI |
|||
img.w=width img.h=height |
|||
size=1 |
|||
ELSEIF max=0 THEN |
|||
max=ValB(line) |
|||
IF max#255 THEN |
|||
Break() |
|||
FI |
|||
FI |
|||
FI |
|||
OD |
|||
RETURN |
|||
PROC LoadPPM6(RgbImage POINTER img CHAR ARRAY path) |
|||
BYTE dev=[1],x,y |
|||
RGB c |
|||
Close(dev) |
|||
Open(dev,path,4) |
|||
LoadHeader(img,"P6",dev) |
|||
FOR y=0 TO img.h-1 |
|||
DO |
|||
FOR x=0 TO img.w-1 |
|||
DO |
|||
c.r=GetD(dev) |
|||
c.g=GetD(dev) |
|||
c.b=GetD(dev) |
|||
SetRgbPixel(img,x,y,c) |
|||
OD |
|||
OD |
|||
Close(dev) |
|||
RETURN |
|||
PROC SaveHeader(GrayImage POINTER img |
|||
CHAR ARRAY format BYTE dev) |
|||
PrintDE(dev,format) |
|||
PrintBD(dev,img.w) |
|||
PutD(dev,32) |
|||
PrintBDE(dev,img.h) |
|||
PrintBDE(dev,255) |
|||
RETURN |
|||
PROC SavePPM2(RgbImage POINTER img CHAR ARRAY path) |
|||
BYTE dev=[1],x,y,c |
|||
Close(dev) |
|||
Open(dev,path,8) |
|||
SaveHeader(img,"P2",dev) |
|||
FOR y=0 TO img.h-1 |
|||
DO |
|||
FOR x=0 TO img.w-1 |
|||
DO |
|||
c=GetGrayPixel(img,x,y) |
|||
PrintBD(dev,c) |
|||
IF x=img.w-1 THEN |
|||
PutDE(dev) |
|||
ELSE |
|||
PutD(dev,32) |
|||
FI |
|||
OD |
|||
OD |
|||
Close(dev) |
|||
RETURN |
|||
PROC Load(CHAR ARRAY path) |
|||
CHAR ARRAY line(255) |
|||
BYTE dev=[1] |
|||
Close(dev) |
|||
Open(dev,path,4) |
|||
WHILE Eof(dev)=0 |
|||
DO |
|||
InputSD(dev,line) |
|||
PrintE(line) |
|||
OD |
|||
Close(dev) |
|||
RETURN |
|||
PROC Main() |
|||
BYTE ARRAY rgbdata(300),graydata(100) |
|||
RgbImage rgbimg |
|||
GrayImage grayimg |
|||
CHAR ARRAY path2="D:PPM2.PPM" |
|||
CHAR ARRAY path6="D:PPM6.PPM" |
|||
Put(125) PutE() ;clear the screen |
|||
InitRgbImage(rgbimg,0,0,rgbdata) |
|||
InitRgbToGray() |
|||
PrintF("Loading %S...%E%E",path6) |
|||
LoadPPM6(rgbimg,path6) |
|||
PrintF("Converting RGB to grayscale...%E%E") |
|||
InitGrayImage(grayimg,rgbimg.w,rgbimg.h,graydata) |
|||
RgbToGray(rgbimg,grayimg) |
|||
PrintF("Saving %S...%E%E",path2) |
|||
SavePPM2(grayimg,path2) |
|||
PrintF("Loading %S...%E%E",path2) |
|||
Load(path2) |
|||
RETURN</syntaxhighlight> |
|||
{{out}} |
|||
[https://gitlab.com/amarok8bit/action-rosetta-code/-/raw/master/images/Read_a_PPM_file.png Screenshot from Atari 8-bit computer] |
|||
<pre> |
|||
Loading D:PPM6.PPM... |
|||
Converting RGB to grayscale... |
|||
Saving D:PPM2.PPM... |
|||
Loading D:PPM2.PPM... |
|||
P2 |
|||
3 4 |
|||
255 |
|||
0 18 182 |
|||
54 201 73 |
|||
237 255 61 |
|||
45 54 74 |
|||
</pre> |
|||
=={{header|Ada}}== |
=={{header|Ada}}== |
||
< |
<syntaxhighlight lang="ada">with Ada.Characters.Latin_1; use Ada.Characters.Latin_1; |
||
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; |
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; |
||
with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO; |
with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO; |
||
Line 77: | Line 342: | ||
return Result; |
return Result; |
||
end; |
end; |
||
end Get_PPM;</ |
end Get_PPM;</syntaxhighlight> |
||
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. |
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. |
||
< |
<syntaxhighlight lang="ada">declare |
||
F1, F2 : File_Type; |
F1, F2 : File_Type; |
||
begin |
begin |
||
Line 87: | Line 352: | ||
Close (F1); |
Close (F1); |
||
Close (F2); |
Close (F2); |
||
end;</ |
end;</syntaxhighlight> |
||
=={{header|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 <code>pixmap_read_ppm</code>, a dynamic file for the implementation of <code>pixmap_read_ppm</code>, 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 <code>pixmap_read_ppm<rgb24></code> you should be able to read any valid PPM, whether raw or plain, and with any valid Maxval. The result is a <code>pixmap1(rgb24)</code> 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 <code>bitmap_read_ppm_task.sats</code>. |
|||
<syntaxhighlight lang="ats"> |
|||
#define ATS_PACKNAME "Rosetta_Code.bitmap_read_ppm_task" |
|||
staload "bitmap_task.sats" |
|||
fn {a : t@ype} |
|||
pixmap_read_ppm : |
|||
(* On failure to read, the return is None_vt(). I do not currently |
|||
provide any indication of why the attempt failed, although in |
|||
practice you probably would wish to add that. *) |
|||
FILEref -> |
|||
Option_vt ([w, h : pos] [p : addr | null < p] |
|||
@(mfree_gc_v p | pixmap (a, w, h, p))) |
|||
</syntaxhighlight> |
|||
===The ATS dynamic file=== |
|||
This file should be called <code>bitmap_read_ppm_task.dats</code>. |
|||
<syntaxhighlight lang="ats"> |
|||
(*------------------------------------------------------------------*) |
|||
#define ATS_DYNLOADFLAG 0 |
|||
#define ATS_PACKNAME "Rosetta_Code.bitmap_read_ppm_task" |
|||
#include "share/atspre_staload.hats" |
|||
staload "bitmap_task.sats" |
|||
(* You need to staload bitmap_task.dats, so the ATS compiler will have |
|||
access to its implementations of templates. But we staload it |
|||
anonymously, so the programmer will not have access. *) |
|||
staload _ = "bitmap_task.dats" |
|||
staload "bitmap_read_ppm_task.sats" |
|||
(*------------------------------------------------------------------*) |
|||
datavtype magic_number_vt = |
|||
| Netpbm_magic_number of int |
|||
| Unknown_magic_number of () |
|||
fn {} |
|||
read_magic_number (inpf : FILEref) : magic_number_vt = |
|||
let |
|||
val i = fileref_getc inpf |
|||
in |
|||
if i <> char2int0 'P' then |
|||
Unknown_magic_number () |
|||
else |
|||
let |
|||
val i = fileref_getc inpf |
|||
in |
|||
if i < char2int0 '1' && char2int0 '7' < i then |
|||
Unknown_magic_number () |
|||
else |
|||
Netpbm_magic_number (i - char2int0 '0') |
|||
end |
|||
end |
|||
fn {} |
|||
get_next_char (inpf : FILEref) : int = |
|||
let |
|||
fnx |
|||
get_next () : int = |
|||
let |
|||
val i = fileref_getc inpf |
|||
in |
|||
if i = char2int0 '#' then |
|||
skip_through_newline () |
|||
else |
|||
i |
|||
end |
|||
and |
|||
skip_through_newline () : int = |
|||
let |
|||
val i = fileref_getc inpf |
|||
in |
|||
if i < 0 then |
|||
i |
|||
else if i = char2int0 '\n' then |
|||
get_next () |
|||
else |
|||
skip_through_newline () |
|||
end |
|||
in |
|||
get_next () |
|||
end |
|||
(* The only tokens we need to scan for, in P1 through P6, are unsigned |
|||
integers. P7 headers (Portable Arbitrary Map) have a completely |
|||
different arrangement, but we are not handling that. *) |
|||
fn {} |
|||
get_next_integer (inpf : FILEref) |
|||
(* A negative return value means we have reached the end. We do |
|||
not distinguish whitespace characters from anything else that |
|||
is not a digit or '#'. (Really I want to use intmax_t here, |
|||
rather than llint, but there is no intmax_t support in the |
|||
prelude. The ats2-xprelude package has support, but I am |
|||
avoiding the dependency. *) |
|||
: llint = |
|||
let |
|||
fnx |
|||
look_for_digit () : llint = |
|||
let |
|||
val i = get_next_char inpf |
|||
in |
|||
if i < char2int0 '0' || char2int0 '9' < i then |
|||
look_for_digit () |
|||
else |
|||
read_digits (g0i2i (i - char2int0 '0')) |
|||
end |
|||
and |
|||
read_digits (x : llint) : llint = |
|||
let |
|||
val i = get_next_char inpf |
|||
in |
|||
if i < char2int0 '0' || char2int0 '9' < i then |
|||
(* I cannot find an "ungetc" in prelude/SATS/filebas.sats, |
|||
so I will use the foreign function interface directly. *) |
|||
let |
|||
typedef FILEstar = $extype"FILE *" |
|||
extern castfn FILEref2star : FILEref -<> FILEstar |
|||
in |
|||
ignoret ($extfcall (int, "ungetc", i, FILEref2star inpf)); |
|||
x |
|||
end |
|||
else |
|||
let |
|||
val digit : llint = g0i2i (i - char2int0 '0') |
|||
in |
|||
read_digits ((10LL * x) + digit) |
|||
end |
|||
end |
|||
in |
|||
look_for_digit () |
|||
end |
|||
fn {} |
|||
read_ppm_header (inpf : FILEref) |
|||
: Option_vt @(ullint, ullint, ullint) = |
|||
let |
|||
val width = get_next_integer inpf |
|||
in |
|||
if width < 0LL then |
|||
None_vt () |
|||
else |
|||
let |
|||
val height = get_next_integer inpf |
|||
in |
|||
if height < 0LL then |
|||
None_vt () |
|||
else |
|||
let |
|||
val maxval = get_next_integer inpf |
|||
in |
|||
if maxval < 0LL then |
|||
None_vt () |
|||
else |
|||
begin |
|||
(* There is supposed to be a whitespace character (or |
|||
comments and whitespace character) after the |
|||
MAXVAL. We will accept anything, whitespace or |
|||
not. *) |
|||
ignoret (fileref_getc inpf); |
|||
Some_vt @(g0i2u width, g0i2u height, g0i2u maxval) |
|||
end |
|||
end |
|||
end |
|||
end |
|||
fn {} |
|||
get_next_single_byte (inpf : FILEref) : llint = |
|||
let |
|||
val i = fileref_getc inpf |
|||
in |
|||
if i < 0 then |
|||
~1LL |
|||
else |
|||
g0i2i i |
|||
end |
|||
fn {} |
|||
get_next_double_byte (inpf : FILEref) : llint = |
|||
let |
|||
val i1 = fileref_getc inpf |
|||
in |
|||
if i1 < 0 then |
|||
~1LL |
|||
else |
|||
let |
|||
val i0 = fileref_getc inpf |
|||
in |
|||
if i0 < 0 then |
|||
~1LL |
|||
else |
|||
let |
|||
val i1 : llint = g0i2i i1 |
|||
and i0 : llint = g0i2i i0 |
|||
in |
|||
(i1 * 256LL) + i0 |
|||
end |
|||
end |
|||
end |
|||
(*------------------------------------------------------------------*) |
|||
(* Implementation is provided only for rgb24. *) |
|||
extern castfn ull2sz : {i : int} ullint i -<> size_t i |
|||
extern castfn ull2u : {i : int} ullint i -<> uint i |
|||
extern castfn ull2u8 : ullint -<> uint8 |
|||
extern fn {} |
|||
read_raw_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>) |
|||
extern fn {} |
|||
read_plain_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>) |
|||
extern fn {} |
|||
read_general_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>) |
|||
extern fn {} |
|||
read_general$width () : [i : pos] size_t i |
|||
extern fn {} |
|||
read_general$height () : [i : pos] size_t i |
|||
extern fn {} |
|||
read_general$maxval () : [i : pos | i <= 65535] uint i |
|||
extern fn {} |
|||
read_general$next_value : FILEref -> llint |
|||
implement |
|||
pixmap_read_ppm<rgb24> inpf = |
|||
case+ read_magic_number inpf of |
|||
| ~ Unknown_magic_number () => None_vt () |
|||
| ~ Netpbm_magic_number num => |
|||
begin |
|||
case+ num of |
|||
| 6 => read_raw_ppm_rgb24 inpf |
|||
| 3 => read_plain_ppm_rgb24 inpf |
|||
| _ => None_vt |
|||
end |
|||
implement {} |
|||
read_raw_ppm_rgb24 inpf = |
|||
case+ read_ppm_header inpf of |
|||
| ~ None_vt () => None_vt () |
|||
| ~ Some_vt @(width, height, maxval) => |
|||
let |
|||
val width = g1ofg0 width |
|||
and height = g1ofg0 height |
|||
and maxval = g1ofg0 maxval |
|||
in |
|||
if (width < 1LLU) + (height < 1LLU) + |
|||
(maxval < 1LLU) + (65535LLU < maxval) then |
|||
None_vt () |
|||
else |
|||
let |
|||
val w : Size_t = ull2sz width |
|||
val h : Size_t = ull2sz height |
|||
val maxval : uInt = ull2u maxval |
|||
in |
|||
if maxval = 255u then |
|||
let |
|||
val @(pfgc | pix) = pixmap_make<rgb24> (w, h) |
|||
val success = |
|||
load<rgb24> (inpf, pix, rgb24_make (255, 0, 0)) |
|||
in |
|||
if ~success then |
|||
begin |
|||
free (pfgc | pix); |
|||
None_vt () |
|||
end |
|||
else |
|||
Some_vt @(pfgc | pix) |
|||
end |
|||
else if maxval < 256u then |
|||
let |
|||
implement read_general$width<> () = w |
|||
implement read_general$height<> () = h |
|||
implement read_general$maxval<> () = maxval |
|||
implement |
|||
read_general$next_value<> inpf = |
|||
get_next_single_byte inpf |
|||
in |
|||
read_general_ppm_rgb24<> inpf |
|||
end |
|||
else |
|||
let |
|||
implement read_general$width<> () = w |
|||
implement read_general$height<> () = h |
|||
implement read_general$maxval<> () = maxval |
|||
implement |
|||
read_general$next_value<> inpf = |
|||
get_next_double_byte inpf |
|||
in |
|||
read_general_ppm_rgb24<> inpf |
|||
end |
|||
end |
|||
end |
|||
implement {} |
|||
read_plain_ppm_rgb24 inpf = |
|||
case+ read_ppm_header inpf of |
|||
| ~ None_vt () => None_vt () |
|||
| ~ Some_vt @(width, height, maxval) => |
|||
let |
|||
val width = g1ofg0 width |
|||
and height = g1ofg0 height |
|||
and maxval = g1ofg0 maxval |
|||
in |
|||
if (width < 1LLU) + (height < 1LLU) + |
|||
(maxval < 1LLU) + (65535LLU < maxval) then |
|||
None_vt () |
|||
else |
|||
let |
|||
val w : Size_t = ull2sz width |
|||
val h : Size_t = ull2sz height |
|||
val maxval : uInt = ull2u maxval |
|||
implement read_general$width<> () = w |
|||
implement read_general$height<> () = h |
|||
implement read_general$maxval<> () = maxval |
|||
implement |
|||
read_general$next_value<> inpf = |
|||
get_next_integer inpf |
|||
in |
|||
read_general_ppm_rgb24<> inpf |
|||
end |
|||
end |
|||
implement {} |
|||
read_general_ppm_rgb24 inpf = |
|||
let |
|||
val [w : int] w = read_general$width<> () |
|||
and [h : int] h = read_general$height<> () |
|||
and maxval = read_general$maxval<> () |
|||
fn |
|||
scale_value (v : ullint) : uint8 = |
|||
if maxval = 255u then |
|||
ull2u8 v |
|||
else |
|||
let |
|||
val maxval : ullint = g0u2u maxval |
|||
val v = 255LLU * v |
|||
val v1 = v / maxval |
|||
and v0 = v mod maxval |
|||
in |
|||
if v0 + v0 < maxval then |
|||
ull2u8 v1 |
|||
else if maxval < v0 + v0 then |
|||
ull2u8 (succ v1) |
|||
else if v1 mod 2LLU = 0LLU then |
|||
ull2u8 v1 |
|||
else |
|||
ull2u8 (succ v1) |
|||
end |
|||
(* For easier programming, start with a fully initialized |
|||
pixmap. The routine probably is I/O-bound, anyway. *) |
|||
val @(pfgc | pix) = |
|||
pixmap_make<rgb24> (w, h, rgb24_make (255, 0, 0)) |
|||
macdef between (i, j, v) = |
|||
let |
|||
val v = ,(v) |
|||
in |
|||
(,(i) <= v) * (v <= ,(j)) |
|||
end |
|||
fun |
|||
loop {x, y : nat | x <= w; y <= h} |
|||
.<h - y, w - x>. |
|||
(pix : !pixmap (rgb24, w, h), |
|||
x : size_t x, |
|||
y : size_t y) |
|||
: bool (* success *) = |
|||
if y = h then |
|||
true |
|||
else if x = w then |
|||
loop (pix, i2sz 0, succ y) |
|||
else |
|||
let |
|||
val maxv : llint = g0u2i maxval |
|||
val vr = read_general$next_value<> inpf |
|||
in |
|||
if ~between (0LL, maxv, vr) then |
|||
false |
|||
else |
|||
let |
|||
val vg = read_general$next_value<> inpf |
|||
in |
|||
if ~between (0LL, maxv, vg) then |
|||
false |
|||
else |
|||
let |
|||
val vb = read_general$next_value<> inpf |
|||
in |
|||
if ~between (0LL, maxv, vb) then |
|||
false |
|||
else |
|||
let |
|||
val r = scale_value (g0i2u vr) |
|||
and g = scale_value (g0i2u vg) |
|||
and b = scale_value (g0i2u vb) |
|||
in |
|||
pix[x, y] := rgb24_make @(r, g, b); |
|||
loop (pix, succ x, y) |
|||
end |
|||
end |
|||
end |
|||
end |
|||
val success = loop (pix, i2sz 0, i2sz 0) |
|||
in |
|||
if ~success then |
|||
begin |
|||
free (pfgc | pix); |
|||
None_vt () |
|||
end |
|||
else |
|||
Some_vt @(pfgc | pix) |
|||
end |
|||
(*------------------------------------------------------------------*) |
|||
#ifdef BITMAP_READ_PPM_TASK_TEST #then |
|||
staload "bitmap_write_ppm_task.sats" |
|||
staload _ = "bitmap_write_ppm_task.dats" |
|||
(* The test program converts a PPM at standard input to a raw PPM with |
|||
MAXVAL 255. *) |
|||
implement |
|||
main0 () = |
|||
let |
|||
val pix_opt = pixmap_read_ppm<rgb24> stdin_ref |
|||
in |
|||
case+ pix_opt of |
|||
| ~ None_vt () => () |
|||
| ~ Some_vt @(pfgc | pix) => |
|||
begin |
|||
ignoret (pixmap_write_ppm (stdout_ref, pix)); |
|||
free (pfgc | pix) |
|||
end |
|||
end |
|||
#endif |
|||
(*------------------------------------------------------------------*) |
|||
</syntaxhighlight> |
|||
===The ATS program file=== |
|||
This file should be called <code>bitmap_read_ppm_task_program.dats</code> (though it actually could be called by another name). |
|||
[[File:Bitmap read ppm task ATS color.jpg|thumb|alt=A gnarly tree on a rise by the sea, in color.]][[File:Bitmap read ppm task ATS gray.jpg|thumb|alt=A gnarly tree on a rise by the sea, in grayscale.]] |
|||
<syntaxhighlight lang="ats"> |
|||
(* The program should be able to read a PPM in raw or plain format, |
|||
with any valid Maxval. The output will be a grayscale raw PPM with |
|||
Maxval=255. |
|||
Compile with "myatscc bitmap_read_ppm_task_program.dats", which |
|||
should give you a program named "bitmap_read_ppm_task_program". *) |
|||
(* |
|||
##myatsccdef=\ |
|||
patscc -std=gnu2x -g -O2 -DATS_MEMALLOC_LIBC \ |
|||
-o $fname($1) $1 \ |
|||
bitmap{,_{{read,write}_ppm,grayscale}}_task.{s,d}ats |
|||
*) |
|||
#include "share/atspre_staload.hats" |
|||
staload "bitmap_task.sats" |
|||
staload "bitmap_read_ppm_task.sats" |
|||
staload "bitmap_write_ppm_task.sats" |
|||
staload "bitmap_grayscale_task.sats" |
|||
staload _ = "bitmap_task.dats" |
|||
staload _ = "bitmap_read_ppm_task.dats" |
|||
staload _ = "bitmap_write_ppm_task.dats" |
|||
staload _ = "bitmap_grayscale_task.dats" |
|||
implement |
|||
main0 (argc, argv) = |
|||
let |
|||
val args = listize_argc_argv (argc, argv) |
|||
val nargs = length args |
|||
val inpf = |
|||
if nargs < 2 then |
|||
stdin_ref |
|||
else if args[1] = "-" then |
|||
stdin_ref |
|||
else |
|||
fileref_open_exn (args[1], file_mode_r) |
|||
val pix_opt = pixmap_read_ppm<rgb24> inpf |
|||
val () = fileref_close inpf |
|||
in |
|||
case+ pix_opt of |
|||
| ~ None_vt () => |
|||
begin |
|||
free args; |
|||
println! ("For some reason, I failed to read the image."); |
|||
exit 1 |
|||
end |
|||
| ~ Some_vt @(pfgc1 | pix1) => |
|||
let |
|||
val @(pfgc2 | pix2) = pixmap_convert<rgb24,gray8> pix1 |
|||
val () = free (pfgc1 | pix1) |
|||
val @(pfgc3 | pix3) = pixmap_convert<gray8,rgb24> pix2 |
|||
val () = free (pfgc2 | pix2) |
|||
val outf = |
|||
if nargs < 3 then |
|||
stdout_ref |
|||
else if args[2] = "-" then |
|||
stdout_ref |
|||
else |
|||
fileref_open_exn (args[2], file_mode_w) |
|||
val success = pixmap_write_ppm<rgb24> (outf, pix3) |
|||
val () = fileref_close outf |
|||
val () = free (pfgc3 | pix3) |
|||
in |
|||
free args; |
|||
if ~success then |
|||
begin |
|||
println! ("For some reason, ", |
|||
"I failed to write a new image."); |
|||
exit 2 |
|||
end |
|||
end |
|||
end |
|||
</syntaxhighlight> |
|||
You can compile the program with the shell command |
|||
<pre>myatscc bitmap_read_ppm_task_program.dats</pre> |
|||
If compilation is successful, the program will be called <code>bitmap_read_ppm_task_program</code>. 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). |
|||
=={{header|AutoHotkey}}== |
=={{header|AutoHotkey}}== |
||
{{works with | AutoHotkey_L}} |
{{works with | AutoHotkey_L}} |
||
Only ppm6 files supported. |
Only ppm6 files supported. |
||
< |
<syntaxhighlight lang="autohotkey">img := ppm_read("lena50.ppm") ; |
||
x := img[4,4] ; get pixel(4,4) |
x := img[4,4] ; get pixel(4,4) |
||
y := img[24,24] ; get pixel(24,24) |
y := img[24,24] ; get pixel(24,24) |
||
Line 148: | Line 968: | ||
return bitmap |
return bitmap |
||
} |
} |
||
#include bitmap_storage.ahk ; from http://rosettacode.org/wiki/Basic_bitmap_storage/AutoHotkey</ |
#include bitmap_storage.ahk ; from http://rosettacode.org/wiki/Basic_bitmap_storage/AutoHotkey</syntaxhighlight> |
||
=={{header|BBC BASIC}}== |
|||
{{works with|BBC BASIC for Windows}} |
|||
<syntaxhighlight 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</syntaxhighlight> |
|||
=={{header|C}}== |
=={{header|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 |
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. |
[[Read image file through a pipe]] without modification. It only understands the P6 file format. |
||
Line 157: | Line 1,008: | ||
Interface: |
Interface: |
||
<lang |
<syntaxhighlight lang="c">image get_ppm(FILE *pf);</syntaxhighlight> |
||
Implementation: |
Implementation: |
||
< |
<syntaxhighlight lang="c">#include "imglib.h" |
||
#define PPMREADBUFLEN 256 |
#define PPMREADBUFLEN 256 |
||
Line 173: | Line 1,024: | ||
if (pf == NULL) return NULL; |
if (pf == NULL) return NULL; |
||
t = fgets(buf, PPMREADBUFLEN, pf); |
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; |
if ( (t == NULL) || ( strncmp(buf, "P6\n", 3) != 0 ) ) return NULL; |
||
do |
do |
||
Line 181: | Line 1,033: | ||
r = sscanf(buf, "%u %u", &w, &h); |
r = sscanf(buf, "%u %u", &w, &h); |
||
if ( r < 2 ) return NULL; |
if ( r < 2 ) return NULL; |
||
// The program fails if the first byte of the image is equal to 32. because |
|||
r = fscanf(pf, "%u", &d); |
|||
r = fscanf(pf, "%u\n", &d); |
|||
if ( (r < 1) || ( d != 255 ) ) return NULL; |
if ( (r < 1) || ( d != 255 ) ) return NULL; |
||
fseek(pf, 1, SEEK_CUR); /* skip one byte, should be whitespace */ |
|||
img = alloc_img(w, h); |
img = alloc_img(w, h); |
||
if ( img != NULL ) |
if ( img != NULL ) |
||
Line 196: | Line 1,049: | ||
return img; |
return img; |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
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): |
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): |
||
< |
<syntaxhighlight lang="c">#include <stdio.h> |
||
#include "imglib.h" |
#include "imglib.h" |
||
Line 215: | Line 1,068: | ||
free_img(source); free_img((image)idest); |
free_img(source); free_img((image)idest); |
||
return 0; |
return 0; |
||
}</ |
}</syntaxhighlight> |
||
=={{header|C sharp|C#}}== |
=={{header|C sharp|C#}}== |
||
Tested with [[Write ppm file#C.23|this solution.]] |
Tested with [[Write ppm file#C.23|this solution.]] |
||
< |
<syntaxhighlight lang="csharp">using System.IO; |
||
class PPMReader |
class PPMReader |
||
{ |
{ |
||
Line 252: | Line 1,104: | ||
return bitmap; |
return bitmap; |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
=={{header|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. |
|||
<syntaxhighlight 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) |
|||
</syntaxhighlight> |
|||
To read the feep.ppm file as shown on the description page for the ppm format use: |
|||
<syntaxhighlight lang="lisp"> |
|||
(read-ppm-image "feep.ppm") |
|||
</syntaxhighlight> |
|||
=={{header|D}}== |
=={{header|D}}== |
||
The Image module contains a loadPPM6 function to load binary PPM images. |
|||
=={{header|Delphi}}== |
|||
Class helper for read and write Bitmap's and Ppm's |
|||
{{Trans|C#}} |
|||
<syntaxhighlight lang="delphi"> |
|||
program BtmAndPpm; |
|||
{$APPTYPE CONSOLE} |
|||
This example uses storage defined on [[Basic bitmap storage]] problem page. |
|||
{$R *.res} |
|||
This is wrap-around defined storage, P6 binary mode. |
|||
uses |
|||
{{libheader|tango}} |
|||
System.SysUtils, |
|||
System.Classes, |
|||
Winapi.Windows, |
|||
Vcl.Graphics; |
|||
type |
|||
<lang D>import tango.core.Exception; |
|||
TBitmapHelper = class helper for TBitmap |
|||
import tango.io.FileConduit; |
|||
private |
|||
import tango.io.MappedBuffer; |
|||
public |
|||
import tango.io.stream.LineStream; |
|||
procedure SaveAsPPM(FileName: TFileName; useGrayScale: Boolean = False); |
|||
import tango.io.protocol.Reader; |
|||
procedure LoadFromPPM(FileName: TFileName; useGrayScale: Boolean = False); |
|||
import tango.text.convert.Integer; |
|||
end; |
|||
function ColorToGray(Color: TColor): TColor; |
|||
class P6Image { |
|||
var |
|||
class BadInputException : Exception { this() { super("Bad file format"); } } |
|||
L: Byte; |
|||
class NoImageException : Exception { this() { super("No image data"); } } |
|||
begin |
|||
static const char[] type = "P6"; |
|||
L := round(0.2126 * GetRValue(Color) + 0.7152 * GetGValue(Color) + 0.0722 * |
|||
MappedBuffer fileBuf; |
|||
GetBValue(Color)); |
|||
ubyte _maxVal, gotImg; |
|||
Result := RGB(L, L, L); |
|||
end; |
|||
{ TBitmapHelper } |
|||
public: |
|||
RgbBitmap bitmap; |
|||
procedure TBitmapHelper.SaveAsPPM(FileName: TFileName; useGrayScale: Boolean = False); |
|||
this (FileConduit input) { |
|||
var |
|||
fileBuf = new MappedBuffer(input); |
|||
i, j, color: Integer; |
|||
if (processHeader(new LineInput(fileBuf))) |
|||
Header: AnsiString; |
|||
throw new BadInputException; |
|||
ppm: TMemoryStream; |
|||
begin |
|||
ppm := TMemoryStream.Create; |
|||
try |
|||
Header := Format('P6'#10'%d %d'#10'255'#10, [Self.Width, Self.Height]); |
|||
writeln(Header); |
|||
ppm.Write(Tbytes(Header), Length(Header)); |
|||
for i := 0 to Self.Height - 1 do |
|||
for j := 0 to Self.Width - 1 do |
|||
begin |
|||
if useGrayScale then |
|||
color := ColorToGray(ColorToRGB(Self.Canvas.Pixels[i, j])) |
|||
else |
|||
color := ColorToRGB(Self.Canvas.Pixels[i, j]); |
|||
ppm.Write(color, 3); |
|||
end; |
|||
ppm.SaveToFile(FileName); |
|||
finally |
|||
ppm.Free; |
|||
end; |
|||
end; |
|||
procedure TBitmapHelper.LoadFromPPM(FileName: TFileName; useGrayScale: Boolean = False); |
|||
ubyte maxVal() { return _maxVal; } |
|||
var |
|||
p: Integer; |
|||
ppm: TMemoryStream; |
|||
sW, sH: string; |
|||
temp: AnsiChar; |
|||
W, H: Integer; |
|||
Color: TColor; |
|||
function ReadChar: AnsiChar; |
|||
int processHeader(LineInput li) { |
|||
begin |
|||
char[] line; |
|||
ppm.Read(Result, 1); |
|||
uint eaten; |
|||
end; |
|||
begin |
|||
li.readln(line); |
|||
ppm := TMemoryStream.Create; |
|||
if (line != type) return 1; |
|||
ppm.LoadFromFile(FileName); |
|||
if ReadChar + ReadChar <> 'P6' then |
|||
exit; |
|||
repeat |
|||
li.readln(line); |
|||
temp := ReadChar; |
|||
// skip comment lines |
|||
if temp in ['0'..'9'] then |
|||
while (line.length && line[0] == '#') li.readln(line); |
|||
sW := sW + temp; |
|||
until temp = ' '; |
|||
auto height = parse(line[eaten..$], 0, &eaten); |
|||
if (!eaten || width > 0xffff_ffff || height > 0xffff_ffff) return 1; |
|||
repeat |
|||
li.readln(line); |
|||
temp := ReadChar; |
|||
if temp in ['0'..'9'] then |
|||
sH := sH + temp; |
|||
until temp = #10; |
|||
W := StrToInt(sW); |
|||
bitmap = RgbBitmap(width, height); |
|||
H := StrToInt(sH); |
|||
if ReadChar + ReadChar + ReadChar <> '255' then |
|||
gotImg = 1; |
|||
exit; |
|||
} |
|||
ReadChar(); //skip newLine |
|||
int processData(MappedBuffer mb) { |
|||
if (! gotImg) throw new NoImageException; |
|||
SetSize(W, H); |
|||
mb.fill(bitmap.data); |
|||
p := 0; |
|||
while ppm.Read(Color, 3) > 0 do |
|||
} |
|||
begin |
|||
}</lang> |
|||
if useGrayScale then |
|||
Color := ColorToGray(Color); |
|||
Canvas.Pixels[p mod W, p div W] := Color; |
|||
inc(p); |
|||
end; |
|||
ppm.Free; |
|||
end; |
|||
begin |
|||
with TBitmap.Create do |
|||
begin |
|||
// Load bmp |
|||
LoadFromFile('Input.bmp'); |
|||
// Save as ppm |
|||
SaveAsPPM('Output.ppm'); |
|||
// Load as ppm and convert in grayscale |
|||
LoadFromPPM('Output.ppm', True); |
|||
// Save as bmp |
|||
Reading from file: |
|||
SaveToFile('Output.bmp'); |
|||
<lang D>auto p6 = new P6Image(new FileConduit("image.ppm"));</lang> |
|||
Free; |
|||
end; |
|||
end. |
|||
</syntaxhighlight> |
|||
=={{header|E}}== |
=={{header|E}}== |
||
< |
<syntaxhighlight lang="e">def chr := <import:java.lang.makeCharacter>.asChar |
||
def readPPM(inputStream) { |
def readPPM(inputStream) { |
||
Line 382: | Line 1,376: | ||
image.replace(data.snapshot()) |
image.replace(data.snapshot()) |
||
return image |
return image |
||
}</ |
}</syntaxhighlight> |
||
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. |
|||
< |
<syntaxhighlight lang="e">def readPPMTask(inputFile, outputFile) { |
||
makeGrayscale \ |
makeGrayscale \ |
||
.fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \ |
.fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \ |
||
.toColor() \ |
.toColor() \ |
||
.writePPM(<import:java.io.makeFileOutputStream>(outputFile)) |
.writePPM(<import:java.io.makeFileOutputStream>(outputFile)) |
||
}</ |
}</syntaxhighlight> |
||
=={{header|Erlang}}== |
|||
<syntaxhighlight lang="erlang"> |
|||
% This module provides basic operations on ppm files: |
|||
% Read from file, create ppm in memory (from generic bitmap) and save to file. |
|||
% Writing PPM files was introduced in roseta code task 'Bitmap/Write a PPM file' |
|||
% but the same code is included here to provide whole set of operations on ppm |
|||
% needed for purposes of this task. |
|||
-module(ppm). |
|||
-export([ppm/1, write/2, read/1]). |
|||
% constants for writing ppm file |
|||
-define(WHITESPACE, <<10>>). |
|||
-define(SPACE, <<32>>). |
|||
% constants for reading ppm file |
|||
-define(WHITESPACES, [9, 10, 13, 32]). |
|||
-define(PPM_HEADER, "P6"). |
|||
% data structure introduced in task Bitmap (module ros_bitmap.erl) |
|||
-record(bitmap, { |
|||
mode = rgb, |
|||
pixels = nil, |
|||
shape = {0, 0} |
|||
}). |
|||
%%%%%%%%% API %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|||
% read ppm file from file |
|||
read(Filename) -> |
|||
{ok, File} = file:read_file(Filename), |
|||
parse(File). |
|||
% create ppm image from bitmap record |
|||
ppm(Bitmap) -> |
|||
{Width, Height} = Bitmap#bitmap.shape, |
|||
Pixels = ppm_pixels(Bitmap), |
|||
Maxval = 255, % original ppm format maximum |
|||
list_to_binary([ |
|||
header(), width_and_height(Width, Height), maxval(Maxval), Pixels]). |
|||
% write bitmap as ppm file |
|||
write(Bitmap, Filename) -> |
|||
Ppm = ppm(Bitmap), |
|||
{ok, File} = file:open(Filename, [binary, write]), |
|||
file:write(File, Ppm), |
|||
file:close(File). |
|||
%%%%%%%%% Reading PPM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|||
parse(Binary) -> |
|||
{?PPM_HEADER, Data} = get_next_token(Binary), |
|||
{Width, HeightAndRest} = get_next_token(Data), |
|||
{Height, MaxValAndRest} = get_next_token(HeightAndRest), |
|||
{_MaxVal, RawPixels} = get_next_token(MaxValAndRest), |
|||
Shape = {list_to_integer(Width), list_to_integer(Height)}, |
|||
Pixels = load_pixels(RawPixels), |
|||
#bitmap{pixels=Pixels, shape=Shape}. |
|||
% load binary as a list of RGB triplets |
|||
load_pixels(Binary) when is_binary(Binary)-> |
|||
load_pixels([], Binary). |
|||
load_pixels(Acc, <<>>) -> |
|||
array:from_list(lists:reverse(Acc)); |
|||
load_pixels(Acc, <<R, G, B, Rest/binary>>) -> |
|||
load_pixels([<<R,G,B>>|Acc], Rest). |
|||
is_whitespace(Byte) -> |
|||
lists:member(Byte, ?WHITESPACES). |
|||
% get next part of PPM file, skip whitespaces, and return the rest of a binary |
|||
get_next_token(Binary) -> |
|||
get_next_token("", true, Binary). |
|||
get_next_token(CurrentToken, false, <<Byte, Rest/binary>>) -> |
|||
case is_whitespace(Byte) of |
|||
true -> |
|||
{lists:reverse(CurrentToken), Rest}; |
|||
false -> |
|||
get_next_token([Byte | CurrentToken], false, Rest) |
|||
end; |
|||
get_next_token(CurrentToken, true, <<Byte, Rest/binary>>) -> |
|||
case is_whitespace(Byte) of |
|||
true -> |
|||
get_next_token(CurrentToken, true, Rest); |
|||
false -> |
|||
get_next_token([Byte | CurrentToken], false, Rest) |
|||
end. |
|||
%%%%%%%%% Writing PPM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
|||
header() -> |
|||
[<<"P6">>, ?WHITESPACE]. |
|||
width_and_height(Width, Height) -> |
|||
[encode_decimal(Width), ?SPACE, encode_decimal(Height), ?WHITESPACE]. |
|||
maxval(Maxval) -> |
|||
[encode_decimal(Maxval), ?WHITESPACE]. |
|||
ppm_pixels(Bitmap) -> |
|||
% 24 bit color depth |
|||
array:to_list(Bitmap#bitmap.pixels). |
|||
encode_decimal(Number) -> |
|||
integer_to_list(Number). |
|||
</syntaxhighlight> |
|||
Usage in accordance with Grayscale Task: |
|||
<syntaxhighlight lang="erlang"> |
|||
Colorful = ppm:read("colorful.ppm"), |
|||
Gray = ros_bitmap:convert(ros_bitmap:convert(Colorful, grey), rgb), |
|||
ppm:write(Gray, "gray.ppm"), |
|||
</syntaxhighlight> |
|||
=={{header|Euphoria}}== |
|||
<syntaxhighlight 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</syntaxhighlight> |
|||
Converting an image to grayscale: |
|||
<syntaxhighlight lang="euphoria">sequence image |
|||
image = read_ppm("image.ppm") |
|||
image = to_gray(image) |
|||
image = to_color(image) |
|||
write_ppm("image_gray.ppm",image)</syntaxhighlight> |
|||
=={{header|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:''' |
|||
[[File:FBSLLena.png|right]] |
|||
<syntaxhighlight 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</syntaxhighlight> |
|||
=={{header|Forth}}== |
=={{header|Forth}}== |
||
< |
<syntaxhighlight lang="forth">: read-ppm { fid -- bmp } |
||
pad dup 80 fid read-line throw 0= abort" Partial line" |
pad dup 80 fid read-line throw 0= abort" Partial line" |
||
s" P6" compare abort" Only P6 supported." |
s" P6" compare abort" Only P6 supported." |
||
Line 431: | Line 1,609: | ||
: bsize ( bmp -- len ) bdim * pixels bdata ; |
: bsize ( bmp -- len ) bdim * pixels bdata ; |
||
test dup bsize test2 dup bsize compare . \ 0 if identical</ |
test dup bsize test2 dup bsize compare . \ 0 if identical</syntaxhighlight> |
||
=={{header|Fortran}}== |
=={{header|Fortran}}== |
||
{{works with|Fortran|90 and later}} |
{{works with|Fortran|90 and later}} |
||
Line 438: | Line 1,615: | ||
(This function is part of module RCImageIO, see [[Write ppm file#Fortran|Write ppm file]]) |
(This function is part of module RCImageIO, see [[Write ppm file#Fortran|Write ppm file]]) |
||
< |
<syntaxhighlight lang="fortran">subroutine read_ppm(u, img) |
||
integer, intent(in) :: u |
integer, intent(in) :: u |
||
type(rgbimage), intent(out) :: img |
type(rgbimage), intent(out) :: img |
||
Line 479: | Line 1,656: | ||
end if |
end if |
||
end subroutine read_ppm</ |
end subroutine read_ppm</syntaxhighlight> |
||
'''Notes''': |
'''Notes''': |
||
Line 486: | Line 1,663: | ||
* comments after the first line are not handled |
* comments after the first line are not handled |
||
=={{header|FreeBASIC}}== |
|||
{{trans|Yabasic}} |
|||
<syntaxhighlight lang="vbnet">Dim As String imagen = "Lena.ppm" |
|||
Sub readPPM (fs As String) |
|||
Dim As Integer x, y, ancho, alto |
|||
Dim As String t, kolor |
|||
Dim As Ubyte r, g, b |
|||
If Len(fs) = 0 Then Print "No PPM file name indicated.": Exit Sub |
|||
Dim As Long ff = Freefile |
|||
Open fs For Binary As #ff |
|||
If Err Then Print "File "; fs; " not found.": Exit Sub |
|||
Input #ff, t, ancho, alto, kolor |
|||
If t = "P6" Then |
|||
Screenres ancho, alto, 32 |
|||
For y = 0 To alto - 1 |
|||
For x = 0 To ancho - 1 |
|||
Get #ff, , r |
|||
Get #ff, , g |
|||
Get #ff, , b |
|||
Pset (x, y), Rgb(r, g, b) |
|||
Next x |
|||
Next y |
|||
Close #ff |
|||
Else |
|||
Print "File is NOT PPM P6 type." |
|||
End If |
|||
End Sub |
|||
readPPM(imagen) |
|||
Sleep</syntaxhighlight> |
|||
=={{header|Go}}== |
|||
<syntaxhighlight 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() |
|||
}</syntaxhighlight> |
|||
Demonstration program, also demonstrating functions from task [[Grayscale image]]: |
|||
<syntaxhighlight 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) |
|||
} |
|||
}</syntaxhighlight> |
|||
=={{header|Haskell}}== |
=={{header|Haskell}}== |
||
The definition of <tt>Bitmap.Netpbm.readNetpbm</tt> is given [[Write ppm file|here]]. |
The definition of <tt>Bitmap.Netpbm.readNetpbm</tt> is given [[Write ppm file|here]]. |
||
< |
<syntaxhighlight lang="haskell">import Bitmap |
||
import Bitmap.RGB |
import Bitmap.RGB |
||
import Bitmap.Gray |
import Bitmap.Gray |
||
Line 499: | Line 1,810: | ||
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>= |
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>= |
||
stToIO . toGrayImage >>= |
stToIO . toGrayImage >>= |
||
writeNetpbm "new.pgm"</ |
writeNetpbm "new.pgm"</syntaxhighlight> |
||
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 <tt>Image RealWorld Gray</tt> back to an <tt>Image RealWorld RGB</tt> first: |
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 <tt>Image RealWorld Gray</tt> back to an <tt>Image RealWorld RGB</tt> first: |
||
< |
<syntaxhighlight lang="haskell">main = |
||
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>= |
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>= |
||
stToIO . (toRGBImage <=< toGrayImage) >>= |
stToIO . (toRGBImage <=< toGrayImage) >>= |
||
writeNetpbm "new.ppm"</ |
writeNetpbm "new.ppm"</syntaxhighlight> |
||
=={{header|J}}== |
=={{header|J}}== |
||
'''Solution:'''<br> |
'''Solution:'''<br> |
||
Uses <tt>makeRGB</tt> from [[Basic bitmap storage#J|Basic bitmap storage]]. |
Uses <tt>makeRGB</tt> from [[Basic bitmap storage#J|Basic bitmap storage]]. |
||
< |
<syntaxhighlight lang="j">require 'files' |
||
readppm=: monad define |
readppm=: monad define |
||
Line 518: | Line 1,828: | ||
if. (_99 0 +./@e. wbyh,maxval) +. 'P6' -.@-: 2{.t do. _1 return. end. |
if. (_99 0 +./@e. wbyh,maxval) +. 'P6' -.@-: 2{.t do. _1 return. end. |
||
(a. i. dat) makeRGB |.wbyh NB. convert to basic bitmap format |
(a. i. dat) makeRGB |.wbyh NB. convert to basic bitmap format |
||
)</ |
)</syntaxhighlight> |
||
'''Example:'''<br> |
'''Example:'''<br> |
||
Using utilities and file from [[Grayscale image#J|Grayscale image]] and [[Write ppm file#J|Write ppm file]].<br> |
Using utilities and file from [[Grayscale image#J|Grayscale image]] and [[Write ppm file#J|Write ppm file]].<br> |
||
Writes a gray PPM file (a color format) which is bigger than necessary. A PGM file would be more appropriate. |
Writes a gray PPM file (a color format) which is bigger than necessary. A PGM file would be more appropriate. |
||
< |
<syntaxhighlight lang="j">myimg=: readppm jpath '~temp/myimg.ppm' |
||
myimgGray=: toColor toGray myimg |
myimgGray=: toColor toGray myimg |
||
myimgGray writeppm jpath '~temp/myimgGray.ppm'</ |
myimgGray writeppm jpath '~temp/myimgGray.ppm'</syntaxhighlight> |
||
=={{header|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. |
|||
<syntaxhighlight lang="java"> |
|||
import java.awt.Color; |
|||
import java.awt.Graphics; |
|||
import java.awt.Image; |
|||
import java.awt.image.BufferedImage; |
|||
import java.awt.image.RenderedImage; |
|||
import java.io.BufferedInputStream; |
|||
import java.io.File; |
|||
import java.io.FileInputStream; |
|||
import java.io.IOException; |
|||
import javax.imageio.ImageIO; |
|||
public class ReadPPMFile { |
|||
public static void main(String[] aArgs) throws IOException { |
|||
// Using the file created in the Bitmap task |
|||
String filePath = "output.ppm"; |
|||
reader = new BufferedInputStream( new FileInputStream(filePath) ); |
|||
final char header1 = (char) reader.read(); |
|||
final char header2 = (char) reader.read(); |
|||
final char header3 = (char) reader.read(); |
|||
if ( header1 != 'P' || header2 != '6' || header3 != END_OF_LINE) { |
|||
reader.close(); |
|||
throw new IllegalArgumentException("Not a valid P6 PPM file"); |
|||
} |
|||
final int width = processCharacters(SPACE_CHARACTER); |
|||
final int height = processCharacters(END_OF_LINE); |
|||
final int maxColorValue = processCharacters(END_OF_LINE); |
|||
if ( maxColorValue < 0 || maxColorValue > 255 ) { |
|||
reader.close(); |
|||
throw new IllegalArgumentException("Maximum color value is outside the range 0..255"); |
|||
} |
|||
// Remove any comments before reading data |
|||
reader.mark(1); |
|||
while ( reader.read() == START_OF_COMMENT ) { |
|||
while ( reader.read() != END_OF_LINE ); |
|||
reader.mark(1); |
|||
} |
|||
reader.reset(); |
|||
// Read data |
|||
BasicBitmapStorage bitmap = new BasicBitmapStorage(width, height); |
|||
byte[] buffer = new byte[width * 3]; |
|||
for ( int y = 0; y < height; y++ ) { |
|||
reader.read(buffer, 0, buffer.length); |
|||
for ( int x = 0; x < width; x++ ) { |
|||
Color color = new Color(Byte.toUnsignedInt(buffer[x * 3]), |
|||
Byte.toUnsignedInt(buffer[x * 3 + 1]), |
|||
Byte.toUnsignedInt(buffer[x * 3 + 2])); |
|||
bitmap.setPixel(x, y, color); |
|||
} |
|||
} |
|||
reader.close(); |
|||
// Convert to gray scale and save to a file |
|||
bitmap.convertToGrayscale(); |
|||
File grayFile = new File("outputGray.jpg"); |
|||
ImageIO.write((RenderedImage) bitmap.getImage(), "jpg", grayFile); |
|||
} |
|||
private static int processCharacters(char aChar) throws IOException { |
|||
StringBuilder characters = new StringBuilder(); |
|||
char ch; |
|||
while ( ( ch = (char) reader.read() ) != aChar ) { |
|||
if ( ch == START_OF_COMMENT ) { |
|||
while ( reader.read() != END_OF_LINE ); |
|||
continue; |
|||
} |
|||
characters.append(ch); |
|||
} |
|||
return Integer.valueOf(characters.toString()); |
|||
} |
|||
private static BufferedInputStream reader; |
|||
private static final char START_OF_COMMENT = '#'; |
|||
private static final char SPACE_CHARACTER = ' '; |
|||
private static final char END_OF_LINE = '\n'; |
|||
} |
|||
final class BasicBitmapStorage { |
|||
public BasicBitmapStorage(int aWidth, int aHeight) { |
|||
image = new BufferedImage(aWidth, aHeight, BufferedImage.TYPE_INT_RGB); |
|||
} |
|||
public void fill(Color aColor) { |
|||
Graphics graphics = image.getGraphics(); |
|||
graphics.setColor(aColor); |
|||
graphics.fillRect(0, 0, image.getWidth(), image.getHeight()); |
|||
} |
|||
public Color getPixel(int aX, int aY) { |
|||
return new Color(image.getRGB(aX, aY)); |
|||
} |
|||
public void setPixel(int aX, int aY, Color aColor) { |
|||
image.setRGB(aX, aY, aColor.getRGB()); |
|||
} |
|||
public Image getImage() { |
|||
return image; |
|||
} |
|||
public void convertToGrayscale() { |
|||
for ( int y = 0; y < image.getHeight(); y++ ) { |
|||
for ( int x = 0; x < image.getWidth(); x++ ) { |
|||
int color = image.getRGB(x, y); |
|||
int alpha = ( color >> 24 ) & 255; |
|||
int red = ( color >> 16 ) & 255; |
|||
int green = ( color >> 8 ) & 255; |
|||
int blue = color & 255; |
|||
final int luminance = (int) ( 0.2126 * red + 0.7152 * green + 0.0722 * blue ); |
|||
alpha = alpha << 24; |
|||
red = luminance << 16; |
|||
green = luminance << 8; |
|||
blue = luminance; |
|||
color = alpha + red + green + blue; |
|||
image.setRGB(x, y, color); |
|||
} |
|||
} |
|||
} |
|||
private final BufferedImage image; |
|||
} |
|||
</syntaxhighlight> |
|||
{{ out }} |
|||
[[Media:ColouredJava.png]] & [[Media:GrayscaleJava.png]] |
|||
=={{header|Julia}}== |
|||
{{works with|Julia|0.6}} |
|||
<syntaxhighlight lang="julia">using Images, FileIO, Netpbm |
|||
rgbimg = load("data/bitmapInputTest.ppm") |
|||
greyimg = Gray.(rgbimg) |
|||
save("data/bitmapOutputTest.ppm", greyimg)</syntaxhighlight> |
|||
=={{header|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. |
|||
<syntaxhighlight lang="scala">// Version 1.2.40 |
|||
import java.awt.Color |
|||
import java.awt.Graphics |
|||
import java.awt.image.BufferedImage |
|||
import java.io.FileInputStream |
|||
import java.io.PushbackInputStream |
|||
import java.io.File |
|||
import javax.imageio.ImageIO |
|||
class BasicBitmapStorage(width: Int, height: Int) { |
|||
val image = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR) |
|||
fun fill(c: Color) { |
|||
val g = image.graphics |
|||
g.color = c |
|||
g.fillRect(0, 0, image.width, image.height) |
|||
} |
|||
fun setPixel(x: Int, y: Int, c: Color) = image.setRGB(x, y, c.getRGB()) |
|||
fun getPixel(x: Int, y: Int) = Color(image.getRGB(x, y)) |
|||
fun toGrayScale() { |
|||
for (x in 0 until image.width) { |
|||
for (y in 0 until image.height) { |
|||
var rgb = image.getRGB(x, y) |
|||
val red = (rgb shr 16) and 0xFF |
|||
val green = (rgb shr 8) and 0xFF |
|||
val blue = rgb and 0xFF |
|||
val lumin = (0.2126 * red + 0.7152 * green + 0.0722 * blue).toInt() |
|||
rgb = (lumin shl 16) or (lumin shl 8) or lumin |
|||
image.setRGB(x, y, rgb) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
fun PushbackInputStream.skipComment() { |
|||
while (read().toChar() != '\n') {} |
|||
} |
|||
fun PushbackInputStream.skipComment(buffer: ByteArray) { |
|||
var nl: Int |
|||
while (true) { |
|||
nl = buffer.indexOf(10) // look for newline at end of comment |
|||
if (nl != -1) break |
|||
read(buffer) // read another buffer full if newline not yet found |
|||
} |
|||
val len = buffer.size |
|||
if (nl < len - 1) unread(buffer, nl + 1, len - nl - 1) |
|||
} |
|||
fun Byte.toUInt() = if (this < 0) 256 + this else this.toInt() |
|||
fun main(args: Array<String>) { |
|||
// use file, output.ppm, created in the Bitmap/Write a PPM file task |
|||
val pbis = PushbackInputStream(FileInputStream("output.ppm"), 80) |
|||
pbis.use { |
|||
with (it) { |
|||
val h1 = read().toChar() |
|||
val h2 = read().toChar() |
|||
val h3 = read().toChar() |
|||
if (h1 != 'P' || h2 != '6' || h3 != '\n') { |
|||
println("Not a P6 PPM file") |
|||
System.exit(1) |
|||
} |
|||
val sb = StringBuilder() |
|||
while (true) { |
|||
val r = read().toChar() |
|||
if (r == '#') { skipComment(); continue } |
|||
if (r == ' ') break // read until space reached |
|||
sb.append(r.toChar()) |
|||
} |
|||
val width = sb.toString().toInt() |
|||
sb.setLength(0) |
|||
while (true) { |
|||
val r = read().toChar() |
|||
if (r == '#') { skipComment(); continue } |
|||
if (r == '\n') break // read until new line reached |
|||
sb.append(r.toChar()) |
|||
} |
|||
val height = sb.toString().toInt() |
|||
sb.setLength(0) |
|||
while (true) { |
|||
val r = read().toChar() |
|||
if (r == '#') { skipComment(); continue } |
|||
if (r == '\n') break // read until new line reached |
|||
sb.append(r.toChar()) |
|||
} |
|||
val maxCol = sb.toString().toInt() |
|||
if (maxCol !in 0..255) { |
|||
println("Maximum color value is outside the range 0..255") |
|||
System.exit(1) |
|||
} |
|||
var buffer = ByteArray(80) |
|||
// get rid of any more opening comments before reading data |
|||
while (true) { |
|||
read(buffer) |
|||
if (buffer[0].toChar() == '#') { |
|||
skipComment(buffer) |
|||
} |
|||
else { |
|||
unread(buffer) |
|||
break |
|||
} |
|||
} |
|||
// read data |
|||
val bbs = BasicBitmapStorage(width, height) |
|||
buffer = ByteArray(width * 3) |
|||
var y = 0 |
|||
while (y < height) { |
|||
read(buffer) |
|||
for (x in 0 until width) { |
|||
val c = Color( |
|||
buffer[x * 3].toUInt(), |
|||
buffer[x * 3 + 1].toUInt(), |
|||
buffer[x * 3 + 2].toUInt() |
|||
) |
|||
bbs.setPixel(x, y, c) |
|||
} |
|||
y++ |
|||
} |
|||
// convert to grayscale and save to a file |
|||
bbs.toGrayScale() |
|||
val grayFile = File("output_gray.jpg") |
|||
ImageIO.write(bbs.image, "jpg", grayFile) |
|||
} |
|||
} |
|||
}</syntaxhighlight> |
|||
=={{header|Lua}}== |
=={{header|Lua}}== |
||
< |
<syntaxhighlight lang="lua">function Read_PPM( filename ) |
||
local fp = io.open( filename, "rb" ) |
local fp = io.open( filename, "rb" ) |
||
if fp == nil then return nil end |
if fp == nil then return nil end |
||
Line 560: | Line 2,156: | ||
return image |
return image |
||
end</ |
end</syntaxhighlight> |
||
=={{header|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. |
|||
<syntaxhighlight lang="m2000 interpreter"> |
|||
Module Checkit { |
|||
Function Bitmap { |
|||
If match("NN") then { |
|||
Read x as long, y as long |
|||
} else.if Match("N") Then { |
|||
\\ is a file? |
|||
Read f |
|||
if not Eof(f) then { |
|||
Line Input #f, p3$ |
|||
If p3$="P3" Then { |
|||
Line Input #f, Comment$ |
|||
if left$(Comment$,1)="#" then { |
|||
Line Input #f, Dimension$ |
|||
} else Dimension$=Comment$ |
|||
long x=Val(piece$(Dimension$," ")(0)) |
|||
long y=Val(piece$(Dimension$," ")(1)) |
|||
do { |
|||
Line Input #f, P255$ |
|||
} until left$(P255$, 1)<>"#" |
|||
If not P255$="255" then Error "Not proper ppm format" |
|||
} |
|||
} |
|||
} else Error "No proper arguments" |
|||
if x<1 or y<1 then Error "Wrong dimensions" |
|||
structure rgb { |
|||
red as byte |
|||
green as byte |
|||
blue as byte |
|||
} |
|||
m=len(rgb)*x mod 4 |
|||
if m>0 then m=4-m ' add some bytes to raster line |
|||
m+=len(rgb) *x |
|||
Structure rasterline { |
|||
{ |
|||
pad as byte*m |
|||
} |
|||
\\ union pad+hline |
|||
hline as rgb*x |
|||
} |
|||
Structure Raster { |
|||
magic as integer*4 |
|||
w as integer*4 |
|||
h as integer*4 |
|||
lines as rasterline*y |
|||
} |
|||
Buffer Clear Image1 as Raster |
|||
\\ 24 chars as header to be used from bitmap render build in functions |
|||
Return Image1, 0!magic:="cDIB", 0!w:=Hex$(x,2), 0!h:=Hex$(y, 2) |
|||
\\ fill white (all 255) |
|||
\\ Str$(string) convert to ascii, so we get all characters from words width to byte width |
|||
if not valid(f) then Return Image1, 0!lines:=Str$(String$(chrcode$(255), Len(rasterline)*y)) |
|||
Buffer Clear Pad as Byte*4 |
|||
SetPixel=Lambda Image1, Pad,aLines=Len(Raster)-Len(Rasterline), blines=-Len(Rasterline) (x, y, c) ->{ |
|||
where=alines+3*x+blines*y |
|||
if c>0 then c=color(c) |
|||
c-! |
|||
Return Pad, 0:=c as long |
|||
Return Image1, 0!where:=Eval(Pad, 2) as byte, 0!where+1:=Eval(Pad, 1) as byte, 0!where+2:=Eval(Pad, 0) as byte |
|||
} |
|||
GetPixel=Lambda Image1,aLines=Len(Raster)-Len(Rasterline), blines=-Len(Rasterline) (x,y) ->{ |
|||
where=alines+3*x+blines*y |
|||
=color(Eval(image1, where+2 as byte), Eval(image1, where+1 as byte), Eval(image1, where as byte)) |
|||
} |
|||
StrDib$=Lambda$ Image1, Raster -> { |
|||
=Eval$(Image1, 0, Len(Raster)) |
|||
} |
|||
CopyImage=Lambda Image1 (image$) -> { |
|||
if left$(image$,12)=Eval$(Image1, 0, 24 ) Then { |
|||
Return Image1, 0:=Image$ |
|||
} Else Error "Can't Copy Image" |
|||
} |
|||
Export2File=Lambda Image1, x, y (f) -> { |
|||
\\ use this between open and close |
|||
Print #f, "P3" |
|||
Print #f,"# Created using M2000 Interpreter" |
|||
Print #f, x;" ";y |
|||
Print #f, 255 |
|||
x2=x-1 |
|||
where=24 |
|||
For y1= 0 to y-1 { |
|||
a$="" |
|||
For x1=0 to x2 { |
|||
Print #f, a$;Eval(Image1, where+2 as byte);" "; |
|||
Print #f, Eval(Image1, where+1 as byte);" "; |
|||
Print #f, Eval(Image1, where as byte); |
|||
where+=3 |
|||
a$=" " |
|||
} |
|||
Print #f |
|||
m=where mod 4 |
|||
if m<>0 then where+=4-m |
|||
} |
|||
} |
|||
if valid(F) then { |
|||
'load RGB values form file |
|||
x0=x-1 |
|||
where=24 |
|||
For y1=y-1 to 0 { |
|||
do { |
|||
Line Input #f, aline$ |
|||
} until left$(aline$,1)<>"#" |
|||
flush ' empty stack |
|||
Stack aline$ ' place all values to stack as FIFO |
|||
For x1=0 to x0 { |
|||
\\ now read from stack using Number |
|||
Return Image1, 0!where+2:=Number as byte, 0!where+1:=Number as byte, 0!where:=Number as byte |
|||
where+=3 |
|||
} |
|||
m=where mod 4 |
|||
if m<>0 then where+=4-m |
|||
} |
|||
} |
|||
Group Bitmap { |
|||
SetPixel=SetPixel |
|||
GetPixel=GetPixel |
|||
Image$=StrDib$ |
|||
Copy=CopyImage |
|||
ToFile=Export2File |
|||
} |
|||
=Bitmap |
|||
} |
|||
A=Bitmap(10, 10) |
|||
Call A.SetPixel(5,5, color(128,0,255)) |
|||
Open "A.PPM" for Output as #F |
|||
Call A.ToFile(F) |
|||
Close #f |
|||
Open "A.PPM" for Input as #F |
|||
Try { |
|||
C=Bitmap(f) |
|||
Copy 400*twipsx,200*twipsy use C.Image$() |
|||
} |
|||
Close #f |
|||
' is the same as this one |
|||
Open "A.PPM" for Input as #F |
|||
Line Input #f, p3$ |
|||
If p3$="P3" Then { |
|||
Line Input #f, Comment$ |
|||
if left$(Comment$,1)="#" then { |
|||
Line Input #f, Dimension$ |
|||
} else Dimension$=Comment$ |
|||
Long x=Val(piece$(Dimension$," ")(0)) |
|||
Long y=Val(piece$(Dimension$," ")(1)) |
|||
do { |
|||
Line Input #f, P255$ |
|||
} until left$(P255$, 1)<>"#" |
|||
If not P255$="255" then Error "Not proper ppm format" |
|||
B=Bitmap(x, y) |
|||
x0=x-1 |
|||
For y1=y-1 to 0 { |
|||
do { |
|||
Line Input #f, aline$ |
|||
} until left$(aline$,1)<>"#" |
|||
flush ' empty stack |
|||
Stack aline$ ' place all values to stack as FIFO |
|||
For x1=0 to x0 { |
|||
\\ now read from stack |
|||
Read red, green, blue |
|||
Call B.setpixel(x1, y1, Color(red, green, blue)) |
|||
} |
|||
} |
|||
} |
|||
Close #f |
|||
If valid("B") then Copy 200*twipsx,200*twipsy use B.Image$() |
|||
} |
|||
Checkit |
|||
</syntaxhighlight> |
|||
=={{header|Mathematica}}/ {{header|Wolfram Language}}== |
|||
<syntaxhighlight lang="mathematica">Import["file.ppm","PPM"] |
|||
</syntaxhighlight> |
|||
=={{header|Nim}}== |
|||
<syntaxhighlight lang="nim">import strutils |
|||
import bitmap |
|||
import streams |
|||
type FormatError = object of CatchableError |
|||
# States used to parse the header. |
|||
type State = enum waitingMagic, waitingWidth, waitingHeight, waitingColors |
|||
#--------------------------------------------------------------------------------------------------- |
|||
iterator tokens(f: Stream): tuple[value: string, lastInLine: bool] = |
|||
## Yield the tokens in the header. |
|||
for line in f.lines: |
|||
if not line.startsWith('#'): |
|||
let fields = line.splitWhitespace() |
|||
for i, t in fields: |
|||
yield (t, i == fields.high) |
|||
#--------------------------------------------------------------------------------------------------- |
|||
proc getInt(s: string): int {.inline.} = |
|||
## Try to parse an int. Raise an exception if not an integer. |
|||
try: |
|||
result = s.parseInt() |
|||
except ValueError: |
|||
raise newException(FormatError, "Invalid value") |
|||
#--------------------------------------------------------------------------------------------------- |
|||
proc header(f: Stream): tuple[width, height: Index] = |
|||
## Read the header and retrun the image width and height. |
|||
var state = waitingMagic |
|||
for (token, lastInLine) in f.tokens: |
|||
case state |
|||
of waitingMagic: |
|||
if token != "P6": |
|||
raise newException(FormatError, "Invalid file header") |
|||
of waitingWidth: |
|||
result.width = token.getInt() |
|||
of waitingHeight: |
|||
result.height = token.getInt() |
|||
of waitingColors: |
|||
if token.getInt() != 255: |
|||
raise newException(FormatError, "Invalid number of colors") |
|||
if not lastInLine: |
|||
raise newException(FormatError, "Invalid data after number of colors") |
|||
break |
|||
state = succ(state) |
|||
#--------------------------------------------------------------------------------------------------- |
|||
proc readPPM*(f: Stream): Image = |
|||
## Read a PPM file from a stream into an image. |
|||
let header = f.header() |
|||
result = newImage(header.width, header.height) |
|||
var |
|||
arr: array[256, int8] |
|||
read = f.readData(addr(arr), 256) |
|||
pos = 0 |
|||
while read != 0: |
|||
for i in 0 ..< read: |
|||
case pos mod 3 |
|||
of 0: result.pixels[pos div 3].r = arr[i].uint8 |
|||
of 1: result.pixels[pos div 3].g = arr[i].uint8 |
|||
of 2: result.pixels[pos div 3].b = arr[i].uint8 |
|||
else: discard |
|||
inc pos |
|||
read = f.readData(addr(arr), 256) |
|||
if pos != 3 * result.w * result.h: |
|||
raise newException(FormatError, "Truncated file") |
|||
#--------------------------------------------------------------------------------------------------- |
|||
proc readPPM*(filename: string): Image = |
|||
## Load a PPM file into an image. |
|||
var file = openFileStream(filename, fmRead) |
|||
result = file.readPPM() |
|||
file.close() |
|||
#——————————————————————————————————————————————————————————————————————————————————————————————————— |
|||
when isMainModule: |
|||
let image = readPPM("output.ppm") |
|||
echo image.h, " ", image.w</syntaxhighlight> |
|||
=={{header|OCaml}}== |
=={{header|OCaml}}== |
||
< |
<syntaxhighlight lang="ocaml">let read_ppm ~filename = |
||
let ic = open_in filename in |
let ic = open_in filename in |
||
let line = input_line ic in |
let line = input_line ic in |
||
Line 601: | Line 2,466: | ||
r_channel, |
r_channel, |
||
g_channel, |
g_channel, |
||
b_channel)</ |
b_channel)</syntaxhighlight> |
||
and converting a given color file to grayscale: |
and converting a given color file to grayscale: |
||
< |
<syntaxhighlight lang="ocaml">let () = |
||
let img = read_ppm ~filename:"logo.ppm" in |
let img = read_ppm ~filename:"logo.ppm" in |
||
let img = to_color(to_grayscale ~img) in |
let img = to_color(to_grayscale ~img) in |
||
output_ppm ~oc:stdout ~img; |
output_ppm ~oc:stdout ~img; |
||
;;</ |
;;</syntaxhighlight> |
||
sending the result to <tt>stdout</tt> allows to see the result without creating a temporary file sending it through a pipe to the '''display''' utility of ''ImageMagick'': |
sending the result to <tt>stdout</tt> 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 - |
ocaml script.ml | display - |
||
=={{header|Oz}}== |
=={{header|Oz}}== |
||
The read function in module <code>"BitmapIO.oz"</code>: |
The read function in module <code>"BitmapIO.oz"</code>: |
||
< |
<syntaxhighlight lang="oz">functor |
||
import |
import |
||
Bitmap |
Bitmap |
||
Line 697: | Line 2,561: | ||
%% Omitted: Write |
%% Omitted: Write |
||
end</ |
end</syntaxhighlight> |
||
The actual task: |
The actual task: |
||
< |
<syntaxhighlight lang="oz">declare |
||
[BitmapIO Grayscale] = {Module.link ['BitmapIO.ozf' 'Grayscale.ozf']} |
[BitmapIO Grayscale] = {Module.link ['BitmapIO.ozf' 'Grayscale.ozf']} |
||
Line 706: | Line 2,570: | ||
G = {Grayscale.toGraymap B} |
G = {Grayscale.toGraymap B} |
||
in |
in |
||
{BitmapIO.write {Grayscale.fromGraymap G} "greyimage.ppm"}</ |
{BitmapIO.write {Grayscale.fromGraymap G} "greyimage.ppm"}</syntaxhighlight> |
||
=={{header|Perl}}== |
=={{header|Perl}}== |
||
{{libheader|Imlib2}} |
{{libheader|Imlib2}} |
||
< |
<syntaxhighlight lang="perl">#! /usr/bin/perl |
||
use strict; |
use strict; |
||
Line 725: | Line 2,588: | ||
$img->save("out1.png"); |
$img->save("out1.png"); |
||
exit 0;</ |
exit 0;</syntaxhighlight> |
||
=={{header|Phix}}== |
|||
Based on [[Bitmap/Read_a_PPM_file#Euphoria|Euphoria]], requires write_ppm() from [[Bitmap/Write_a_PPM_file#Phix|Write_a_PPM_file]], to_grey from [[Grayscale_image#Phix|Grayscale_image]]<br> |
|||
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 |
|||
<syntaxhighlight lang="phix">-- demo\rosetta\Bitmap_read_ppm.exw (runnable version) |
|||
function read_ppm(string filename) |
|||
sequence image, line |
|||
integer dimx, dimy, maxcolor |
|||
atom fn = open(filename, "rb") |
|||
if fn<0 then |
|||
return -1 -- unable to open |
|||
end if |
|||
line = gets(fn) |
|||
if line!="P6\n" then |
|||
return -1 -- only ppm6 files are supported |
|||
end if |
|||
line = gets(fn) |
|||
{{dimx,dimy}} = scanf(line,"%d %d%s") |
|||
line = gets(fn) |
|||
{{maxcolor}} = scanf(line,"%d%s") |
|||
image = repeat(repeat(0,dimy),dimx) |
|||
for y=1 to dimy do |
|||
for x=1 to dimx do |
|||
image[x][y] = getc(fn)*#10000 + getc(fn)*#100 + getc(fn) |
|||
end for |
|||
end for |
|||
close(fn) |
|||
return image |
|||
end function |
|||
--include ppm.e -- read_ppm(), write_ppm(), to_grey() (as distributed, instead of the above) |
|||
sequence img = read_ppm("Lena.ppm") |
|||
img = to_grey(img) |
|||
write_ppm("LenaGray.ppm",img)</syntaxhighlight> |
|||
=={{header|PicoLisp}}== |
|||
<syntaxhighlight 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 ) ) ) ) ) )</syntaxhighlight> |
|||
Read a color image "img.ppm", convert and write to "img.pgm": |
|||
<syntaxhighlight lang="picolisp">(pgmWrite (ppm->pgm (ppmRead "img.ppm")) "img.pgm")</syntaxhighlight> |
|||
=={{header|PL/I}}== |
=={{header|PL/I}}== |
||
<syntaxhighlight lang="pl/i"> |
|||
<lang PL/I> |
|||
/* BITMAP FILE: read in a file in PPM format, P6 (binary). 14/5/2010 */ |
/* BITMAP FILE: read in a file in PPM format, P6 (binary). 14/5/2010 */ |
||
test: procedure options (main); |
test: procedure options (main); |
||
Line 792: | Line 2,710: | ||
return (index('0123456789', ch) > 0); |
return (index('0123456789', ch) > 0); |
||
end is_digit; |
end is_digit; |
||
end test;</ |
end test;</syntaxhighlight> |
||
=={{header|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> |
|||
=={{header|PureBasic}}== |
=={{header|PureBasic}}== |
||
< |
<syntaxhighlight lang="purebasic">Structure PPMColor |
||
r.c |
r.c |
||
g.c |
g.c |
||
Line 874: | Line 2,769: | ||
EndIf |
EndIf |
||
EndIf |
EndIf |
||
EndProcedure</ |
EndProcedure</syntaxhighlight> |
||
To complete the task, the following code should be added to the above fragment and to the PureBasic solutions for [[Grayscale_image#PureBasic|Grayscale image]] and [[Bitmap/Write_a_PPM_file#PureBasic|Write a PPM file]] |
To complete the task, the following code should be added to the above fragment and to the PureBasic solutions for [[Grayscale_image#PureBasic|Grayscale image]] and [[Bitmap/Write_a_PPM_file#PureBasic|Write a PPM file]] |
||
< |
<syntaxhighlight lang="purebasic">Define file.s, file2.s, image = 3 |
||
file = OpenFileRequester("Select source image file", "", "PPM image (*.ppm)|*.ppm", 0) |
file = OpenFileRequester("Select source image file", "", "PPM image (*.ppm)|*.ppm", 0) |
||
If file And LCase(GetExtensionPart(file)) = "ppm" |
If file And LCase(GetExtensionPart(file)) = "ppm" |
||
Line 884: | Line 2,779: | ||
file2 = Left(file, Len(file) - Len(GetExtensionPart(file))) + "_grayscale." + GetExtensionPart(file) |
file2 = Left(file, Len(file) - Len(GetExtensionPart(file))) + "_grayscale." + GetExtensionPart(file) |
||
SaveImageAsPPM(image, file2, 1) |
SaveImageAsPPM(image, file2, 1) |
||
EndIf</ |
EndIf</syntaxhighlight> |
||
=={{header|Python}}== |
=={{header|Python}}== |
||
{{works with|Python|3.1}} |
{{works with|Python|3.1}} |
||
Extending the example given [[Basic_bitmap_storage#Alternative_version|here]] |
Extending the example given [[Basic_bitmap_storage#Alternative_version|here]] |
||
< |
<syntaxhighlight lang="python"># With help from http://netpbm.sourceforge.net/doc/ppm.html |
||
# String masquerading as ppm file (version P3) |
# String masquerading as ppm file (version P3) |
||
Line 958: | Line 2,852: | ||
4 4 4 0 0 0 0 0 0 0 0 0 |
4 4 4 0 0 0 0 0 0 0 0 0 |
||
'''</ |
'''</syntaxhighlight> |
||
=={{header|Racket}}== |
|||
<syntaxhighlight 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)) |
|||
</syntaxhighlight> |
|||
=={{header|Raku}}== |
|||
(formerly Perl 6) |
|||
{{works with|Rakudo|2017.09}} |
|||
Uses pieces from [[Bitmap#Raku| Bitmap]], [[Bitmap/Write_a_PPM_file#Raku| Write a PPM file]] and [[Grayscale_image#Raku| Grayscale image]] tasks. Included here to make a complete, runnable program. |
|||
<syntaxhighlight lang="raku" line>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;</syntaxhighlight> |
|||
See [https://github.com/thundergnat/rc/blob/master/img/camelia.png camelia], and [https://github.com/thundergnat/rc/blob/master/img/camelia-gs.png camelia-gs] images. (converted to .png as .ppm format is not widely supported). |
|||
=={{header|REXX}}== |
|||
The input file '''Lenna50.ppm''' is a '''PPM''' format of |
|||
<br>the input file '''Lenna50.jpg''' used elsewhere on Rosetta Code. |
|||
This REXX program handles alternative delimiters as well as comments within the PPM header. |
|||
<syntaxhighlight lang="rexx">/*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. */</syntaxhighlight> |
|||
{{out|output}} |
|||
<pre> |
|||
File greyscale.ppm was created. |
|||
</pre> |
|||
=={{header|Ruby}}== |
=={{header|Ruby}}== |
||
Extending [[Basic_bitmap_storage#Ruby]] |
Extending [[Basic_bitmap_storage#Ruby]] |
||
< |
<syntaxhighlight lang="ruby">class Pixmap |
||
# 'open' is a class method |
# 'open' is a class method |
||
def self.open(filename) |
def self.open(filename) |
||
Line 994: | Line 3,000: | ||
# then, convert to grayscale |
# then, convert to grayscale |
||
Pixmap.open('testcross.ppm').to_grayscale!.save('testgray.ppm')</ |
Pixmap.open('testcross.ppm').to_grayscale!.save('testgray.ppm')</syntaxhighlight> |
||
=={{header|Rust}}== |
|||
<syntaxhighlight lang="rust"> |
|||
parser.rs: |
|||
use super::{Color, ImageFormat}; |
|||
use std::str::from_utf8; |
|||
use std::str::FromStr; |
|||
pub fn parse_version(input: &[u8]) -> nom::IResult<&[u8], ImageFormat> { |
|||
use nom::branch::alt; |
|||
use nom::bytes::complete::tag; |
|||
use nom::character::complete::line_ending; |
|||
use nom::combinator::map; |
|||
use nom::sequence::terminated; |
|||
// starts with P3/P6 ends with a CR/LF |
|||
terminated( |
|||
alt(( |
|||
map(tag("P3".as_bytes()), |_| ImageFormat::P3), |
|||
map(tag("P6".as_bytes()), |_| ImageFormat::P6), |
|||
)), |
|||
line_ending, |
|||
)(input) |
|||
} |
|||
pub fn parse_image_attributes(input: &[u8]) -> nom::IResult<&[u8], (usize, usize, usize)> { |
|||
use nom::character::complete::line_ending; |
|||
use nom::character::complete::{digit1, space1}; |
|||
use nom::sequence::terminated; |
|||
use nom::sequence::tuple; |
|||
// 3 numbers separated by spaces ends with a CR/LF |
|||
terminated(tuple((digit1, space1, digit1, space1, digit1)), line_ending)(input).map( |
|||
|(next_input, result)| { |
|||
( |
|||
next_input, |
|||
( |
|||
usize::from_str_radix(from_utf8(result.0).unwrap(), 10).unwrap(), |
|||
usize::from_str_radix(from_utf8(result.2).unwrap(), 10).unwrap(), |
|||
usize::from_str_radix(from_utf8(result.4).unwrap(), 10).unwrap(), |
|||
), |
|||
) |
|||
}, |
|||
) |
|||
} |
|||
pub fn parse_color_binary(input: &[u8]) -> nom::IResult<&[u8], Color> { |
|||
use nom::number::complete::u8 as nom_u8; |
|||
use nom::sequence::tuple; |
|||
tuple((nom_u8, nom_u8, nom_u8))(input).map(|(next_input, res)| { |
|||
( |
|||
next_input, |
|||
Color { |
|||
red: res.0, |
|||
green: res.1, |
|||
blue: res.2, |
|||
}, |
|||
) |
|||
}) |
|||
} |
|||
pub fn parse_data_binary(input: &[u8]) -> nom::IResult<&[u8], Vec<Color>> { |
|||
use nom::multi::many0; |
|||
many0(parse_color_binary)(input) |
|||
} |
|||
pub fn parse_color_ascii(input: &[u8]) -> nom::IResult<&[u8], Color> { |
|||
use nom::character::complete::{digit1, space0, space1}; |
|||
use nom::sequence::tuple; |
|||
tuple((digit1, space1, digit1, space1, digit1, space0))(input).map(|(next_input, res)| { |
|||
( |
|||
next_input, |
|||
Color { |
|||
red: u8::from_str(from_utf8(res.0).unwrap()).unwrap(), |
|||
green: u8::from_str(from_utf8(res.2).unwrap()).unwrap(), |
|||
blue: u8::from_str(from_utf8(res.4).unwrap()).unwrap(), |
|||
}, |
|||
) |
|||
}) |
|||
} |
|||
pub fn parse_data_ascii(input: &[u8]) -> nom::IResult<&[u8], Vec<Color>> { |
|||
use nom::multi::many0; |
|||
many0(parse_color_ascii)(input) |
|||
} |
|||
lib.rs: |
|||
extern crate nom; |
|||
extern crate thiserror; |
|||
mod parser; |
|||
use std::default::Default; |
|||
use std::fmt; |
|||
use std::io::{BufWriter, Error, Write}; |
|||
use std::ops::{Index, IndexMut}; |
|||
use std::{fs::File, io::Read}; |
|||
use thiserror::Error; |
|||
#[derive(Copy, Clone, Default, PartialEq, Debug)] |
|||
pub struct Color { |
|||
pub red: u8, |
|||
pub green: u8, |
|||
pub blue: u8, |
|||
} |
|||
#[derive(Copy, Clone, PartialEq, Debug)] |
|||
pub enum ImageFormat { |
|||
P3, |
|||
P6, |
|||
} |
|||
impl From<&str> for ImageFormat { |
|||
fn from(i: &str) -> Self { |
|||
match i.to_lowercase().as_str() { |
|||
"p3" => ImageFormat::P3, |
|||
"p6" => ImageFormat::P6, |
|||
_ => unimplemented!("no other formats supported"), |
|||
} |
|||
} |
|||
} |
|||
impl fmt::Display for ImageFormat { |
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
|||
match self { |
|||
ImageFormat::P3 => { |
|||
write!(f, "P3") |
|||
} |
|||
ImageFormat::P6 => { |
|||
write!(f, "P6") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
#[derive(Error, Debug)] |
|||
pub enum ImageError { |
|||
#[error("File not found")] |
|||
FileNotFound, |
|||
#[error("File not readable")] |
|||
FileNotReadable, |
|||
#[error("Invalid header information")] |
|||
InvalidHeader, |
|||
#[error("Invalid information in the data block")] |
|||
InvalidData, |
|||
#[error("Invalid max color information")] |
|||
InvalidMaxColor, |
|||
#[error("File is incomplete")] |
|||
IncompleteFile, |
|||
#[error("unknown data store error")] |
|||
Unknown, |
|||
} |
|||
pub struct Image { |
|||
pub format: ImageFormat, |
|||
pub width: usize, |
|||
pub height: usize, |
|||
pub data: Vec<Color>, |
|||
} |
|||
impl Image { |
|||
#[must_use] |
|||
pub fn new(width: usize, height: usize) -> Self { |
|||
Self { |
|||
format: ImageFormat::P6, |
|||
width, |
|||
height, |
|||
data: vec![Color::default(); width * height], |
|||
} |
|||
} |
|||
pub fn fill(&mut self, color: Color) { |
|||
for elem in &mut self.data { |
|||
*elem = color; |
|||
} |
|||
} |
|||
/// # Errors |
|||
/// |
|||
/// Will return `Error` if `filename` does not exist or the user does not have |
|||
/// permission to write to it, or the write operation fails. |
|||
pub fn write_ppm(&self, filename: &str) -> Result<(), Error> { |
|||
let file = File::create(filename)?; |
|||
let mut writer = BufWriter::new(file); |
|||
writeln!(&mut writer, "{}", self.format.to_string())?; |
|||
writeln!(&mut writer, "{} {} 255", self.width, self.height)?; |
|||
match self.format { |
|||
ImageFormat::P3 => { |
|||
writer.write_all( |
|||
&self |
|||
.data |
|||
.iter() |
|||
.flat_map(|color| { |
|||
vec![ |
|||
color.red.to_string(), |
|||
color.green.to_string(), |
|||
color.blue.to_string(), |
|||
] |
|||
}) |
|||
.collect::<Vec<String>>() |
|||
.join(" ") |
|||
.as_bytes(), |
|||
)?; |
|||
} |
|||
ImageFormat::P6 => { |
|||
writer.write_all( |
|||
&self |
|||
.data |
|||
.iter() |
|||
.flat_map(|color| vec![color.red, color.green, color.blue]) |
|||
.collect::<Vec<u8>>(), |
|||
)?; |
|||
} |
|||
} |
|||
Ok(()) |
|||
} |
|||
/// # Panics |
|||
/// |
|||
/// Panics if the format is not P6 or P3 PPM |
|||
/// # Errors |
|||
/// |
|||
/// Will return `Error` if `filename` does not exist or the user does not have |
|||
/// permission to read it or the read operation fails, or the file format does not |
|||
/// match the specification |
|||
pub fn read_ppm(filename: &str) -> Result<Image, ImageError> { |
|||
let mut file = File::open(filename).map_err(|_| ImageError::FileNotFound)?; |
|||
let mut data: Vec<u8> = Vec::new(); |
|||
file.read_to_end(&mut data) |
|||
.map_err(|_| ImageError::FileNotReadable)?; |
|||
let (i, format) = parser::parse_version(&data).map_err(|_| ImageError::InvalidHeader)?; |
|||
let (i, (width, height, max_color)) = |
|||
parser::parse_image_attributes(i).map_err(|_| ImageError::InvalidHeader)?; |
|||
if max_color != 255 { |
|||
return Err(ImageError::InvalidMaxColor); |
|||
} |
|||
let (_, data) = match format { |
|||
ImageFormat::P3 => parser::parse_data_ascii(i).map_err(|_| ImageError::InvalidData)?, |
|||
ImageFormat::P6 => parser::parse_data_binary(i).map_err(|_| ImageError::InvalidData)?, |
|||
}; |
|||
if data.len() != height * width { |
|||
return Err(ImageError::IncompleteFile); |
|||
}; |
|||
Ok(Image { |
|||
format, |
|||
width, |
|||
height, |
|||
data, |
|||
}) |
|||
} |
|||
} |
|||
impl Index<(usize, usize)> for Image { |
|||
type Output = Color; |
|||
fn index(&self, (x, y): (usize, usize)) -> &Color { |
|||
&self.data[x + y * self.width] |
|||
} |
|||
} |
|||
impl IndexMut<(usize, usize)> for Image { |
|||
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color { |
|||
&mut self.data[x + y * self.width] |
|||
} |
|||
} |
|||
use bitmap::Image; |
|||
// see read_ppm implementation in the bitmap library |
|||
pub fn main() { |
|||
// read a PPM image, which was produced by the write-a-ppm-file task |
|||
let image = Image::read_ppm("./test_image.ppm").unwrap(); |
|||
println!("Read using nom parsing:"); |
|||
println!("Format: {:?}", image.format); |
|||
println!("Dimensions: {} x {}", image.height, image.width); |
|||
} |
|||
</syntaxhighlight> |
|||
=={{header|Scala}}== |
=={{header|Scala}}== |
||
Uses the [[Basic_bitmap_storage#Scala| |
Uses the [[Basic_bitmap_storage#Scala|Basic Bitmap Storage]] and [[Grayscale_image#Scala|Grayscale Bitmap]] classes. |
||
<lang scala>import scala.io._ |
|||
See also Task [[Write_ppm_file#Scala|Write a PPM File]] for save code. |
|||
<syntaxhighlight lang="scala">import scala.io._ |
|||
import scala.swing._ |
import scala.swing._ |
||
import java.io._ |
import java.io._ |
||
Line 1,043: | Line 3,336: | ||
out |
out |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
Usage: |
Usage: |
||
< |
<syntaxhighlight lang="scala">object PixmapTest { |
||
def main(args: Array[String]): Unit = { |
def main(args: Array[String]): Unit = { |
||
val img=Pixmap.load("image.ppm"). |
val img=Pixmap.load("image.ppm").get |
||
val grayImg=BitmapOps.grayscale(img); |
|||
Pixmap.save(grayImg, "image_gray.ppm") |
|||
val mainframe=new MainFrame(){ |
val mainframe=new MainFrame(){ |
||
Line 1,054: | Line 3,349: | ||
visible=true |
visible=true |
||
contents=new Label(){ |
contents=new Label(){ |
||
icon=new ImageIcon( |
icon=new ImageIcon(grayImg.image) |
||
} |
} |
||
} |
} |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
=={{header|Seed7}}== |
|||
<syntaxhighlight 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.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;</syntaxhighlight> |
|||
=={{header|Tcl}}== |
=={{header|Tcl}}== |
||
{{libheader|Tk}} |
{{libheader|Tk}} |
||
The actual PPM reader is built into the photo image engine: |
The actual PPM reader is built into the photo image engine: |
||
< |
<syntaxhighlight lang="tcl">package require Tk |
||
proc readPPM {image file} { |
proc readPPM {image file} { |
||
$image read $file -format ppm |
$image read $file -format ppm |
||
}</ |
}</syntaxhighlight> |
||
Thus, to read a PPM, convert it to grayscale, and write it back out again becomes this (which requires Tcl 8.6 for <code>try</code>/<code>finally</code>); the PPM reader and writer are inlined because they are trivial at the script level: |
Thus, to read a PPM, convert it to grayscale, and write it back out again becomes this (which requires Tcl 8.6 for <code>try</code>/<code>finally</code>); the PPM reader and writer are inlined because they are trivial at the script level: |
||
< |
<syntaxhighlight lang="tcl">package require Tk |
||
proc grayscaleFile {filename {newFilename ""}} { |
proc grayscaleFile {filename {newFilename ""}} { |
||
Line 1,089: | Line 3,420: | ||
image delete $buffer |
image delete $buffer |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
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: |
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: |
||
< |
<syntaxhighlight lang="tcl">package require Tk |
||
proc grayscaleFile {filename {newFilename ""}} { |
proc grayscaleFile {filename {newFilename ""}} { |
||
Line 1,103: | Line 3,434: | ||
image delete $buffer |
image delete $buffer |
||
} |
} |
||
}</ |
}</syntaxhighlight> |
||
=={{header|UNIX Shell}}== |
|||
{{works with|ksh93}} |
|||
Ref: [[Bitmap#UNIX Shell]] |
|||
Add the following functions to the <tt>RGBColor_t</tt> type |
|||
<syntaxhighlight 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 |
|||
}</syntaxhighlight> |
|||
Add the following function to the <tt>Bitmap_t</tt> type |
|||
<syntaxhighlight 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<&- |
|||
}</syntaxhighlight> |
|||
Now we can: |
|||
<syntaxhighlight 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"</syntaxhighlight> |
|||
=={{header|Vedit macro language}}== |
=={{header|Vedit macro language}}== |
||
< |
<syntaxhighlight lang="vedit">// Load a PPM file |
||
// @10 = filename |
// @10 = filename |
||
// On return: |
// On return: |
||
Line 1,122: | Line 3,524: | ||
Search("|X", ADVANCE) // skip maxval (assume 255) |
Search("|X", ADVANCE) // skip maxval (assume 255) |
||
Del_Block(0,CP) // remove the header |
Del_Block(0,CP) // remove the header |
||
Return</ |
Return</syntaxhighlight> |
||
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]]. |
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]]. |
||
< |
<syntaxhighlight lang="vedit">// Load RGB image |
||
Reg_Set(10, "|(USER_MACRO)\example.ppm") |
Reg_Set(10, "|(USER_MACRO)\example.ppm") |
||
Call("LOAD_PPM") |
Call("LOAD_PPM") |
||
Line 1,143: | Line 3,545: | ||
// Cleanup and exit |
// Cleanup and exit |
||
Buf_Switch(#20) Buf_Quit(OK) |
Buf_Switch(#20) Buf_Quit(OK) |
||
return</ |
return</syntaxhighlight> |
||
=={{header|Wren}}== |
|||
{{libheader|DOME}} |
|||
This assumes that [https://rosettacode.org/wiki/File:Lenna100.jpg 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. |
|||
<syntaxhighlight lang="wren">import "graphics" for Canvas, ImageData, Color |
|||
import "dome" for Window, Process |
|||
import "io" for FileSystem |
|||
class Bitmap { |
|||
construct new(fileName, fileName2, width, height) { |
|||
Window.title = "Bitmap - read PPM file" |
|||
Window.resize(width, height) |
|||
Canvas.resize(width, height) |
|||
_w = width |
|||
_h = height |
|||
_fn2 = fileName2 |
|||
loadPPMFile(fileName) |
|||
} |
|||
init() { |
|||
toGrayScale() |
|||
// display images side by side |
|||
_bmp.draw(0, 0) |
|||
_bmp2.draw(536, 0) |
|||
// save gray scale image to file |
|||
_bmp2.saveToFile(_fn2) |
|||
} |
|||
loadPPMFile(fileName) { |
|||
var ppm = FileSystem.load(fileName) |
|||
var count = ppm.count // ensure file is fully loaded before proceeding |
|||
if (ppm[0..1] != "P6") { |
|||
System.print("The loaded file is not a P6 file.") |
|||
Process.exit() |
|||
} |
|||
var lines = ppm.split("\n") |
|||
if (Num.fromString(lines[2]) > 255) { |
|||
System.print("The maximum color value can't exceed 255.") |
|||
Process.exit() |
|||
} |
|||
var wh = lines[1].split(" ") |
|||
var w = Num.fromString(wh[0]) |
|||
var h = Num.fromString(wh[1]) |
|||
_bmp = ImageData.create(fileName, w, h) |
|||
var bytes = ppm.bytes |
|||
var i = bytes.count - 3 * w * h |
|||
for (y in 0...h) { |
|||
for (x in 0...w) { |
|||
var r = bytes[i] |
|||
var g = bytes[i+1] |
|||
var b = bytes[i+2] |
|||
var c = Color.rgb(r, g, b) |
|||
pset(x, y, c) |
|||
i = i + 3 |
|||
} |
|||
} |
|||
} |
|||
toGrayScale() { |
|||
_bmp2 = ImageData.create("gray scale", _bmp.width, _bmp.height) |
|||
for (x in 0..._bmp.width) { |
|||
for (y in 0..._bmp.height) { |
|||
var c1 = _bmp.pget(x, y) |
|||
var lumin = (0.2126 * c1.r + 0.7152 * c1.g + 0.0722 * c1.b).floor |
|||
var c2 = Color.rgb(lumin, lumin,lumin, c1.a) |
|||
_bmp2.pset(x, y, c2) |
|||
} |
|||
} |
|||
} |
|||
pset(x, y, col) { _bmp.pset(x, y, col) } |
|||
pget(x, y) { _bmp.pget(x, y) } |
|||
update() {} |
|||
draw(alpha) {} |
|||
} |
|||
var Game = Bitmap.new("Lenna100.ppm", "Lenna100_gs.jpg", 1048, 512)</syntaxhighlight> |
|||
=={{header|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. |
|||
<syntaxhighlight 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 |
|||
]</syntaxhighlight> |
|||
=={{header|Yabasic}}== |
|||
<syntaxhighlight lang="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</syntaxhighlight> |
|||
=={{header|zkl}}== |
|||
{{trans|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. |
|||
[[File:FBSLLena.png|right]] |
|||
<syntaxhighlight 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);</syntaxhighlight> |
|||
{{omit from|AWK}} |
|||
{{omit from|Lotus 123 Macro Scripting}} |
|||
{{omit from|PARI/GP}} |
{{omit from|PARI/GP}} |
Latest revision as of 10:02, 19 May 2024
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.
11l
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
PROC LoadHeader(RgbImage POINTER img
CHAR ARRAY format BYTE dev)
CHAR ARRAY line(255)
BYTE header,size,max,width,height
header=0 size=0 max=0
WHILE max=0
DO
InputSD(dev,line)
IF line(0)>0 AND line(1)#'# THEN
IF header=0 THEN
IF SCompare(format,format)#0 THEN
Break()
FI
header=1
ELSEIF size=0 THEN
DecodeSize(line,@width,@height)
IF width=0 OR height=0 THEN
Break()
FI
img.w=width img.h=height
size=1
ELSEIF max=0 THEN
max=ValB(line)
IF max#255 THEN
Break()
FI
FI
FI
OD
RETURN
PROC LoadPPM6(RgbImage POINTER img CHAR ARRAY path)
BYTE dev=[1],x,y
RGB c
Close(dev)
Open(dev,path,4)
LoadHeader(img,"P6",dev)
FOR y=0 TO img.h-1
DO
FOR x=0 TO img.w-1
DO
c.r=GetD(dev)
c.g=GetD(dev)
c.b=GetD(dev)
SetRgbPixel(img,x,y,c)
OD
OD
Close(dev)
RETURN
PROC SaveHeader(GrayImage POINTER img
CHAR ARRAY format BYTE dev)
PrintDE(dev,format)
PrintBD(dev,img.w)
PutD(dev,32)
PrintBDE(dev,img.h)
PrintBDE(dev,255)
RETURN
PROC SavePPM2(RgbImage POINTER img CHAR ARRAY path)
BYTE dev=[1],x,y,c
Close(dev)
Open(dev,path,8)
SaveHeader(img,"P2",dev)
FOR y=0 TO img.h-1
DO
FOR x=0 TO img.w-1
DO
c=GetGrayPixel(img,x,y)
PrintBD(dev,c)
IF x=img.w-1 THEN
PutDE(dev)
ELSE
PutD(dev,32)
FI
OD
OD
Close(dev)
RETURN
PROC Load(CHAR ARRAY path)
CHAR ARRAY line(255)
BYTE dev=[1]
Close(dev)
Open(dev,path,4)
WHILE Eof(dev)=0
DO
InputSD(dev,line)
PrintE(line)
OD
Close(dev)
RETURN
PROC Main()
BYTE ARRAY rgbdata(300),graydata(100)
RgbImage rgbimg
GrayImage grayimg
CHAR ARRAY path2="D:PPM2.PPM"
CHAR ARRAY path6="D:PPM6.PPM"
Put(125) PutE() ;clear the screen
InitRgbImage(rgbimg,0,0,rgbdata)
InitRgbToGray()
PrintF("Loading %S...%E%E",path6)
LoadPPM6(rgbimg,path6)
PrintF("Converting RGB to grayscale...%E%E")
InitGrayImage(grayimg,rgbimg.w,rgbimg.h,graydata)
RgbToGray(rgbimg,grayimg)
PrintF("Saving %S...%E%E",path2)
SavePPM2(grayimg,path2)
PrintF("Loading %S...%E%E",path2)
Load(path2)
RETURN
- Output:
Screenshot from Atari 8-bit computer
Loading D:PPM6.PPM... Converting RGB to grayscale... Saving D:PPM2.PPM... Loading D:PPM2.PPM... P2 3 4 255 0 18 182 54 201 73 237 255 61 45 54 74
Ada
with Ada.Characters.Latin_1; use Ada.Characters.Latin_1;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO;
function Get_PPM (File : File_Type) return Image is
use Ada.Characters.Latin_1;
use Ada.Integer_Text_IO;
function Get_Line return String is -- Skips comments
Byte : Character;
Buffer : String (1..80);
begin
loop
for I in Buffer'Range loop
Character'Read (Stream (File), Byte);
if Byte = LF then
exit when Buffer (1) = '#';
return Buffer (1..I - 1);
end if;
Buffer (I) := Byte;
end loop;
if Buffer (1) /= '#' then
raise Data_Error;
end if;
end loop;
end Get_Line;
Height : Integer;
Width : Integer;
begin
if Get_Line /= "P6" then
raise Data_Error;
end if;
declare
Line : String := Get_Line;
Start : Integer := Line'First;
Last : Positive;
begin
Get (Line, Width, Last); Start := Start + Last;
Get (Line (Start..Line'Last), Height, Last); Start := Start + Last;
if Start <= Line'Last then
raise Data_Error;
end if;
if Width < 1 or else Height < 1 then
raise Data_Error;
end if;
end;
if Get_Line /= "255" then
raise Data_Error;
end if;
declare
Result : Image (1..Height, 1..Width);
Buffer : String (1..Width * 3);
Index : Positive;
begin
for I in Result'Range (1) loop
String'Read (Stream (File), Buffer);
Index := Buffer'First;
for J in Result'Range (2) loop
Result (I, J) :=
( R => Luminance (Character'Pos (Buffer (Index))),
G => Luminance (Character'Pos (Buffer (Index + 1))),
B => Luminance (Character'Pos (Buffer (Index + 2)))
);
Index := Index + 3;
end loop;
end loop;
return Result;
end;
end Get_PPM;
The implementation propagates Data_Error when the file format is incorrect. End_Error is propagated when the file end is prematurely met. The following example illustrates conversion of a color file to grayscale.
declare
F1, F2 : File_Type;
begin
Open (F1, In_File, "city.ppm");
Create (F2, Out_File, "city_grayscale.ppm");
Put_PPM (F2, Color (Grayscale (Get_PPM (F1))));
Close (F1);
Close (F2);
end;
ATS
For this you will need the static and dynamic ATS source files of Bitmap#ATS, Grayscale_image#ATS, and Bitmap/Write_a_PPM_file#ATS. (You do not need libnetpbm, although one could easily use it with ATS.)
There are three files here: a static file for the interface to pixmap_read_ppm
, a dynamic file for the implementation of pixmap_read_ppm
, and a file for the program that converts an image to grayscale. (The last is a dynamic file, but we will call it the program file.)
With pixmap_read_ppm<rgb24>
you should be able to read any valid PPM, whether raw or plain, and with any valid Maxval. The result is a pixmap1(rgb24)
with implicit Maxval of 255. The reader tries to be very permissive, although there seems not much I can do about the strange way comments work in PPM.
The ATS static file
This file should be called bitmap_read_ppm_task.sats
.
#define ATS_PACKNAME "Rosetta_Code.bitmap_read_ppm_task"
staload "bitmap_task.sats"
fn {a : t@ype}
pixmap_read_ppm :
(* On failure to read, the return is None_vt(). I do not currently
provide any indication of why the attempt failed, although in
practice you probably would wish to add that. *)
FILEref ->
Option_vt ([w, h : pos] [p : addr | null < p]
@(mfree_gc_v p | pixmap (a, w, h, p)))
The ATS dynamic file
This file should be called bitmap_read_ppm_task.dats
.
(*------------------------------------------------------------------*)
#define ATS_DYNLOADFLAG 0
#define ATS_PACKNAME "Rosetta_Code.bitmap_read_ppm_task"
#include "share/atspre_staload.hats"
staload "bitmap_task.sats"
(* You need to staload bitmap_task.dats, so the ATS compiler will have
access to its implementations of templates. But we staload it
anonymously, so the programmer will not have access. *)
staload _ = "bitmap_task.dats"
staload "bitmap_read_ppm_task.sats"
(*------------------------------------------------------------------*)
datavtype magic_number_vt =
| Netpbm_magic_number of int
| Unknown_magic_number of ()
fn {}
read_magic_number (inpf : FILEref) : magic_number_vt =
let
val i = fileref_getc inpf
in
if i <> char2int0 'P' then
Unknown_magic_number ()
else
let
val i = fileref_getc inpf
in
if i < char2int0 '1' && char2int0 '7' < i then
Unknown_magic_number ()
else
Netpbm_magic_number (i - char2int0 '0')
end
end
fn {}
get_next_char (inpf : FILEref) : int =
let
fnx
get_next () : int =
let
val i = fileref_getc inpf
in
if i = char2int0 '#' then
skip_through_newline ()
else
i
end
and
skip_through_newline () : int =
let
val i = fileref_getc inpf
in
if i < 0 then
i
else if i = char2int0 '\n' then
get_next ()
else
skip_through_newline ()
end
in
get_next ()
end
(* The only tokens we need to scan for, in P1 through P6, are unsigned
integers. P7 headers (Portable Arbitrary Map) have a completely
different arrangement, but we are not handling that. *)
fn {}
get_next_integer (inpf : FILEref)
(* A negative return value means we have reached the end. We do
not distinguish whitespace characters from anything else that
is not a digit or '#'. (Really I want to use intmax_t here,
rather than llint, but there is no intmax_t support in the
prelude. The ats2-xprelude package has support, but I am
avoiding the dependency. *)
: llint =
let
fnx
look_for_digit () : llint =
let
val i = get_next_char inpf
in
if i < char2int0 '0' || char2int0 '9' < i then
look_for_digit ()
else
read_digits (g0i2i (i - char2int0 '0'))
end
and
read_digits (x : llint) : llint =
let
val i = get_next_char inpf
in
if i < char2int0 '0' || char2int0 '9' < i then
(* I cannot find an "ungetc" in prelude/SATS/filebas.sats,
so I will use the foreign function interface directly. *)
let
typedef FILEstar = $extype"FILE *"
extern castfn FILEref2star : FILEref -<> FILEstar
in
ignoret ($extfcall (int, "ungetc", i, FILEref2star inpf));
x
end
else
let
val digit : llint = g0i2i (i - char2int0 '0')
in
read_digits ((10LL * x) + digit)
end
end
in
look_for_digit ()
end
fn {}
read_ppm_header (inpf : FILEref)
: Option_vt @(ullint, ullint, ullint) =
let
val width = get_next_integer inpf
in
if width < 0LL then
None_vt ()
else
let
val height = get_next_integer inpf
in
if height < 0LL then
None_vt ()
else
let
val maxval = get_next_integer inpf
in
if maxval < 0LL then
None_vt ()
else
begin
(* There is supposed to be a whitespace character (or
comments and whitespace character) after the
MAXVAL. We will accept anything, whitespace or
not. *)
ignoret (fileref_getc inpf);
Some_vt @(g0i2u width, g0i2u height, g0i2u maxval)
end
end
end
end
fn {}
get_next_single_byte (inpf : FILEref) : llint =
let
val i = fileref_getc inpf
in
if i < 0 then
~1LL
else
g0i2i i
end
fn {}
get_next_double_byte (inpf : FILEref) : llint =
let
val i1 = fileref_getc inpf
in
if i1 < 0 then
~1LL
else
let
val i0 = fileref_getc inpf
in
if i0 < 0 then
~1LL
else
let
val i1 : llint = g0i2i i1
and i0 : llint = g0i2i i0
in
(i1 * 256LL) + i0
end
end
end
(*------------------------------------------------------------------*)
(* Implementation is provided only for rgb24. *)
extern castfn ull2sz : {i : int} ullint i -<> size_t i
extern castfn ull2u : {i : int} ullint i -<> uint i
extern castfn ull2u8 : ullint -<> uint8
extern fn {}
read_raw_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>)
extern fn {}
read_plain_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>)
extern fn {}
read_general_ppm_rgb24 : $d2ctype (pixmap_read_ppm<rgb24>)
extern fn {}
read_general$width () : [i : pos] size_t i
extern fn {}
read_general$height () : [i : pos] size_t i
extern fn {}
read_general$maxval () : [i : pos | i <= 65535] uint i
extern fn {}
read_general$next_value : FILEref -> llint
implement
pixmap_read_ppm<rgb24> inpf =
case+ read_magic_number inpf of
| ~ Unknown_magic_number () => None_vt ()
| ~ Netpbm_magic_number num =>
begin
case+ num of
| 6 => read_raw_ppm_rgb24 inpf
| 3 => read_plain_ppm_rgb24 inpf
| _ => None_vt
end
implement {}
read_raw_ppm_rgb24 inpf =
case+ read_ppm_header inpf of
| ~ None_vt () => None_vt ()
| ~ Some_vt @(width, height, maxval) =>
let
val width = g1ofg0 width
and height = g1ofg0 height
and maxval = g1ofg0 maxval
in
if (width < 1LLU) + (height < 1LLU) +
(maxval < 1LLU) + (65535LLU < maxval) then
None_vt ()
else
let
val w : Size_t = ull2sz width
val h : Size_t = ull2sz height
val maxval : uInt = ull2u maxval
in
if maxval = 255u then
let
val @(pfgc | pix) = pixmap_make<rgb24> (w, h)
val success =
load<rgb24> (inpf, pix, rgb24_make (255, 0, 0))
in
if ~success then
begin
free (pfgc | pix);
None_vt ()
end
else
Some_vt @(pfgc | pix)
end
else if maxval < 256u then
let
implement read_general$width<> () = w
implement read_general$height<> () = h
implement read_general$maxval<> () = maxval
implement
read_general$next_value<> inpf =
get_next_single_byte inpf
in
read_general_ppm_rgb24<> inpf
end
else
let
implement read_general$width<> () = w
implement read_general$height<> () = h
implement read_general$maxval<> () = maxval
implement
read_general$next_value<> inpf =
get_next_double_byte inpf
in
read_general_ppm_rgb24<> inpf
end
end
end
implement {}
read_plain_ppm_rgb24 inpf =
case+ read_ppm_header inpf of
| ~ None_vt () => None_vt ()
| ~ Some_vt @(width, height, maxval) =>
let
val width = g1ofg0 width
and height = g1ofg0 height
and maxval = g1ofg0 maxval
in
if (width < 1LLU) + (height < 1LLU) +
(maxval < 1LLU) + (65535LLU < maxval) then
None_vt ()
else
let
val w : Size_t = ull2sz width
val h : Size_t = ull2sz height
val maxval : uInt = ull2u maxval
implement read_general$width<> () = w
implement read_general$height<> () = h
implement read_general$maxval<> () = maxval
implement
read_general$next_value<> inpf =
get_next_integer inpf
in
read_general_ppm_rgb24<> inpf
end
end
implement {}
read_general_ppm_rgb24 inpf =
let
val [w : int] w = read_general$width<> ()
and [h : int] h = read_general$height<> ()
and maxval = read_general$maxval<> ()
fn
scale_value (v : ullint) : uint8 =
if maxval = 255u then
ull2u8 v
else
let
val maxval : ullint = g0u2u maxval
val v = 255LLU * v
val v1 = v / maxval
and v0 = v mod maxval
in
if v0 + v0 < maxval then
ull2u8 v1
else if maxval < v0 + v0 then
ull2u8 (succ v1)
else if v1 mod 2LLU = 0LLU then
ull2u8 v1
else
ull2u8 (succ v1)
end
(* For easier programming, start with a fully initialized
pixmap. The routine probably is I/O-bound, anyway. *)
val @(pfgc | pix) =
pixmap_make<rgb24> (w, h, rgb24_make (255, 0, 0))
macdef between (i, j, v) =
let
val v = ,(v)
in
(,(i) <= v) * (v <= ,(j))
end
fun
loop {x, y : nat | x <= w; y <= h}
.<h - y, w - x>.
(pix : !pixmap (rgb24, w, h),
x : size_t x,
y : size_t y)
: bool (* success *) =
if y = h then
true
else if x = w then
loop (pix, i2sz 0, succ y)
else
let
val maxv : llint = g0u2i maxval
val vr = read_general$next_value<> inpf
in
if ~between (0LL, maxv, vr) then
false
else
let
val vg = read_general$next_value<> inpf
in
if ~between (0LL, maxv, vg) then
false
else
let
val vb = read_general$next_value<> inpf
in
if ~between (0LL, maxv, vb) then
false
else
let
val r = scale_value (g0i2u vr)
and g = scale_value (g0i2u vg)
and b = scale_value (g0i2u vb)
in
pix[x, y] := rgb24_make @(r, g, b);
loop (pix, succ x, y)
end
end
end
end
val success = loop (pix, i2sz 0, i2sz 0)
in
if ~success then
begin
free (pfgc | pix);
None_vt ()
end
else
Some_vt @(pfgc | pix)
end
(*------------------------------------------------------------------*)
#ifdef BITMAP_READ_PPM_TASK_TEST #then
staload "bitmap_write_ppm_task.sats"
staload _ = "bitmap_write_ppm_task.dats"
(* The test program converts a PPM at standard input to a raw PPM with
MAXVAL 255. *)
implement
main0 () =
let
val pix_opt = pixmap_read_ppm<rgb24> stdin_ref
in
case+ pix_opt of
| ~ None_vt () => ()
| ~ Some_vt @(pfgc | pix) =>
begin
ignoret (pixmap_write_ppm (stdout_ref, pix));
free (pfgc | pix)
end
end
#endif
(*------------------------------------------------------------------*)
The ATS program file
This file should be called bitmap_read_ppm_task_program.dats
(though it actually could be called by another name).
(* The program should be able to read a PPM in raw or plain format,
with any valid Maxval. The output will be a grayscale raw PPM with
Maxval=255.
Compile with "myatscc bitmap_read_ppm_task_program.dats", which
should give you a program named "bitmap_read_ppm_task_program". *)
(*
##myatsccdef=\
patscc -std=gnu2x -g -O2 -DATS_MEMALLOC_LIBC \
-o $fname($1) $1 \
bitmap{,_{{read,write}_ppm,grayscale}}_task.{s,d}ats
*)
#include "share/atspre_staload.hats"
staload "bitmap_task.sats"
staload "bitmap_read_ppm_task.sats"
staload "bitmap_write_ppm_task.sats"
staload "bitmap_grayscale_task.sats"
staload _ = "bitmap_task.dats"
staload _ = "bitmap_read_ppm_task.dats"
staload _ = "bitmap_write_ppm_task.dats"
staload _ = "bitmap_grayscale_task.dats"
implement
main0 (argc, argv) =
let
val args = listize_argc_argv (argc, argv)
val nargs = length args
val inpf =
if nargs < 2 then
stdin_ref
else if args[1] = "-" then
stdin_ref
else
fileref_open_exn (args[1], file_mode_r)
val pix_opt = pixmap_read_ppm<rgb24> inpf
val () = fileref_close inpf
in
case+ pix_opt of
| ~ None_vt () =>
begin
free args;
println! ("For some reason, I failed to read the image.");
exit 1
end
| ~ Some_vt @(pfgc1 | pix1) =>
let
val @(pfgc2 | pix2) = pixmap_convert<rgb24,gray8> pix1
val () = free (pfgc1 | pix1)
val @(pfgc3 | pix3) = pixmap_convert<gray8,rgb24> pix2
val () = free (pfgc2 | pix2)
val outf =
if nargs < 3 then
stdout_ref
else if args[2] = "-" then
stdout_ref
else
fileref_open_exn (args[2], file_mode_w)
val success = pixmap_write_ppm<rgb24> (outf, pix3)
val () = fileref_close outf
val () = free (pfgc3 | pix3)
in
free args;
if ~success then
begin
println! ("For some reason, ",
"I failed to write a new image.");
exit 2
end
end
end
You can compile the program with the shell command
myatscc bitmap_read_ppm_task_program.dats
If compilation is successful, the program will be called bitmap_read_ppm_task_program
. You can give up to two arguments (any others will be ignored). The first argument is a file name for the input file, the second is the file name for the output file. Either argument can be "-", meaning to use the respective standard input or output. An argument omitted is equivalent to "-".
Shown in the margin are before and after for SIPI test image 4.1.06 (not counting that I have converted the PPM files to JPEGs).
AutoHotkey
Only ppm6 files supported.
img := ppm_read("lena50.ppm") ;
x := img[4,4] ; get pixel(4,4)
y := img[24,24] ; get pixel(24,24)
msgbox % x.rgb() " " y.rgb()
img.write("lena50copy.ppm")
return
ppm_read(filename, ppmo=0) ; only ppm6 files supported
{
if !ppmo ; if image not already in memory, read from filename
fileread, ppmo, % filename
index := 1
pos := 1
loop, parse, ppmo, `n, `r
{
if (substr(A_LoopField, 1, 1) == "#")
continue
loop,
{
if !pos := regexmatch(ppmo, "\d+", pixel, pos)
break
bitmap%A_Index% := pixel
if (index == 4)
Break
pos := regexmatch(ppmo, "\s", x, pos)
index ++
}
}
type := bitmap1
width := bitmap2
height := bitmap3
maxcolor := bitmap4
bitmap := Bitmap(width, height, color(0,0,0))
index := 1
i := 1
j := 1
bits := pos
loop % width * height
{
bitmap[i, j, "r"] := numget(ppmo, 3 * A_Index + bits, "uchar")
bitmap[i, j, "g"] := numget(ppmo, 3 * A_Index + bits + 1, "uchar")
bitmap[i, j, "b"] := numget(ppmo, 3 * A_Index + bits + 2, "uchar")
if (j == width)
{
j := 1
i += 1
}
else
j++
}
return bitmap
}
#include bitmap_storage.ahk ; from http://rosettacode.org/wiki/Basic_bitmap_storage/AutoHotkey
BBC BASIC
f% = OPENIN("c:\lena.ppm")
IF f%=0 ERROR 100, "Failed to open input file"
IF GET$#f% <> "P6" ERROR 101, "File is not in P6 format"
REPEAT
in$ = GET$#f%
UNTIL LEFT$(in$,1) <> "#"
size$ = in$
max$ = GET$#f%
Width% = VAL(size$)
space% = INSTR(size$, " ")
Height% = VALMID$(size$, space%)
VDU 23,22,Width%;Height%;8,16,16,128
FOR y% = Height%-1 TO 0 STEP -1
FOR x% = 0 TO Width%-1
r% = BGET#f% : g% = BGET#f% : b% = BGET#f%
l% = INT(0.3*r% + 0.59*g% + 0.11*b% + 0.5)
PROCsetpixel(x%,y%,l%,l%,l%)
NEXT
NEXT y%
END
DEF PROCsetpixel(x%,y%,r%,g%,b%)
COLOUR 1,r%,g%,b%
GCOL 1
LINE x%*2,y%*2,x%*2,y%*2
ENDPROC
C
It is up to the caller to open the file and pass the handler to the function. So this code can be used in Read image file through a pipe without modification. It only understands the P6 file format.
Interface:
image get_ppm(FILE *pf);
Implementation:
#include "imglib.h"
#define PPMREADBUFLEN 256
image get_ppm(FILE *pf)
{
char buf[PPMREADBUFLEN], *t;
image img;
unsigned int w, h, d;
int r;
if (pf == NULL) return NULL;
t = fgets(buf, PPMREADBUFLEN, pf);
/* the code fails if the white space following "P6" is not '\n' */
if ( (t == NULL) || ( strncmp(buf, "P6\n", 3) != 0 ) ) return NULL;
do
{ /* Px formats can have # comments after first line */
t = fgets(buf, PPMREADBUFLEN, pf);
if ( t == NULL ) return NULL;
} while ( strncmp(buf, "#", 1) == 0 );
r = sscanf(buf, "%u %u", &w, &h);
if ( r < 2 ) return NULL;
r = fscanf(pf, "%u", &d);
if ( (r < 1) || ( d != 255 ) ) return NULL;
fseek(pf, 1, SEEK_CUR); /* skip one byte, should be whitespace */
img = alloc_img(w, h);
if ( img != NULL )
{
size_t rd = fread(img->buf, sizeof(pixel), w*h, pf);
if ( rd < w*h )
{
free_img(img);
return NULL;
}
return img;
}
}
The following acts as a filter to convert a PPM file read from standard input into a PPM gray image, and it outputs the converted image to standard output (see Grayscale image, Write ppm file, and Raster graphics operations in general):
#include <stdio.h>
#include "imglib.h"
int main()
{
image source;
grayimage idest;
source = get_ppm(stdin);
idest = tograyscale(source);
free_img(source);
source = tocolor(idest);
output_ppm(stdout, source);
free_img(source); free_img((image)idest);
return 0;
}
C#
Tested with this solution.
using System.IO;
class PPMReader
{
public static Bitmap ReadBitmapFromPPM(string file)
{
var reader = new BinaryReader(new FileStream(file, FileMode.Open));
if (reader.ReadChar() != 'P' || reader.ReadChar() != '6')
return null;
reader.ReadChar(); //Eat newline
string widths = "", heights = "";
char temp;
while ((temp = reader.ReadChar()) != ' ')
widths += temp;
while ((temp = reader.ReadChar()) >= '0' && temp <= '9')
heights += temp;
if (reader.ReadChar() != '2' || reader.ReadChar() != '5' || reader.ReadChar() != '5')
return null;
reader.ReadChar(); //Eat the last newline
int width = int.Parse(widths),
height = int.Parse(heights);
Bitmap bitmap = new Bitmap(width, height);
//Read in the pixels
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
bitmap.SetPixel(x, y, new Bitmap.Color()
{
Red = reader.ReadByte(),
Green = reader.ReadByte(),
Blue = reader.ReadByte()
});
return bitmap;
}
}
Common Lisp
The function read-ppm-image reads either a P6 or P3 file depending on the file contents. The package description assumes that you have the Basic bitmap storage#Common Lisp package.
(in-package #:rgb-pixel-buffer)
(defparameter *whitespaces-chars* '(#\SPACE #\RETURN #\TAB #\NEWLINE #\LINEFEED))
(defun read-header-chars (stream &optional (delimiter-list *whitespaces-chars*))
(do ((c (read-char stream nil :eof)
(read-char stream nil :eof))
(vals nil (if (or (null c) (char= c #\#)) vals (cons c vals)))) ;;don't collect comment chars
((or (eql c :eof) (member c delimiter-list)) (map 'string #'identity (nreverse vals))) ;;return strings
(when (char= c #\#) ;;skip comments
(read-line stream))))
(defun read-ppm-file-header (file)
(with-open-file (s file :direction :input)
(do ((failure-count 0 (1+ failure-count))
(tokens nil (let ((t1 (read-header-chars s)))
(if (> (length t1) 0)
(cons t1 tokens)
tokens))))
((>= (length tokens) 4) (values (nreverse tokens)
(file-position s)))
(when (>= failure-count 10)
(error (format nil "File ~a does not seem to be a proper ppm file - maybe too many comment lines" file)))
(when (= (length tokens) 1)
(when (not (or (string= (first tokens) "P6") (string= (first tokens) "P3")))
(error (format nil "File ~a is not a ppm file - wrong magic-number. Read ~a instead of P6 or P3 " file (first tokens))))))))
(defun read-ppm-image (file)
(flet ((image-data-reader (stream start-position width height image-build-function read-function)
(file-position stream start-position)
(dotimes (row height)
(dotimes (col width)
(funcall image-build-function row col (funcall read-function stream))))))
(multiple-value-bind (header file-pos) (read-ppm-file-header file)
(let* ((image-type (first header))
(width (parse-integer (second header) :junk-allowed t))
(height (parse-integer (third header) :junk-allowed t))
(max-value (parse-integer (fourth header) :junk-allowed t))
(image (make-rgb-pixel-buffer width height)))
(when (> max-value 255)
(error "unsupported depth - convert to 1byte depth with pamdepth"))
(cond ((string= "P6" image-type)
(with-open-file (stream file :direction :input :element-type '(unsigned-byte 8))
(image-data-reader stream
file-pos
width
height
#'(lambda (w h val)
(setf (rgb-pixel image w h) val))
#'(lambda (stream)
(make-rgb-pixel (read-byte stream)
(read-byte stream)
(read-byte stream))))
image))
((string= "P3" image-type)
(with-open-file (stream file :direction :input)
(image-data-reader stream
file-pos
width
height
#'(lambda (w h val)
(setf (rgb-pixel image w h) val))
#'(lambda (stream)
(make-rgb-pixel (read stream)
(read stream)
(read stream))))
image))
(t 'unsupported))
image))))
(export 'read-ppm-image)
To read the feep.ppm file as shown on the description page for the ppm format use:
(read-ppm-image "feep.ppm")
D
The Image module contains a loadPPM6 function to load binary PPM images.
Delphi
Class helper for read and write Bitmap's and Ppm's
program BtmAndPpm;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
System.Classes,
Winapi.Windows,
Vcl.Graphics;
type
TBitmapHelper = class helper for TBitmap
private
public
procedure SaveAsPPM(FileName: TFileName; useGrayScale: Boolean = False);
procedure LoadFromPPM(FileName: TFileName; useGrayScale: Boolean = False);
end;
function ColorToGray(Color: TColor): TColor;
var
L: Byte;
begin
L := round(0.2126 * GetRValue(Color) + 0.7152 * GetGValue(Color) + 0.0722 *
GetBValue(Color));
Result := RGB(L, L, L);
end;
{ TBitmapHelper }
procedure TBitmapHelper.SaveAsPPM(FileName: TFileName; useGrayScale: Boolean = False);
var
i, j, color: Integer;
Header: AnsiString;
ppm: TMemoryStream;
begin
ppm := TMemoryStream.Create;
try
Header := Format('P6'#10'%d %d'#10'255'#10, [Self.Width, Self.Height]);
writeln(Header);
ppm.Write(Tbytes(Header), Length(Header));
for i := 0 to Self.Height - 1 do
for j := 0 to Self.Width - 1 do
begin
if useGrayScale then
color := ColorToGray(ColorToRGB(Self.Canvas.Pixels[i, j]))
else
color := ColorToRGB(Self.Canvas.Pixels[i, j]);
ppm.Write(color, 3);
end;
ppm.SaveToFile(FileName);
finally
ppm.Free;
end;
end;
procedure TBitmapHelper.LoadFromPPM(FileName: TFileName; useGrayScale: Boolean = False);
var
p: Integer;
ppm: TMemoryStream;
sW, sH: string;
temp: AnsiChar;
W, H: Integer;
Color: TColor;
function ReadChar: AnsiChar;
begin
ppm.Read(Result, 1);
end;
begin
ppm := TMemoryStream.Create;
ppm.LoadFromFile(FileName);
if ReadChar + ReadChar <> 'P6' then
exit;
repeat
temp := ReadChar;
if temp in ['0'..'9'] then
sW := sW + temp;
until temp = ' ';
repeat
temp := ReadChar;
if temp in ['0'..'9'] then
sH := sH + temp;
until temp = #10;
W := StrToInt(sW);
H := StrToInt(sH);
if ReadChar + ReadChar + ReadChar <> '255' then
exit;
ReadChar(); //skip newLine
SetSize(W, H);
p := 0;
while ppm.Read(Color, 3) > 0 do
begin
if useGrayScale then
Color := ColorToGray(Color);
Canvas.Pixels[p mod W, p div W] := Color;
inc(p);
end;
ppm.Free;
end;
begin
with TBitmap.Create do
begin
// Load bmp
LoadFromFile('Input.bmp');
// Save as ppm
SaveAsPPM('Output.ppm');
// Load as ppm and convert in grayscale
LoadFromPPM('Output.ppm', True);
// Save as bmp
SaveToFile('Output.bmp');
Free;
end;
end.
E
def chr := <import:java.lang.makeCharacter>.asChar
def readPPM(inputStream) {
# Proper native-to-E stream IO facilities have not been designed and
# implemented yet, so we are borrowing Java's. Poorly. This *will* be
# improved eventually.
# Reads one header token, skipping comments and whitespace, and exactly
# one trailing whitespace character
def readToken() {
var token := ""
var c := chr(inputStream.read())
while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '#') {
if (c == '#') {
while (c != '\n') { c := chr(inputStream.read()) }
}
# skip over initial whitespace
c := chr(inputStream.read())
}
while (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) {
if (c == '#') {
while (c != '\n') { c := chr(inputStream.read()) }
} else {
token += E.toString(c)
c := chr(inputStream.read())
}
}
return token
}
# Header
require(readToken() == "P6")
def width := __makeInt(readToken())
def height := __makeInt(readToken())
def maxval := __makeInt(readToken())
def size := width * height * 3
# Body
# See [[Basic bitmap storage]] for the definition and origin of sign()
def data := <elib:tables.makeFlexList>.fromType(<type:java.lang.Byte>, size)
if (maxval >= 256) {
for _ in 1..size {
data.push(sign((inputStream.read() * 256 + inputStream.read()) * 255 // maxval))
}
} else {
for _ in 1..size {
data.push(sign(inputStream.read() * 255 // maxval))
}
}
def image := makeImage(width, height)
image.replace(data.snapshot())
return image
}
Note: As of this writing the grayscale image task has not been implemented, so the task code (below) won't actually run yet. But readPPM above has been tested separately.
def readPPMTask(inputFile, outputFile) {
makeGrayscale \
.fromColor(readPPM(<import:java.io.makeFileInputStream>(inputFile))) \
.toColor() \
.writePPM(<import:java.io.makeFileOutputStream>(outputFile))
}
Erlang
% This module provides basic operations on ppm files:
% Read from file, create ppm in memory (from generic bitmap) and save to file.
% Writing PPM files was introduced in roseta code task 'Bitmap/Write a PPM file'
% but the same code is included here to provide whole set of operations on ppm
% needed for purposes of this task.
-module(ppm).
-export([ppm/1, write/2, read/1]).
% constants for writing ppm file
-define(WHITESPACE, <<10>>).
-define(SPACE, <<32>>).
% constants for reading ppm file
-define(WHITESPACES, [9, 10, 13, 32]).
-define(PPM_HEADER, "P6").
% data structure introduced in task Bitmap (module ros_bitmap.erl)
-record(bitmap, {
mode = rgb,
pixels = nil,
shape = {0, 0}
}).
%%%%%%%%% API %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% read ppm file from file
read(Filename) ->
{ok, File} = file:read_file(Filename),
parse(File).
% create ppm image from bitmap record
ppm(Bitmap) ->
{Width, Height} = Bitmap#bitmap.shape,
Pixels = ppm_pixels(Bitmap),
Maxval = 255, % original ppm format maximum
list_to_binary([
header(), width_and_height(Width, Height), maxval(Maxval), Pixels]).
% write bitmap as ppm file
write(Bitmap, Filename) ->
Ppm = ppm(Bitmap),
{ok, File} = file:open(Filename, [binary, write]),
file:write(File, Ppm),
file:close(File).
%%%%%%%%% Reading PPM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
parse(Binary) ->
{?PPM_HEADER, Data} = get_next_token(Binary),
{Width, HeightAndRest} = get_next_token(Data),
{Height, MaxValAndRest} = get_next_token(HeightAndRest),
{_MaxVal, RawPixels} = get_next_token(MaxValAndRest),
Shape = {list_to_integer(Width), list_to_integer(Height)},
Pixels = load_pixels(RawPixels),
#bitmap{pixels=Pixels, shape=Shape}.
% load binary as a list of RGB triplets
load_pixels(Binary) when is_binary(Binary)->
load_pixels([], Binary).
load_pixels(Acc, <<>>) ->
array:from_list(lists:reverse(Acc));
load_pixels(Acc, <<R, G, B, Rest/binary>>) ->
load_pixels([<<R,G,B>>|Acc], Rest).
is_whitespace(Byte) ->
lists:member(Byte, ?WHITESPACES).
% get next part of PPM file, skip whitespaces, and return the rest of a binary
get_next_token(Binary) ->
get_next_token("", true, Binary).
get_next_token(CurrentToken, false, <<Byte, Rest/binary>>) ->
case is_whitespace(Byte) of
true ->
{lists:reverse(CurrentToken), Rest};
false ->
get_next_token([Byte | CurrentToken], false, Rest)
end;
get_next_token(CurrentToken, true, <<Byte, Rest/binary>>) ->
case is_whitespace(Byte) of
true ->
get_next_token(CurrentToken, true, Rest);
false ->
get_next_token([Byte | CurrentToken], false, Rest)
end.
%%%%%%%%% Writing PPM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
header() ->
[<<"P6">>, ?WHITESPACE].
width_and_height(Width, Height) ->
[encode_decimal(Width), ?SPACE, encode_decimal(Height), ?WHITESPACE].
maxval(Maxval) ->
[encode_decimal(Maxval), ?WHITESPACE].
ppm_pixels(Bitmap) ->
% 24 bit color depth
array:to_list(Bitmap#bitmap.pixels).
encode_decimal(Number) ->
integer_to_list(Number).
Usage in accordance with Grayscale Task:
Colorful = ppm:read("colorful.ppm"),
Gray = ros_bitmap:convert(ros_bitmap:convert(Colorful, grey), rgb),
ppm:write(Gray, "gray.ppm"),
Euphoria
include get.e
function get2(integer fn)
sequence temp
temp = get(fn)
return temp[2] - temp[1]*temp[1]
end function
function read_ppm(sequence filename)
sequence image, line
integer dimx, dimy, maxcolor
atom fn
fn = open(filename, "rb")
if fn < 0 then
return -1 -- unable to open
end if
line = gets(fn)
if not equal(line,"P6\n") then
return -1 -- only ppm6 files are supported
end if
dimx = get2(fn)
if dimx < 0 then
return -1
end if
dimy = get2(fn)
if dimy < 0 then
return -1
end if
maxcolor = get2(fn)
if maxcolor != 255 then
return -1 -- maxcolors other then 255 are not supported
end if
image = repeat(repeat(0,dimy),dimx)
for y = 1 to dimy do
for x = 1 to dimx do
image[x][y] = getc(fn)*#10000 + getc(fn)*#100 + getc(fn)
end for
end for
close(fn)
return image
end function
Converting an image to grayscale:
sequence image
image = read_ppm("image.ppm")
image = to_gray(image)
image = to_color(image)
write_ppm("image_gray.ppm",image)
FBSL
Read a colored PPM file, convert it to grayscale and write back to disk under a different name. Sanity checks are omitted for brevity.
24-bpp P6 PPM solution:
#ESCAPECHARS ON
DIM colored = ".\\Lena.ppm", grayscale = ".\\LenaGry.ppm"
DIM head, tail, r, g, b, l, ptr, blobsize
FILEGET(FILEOPEN(colored, BINARY), FILELEN(colored)): FILECLOSE(FILEOPEN) ' Load buffer
blobsize = INSTR(FILEGET, "\n255\n") + 4 ' Get sizeof PPM header
head = @FILEGET + blobsize: tail = @FILEGET + FILELEN ' Set loop bounds
FOR ptr = head TO tail STEP 3 ' Transform color triplets
r = PEEK(ptr + 0, 1) ' Read colors stored in RGB order
g = PEEK(ptr + 1, 1)
b = PEEK(ptr + 2, 1)
l = 0.2126 * r + 0.7152 * g + 0.0722 * b ' Derive luminance
POKE(ptr + 0, CHR(l))(ptr + 1, CHR)(ptr + 2, CHR) ' Write grayscale
NEXT
FILEPUT(FILEOPEN(grayscale, BINARY_NEW), FILEGET): FILECLOSE(FILEOPEN) ' Save buffer
Forth
: read-ppm { fid -- bmp }
pad dup 80 fid read-line throw 0= abort" Partial line"
s" P6" compare abort" Only P6 supported."
pad dup 80 fid read-line throw 0= abort" Partial line"
0. 2swap >number
1 /string \ skip space
0. 2swap >number
2drop drop nip ( w h )
bitmap { bmp }
pad dup 80 fid read-line throw 0= abort" Partial line"
s" 255" compare abort" Only 8-bits per color channel supported"
0 pad !
bmp bdim
0 do
dup 0 do
pad 3 fid read-file throw
3 - abort" Not enough pixel data in file"
pad @ i j bmp b!
loop
loop drop
bmp ;
\ testing round-trip
4 3 bitmap value test
red test bfill
green 1 2 test b!
s" red.ppm" w/o create-file throw
test over write-ppm
close-file throw
s" red.ppm" r/o open-file throw
dup read-ppm value test2
close-file throw
: bsize ( bmp -- len ) bdim * pixels bdata ;
test dup bsize test2 dup bsize compare . \ 0 if identical
Fortran
(This function is part of module RCImageIO, see Write ppm file)
subroutine read_ppm(u, img)
integer, intent(in) :: u
type(rgbimage), intent(out) :: img
integer :: i, j, ncol, cc
character(2) :: sign
character :: ccode
img%width = 0
img%height = 0
nullify(img%red)
nullify(img%green)
nullify(img%blue)
read(u, '(A2)') sign
read(u, *) img%width, img%height
read(u, *) ncol
write(0,*) sign
write(0,*) img%width, img%height
write(0,*) ncol
if ( ncol /= 255 ) return
call alloc_img(img, img%width, img%height)
if ( valid_image(img) ) then
do j=1, img%height
do i=1, img%width
read(u, '(A1)', advance='no', iostat=status) ccode
cc = iachar(ccode)
img%red(i,j) = cc
read(u, '(A1)', advance='no', iostat=status) ccode
cc = iachar(ccode)
img%green(i,j) = cc
read(u, '(A1)', advance='no', iostat=status) ccode
cc = iachar(ccode)
img%blue(i,j) = cc
end do
end do
end if
end subroutine read_ppm
Notes:
- doing formatted I/O with Fortran is a pain... And unformatted does not mean free; Fortran2003 has streams, but they are not implemented (yet) in GNU Fortran compiler. Here (as in the write part) I've tried to handle the PPM format through formatted I/O. The tests worked but I have not tried still everything.
- comments after the first line are not handled
FreeBASIC
Dim As String imagen = "Lena.ppm"
Sub readPPM (fs As String)
Dim As Integer x, y, ancho, alto
Dim As String t, kolor
Dim As Ubyte r, g, b
If Len(fs) = 0 Then Print "No PPM file name indicated.": Exit Sub
Dim As Long ff = Freefile
Open fs For Binary As #ff
If Err Then Print "File "; fs; " not found.": Exit Sub
Input #ff, t, ancho, alto, kolor
If t = "P6" Then
Screenres ancho, alto, 32
For y = 0 To alto - 1
For x = 0 To ancho - 1
Get #ff, , r
Get #ff, , g
Get #ff, , b
Pset (x, y), Rgb(r, g, b)
Next x
Next y
Close #ff
Else
Print "File is NOT PPM P6 type."
End If
End Sub
readPPM(imagen)
Sleep
Go
package raster
import (
"errors"
"io"
"io/ioutil"
"os"
"regexp"
"strconv"
)
// ReadFrom constructs a Bitmap object from an io.Reader.
func ReadPpmFrom(r io.Reader) (b *Bitmap, err error) {
var all []byte
all, err = ioutil.ReadAll(r)
if err != nil {
return
}
bss := rxHeader.FindSubmatch(all)
if bss == nil {
return nil, errors.New("unrecognized ppm header")
}
x, _ := strconv.Atoi(string(bss[3]))
y, _ := strconv.Atoi(string(bss[6]))
maxval, _ := strconv.Atoi(string(bss[9]))
if maxval > 255 {
return nil, errors.New("16 bit ppm not supported")
}
allCmts := append(append(append(bss[1], bss[4]...), bss[7]...), bss[10]...)
b = NewBitmap(x, y)
b.Comments = rxComment.FindAllString(string(allCmts), -1)
b3 := all[len(bss[0]):]
var n1 int
for i := range b.px {
b.px[i].R = byte(int(b3[n1]) * 255 / maxval)
b.px[i].G = byte(int(b3[n1+1]) * 255 / maxval)
b.px[i].B = byte(int(b3[n1+2]) * 255 / maxval)
n1 += 3
}
return
}
const (
// single whitespace character
ws = "[ \n\r\t\v\f]"
// isolated comment
cmt = "#[^\n\r]*"
// comment sub expression
cmts = "(" + ws + "*" + cmt + "[\n\r])"
// number with leading comments
num = "(" + cmts + "+" + ws + "*|" + ws + "+)([0-9]+)"
)
var rxHeader = regexp.MustCompile("^P6" + num + num + num +
"(" + cmts + "*" + ")" + ws)
var rxComment = regexp.MustCompile(cmt)
// ReadFile writes binary P6 format PPM from the specified filename.
func ReadPpmFile(fn string) (b *Bitmap, err error) {
var f *os.File
if f, err = os.Open(fn); err != nil {
return
}
if b, err = ReadPpmFrom(f); err != nil {
return
}
return b, f.Close()
}
Demonstration program, also demonstrating functions from task Grayscale image:
package main
// Files required to build supporting package raster are found in:
// * This task (immediately above)
// * Bitmap
// * Grayscale image
// * Write a PPM file
import (
"raster"
"fmt"
)
func main() {
// (A file with this name is output by the Go solution to the task
// "Bitmap/Read an image through a pipe," but of course any 8-bit
// P6 PPM file should work.)
b, err := raster.ReadPpmFile("pipein.ppm")
if err != nil {
fmt.Println(err)
return
}
b = b.Grmap().Bitmap()
err = b.WritePpmFile("grayscale.ppm")
if err != nil {
fmt.Println(err)
}
}
Haskell
The definition of Bitmap.Netpbm.readNetpbm is given here.
import Bitmap
import Bitmap.RGB
import Bitmap.Gray
import Bitmap.Netpbm
import Control.Monad
import Control.Monad.ST
main =
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>=
stToIO . toGrayImage >>=
writeNetpbm "new.pgm"
The above writes a PGM, not a PPM, since the image being output is in grayscale. If you actually want a gray PPM, convert the Image RealWorld Gray back to an Image RealWorld RGB first:
main =
(readNetpbm "original.ppm" :: IO (Image RealWorld RGB)) >>=
stToIO . (toRGBImage <=< toGrayImage) >>=
writeNetpbm "new.ppm"
J
Solution:
Uses makeRGB from Basic bitmap storage.
require 'files'
readppm=: monad define
dat=. fread y NB. read from file
msk=. 1 ,~ (*. 3 >: +/\) (LF&=@}: *. '#'&~:@}.) dat NB. mark field ends
't wbyh maxval dat'=. msk <;._2 dat NB. parse
'wbyh maxval'=. 2 1([ {. [: _99&". (LF,' ')&charsub)&.> wbyh;maxval NB. convert to numeric
if. (_99 0 +./@e. wbyh,maxval) +. 'P6' -.@-: 2{.t do. _1 return. end.
(a. i. dat) makeRGB |.wbyh NB. convert to basic bitmap format
)
Example:
Using utilities and file from Grayscale image and Write ppm file.
Writes a gray PPM file (a color format) which is bigger than necessary. A PGM file would be more appropriate.
myimg=: readppm jpath '~temp/myimg.ppm'
myimgGray=: toColor toGray myimg
myimgGray writeppm jpath '~temp/myimgGray.ppm'
Java
For convenience, the code for the class used in the Bitmap task here and integrated with the code in the Grayscale image is included.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ReadPPMFile {
public static void main(String[] aArgs) throws IOException {
// Using the file created in the Bitmap task
String filePath = "output.ppm";
reader = new BufferedInputStream( new FileInputStream(filePath) );
final char header1 = (char) reader.read();
final char header2 = (char) reader.read();
final char header3 = (char) reader.read();
if ( header1 != 'P' || header2 != '6' || header3 != END_OF_LINE) {
reader.close();
throw new IllegalArgumentException("Not a valid P6 PPM file");
}
final int width = processCharacters(SPACE_CHARACTER);
final int height = processCharacters(END_OF_LINE);
final int maxColorValue = processCharacters(END_OF_LINE);
if ( maxColorValue < 0 || maxColorValue > 255 ) {
reader.close();
throw new IllegalArgumentException("Maximum color value is outside the range 0..255");
}
// Remove any comments before reading data
reader.mark(1);
while ( reader.read() == START_OF_COMMENT ) {
while ( reader.read() != END_OF_LINE );
reader.mark(1);
}
reader.reset();
// Read data
BasicBitmapStorage bitmap = new BasicBitmapStorage(width, height);
byte[] buffer = new byte[width * 3];
for ( int y = 0; y < height; y++ ) {
reader.read(buffer, 0, buffer.length);
for ( int x = 0; x < width; x++ ) {
Color color = new Color(Byte.toUnsignedInt(buffer[x * 3]),
Byte.toUnsignedInt(buffer[x * 3 + 1]),
Byte.toUnsignedInt(buffer[x * 3 + 2]));
bitmap.setPixel(x, y, color);
}
}
reader.close();
// Convert to gray scale and save to a file
bitmap.convertToGrayscale();
File grayFile = new File("outputGray.jpg");
ImageIO.write((RenderedImage) bitmap.getImage(), "jpg", grayFile);
}
private static int processCharacters(char aChar) throws IOException {
StringBuilder characters = new StringBuilder();
char ch;
while ( ( ch = (char) reader.read() ) != aChar ) {
if ( ch == START_OF_COMMENT ) {
while ( reader.read() != END_OF_LINE );
continue;
}
characters.append(ch);
}
return Integer.valueOf(characters.toString());
}
private static BufferedInputStream reader;
private static final char START_OF_COMMENT = '#';
private static final char SPACE_CHARACTER = ' ';
private static final char END_OF_LINE = '\n';
}
final class BasicBitmapStorage {
public BasicBitmapStorage(int aWidth, int aHeight) {
image = new BufferedImage(aWidth, aHeight, BufferedImage.TYPE_INT_RGB);
}
public void fill(Color aColor) {
Graphics graphics = image.getGraphics();
graphics.setColor(aColor);
graphics.fillRect(0, 0, image.getWidth(), image.getHeight());
}
public Color getPixel(int aX, int aY) {
return new Color(image.getRGB(aX, aY));
}
public void setPixel(int aX, int aY, Color aColor) {
image.setRGB(aX, aY, aColor.getRGB());
}
public Image getImage() {
return image;
}
public void convertToGrayscale() {
for ( int y = 0; y < image.getHeight(); y++ ) {
for ( int x = 0; x < image.getWidth(); x++ ) {
int color = image.getRGB(x, y);
int alpha = ( color >> 24 ) & 255;
int red = ( color >> 16 ) & 255;
int green = ( color >> 8 ) & 255;
int blue = color & 255;
final int luminance = (int) ( 0.2126 * red + 0.7152 * green + 0.0722 * blue );
alpha = alpha << 24;
red = luminance << 16;
green = luminance << 8;
blue = luminance;
color = alpha + red + green + blue;
image.setRGB(x, y, color);
}
}
}
private final BufferedImage image;
}
- Output:
Julia
using Images, FileIO, Netpbm
rgbimg = load("data/bitmapInputTest.ppm")
greyimg = Gray.(rgbimg)
save("data/bitmapOutputTest.ppm", greyimg)
Kotlin
For convenience, we repeat the code for the class used in the Bitmap task here and integrate the code in the Grayscale image task within it.
// Version 1.2.40
import java.awt.Color
import java.awt.Graphics
import java.awt.image.BufferedImage
import java.io.FileInputStream
import java.io.PushbackInputStream
import java.io.File
import javax.imageio.ImageIO
class BasicBitmapStorage(width: Int, height: Int) {
val image = BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR)
fun fill(c: Color) {
val g = image.graphics
g.color = c
g.fillRect(0, 0, image.width, image.height)
}
fun setPixel(x: Int, y: Int, c: Color) = image.setRGB(x, y, c.getRGB())
fun getPixel(x: Int, y: Int) = Color(image.getRGB(x, y))
fun toGrayScale() {
for (x in 0 until image.width) {
for (y in 0 until image.height) {
var rgb = image.getRGB(x, y)
val red = (rgb shr 16) and 0xFF
val green = (rgb shr 8) and 0xFF
val blue = rgb and 0xFF
val lumin = (0.2126 * red + 0.7152 * green + 0.0722 * blue).toInt()
rgb = (lumin shl 16) or (lumin shl 8) or lumin
image.setRGB(x, y, rgb)
}
}
}
}
fun PushbackInputStream.skipComment() {
while (read().toChar() != '\n') {}
}
fun PushbackInputStream.skipComment(buffer: ByteArray) {
var nl: Int
while (true) {
nl = buffer.indexOf(10) // look for newline at end of comment
if (nl != -1) break
read(buffer) // read another buffer full if newline not yet found
}
val len = buffer.size
if (nl < len - 1) unread(buffer, nl + 1, len - nl - 1)
}
fun Byte.toUInt() = if (this < 0) 256 + this else this.toInt()
fun main(args: Array<String>) {
// use file, output.ppm, created in the Bitmap/Write a PPM file task
val pbis = PushbackInputStream(FileInputStream("output.ppm"), 80)
pbis.use {
with (it) {
val h1 = read().toChar()
val h2 = read().toChar()
val h3 = read().toChar()
if (h1 != 'P' || h2 != '6' || h3 != '\n') {
println("Not a P6 PPM file")
System.exit(1)
}
val sb = StringBuilder()
while (true) {
val r = read().toChar()
if (r == '#') { skipComment(); continue }
if (r == ' ') break // read until space reached
sb.append(r.toChar())
}
val width = sb.toString().toInt()
sb.setLength(0)
while (true) {
val r = read().toChar()
if (r == '#') { skipComment(); continue }
if (r == '\n') break // read until new line reached
sb.append(r.toChar())
}
val height = sb.toString().toInt()
sb.setLength(0)
while (true) {
val r = read().toChar()
if (r == '#') { skipComment(); continue }
if (r == '\n') break // read until new line reached
sb.append(r.toChar())
}
val maxCol = sb.toString().toInt()
if (maxCol !in 0..255) {
println("Maximum color value is outside the range 0..255")
System.exit(1)
}
var buffer = ByteArray(80)
// get rid of any more opening comments before reading data
while (true) {
read(buffer)
if (buffer[0].toChar() == '#') {
skipComment(buffer)
}
else {
unread(buffer)
break
}
}
// read data
val bbs = BasicBitmapStorage(width, height)
buffer = ByteArray(width * 3)
var y = 0
while (y < height) {
read(buffer)
for (x in 0 until width) {
val c = Color(
buffer[x * 3].toUInt(),
buffer[x * 3 + 1].toUInt(),
buffer[x * 3 + 2].toUInt()
)
bbs.setPixel(x, y, c)
}
y++
}
// convert to grayscale and save to a file
bbs.toGrayScale()
val grayFile = File("output_gray.jpg")
ImageIO.write(bbs.image, "jpg", grayFile)
}
}
}
Lua
function Read_PPM( filename )
local fp = io.open( filename, "rb" )
if fp == nil then return nil end
local data = fp:read( "*line" )
if data ~= "P6" then return nil end
repeat
data = fp:read( "*line" )
until string.find( data, "#" ) == nil
local image = {}
local size_x, size_y
size_x = string.match( data, "%d+" )
size_y = string.match( data, "%s%d+" )
data = fp:read( "*line" )
if tonumber(data) ~= 255 then return nil end
for i = 1, size_x do
image[i] = {}
end
for j = 1, size_y do
for i = 1, size_x do
image[i][j] = { string.byte( fp:read(1) ), string.byte( fp:read(1) ), string.byte( fp:read(1) ) }
end
end
fp:close()
return image
end
M2000 Interpreter
Now function Bitmap has double signature. With two numbers make a bitmap,with all pixels white. With one number, expect that it is a file number and read file, and then return the bitmap.
Module Checkit {
Function Bitmap {
If match("NN") then {
Read x as long, y as long
} else.if Match("N") Then {
\\ is a file?
Read f
if not Eof(f) then {
Line Input #f, p3$
If p3$="P3" Then {
Line Input #f, Comment$
if left$(Comment$,1)="#" then {
Line Input #f, Dimension$
} else Dimension$=Comment$
long x=Val(piece$(Dimension$," ")(0))
long y=Val(piece$(Dimension$," ")(1))
do {
Line Input #f, P255$
} until left$(P255$, 1)<>"#"
If not P255$="255" then Error "Not proper ppm format"
}
}
} else Error "No proper arguments"
if x<1 or y<1 then Error "Wrong dimensions"
structure rgb {
red as byte
green as byte
blue as byte
}
m=len(rgb)*x mod 4
if m>0 then m=4-m ' add some bytes to raster line
m+=len(rgb) *x
Structure rasterline {
{
pad as byte*m
}
\\ union pad+hline
hline as rgb*x
}
Structure Raster {
magic as integer*4
w as integer*4
h as integer*4
lines as rasterline*y
}
Buffer Clear Image1 as Raster
\\ 24 chars as header to be used from bitmap render build in functions
Return Image1, 0!magic:="cDIB", 0!w:=Hex$(x,2), 0!h:=Hex$(y, 2)
\\ fill white (all 255)
\\ Str$(string) convert to ascii, so we get all characters from words width to byte width
if not valid(f) then Return Image1, 0!lines:=Str$(String$(chrcode$(255), Len(rasterline)*y))
Buffer Clear Pad as Byte*4
SetPixel=Lambda Image1, Pad,aLines=Len(Raster)-Len(Rasterline), blines=-Len(Rasterline) (x, y, c) ->{
where=alines+3*x+blines*y
if c>0 then c=color(c)
c-!
Return Pad, 0:=c as long
Return Image1, 0!where:=Eval(Pad, 2) as byte, 0!where+1:=Eval(Pad, 1) as byte, 0!where+2:=Eval(Pad, 0) as byte
}
GetPixel=Lambda Image1,aLines=Len(Raster)-Len(Rasterline), blines=-Len(Rasterline) (x,y) ->{
where=alines+3*x+blines*y
=color(Eval(image1, where+2 as byte), Eval(image1, where+1 as byte), Eval(image1, where as byte))
}
StrDib$=Lambda$ Image1, Raster -> {
=Eval$(Image1, 0, Len(Raster))
}
CopyImage=Lambda Image1 (image$) -> {
if left$(image$,12)=Eval$(Image1, 0, 24 ) Then {
Return Image1, 0:=Image$
} Else Error "Can't Copy Image"
}
Export2File=Lambda Image1, x, y (f) -> {
\\ use this between open and close
Print #f, "P3"
Print #f,"# Created using M2000 Interpreter"
Print #f, x;" ";y
Print #f, 255
x2=x-1
where=24
For y1= 0 to y-1 {
a$=""
For x1=0 to x2 {
Print #f, a$;Eval(Image1, where+2 as byte);" ";
Print #f, Eval(Image1, where+1 as byte);" ";
Print #f, Eval(Image1, where as byte);
where+=3
a$=" "
}
Print #f
m=where mod 4
if m<>0 then where+=4-m
}
}
if valid(F) then {
'load RGB values form file
x0=x-1
where=24
For y1=y-1 to 0 {
do {
Line Input #f, aline$
} until left$(aline$,1)<>"#"
flush ' empty stack
Stack aline$ ' place all values to stack as FIFO
For x1=0 to x0 {
\\ now read from stack using Number
Return Image1, 0!where+2:=Number as byte, 0!where+1:=Number as byte, 0!where:=Number as byte
where+=3
}
m=where mod 4
if m<>0 then where+=4-m
}
}
Group Bitmap {
SetPixel=SetPixel
GetPixel=GetPixel
Image$=StrDib$
Copy=CopyImage
ToFile=Export2File
}
=Bitmap
}
A=Bitmap(10, 10)
Call A.SetPixel(5,5, color(128,0,255))
Open "A.PPM" for Output as #F
Call A.ToFile(F)
Close #f
Open "A.PPM" for Input as #F
Try {
C=Bitmap(f)
Copy 400*twipsx,200*twipsy use C.Image$()
}
Close #f
' is the same as this one
Open "A.PPM" for Input as #F
Line Input #f, p3$
If p3$="P3" Then {
Line Input #f, Comment$
if left$(Comment$,1)="#" then {
Line Input #f, Dimension$
} else Dimension$=Comment$
Long x=Val(piece$(Dimension$," ")(0))
Long y=Val(piece$(Dimension$," ")(1))
do {
Line Input #f, P255$
} until left$(P255$, 1)<>"#"
If not P255$="255" then Error "Not proper ppm format"
B=Bitmap(x, y)
x0=x-1
For y1=y-1 to 0 {
do {
Line Input #f, aline$
} until left$(aline$,1)<>"#"
flush ' empty stack
Stack aline$ ' place all values to stack as FIFO
For x1=0 to x0 {
\\ now read from stack
Read red, green, blue
Call B.setpixel(x1, y1, Color(red, green, blue))
}
}
}
Close #f
If valid("B") then Copy 200*twipsx,200*twipsy use B.Image$()
}
Checkit
Mathematica/ Wolfram Language
Import["file.ppm","PPM"]
Nim
import strutils
import bitmap
import streams
type FormatError = object of CatchableError
# States used to parse the header.
type State = enum waitingMagic, waitingWidth, waitingHeight, waitingColors
#---------------------------------------------------------------------------------------------------
iterator tokens(f: Stream): tuple[value: string, lastInLine: bool] =
## Yield the tokens in the header.
for line in f.lines:
if not line.startsWith('#'):
let fields = line.splitWhitespace()
for i, t in fields:
yield (t, i == fields.high)
#---------------------------------------------------------------------------------------------------
proc getInt(s: string): int {.inline.} =
## Try to parse an int. Raise an exception if not an integer.
try:
result = s.parseInt()
except ValueError:
raise newException(FormatError, "Invalid value")
#---------------------------------------------------------------------------------------------------
proc header(f: Stream): tuple[width, height: Index] =
## Read the header and retrun the image width and height.
var state = waitingMagic
for (token, lastInLine) in f.tokens:
case state
of waitingMagic:
if token != "P6":
raise newException(FormatError, "Invalid file header")
of waitingWidth:
result.width = token.getInt()
of waitingHeight:
result.height = token.getInt()
of waitingColors:
if token.getInt() != 255:
raise newException(FormatError, "Invalid number of colors")
if not lastInLine:
raise newException(FormatError, "Invalid data after number of colors")
break
state = succ(state)
#---------------------------------------------------------------------------------------------------
proc readPPM*(f: Stream): Image =
## Read a PPM file from a stream into an image.
let header = f.header()
result = newImage(header.width, header.height)
var
arr: array[256, int8]
read = f.readData(addr(arr), 256)
pos = 0
while read != 0:
for i in 0 ..< read:
case pos mod 3
of 0: result.pixels[pos div 3].r = arr[i].uint8
of 1: result.pixels[pos div 3].g = arr[i].uint8
of 2: result.pixels[pos div 3].b = arr[i].uint8
else: discard
inc pos
read = f.readData(addr(arr), 256)
if pos != 3 * result.w * result.h:
raise newException(FormatError, "Truncated file")
#---------------------------------------------------------------------------------------------------
proc readPPM*(filename: string): Image =
## Load a PPM file into an image.
var file = openFileStream(filename, fmRead)
result = file.readPPM()
file.close()
#———————————————————————————————————————————————————————————————————————————————————————————————————
when isMainModule:
let image = readPPM("output.ppm")
echo image.h, " ", image.w
OCaml
let read_ppm ~filename =
let ic = open_in filename in
let line = input_line ic in
if line <> "P6" then invalid_arg "not a P6 ppm file";
let line = input_line ic in
let line =
try if line.[0] = '#' (* skip comments *)
then input_line ic
else line
with _ -> line
in
let width, height =
Scanf.sscanf line "%d %d" (fun w h -> (w, h))
in
let line = input_line ic in
if line <> "255" then invalid_arg "not a 8 bit depth image";
let all_channels =
let kind = Bigarray.int8_unsigned
and layout = Bigarray.c_layout
in
Bigarray.Array3.create kind layout 3 width height
in
let r_channel = Bigarray.Array3.slice_left_2 all_channels 0
and g_channel = Bigarray.Array3.slice_left_2 all_channels 1
and b_channel = Bigarray.Array3.slice_left_2 all_channels 2
in
for y = 0 to pred height do
for x = 0 to pred width do
r_channel.{x,y} <- (input_byte ic);
g_channel.{x,y} <- (input_byte ic);
b_channel.{x,y} <- (input_byte ic);
done;
done;
close_in ic;
(all_channels,
r_channel,
g_channel,
b_channel)
and converting a given color file to grayscale:
let () =
let img = read_ppm ~filename:"logo.ppm" in
let img = to_color(to_grayscale ~img) in
output_ppm ~oc:stdout ~img;
;;
sending the result to stdout allows to see the result without creating a temporary file sending it through a pipe to the display utility of ImageMagick:
ocaml script.ml | display -
Oz
The read function in module "BitmapIO.oz"
:
functor
import
Bitmap
Open
export
Read
%% Write
define
fun {Read Filename}
F = {New Open.file init(name:Filename)}
fun {ReadColor8 _}
Bytes = {F read(list:$ size:3)}
in
{List.toTuple color Bytes}
end
fun {ReadColor16 _}
Bytes = {F read(list:$ size:6)}
in
{List.toTuple color {Map {PairUp Bytes} FromBytes}}
end
in
try
Magic = {F read(size:2 list:$)}
if Magic \= "P6" then raise bitmapIO(read unsupportedFormat(Magic)) end end
Width = {ReadNumber F}
Height = {ReadNumber F}
MaxVal = {ReadNumber F}
MaxVal =< 0xffff = true
Reader = if MaxVal =< 0xff then ReadColor8 else ReadColor16 end
B = {Bitmap.new Width Height}
in
{Bitmap.transform B Reader}
B
finally
{F close}
end
end
fun {ReadNumber F}
Ds
in
{SkipWS F}
Ds = for collect:Collect break:Break do
[C] = {F read(list:$ size:1)}
in
if {Char.isDigit C} then {Collect C}
else {Break}
end
end
{SkipWS F}
{String.toInt Ds}
end
proc {SkipWS F}
[C] = {F read(list:$ size:1)}
in
if {Char.isSpace C} then {SkipWS F}
elseif C == &# then
{SkipLine F}
else
{F seek(whence:current offset:~1)}
end
end
proc {SkipLine F}
[C] = {F read(list:$ size:1)}
in
if C \= &\n andthen C \= &\r then {SkipLine F} end
end
fun {PairUp Xs}
case Xs of X1|X2|Xr then [X1 X2]|{PairUp Xr}
[] nil then nil
end
end
fun {FromBytes [C1 C2]}
C1 * 0x100 + C2
end
%% Omitted: Write
end
The actual task:
declare
[BitmapIO Grayscale] = {Module.link ['BitmapIO.ozf' 'Grayscale.ozf']}
B = {BitmapIO.read "image.ppm"}
G = {Grayscale.toGraymap B}
in
{BitmapIO.write {Grayscale.fromGraymap G} "greyimage.ppm"}
Perl
#! /usr/bin/perl
use strict;
use Image::Imlib2;
my $img = Image::Imlib2->load("out0.ppm");
# let's do something with it now
$img->set_color(255, 255, 255, 255);
$img->draw_line(0,0, $img->width,$img->height);
$img->image_set_format("png");
$img->save("out1.png");
exit 0;
Phix
Based on Euphoria, requires write_ppm() from Write_a_PPM_file, to_grey from Grayscale_image
Note that demo\rosetta\Bitmap_read_ppm.exw is just the last 3 lines with the include ppm.e since that contains the read_ppm() (abeit with a few more options and other tweaks) also used by several other examples, and covers the above requirements. Results may be verified with demo\rosetta\viewppm.exw
-- demo\rosetta\Bitmap_read_ppm.exw (runnable version)
function read_ppm(string filename)
sequence image, line
integer dimx, dimy, maxcolor
atom fn = open(filename, "rb")
if fn<0 then
return -1 -- unable to open
end if
line = gets(fn)
if line!="P6\n" then
return -1 -- only ppm6 files are supported
end if
line = gets(fn)
{{dimx,dimy}} = scanf(line,"%d %d%s")
line = gets(fn)
{{maxcolor}} = scanf(line,"%d%s")
image = repeat(repeat(0,dimy),dimx)
for y=1 to dimy do
for x=1 to dimx do
image[x][y] = getc(fn)*#10000 + getc(fn)*#100 + getc(fn)
end for
end for
close(fn)
return image
end function
--include ppm.e -- read_ppm(), write_ppm(), to_grey() (as distributed, instead of the above)
sequence img = read_ppm("Lena.ppm")
img = to_grey(img)
write_ppm("LenaGray.ppm",img)
PicoLisp
(de ppmRead (File)
(in File
(unless (and `(hex "5036") (rd 2)) # P6
(quit "Wrong file format" File) )
(rd 1)
(let (DX 0 DY 0 Max 0 C)
(while (>= 9 (setq C (- (rd 1) `(char "0"))) 0)
(setq DX (+ (* 10 DX) C)) )
(while (>= 9 (setq C (- (rd 1) `(char "0"))) 0)
(setq DY (+ (* 10 DY) C)) )
(while (>= 9 (setq C (- (rd 1) `(char "0"))) 0)
(setq Max (+ (* 10 Max) C)) )
(prog1
(make (do DY (link (need DX))))
(for Y @
(map
'((X) (set X (list (rd 1) (rd 1) (rd 1))))
Y ) ) ) ) ) )
Read a color image "img.ppm", convert and write to "img.pgm":
(pgmWrite (ppm->pgm (ppmRead "img.ppm")) "img.pgm")
PL/I
/* BITMAP FILE: read in a file in PPM format, P6 (binary). 14/5/2010 */
test: procedure options (main);
declare (m, n, max_color, i, j) fixed binary (31);
declare ch character (1), ID character (2);
declare 1 pixel union,
2 color bit(24) aligned,
2 primary_colors,
3 R char (1),
3 G char (1),
3 B char (1);
declare in file record;
open file (in) title ('/IMAGE.PPM,TYPE(FIXED),RECSIZE(1)' ) input;
call get_char;
ID = ch;
call get_char;
substr(ID, 2,1) = ch;
/* Read in the dimensions of the image */
call get_integer (m);
call get_integer (n);
/* Read in the maximum color size used */
call get_integer (max_color);
/* The previous call reads in ONE line feed or CR or other terminator */
/* character. */
begin;
declare image (0:m-1,0:n-1) bit (24);
do i = 0 to hbound(image, 1);
do j = 0 to hbound(image,2);
read file (in) into (R);
read file (in) into (G);
read file (in) into (B);
image(i,j) = color;
end;
end;
end;
get_char: procedure;
do until (ch ^= ' ');
read file (in) into (ch);
end;
end get_char;
get_integer: procedure (value);
declare value fixed binary (31);
do until (ch = ' ');
read file (in) into (ch);
end;
value = 0;
do until (is_digit(ch));
value = value*10 + ch;
read file (in) into (ch);
end;
end get_integer;
is_digit: procedure (ch) returns (bit(1));
declare ch character (1);
return (index('0123456789', ch) > 0);
end is_digit;
end test;
PureBasic
Structure PPMColor
r.c
g.c
b.c
EndStructure
Procedure LoadImagePPM(Image, file$)
; Author Roger Rösch (Nickname Macros)
IDFile = ReadFile(#PB_Any, file$)
If IDFile
If CreateImage(Image, 1, 1)
Format$ = ReadString(IDFile)
ReadString(IDFile) ; skip comment
Dimensions$ = ReadString(IDFile)
w = Val(StringField(Dimensions$, 1, " "))
h = Val(StringField(Dimensions$, 2, " "))
ResizeImage(Image, w, h)
StartDrawing(ImageOutput(Image))
max = Val(ReadString(IDFile)) ; Maximal Value for a color
Select Format$
Case "P3" ; File in ASCII format
; Exract everey number remaining in th file into an array using an RegEx
Stringlen = Lof(IDFile) - Loc(IDFile)
content$ = Space(Stringlen)
Dim color.s(0)
ReadData(IDFile, @content$, Stringlen)
CreateRegularExpression(1, "\d+")
ExtractRegularExpression(1, content$, color())
; Plot color information on our empty Image
For y = 0 To h - 1
For x = 0 To w - 1
pos = (y*w + x)*3
r=Val(color(pos))*255 / max
g=Val(color(pos+1))*255 / max
b=Val(color(pos+2))*255 / max
Plot(x, y, RGB(r,g,b))
Next
Next
Case "P6" ;File In binary format
; Read whole bytes into a buffer because its faster than reading single ones
Bufferlen = Lof(IDFile) - Loc(IDFile)
*Buffer = AllocateMemory(Bufferlen)
ReadData(IDFile, *Buffer, Bufferlen)
; Plot color information on our empty Image
For y = 0 To h - 1
For x = 0 To w - 1
*color.PPMColor = pos + *Buffer
Plot(x, y, RGB(*color\r*255 / max, *color\g*255 / max, *color\b*255 / max))
pos + 3
Next
Next
EndSelect
StopDrawing()
; Return 1 if successfully loaded to behave as other PureBasic functions
ProcedureReturn 1
EndIf
EndIf
EndProcedure
To complete the task, the following code should be added to the above fragment and to the PureBasic solutions for Grayscale image and Write a PPM file
Define file.s, file2.s, image = 3
file = OpenFileRequester("Select source image file", "", "PPM image (*.ppm)|*.ppm", 0)
If file And LCase(GetExtensionPart(file)) = "ppm"
LoadImagePPM(image, file)
ImageGrayout(image)
file2 = Left(file, Len(file) - Len(GetExtensionPart(file))) + "_grayscale." + GetExtensionPart(file)
SaveImageAsPPM(image, file2, 1)
EndIf
Python
Extending the example given here
# With help from http://netpbm.sourceforge.net/doc/ppm.html
# String masquerading as ppm file (version P3)
import io
ppmtxt = '''P3
# feep.ppm
4 4
15
0 0 0 0 0 0 0 0 0 15 0 15
0 0 0 0 15 7 0 0 0 0 0 0
0 0 0 0 0 0 0 15 7 0 0 0
15 0 15 0 0 0 0 0 0 0 0 0
'''
def tokenize(f):
for line in f:
if line[0] != '#':
for t in line.split():
yield t
def ppmp3tobitmap(f):
t = tokenize(f)
nexttoken = lambda : next(t)
assert 'P3' == nexttoken(), 'Wrong filetype'
width, height, maxval = (int(nexttoken()) for i in range(3))
bitmap = Bitmap(width, height, Colour(0, 0, 0))
for h in range(height-1, -1, -1):
for w in range(0, width):
bitmap.set(w, h, Colour( *(int(nexttoken()) for i in range(3))))
return bitmap
print('Original Colour PPM file')
print(ppmtxt)
ppmfile = io.StringIO(ppmtxt)
bitmap = ppmp3tobitmap(ppmfile)
print('Grey PPM:')
bitmap.togreyscale()
ppmfileout = io.StringIO('')
bitmap.writeppmp3(ppmfileout)
print(ppmfileout.getvalue())
'''
The print statements above produce the following output:
Original Colour PPM file
P3
# feep.ppm
4 4
15
0 0 0 0 0 0 0 0 0 15 0 15
0 0 0 0 15 7 0 0 0 0 0 0
0 0 0 0 0 0 0 15 7 0 0 0
15 0 15 0 0 0 0 0 0 0 0 0
Grey PPM:
P3
# generated from Bitmap.writeppmp3
4 4
11
0 0 0 0 0 0 0 0 0 4 4 4
0 0 0 11 11 11 0 0 0 0 0 0
0 0 0 0 0 0 11 11 11 0 0 0
4 4 4 0 0 0 0 0 0 0 0 0
'''
Racket
#lang racket
(require racket/draw)
(define (read-ppm port)
(parameterize ([current-input-port port])
(define magic (read))
(define width (read))
(define height (read))
(define maxcol (read))
(define bm (make-object bitmap% width height))
(define dc (new bitmap-dc% [bitmap bm]))
(send dc set-smoothing 'unsmoothed)
(define (adjust v) (* 255 (/ v maxcol)))
(for/list ([x width])
(for/list ([y height])
(define red (read))
(define green (read))
(define blue (read))
(define color (make-object color% (adjust red) (adjust green) (adjust blue)))
(send dc set-pen color 1 'solid)
(send dc draw-point x y)))
bm))
Raku
(formerly Perl 6)
Uses pieces from Bitmap, Write a PPM file and Grayscale image tasks. Included here to make a complete, runnable program.
class Pixel { has UInt ($.R, $.G, $.B) }
class Bitmap {
has UInt ($.width, $.height);
has Pixel @.data;
}
role PGM {
has @.GS;
method P5 returns Blob {
"P5\n{self.width} {self.height}\n255\n".encode('ascii')
~ Blob.new: self.GS
}
}
sub load-ppm ( $ppm ) {
my $fh = $ppm.IO.open( :enc('ISO-8859-1') );
my $type = $fh.get;
my ($width, $height) = $fh.get.split: ' ';
my $depth = $fh.get;
Bitmap.new( width => $width.Int, height => $height.Int,
data => ( $fh.slurp.ords.rotor(3).map:
{ Pixel.new(R => $_[0], G => $_[1], B => $_[2]) } )
)
}
sub grayscale ( Bitmap $bmp ) {
$bmp.GS = map { (.R*0.2126 + .G*0.7152 + .B*0.0722).round(1) min 255 }, $bmp.data;
}
my $filename = './camelia.ppm';
my Bitmap $b = load-ppm( $filename ) but PGM;
grayscale($b);
'./camelia-gs.pgm'.IO.open(:bin, :w).write: $b.P5;
See camelia, and camelia-gs images. (converted to .png as .ppm format is not widely supported).
REXX
The input file Lenna50.ppm is a PPM format of
the input file Lenna50.jpg used elsewhere on Rosetta Code.
This REXX program handles alternative delimiters as well as comments within the PPM header.
/*REXX program reads a PPM formatted image file, and creates a gray─scale image of it. */
parse arg iFN oFN /*obtain optional argument from the CL.*/
if iFN=='' | iFN=="," then iFN= 'Lenna50' /*Not specified? Then use the default.*/
if oFN=='' | oFN=="," then oFN= 'greyscale' /* " " " " " " */
iFID= iFN'.ppm'; oFID= oFN'.ppm' /*complete the input and output FIDs.*/
call charin iFID, 1, 0 /*set the position of the input file. */
y=charin(iFID, , copies(9, digits() ) ) /*read the entire input file ───► X */
parse var y id 3 c 4 3 width height # pixels /*extract header info from the PPM hdr.*/
LF= 'a'x /*define a comment separator (in hdr).*/ /* ◄─── LF delimiters & comments*/
if c==LF then do; commentEND=pos(LF, y, 4) /*point to the last char in the comment*/ /* ◄─── LF delimiters & comments*/
parse var y =(commentEND) +1 width height # pixels /* ◄─── LF delimiters & comments*/
end /* ◄─── LF delimiters & comments*/
/* [↓] has an alternative delimiter? */ /* ◄─── LF delimiters & comments*/
z=pos(LF, height); if z\==0 then parse var height height =(z) +1 # pixels /* ◄─── LF delimiters & comments*/
z=pos(LF, # ); if z\==0 then parse var # # =(z) +1 pixels /* ◄─── LF delimiters & comments*/
chunk=4000 /*chunk size to be written at one time.*/
LenPixels= length(pixels)
do j=0 for 256; _=d2c(j); @._=j; @@.j=_ /*build two tables for fast conversions*/
end /*j*/
call charout oFID, , 1 /*set the position of the output file. */
call charout oFID, id || width height #' ' /*write the header followed by a blank.*/
!=1
do until !>=LenPixels; $= /*$: partial output string so far.*/
do !=! by 3 for chunk /*chunk: # pixels converted at 1 time.*/
parse var pixels =(!) r +1 g +1 b +1 /*obtain the next RGB of a PPM pixel.*/
if r=='' then leave /*has the end─of─string been reached? */
_=(.2126*@.r + .7152*@.g + .0722*@.b )%1 /*an integer RGB greyscale of a pixel. */
$=$ || @@._ || @@._ || @@._ /*lump (grey) R G B pixels together. */
end /*!*/ /* [↑] D2C converts decimal ───► char*/
call charout oFID, $ /*write the next bunch of pixels. */
end /*until*/
call charout oFID /*close the output file just to be safe*/
say 'File ' oFID " was created." /*stick a fork in it, we're all done. */
- output:
File greyscale.ppm was created.
Ruby
Extending Basic_bitmap_storage#Ruby
class Pixmap
# 'open' is a class method
def self.open(filename)
bitmap = nil
File.open(filename, 'r') do |f|
header = [f.gets.chomp, f.gets.chomp, f.gets.chomp]
width, height = header[1].split.map {|n| n.to_i }
if header[0] != 'P6' or header[2] != '255' or width < 1 or height < 1
raise StandardError, "file '#{filename}' does not start with the expected header"
end
f.binmode
bitmap = self.new(width, height)
height.times do |y|
width.times do |x|
# read 3 bytes
red, green, blue = f.read(3).unpack('C3')
bitmap[x,y] = RGBColour.new(red, green, blue)
end
end
end
bitmap
end
end
# create an image: a green cross on a blue background
colour_bitmap = Pixmap.new(20, 30)
colour_bitmap.fill(RGBColour::BLUE)
colour_bitmap.height.times {|y| [9,10,11].each {|x| colour_bitmap[x,y]=RGBColour::GREEN}}
colour_bitmap.width.times {|x| [14,15,16].each {|y| colour_bitmap[x,y]=RGBColour::GREEN}}
colour_bitmap.save('testcross.ppm')
# then, convert to grayscale
Pixmap.open('testcross.ppm').to_grayscale!.save('testgray.ppm')
Rust
parser.rs:
use super::{Color, ImageFormat};
use std::str::from_utf8;
use std::str::FromStr;
pub fn parse_version(input: &[u8]) -> nom::IResult<&[u8], ImageFormat> {
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::line_ending;
use nom::combinator::map;
use nom::sequence::terminated;
// starts with P3/P6 ends with a CR/LF
terminated(
alt((
map(tag("P3".as_bytes()), |_| ImageFormat::P3),
map(tag("P6".as_bytes()), |_| ImageFormat::P6),
)),
line_ending,
)(input)
}
pub fn parse_image_attributes(input: &[u8]) -> nom::IResult<&[u8], (usize, usize, usize)> {
use nom::character::complete::line_ending;
use nom::character::complete::{digit1, space1};
use nom::sequence::terminated;
use nom::sequence::tuple;
// 3 numbers separated by spaces ends with a CR/LF
terminated(tuple((digit1, space1, digit1, space1, digit1)), line_ending)(input).map(
|(next_input, result)| {
(
next_input,
(
usize::from_str_radix(from_utf8(result.0).unwrap(), 10).unwrap(),
usize::from_str_radix(from_utf8(result.2).unwrap(), 10).unwrap(),
usize::from_str_radix(from_utf8(result.4).unwrap(), 10).unwrap(),
),
)
},
)
}
pub fn parse_color_binary(input: &[u8]) -> nom::IResult<&[u8], Color> {
use nom::number::complete::u8 as nom_u8;
use nom::sequence::tuple;
tuple((nom_u8, nom_u8, nom_u8))(input).map(|(next_input, res)| {
(
next_input,
Color {
red: res.0,
green: res.1,
blue: res.2,
},
)
})
}
pub fn parse_data_binary(input: &[u8]) -> nom::IResult<&[u8], Vec<Color>> {
use nom::multi::many0;
many0(parse_color_binary)(input)
}
pub fn parse_color_ascii(input: &[u8]) -> nom::IResult<&[u8], Color> {
use nom::character::complete::{digit1, space0, space1};
use nom::sequence::tuple;
tuple((digit1, space1, digit1, space1, digit1, space0))(input).map(|(next_input, res)| {
(
next_input,
Color {
red: u8::from_str(from_utf8(res.0).unwrap()).unwrap(),
green: u8::from_str(from_utf8(res.2).unwrap()).unwrap(),
blue: u8::from_str(from_utf8(res.4).unwrap()).unwrap(),
},
)
})
}
pub fn parse_data_ascii(input: &[u8]) -> nom::IResult<&[u8], Vec<Color>> {
use nom::multi::many0;
many0(parse_color_ascii)(input)
}
lib.rs:
extern crate nom;
extern crate thiserror;
mod parser;
use std::default::Default;
use std::fmt;
use std::io::{BufWriter, Error, Write};
use std::ops::{Index, IndexMut};
use std::{fs::File, io::Read};
use thiserror::Error;
#[derive(Copy, Clone, Default, PartialEq, Debug)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum ImageFormat {
P3,
P6,
}
impl From<&str> for ImageFormat {
fn from(i: &str) -> Self {
match i.to_lowercase().as_str() {
"p3" => ImageFormat::P3,
"p6" => ImageFormat::P6,
_ => unimplemented!("no other formats supported"),
}
}
}
impl fmt::Display for ImageFormat {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ImageFormat::P3 => {
write!(f, "P3")
}
ImageFormat::P6 => {
write!(f, "P6")
}
}
}
}
#[derive(Error, Debug)]
pub enum ImageError {
#[error("File not found")]
FileNotFound,
#[error("File not readable")]
FileNotReadable,
#[error("Invalid header information")]
InvalidHeader,
#[error("Invalid information in the data block")]
InvalidData,
#[error("Invalid max color information")]
InvalidMaxColor,
#[error("File is incomplete")]
IncompleteFile,
#[error("unknown data store error")]
Unknown,
}
pub struct Image {
pub format: ImageFormat,
pub width: usize,
pub height: usize,
pub data: Vec<Color>,
}
impl Image {
#[must_use]
pub fn new(width: usize, height: usize) -> Self {
Self {
format: ImageFormat::P6,
width,
height,
data: vec![Color::default(); width * height],
}
}
pub fn fill(&mut self, color: Color) {
for elem in &mut self.data {
*elem = color;
}
}
/// # Errors
///
/// Will return `Error` if `filename` does not exist or the user does not have
/// permission to write to it, or the write operation fails.
pub fn write_ppm(&self, filename: &str) -> Result<(), Error> {
let file = File::create(filename)?;
let mut writer = BufWriter::new(file);
writeln!(&mut writer, "{}", self.format.to_string())?;
writeln!(&mut writer, "{} {} 255", self.width, self.height)?;
match self.format {
ImageFormat::P3 => {
writer.write_all(
&self
.data
.iter()
.flat_map(|color| {
vec![
color.red.to_string(),
color.green.to_string(),
color.blue.to_string(),
]
})
.collect::<Vec<String>>()
.join(" ")
.as_bytes(),
)?;
}
ImageFormat::P6 => {
writer.write_all(
&self
.data
.iter()
.flat_map(|color| vec![color.red, color.green, color.blue])
.collect::<Vec<u8>>(),
)?;
}
}
Ok(())
}
/// # Panics
///
/// Panics if the format is not P6 or P3 PPM
/// # Errors
///
/// Will return `Error` if `filename` does not exist or the user does not have
/// permission to read it or the read operation fails, or the file format does not
/// match the specification
pub fn read_ppm(filename: &str) -> Result<Image, ImageError> {
let mut file = File::open(filename).map_err(|_| ImageError::FileNotFound)?;
let mut data: Vec<u8> = Vec::new();
file.read_to_end(&mut data)
.map_err(|_| ImageError::FileNotReadable)?;
let (i, format) = parser::parse_version(&data).map_err(|_| ImageError::InvalidHeader)?;
let (i, (width, height, max_color)) =
parser::parse_image_attributes(i).map_err(|_| ImageError::InvalidHeader)?;
if max_color != 255 {
return Err(ImageError::InvalidMaxColor);
}
let (_, data) = match format {
ImageFormat::P3 => parser::parse_data_ascii(i).map_err(|_| ImageError::InvalidData)?,
ImageFormat::P6 => parser::parse_data_binary(i).map_err(|_| ImageError::InvalidData)?,
};
if data.len() != height * width {
return Err(ImageError::IncompleteFile);
};
Ok(Image {
format,
width,
height,
data,
})
}
}
impl Index<(usize, usize)> for Image {
type Output = Color;
fn index(&self, (x, y): (usize, usize)) -> &Color {
&self.data[x + y * self.width]
}
}
impl IndexMut<(usize, usize)> for Image {
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Color {
&mut self.data[x + y * self.width]
}
}
use bitmap::Image;
// see read_ppm implementation in the bitmap library
pub fn main() {
// read a PPM image, which was produced by the write-a-ppm-file task
let image = Image::read_ppm("./test_image.ppm").unwrap();
println!("Read using nom parsing:");
println!("Format: {:?}", image.format);
println!("Dimensions: {} x {}", image.height, image.width);
}
Scala
Uses the Basic Bitmap Storage and Grayscale Bitmap classes.
See also Task Write a PPM File for save code.
import scala.io._
import scala.swing._
import java.io._
import java.awt.Color
import javax.swing.ImageIcon
object Pixmap {
private case class PpmHeader(format:String, width:Int, height:Int, maxColor:Int)
def load(filename:String):Option[RgbBitmap]={
implicit val in=new BufferedInputStream(new FileInputStream(filename))
val header=readHeader
if(header.format=="P6")
{
val bm=new RgbBitmap(header.width, header.height);
for(y <- 0 until bm.height; x <- 0 until bm.width; c=readColor)
bm.setPixel(x, y, c)
return Some(bm)
}
None
}
private def readHeader(implicit in:InputStream)={
var format=readLine
var line=readLine
while(line.startsWith("#")) //skip comments
line=readLine
val parts=line.split("\\s")
val width=parts(0).toInt
val height=parts(1).toInt
val maxColor=readLine.toInt
new PpmHeader(format, width, height, maxColor)
}
private def readColor(implicit in:InputStream)=new Color(in.read, in.read, in.read)
private def readLine(implicit in:InputStream)={
var out=""
var b=in.read
while(b!=0xA){out+=b.toChar; b=in.read}
out
}
}
Usage:
object PixmapTest {
def main(args: Array[String]): Unit = {
val img=Pixmap.load("image.ppm").get
val grayImg=BitmapOps.grayscale(img);
Pixmap.save(grayImg, "image_gray.ppm")
val mainframe=new MainFrame(){
title="Test"
visible=true
contents=new Label(){
icon=new ImageIcon(grayImg.image)
}
}
}
}
Seed7
$ include "seed7_05.s7i";
include "draw.s7i";
include "color.s7i";
const func PRIMITIVE_WINDOW: getPPM (in string: fileName) is func
result
var PRIMITIVE_WINDOW: aWindow is PRIMITIVE_WINDOW.value;
local
var file: ppmFile is STD_NULL;
var string: line is "";
var integer: width is 0;
var integer: height is 0;
var integer: x is 0;
var integer: y is 0;
var color: pixColor is black;
begin
ppmFile := open(fileName, "r");
if ppmFile <> STD_NULL then
if getln(ppmFile) = "P6" then
repeat
line := getln(ppmFile);
until line = "" or line[1] <> '#';
read(ppmFile, width);
readln(ppmFile, height);
aWindow := newPixmap(width, height);
for y range 0 to pred(height) do
for x range 0 to pred(width) do
pixColor.redLight := ord(getc(ppmFile));
pixColor.greenLight := ord(getc(ppmFile));
pixColor.blueLight := ord(getc(ppmFile));
end for;
end for;
end if;
close(ppmFile);
end if;
end func;
Tcl
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
Ref: Bitmap#UNIX Shell
Add the following functions to the RGBColor_t type
function setrgb {
_.r=$1
_.g=$2
_.b=$3
}
function grayscale {
integer x=$(( round( 0.2126*_.r + 0.7152*_.g + 0.0722*_.b ) ))
_.r=$x
_.g=$x
_.b=$x
}
Add the following function to the Bitmap_t type
function grayscale {
RGBColor_t c
for ((y=0; y<_.height; y++)); do
for ((x=0; x<_.width; x++)); do
c.setrgb ${_.data[y][x]}
c.grayscale
_.data[y][x]=$(c.to_s)
done
done
}
function read {
exec 4<"$1"
typeset filetype
read -u4 filetype
if [[ $filetype != "P3" ]]; then
print -u2 "error: I can only read P3 type PPM files"
else
read -u4 _.width _.height
integer maxval
read -u4 maxval
integer x y r g b
typeset -a bytes
for ((y=0; y<_.height; y++)); do
read -u4 -A bytes
for ((x=0; x<_.width; x++)); do
r=${bytes[3*x+0]}
g=${bytes[3*x+1]}
b=${bytes[3*x+2]}
if (( r > maxval || g > maxval || b > maxval )); then
print -u2 "error: invalid color ($r $g $b), max=$maxval"
return 1
fi
_.data[y][x]="$r $g $b"
done
done
fi
exec 4<&-
}
Now we can:
Bitmap_t c
c.read "$HOME/tmp/bitmap.ppm"
c.to_s
if [[ $(c.to_s) == $(cat "$HOME/tmp/bitmap.ppm") ]]; then
echo looks OK
else
echo something is wrong
fi
c.grayscale
c.to_s
c.write "$HOME/tmp/bitmap_g.ppm"
Vedit macro language
// Load a PPM file
// @10 = filename
// On return:
// #10 points to buffer containing pixel data,
// #11 = width, #12 = height.
:LOAD_PPM:
File_Open(@10)
BOF
Search("|X", ADVANCE) // skip "P6"
#11 = Num_Eval(ADVANCE) // #11 = width
Match("|X", ADVANCE) // skip separator
#12 = Num_Eval(ADVANCE) // #12 = height
Match("|X", ADVANCE)
Search("|X", ADVANCE) // skip maxval (assume 255)
Del_Block(0,CP) // remove the header
Return
Example of usage. In addition to LOAD_PPM routine above, you need routine RGB_TO_GRAYSCALE from Grayscale image and routine SAVE_PPM from Write ppm file.
// Load RGB image
Reg_Set(10, "|(USER_MACRO)\example.ppm")
Call("LOAD_PPM")
// Convert to grayscale
#10 = Buf_Num
Call("RGB_TO_GRAYSCALE")
Buf_Switch(#10) Buf_Quit(OK)
// Convert to RGB
Call("GRAYSCALE_TO_RGB")
// Save the image
Reg_Set(10, "|(USER_MACRO)\example_gray.ppm")
Call("SAVE_PPM")
// Cleanup and exit
Buf_Switch(#20) Buf_Quit(OK)
return
Wren
This assumes that Lenna100.jpg, a 512 x 512 color image of the eponymous lady, has already been converted to Lenna100.ppm using a variation of the 'Write a PPM file' task.
import "graphics" for Canvas, ImageData, Color
import "dome" for Window, Process
import "io" for FileSystem
class Bitmap {
construct new(fileName, fileName2, width, height) {
Window.title = "Bitmap - read PPM file"
Window.resize(width, height)
Canvas.resize(width, height)
_w = width
_h = height
_fn2 = fileName2
loadPPMFile(fileName)
}
init() {
toGrayScale()
// display images side by side
_bmp.draw(0, 0)
_bmp2.draw(536, 0)
// save gray scale image to file
_bmp2.saveToFile(_fn2)
}
loadPPMFile(fileName) {
var ppm = FileSystem.load(fileName)
var count = ppm.count // ensure file is fully loaded before proceeding
if (ppm[0..1] != "P6") {
System.print("The loaded file is not a P6 file.")
Process.exit()
}
var lines = ppm.split("\n")
if (Num.fromString(lines[2]) > 255) {
System.print("The maximum color value can't exceed 255.")
Process.exit()
}
var wh = lines[1].split(" ")
var w = Num.fromString(wh[0])
var h = Num.fromString(wh[1])
_bmp = ImageData.create(fileName, w, h)
var bytes = ppm.bytes
var i = bytes.count - 3 * w * h
for (y in 0...h) {
for (x in 0...w) {
var r = bytes[i]
var g = bytes[i+1]
var b = bytes[i+2]
var c = Color.rgb(r, g, b)
pset(x, y, c)
i = i + 3
}
}
}
toGrayScale() {
_bmp2 = ImageData.create("gray scale", _bmp.width, _bmp.height)
for (x in 0..._bmp.width) {
for (y in 0..._bmp.height) {
var c1 = _bmp.pget(x, y)
var lumin = (0.2126 * c1.r + 0.7152 * c1.g + 0.0722 * c1.b).floor
var c2 = Color.rgb(lumin, lumin,lumin, c1.a)
_bmp2.pset(x, y, c2)
}
}
}
pset(x, y, col) { _bmp.pset(x, y, col) }
pget(x, y) { _bmp.pget(x, y) }
update() {}
draw(alpha) {}
}
var Game = Bitmap.new("Lenna100.ppm", "Lenna100_gs.jpg", 1048, 512)
XPL0
The simplicity of redirecting an input file on the command line doesn't work for files that contain binary data ($03 will abort a program). Image files larger than 1280x1024 are clipped to the screen dimensions.
include c:\cxpl\codes; \intrinsic 'code' declarations
func OpenInFile; \Open for input the file typed on command line
int CpuReg, Handle;
char CmdTail($80);
[CpuReg:= GetReg;
Blit(CpuReg(11), $81, CpuReg(12), CmdTail, $7F); \get copy of command line
Trap(false); \turn off error trapping
Handle:= FOpen(CmdTail, 0); \open named file for input
FSet(Handle, ^I); \assign file to input device 3
OpenI(3); \initialize input buffer pointers
if GetErr then return false;
Trap(true);
return true;
];
int C, X, Y, Width, Height, Max, Lum;
real Red, Green, Blue;
[if not OpenInFile then [Text(0, "File not found"); exit];
if ChIn(3)#^P or ChIn(3)#^6 then [Text(0, "Not P6 PPM file"); exit];
repeat loop [C:= ChIn(3);
if C # ^# then quit;
repeat C:= ChIn(3) until C=$0A\EOL\;
];
until C>=^0 & C<=^9;
Backup; \back up so IntIn re-reads first digit
Width:= IntIn(3); \(skips any whitespace)
Height:= IntIn(3);
Max:= IntIn(3) + 1; \(255/15=17; 256/16=16)
case of
Width<= 640 & Height<=480: SetVid($112);
Width<= 800 & Height<=600: SetVid($115);
Width<=1024 & Height<=768: SetVid($118)
other SetVid($11B); \1280x1024
for Y:= 0 to Height-1 do
for X:= 0 to Width-1 do
[Red := float(ChIn(3)*256/Max) * 0.21; \convert color to grayscale
Green:= float(ChIn(3)*256/Max) * 0.72;
Blue := float(ChIn(3)*256/Max) * 0.07;
Lum:= fix(Red) + fix(Green) + fix(Blue);
Point(X, Y, Lum<<16 + Lum<<8 + Lum);
];
X:= ChIn(1); \wait for keystroke
SetVid(3); \restore normal text display
]
Yabasic
sub readPPM(f$)
local ff, x, y, t$, dcol$, wid, hei
if f$ = "" print "No PPM file name indicate." : return false
ff = open (f$, "rb")
if not ff print "File ", f$, " not found." : return false
input #ff t$, wid, hei, dcol$
if t$ = "P6" then
open window wid, hei
for x = 0 to hei - 1
for y = 0 to wid - 1
color peek(#ff), peek(#ff), peek(#ff)
dot y, x
next y
next x
close #ff
else
print "File is NOT PPM P6 type." : return false
end if
return true
end sub
zkl
Read a colored PPM file, convert it (in place) to grayscale and write the new image back to disk under a different name. Sanity checks are omitted.
I used a slightly different image from what is shown, but the results are the same.
//24-bpp P6 PPM solution:
image:=File("lena.ppm","rb").read();
start:=image.find("\n255\n")+5; // Get sizeof PPM header
foreach n in ([start..image.len()-1,3]){ // Transform color triplets
r,g,b:=image[n,3]; // Read colors stored in RGB order
l:=(0.2126*r + 0.7152*g + 0.0722*b).toInt(); // Derive luminance
image[n,3]=T(l,l,l);
}
File("lenaGrey.ppm","wb").write(image);