91 lines
3 KiB
Nim
91 lines
3 KiB
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
|