Read/load & write/save PPM & PGM portable bitmap formats
Posted: Mon May 23, 2016 3:21 pm
... the what formats? I hadn't heard of PPM or PGM either! but I came across a program that used them because it's a really simple format as it turns out.
Photoshop and GIMP can read and write them.
In Photoshop it will save as PGM format in Mode->Grayscale, and PPM format in Mode->RGB (but not Indexed), which you can access simply with File -> Save As.
It's an 80's format and only appears to have semi-official specs - this page has some really good info:
http://paulbourke.net/dataformats/ppm/
and apart from that i just used a hex editor to look at Photoshop saves
Here's a hex edit of a full PPM file (the header component highlighted, the rest is raw RGB data), from an image 5px width by 3px height, where the first row of pixels are all nearly-red (FF0101), the 2nd row is nearly-green (02FF02), and the 3rd row is nearly-blue (0303FF), stored in RGB order (not BGR):

The header "P6" means binary RGB format, 5 and 3 the dimensions, 255 the maximum value, each separated by a $0A line-feed. Pretty simple! Then the raw data, which is 3 bytes for PPM/P6 format (R,G,B), or 1 byte for PGM/P5 grayscale format.
Here in my implementation i've added Read & Write support, but only for BINARY versions of the format, not the ASCII text versions. For saving it supports 24bit and 32bit images as input, and can save as either 8bit grayscale (PGM) or 24bit RGB (PPM). It can also read both PGM and PPM formats. When saving using the grayscale PGM format it simply saves the first channel (blue) value; it assumes you've already converted the image to grayscale (so R,G,B would all be the same). It also correctly draws up or down depending which OS you're using, and I believe i've added all necessary error checks. All tests of my output were byte-for-byte identical to Photoshop's output.
So it should be a fairly complete and multiOS implementation, at least as far as the binary versions of PGM and PPM go which are the two main ones. It does not support the PBM (1bit monochrome) or PNM (content-independent weirdness) formats.
Sample PPM & PGM images
http://www.sendspace.com/file/4jk38a
(167kb, one of each format, saved using Photoshop)
Read PPM/PGM:
Save PPM / PGM:
Photoshop and GIMP can read and write them.
In Photoshop it will save as PGM format in Mode->Grayscale, and PPM format in Mode->RGB (but not Indexed), which you can access simply with File -> Save As.
It's an 80's format and only appears to have semi-official specs - this page has some really good info:
http://paulbourke.net/dataformats/ppm/
and apart from that i just used a hex editor to look at Photoshop saves
Here's a hex edit of a full PPM file (the header component highlighted, the rest is raw RGB data), from an image 5px width by 3px height, where the first row of pixels are all nearly-red (FF0101), the 2nd row is nearly-green (02FF02), and the 3rd row is nearly-blue (0303FF), stored in RGB order (not BGR):

