Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 50 additions & 34 deletions src/pixie/fileformats/ppm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ proc decodeHeader(data: string): PpmHeader {.raises: [PixieError].} =
if data.len <= 10: # Each part + whitespace
raise newException(PixieError, "Invalid PPM file header")

var commentMode, readWhitespace: bool
var i, readFields: int
var field: string
var
commentMode, readWhitespace: bool
i, readFields: int
field: string
while readFields < 4:
let c = readUint8(data, i).char
if c == '#':
Expand All @@ -41,7 +42,8 @@ proc decodeHeader(data: string): PpmHeader {.raises: [PixieError].} =
result.maxVal = parseInt(field)
else:
discard
except ValueError: failInvalid()
except ValueError:
failInvalid()
field = ""
elif not (c in Whitespace):
field.add(c)
Expand All @@ -54,49 +56,64 @@ proc decodeP6Data(data: string, maxVal: int): seq[ColorRGBX] {.raises: [].} =
let needsUint16 = maxVal > 0xFF

result = newSeq[ColorRGBX](
if needsUint16: data.len div 6
else: data.len div 3
if needsUint16:
data.len div 6
else:
data.len div 3
)

# Let's calculate the real maximum value multiplier.
# rgbx() accepts a maximum value of 0xFF. Most of the time,
# maxVal is set to 0xFF as well, so in most cases it is 1
let valueMultiplier = 0xFF / maxVal
# rgbx() accepts a maximum value of 255. Most of the time,
# maxVal is set to 255 as well, so in most cases it is 1
let valueMultiplier = (255 / maxVal).float32

# if comparison in for loops is expensive, so let's unroll it
if not needsUint16:
for i in 0 ..< result.len:
let
red = (readUint8(data, i + (i * 2)).float * valueMultiplier + 0.5).uint8
green = (readUint8(data, i + 1 + (i * 2)).float * valueMultiplier + 0.5).uint8
blue = (readUint8(data, i + 2 + (i * 2)).float * valueMultiplier + 0.5).uint8
result[i] = rgbx(red, green, blue, 0xFF)
red = data.readUint8(i + (i * 2)).float32
green = data.readUint8(i + 1 + (i * 2)).float32
blue = data.readUint8(i + 2 + (i * 2)).float32
result[i] = rgbx(
(red * valueMultiplier + 0.5).uint8,
(green * valueMultiplier + 0.5).uint8,
(blue * valueMultiplier + 0.5).uint8,
255
)
else:
for i in 0 ..< result.len:
let
red = (readUint16(data, i + (i * 5)).swap.float * valueMultiplier + 0.5).uint8
green = (readUint16(data, i + 2 + (i * 5)).swap.float * valueMultiplier + 0.5).uint8
blue = (readUint16(data, i + 4 + (i * 5)).swap.float * valueMultiplier + 0.5).uint8
result[i] = rgbx(red, green, blue, 0xFF)
red = data.readUint16(i + (i * 5)).swap.float32
green = data.readUint16(i + 2 + (i * 5)).swap.float32
blue = data.readUint16(i + 4 + (i * 5)).swap.float32
result[i] = rgbx(
(red * valueMultiplier + 0.5).uint8,
(green * valueMultiplier + 0.5).uint8,
(blue * valueMultiplier + 0.5).uint8,
255
)

proc decodeP3Data(data: string, maxVal: int): seq[ColorRGBX] {.raises: [PixieError].} =
let needsUint16 = maxVal > 0xFF
let maxLen = (
if needsUint16: data.splitWhitespace.len * 2
else: data.splitWhitespace.len
)
var p6data = newStringOfCap(maxLen)
let
needsUint16 = maxVal > 0xFF
maxLen =
if needsUint16:
data.splitWhitespace.len * 2
else:
data.splitWhitespace.len

var p6data = newStringOfCap(maxLen)
try:
if not needsUint16:
for line in data.splitLines():
for sample in line.split('#', 1)[0].splitWhitespace():
p6data.add(parseInt(sample).chr)
p6data.add(parseInt(sample).char)
else:
for line in data.splitLines():
for sample in line.split('#', 1)[0].splitWhitespace():
p6data.addUint16(parseInt(sample).uint16.swap)
except ValueError: failInvalid()
except ValueError:
failInvalid()

result = decodeP6Data(p6data, maxVal)

Expand All @@ -105,19 +122,18 @@ proc decodePpm*(data: string): Image {.raises: [PixieError].} =

let header = decodeHeader(data)

if not (header.version in ppmSignatures): failInvalid()
if 0 > header.maxVal or header.maxVal > 0xFFFF: failInvalid()
if not (header.version in ppmSignatures):
failInvalid()

if 0 > header.maxVal or header.maxVal > 0xFFFF:
failInvalid()

result = newImage(header.width, header.height)
result.data = (
result.data =
if header.version == "P3":
decodeP3Data(data[header.dataOffset .. ^1], header.maxVal)
else: decodeP6Data(data[header.dataOffset .. ^1], header.maxVal)
)

proc decodePpm*(data: seq[uint8]): Image {.inline, raises: [PixieError].} =
## Decodes Portable Pixel Map data into an Image.
decodePpm(cast[string](data))
else:
decodeP6Data(data[header.dataOffset .. ^1], header.maxVal)

proc encodePpm*(image: Image): string {.raises: [].} =
## Encodes an image into the PPM file format (version P6).
Expand Down
4 changes: 3 additions & 1 deletion src/pixie/fontformats/opentype.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2126,7 +2126,9 @@ proc getGlyphId(opentype: OpenType, rune: Rune): uint16 =
proc hasGlyph*(opentype: OpenType, rune: Rune): bool =
rune in opentype.cmap.runeToGlyphId

proc parseGlyfGlyph(opentype: OpenType, glyphId: uint16): Path {.raises: [PixieError], gcsafe.}
proc parseGlyfGlyph(
opentype: OpenType, glyphId: uint16
): Path {.raises: [PixieError], gcsafe.}

proc parseGlyphPath(
buf: string, offset, numberOfContours: int
Expand Down
55 changes: 38 additions & 17 deletions src/pixie/images.nim
Original file line number Diff line number Diff line change
Expand Up @@ -837,27 +837,37 @@ proc drawUber(
when type(a) is Image:
when type(b) is Image:
for q in [0, 4, 8, 12]:
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
if mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, mm_setzero_si128())) != 0xffff:
if (mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) and 0x8888) == 0x8888:
let
sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) != 0xffff:
let eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if (mm_movemask_epi8(eq255) and 0x8888) == 0x8888:
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, sourceVec)
else:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
let
backdropIdx = a.dataIndex(x + q, y)
backdropVec = mm_loadu_si128(a.data[backdropIdx].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
a.data[backdropIdx].addr,
blendNormalInlineSimd(backdropVec, sourceVec)
)
else: # b is a Mask
var values = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
for q in [0, 4, 8, 12]:
let sourceVec = unpackAlphaValues(values)
if mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, mm_setzero_si128())) != 0xffff:
if (mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) and 0x8888) == 0x8888:
let
sourceVec = unpackAlphaValues(values)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) != 0xffff:
let eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if (mm_movemask_epi8(eq255) and 0x8888) == 0x8888:
discard
else:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
let
backdropIdx = a.dataIndex(x + q, y)
backdropVec = mm_loadu_si128(a.data[backdropIdx].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
a.data[backdropIdx].addr,
blendNormalInlineSimd(backdropVec, sourceVec)
)
# Shuffle 32 bits off for the next iteration
Expand Down Expand Up @@ -885,9 +895,14 @@ proc drawUber(
when type(a) is Image:
when type(b) is Image:
for q in [0, 4, 8, 12]:
let sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
if mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, mm_setzero_si128())) == 0xffff:
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, mm_setzero_si128())
let
sourceVec = mm_loadu_si128(b.data[b.dataIndex(sx + q, sy)].addr)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
if mm_movemask_epi8(eqZer0) == 0xffff:
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
mm_setzero_si128()
)
elif mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) != 0xffff:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
mm_storeu_si128(
Expand All @@ -897,10 +912,16 @@ proc drawUber(
else: # b is a Mask
var values = mm_loadu_si128(b.data[b.dataIndex(sx, sy)].addr)
for q in [0, 4, 8, 12]:
let sourceVec = unpackAlphaValues(values)
if mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, mm_setzero_si128())) == 0xffff:
mm_storeu_si128(a.data[a.dataIndex(x + q, y)].addr, mm_setzero_si128())
elif (mm_movemask_epi8(mm_cmpeq_epi8(sourceVec, vec255)) and 0x8888) != 0x8888:
let
sourceVec = unpackAlphaValues(values)
eqZer0 = mm_cmpeq_epi8(sourceVec, mm_setzero_si128())
eq255 = mm_cmpeq_epi8(sourceVec, vec255)
if mm_movemask_epi8(eqZer0) == 0xffff:
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
mm_setzero_si128()
)
elif (mm_movemask_epi8(eq255) and 0x8888) != 0x8888:
let backdropVec = mm_loadu_si128(a.data[a.dataIndex(x + q, y)].addr)
mm_storeu_si128(
a.data[a.dataIndex(x + q, y)].addr,
Expand Down
4 changes: 1 addition & 3 deletions tests/test_ppm.nim
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ block:
))
writeFile("tests/fileformats/ppm/feep." & $format & ".ppm", encodePpm(image))

let image = decodePpm(readFile(
"tests/fileformats/ppm/feep.p3.hidepth.master.ppm"
))
let image = decodePpm(readFile("tests/fileformats/ppm/feep.p3.hidepth.master.ppm"))
writeFile("tests/fileformats/ppm/feep.p3.hidepth.ppm", encodePpm(image))

# produced output should be identical to P6 master
Expand Down