The header "P6" means binary RGB format, 5 and 3 the dimensions, 255 the maximum value, each separated by a $0A line-feed. Pretty simple! Then the raw data, which is 3 bytes for PPM/P6 format (R,G,B), or 1 byte for PGM/P5 grayscale format.
Here in my implementation i've added Read & Write support, but only for BINARY versions of the format, not the ASCII text versions. For saving it supports 24bit and 32bit images as input, and can save as either 8bit grayscale (PGM) or 24bit RGB (PPM). It can also read both PGM and PPM formats. When saving using the grayscale PGM format it simply saves the first channel (blue) value; it assumes you've already converted the image to grayscale (so R,G,B would all be the same). It also correctly draws up or down depending which OS you're using, and I believe i've added all necessary error checks. All tests of my output were byte-for-byte identical to Photoshop's output.
So it should be a fairly complete and multiOS implementation, at least as far as the binary versions of PGM and PPM go which are the two main ones. It does not support the PBM (1bit monochrome) or PNM (content-independent weirdness) formats.
Sample PPM & PGM images
http://www.sendspace.com/file/4jk38a
(167kb, one of each format, saved using Photoshop)
Read PPM/PGM:
Code: Select all
EnableExplicit
Structure structRGB
B.a
G.a
R.a
EndStructure
Procedure.i ReadPortableMapImage(ImgFile.s) ;Returns image handle or 0. Supports PPM & PGM (binary only, not ascii)
Protected Pfmt, hFile, hImg, width, height, flen, pitch, startaddr, endaddr, elements, x, y, find
Protected *draw, *next.Ascii, *buf, *row.Ascii, *char.Ascii, Magic.w, *Magic.Word = @Magic
Protected *RGBin.structRGB, *RGBout.structRGB, *bytein.Ascii
Dim element.s(2)
hFile = ReadFile(#PB_Any, ImgFile, #PB_File_SharedRead | #PB_File_SharedWrite)
If hFile = 0
ProcedureReturn 0
EndIf
flen = Lof(hFile)
If flen < 12
ErrReturn:
If *buf: FreeMemory(*buf): EndIf
If IsImage(hImg): FreeImage(hImg): EndIf
CloseFile(hFile)
ProcedureReturn 0
EndIf
*buf = AllocateMemory(flen)
If *buf = 0: Goto ErrReturn: EndIf
ReadData(hFile, @Magic, 2) ;Read first 2 bytes to check Magic to ensure file is a supported format
Select *Magic\w
Case $3550: Pfmt=1 ;"P5" PGM Grayscale
Case $3650: Pfmt=3 ;"P6" PPM Bitmap
Default:
Goto ErrReturn
EndSelect
ReadData(hFile, *buf, flen-2)
*char = *buf
Repeat
Select *char\a
Case $0D, $0A, $20, $09: ;Skip chars CR, LF, SPC, TAB
If startaddr <> 0
endaddr = *char
element(elements) = PeekS(startaddr, endaddr-startaddr, #PB_Ascii)
find = FindString(element(elements), "#")
If find
element(elements) = Left(element(elements), find-1)
EndIf
element(elements) = Trim(element(elements))
If Left(element(elements),1) <> "#": elements+1: EndIf ;ignore comments
If elements = 3
startaddr = *char+1
Break
EndIf
startaddr = 0
EndIf
Default:
If startaddr = 0
startaddr = *char
EndIf
EndSelect
*char+1
Until *char = *buf+flen
If Val(element(2)) <> 255: Goto ErrReturn: EndIf ;only the full/normal range (0-255) supported in this implementation
width = Val(element(0))
height = Val(element(1))
If width <= 0 Or height <= 0: Goto ErrReturn: EndIf
If Pfmt = 3
If (flen - (startaddr - *buf)) < ((width*height)*3): Goto ErrReturn: EndIf ;incomplete data
Else ;Pfmt = 1
If (flen - (startaddr - *buf)) < (width*height): Goto ErrReturn: EndIf ;incomplete data
EndIf
hImg = CreateImage(#PB_Any, width, height, 24)
If hImg = 0: Goto ErrReturn: EndIf
If StartDrawing(ImageOutput(hImg)) = 0: Goto ErrReturn: EndIf
pitch = DrawingBufferPitch()
*RGBin = startaddr
*bytein = *RGBin
startaddr = DrawingBuffer()
For y = 0 To height-1
CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;draw rows bottom-to-top
*RGBout = startaddr + (pitch*height) - (pitch*(y+1))
CompilerElse ;draw rows top-to-bottom
*RGBout = startaddr + (y * pitch)
CompilerEndIf
If Pfmt = 3 ;RGB Bitmap
For x = 1 To width
CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;BGRA
*RGBout\B = *RGBin\R
*RGBout\G = *RGBin\G
*RGBout\R = *RGBin\B
CompilerElse ;RGBA
*RGBout\B = *RGBin\B
*RGBout\G = *RGBin\G
*RGBout\R = *RGBin\R
CompilerEndIf
*RGBout+3: *RGBin+3
Next x
Else ;Pfmt = 1 ;Grayscale
For x = 1 To width
*RGBout\R = *bytein\a
*RGBout\G = *bytein\a
*RGBout\B = *bytein\a
*RGBout+3: *bytein+1
Next x
EndIf
Next y
StopDrawing()
FreeMemory(*buf)
ProcedureReturn hImg
EndProcedure
Define hImg = ReadPortableMapImage("c:\temp\input.ppm")
;Define hImg = ReadPortableMapImage("c:\temp\input.pgm")
If hImg = 0
MessageRequester("Error","Couldnt load file"): End
EndIf
If OpenWindow(0, 0, 0, ImageWidth(hImg), ImageHeight(hImg), "PPM + PGM Image Reader", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
ImageGadget(0, 0, 0, ImageWidth(hImg), ImageHeight(hImg), ImageID(hImg))
Repeat
Define Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
EndIf
Code: Select all
EnableExplicit
Structure structRGB
B.a
G.a
R.a
EndStructure
Procedure.i SavePortableMapImage(hImg, OutFile.s, Grayscale.i) ;hImg=24 or 32bit image. Grayscale = 1 (8bit) or 0 (24bit RGB)
Protected hFile, width, height, depth, pitch, hdr.s, drawbuf, *buf, *RGBin.structRGB, *RGBout.structRGB, *byteout.Ascii, x,y
If Not IsImage(hImg): ProcedureReturn 0: EndIf
depth = ImageDepth(hImg)
If depth = 24: depth = 3: ElseIf depth = 32: depth = 4: Else: ProcedureReturn 0: EndIf
hFile = CreateFile(#PB_Any, OutFile)
If hFile = 0: ProcedureReturn 0: EndIf
If Not StartDrawing(ImageOutput(hImg))
ErrReturn:
CloseFile(hFile)
ProcedureReturn 0
EndIf
width = ImageWidth(hImg): height = ImageHeight(hImg)
pitch = DrawingBufferPitch()
drawbuf = DrawingBuffer()
If Grayscale = 1: hdr = "P5": Else: hdr = "P6": EndIf
hdr + Chr($0A)+Str(width)+Chr($0A)+Str(height)+Chr($0A)+"255"+Chr($0A)
*buf = AllocateMemory(Len(hdr) + (width*height)*3)
If Not *buf: Goto ErrReturn: EndIf
PokeS(*buf, hdr, -1, #PB_Ascii)
*RGBout = *buf + Len(hdr)
*byteout = *RGBout
*RGBin = drawbuf
For y = 0 To height-1
CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;draw bottom-to-top
*RGBin = drawbuf + (pitch*height) - (pitch*(y+1))
CompilerElse ;draw top-to-bottom
*RGBin = drawbuf + (y * pitch)
CompilerEndIf
If Grayscale = 0
For x = 0 To width-1
CompilerIf #PB_Compiler_OS = #PB_OS_Windows ;BGRA
*RGBout\R = *RGBin\B
*RGBout\G = *RGBin\G
*RGBout\B = *RGBin\R
CompilerElse ;Linux/OSX=RGBA
*RGBout\R = *RGBin\R
*RGBout\G = *RGBin\G
*RGBout\B = *RGBin\B
CompilerEndIf
*RGBin+depth: *RGBout+3
Next x
Else
For x = 0 To width-1
*byteout\a = *RGBin\B
*RGBin+depth: *byteout+1
Next x
EndIf
Next y
StopDrawing()
WriteData(hFile, *buf, MemorySize(*buf))
CloseFile(hFile)
ProcedureReturn 1
EndProcedure
Define hImg = LoadImage(#PB_Any, "c:\temp\input.bmp") ;can be 24bit or 32bit
SavePortableMapImage(hImg, "c:\temp\output.ppm", 0) ;save as 24bit RGB
SavePortableMapImage(hImg, "c:\temp\output.pgm", 1) ;save as 8bit grayscale
FreeImage(hImg)