Skip to content
Merged
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
158 changes: 92 additions & 66 deletions src/pixie/fileformats/jpeg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type
widthCoeff, heightCoeff: int
coeff, lineBuf: seq[uint8]
blocks: seq[seq[array[64, int16]]]
channel: seq[uint8]
channel: Mask

DecoderState = object
buffer: ptr UncheckedArray[uint8]
Expand All @@ -84,9 +84,46 @@ type
eobRun: int
hitEnd: bool

Mask = ref object
## Mask object that holds mask opacity data.
width*, height*: int
data*: seq[uint8]

UnsafeMask = distinct Mask

when defined(release):
{.push checks: off.}

proc newMask(width, height: int): Mask {.raises: [PixieError].} =
## Creates a new mask with the parameter dimensions.
if width <= 0 or height <= 0:
raise newException(PixieError, "Mask width and height must be > 0")

result = Mask()
result.width = width
result.height = height
result.data = newSeq[uint8](width * height)

template dataIndex(mask: Mask, x, y: int): int =
mask.width * y + x

template unsafe(src: Mask): UnsafeMask =
cast[UnsafeMask](src)

template `[]`(view: UnsafeMask, x, y: int): uint8 =
## Gets a value from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory reads.
cast[Mask](view).data[cast[Mask](view).dataIndex(x, y)]

template `[]=`(view: UnsafeMask, x, y: int, color: uint8) =
## Sets a value from (x, y) coordinates.
## * No bounds checking *
## Make sure that x, y are in bounds.
## Failure in the assumptions will case unsafe memory writes.
cast[Mask](view).data[cast[Mask](view).dataIndex(x, y)] = color

template failInvalid(reason = "unable to load") =
## Throw exception with a reason.
raise newException(PixieError, "Invalid JPEG, " & reason)
Expand Down Expand Up @@ -321,8 +358,7 @@ proc decodeSOF0(state: var DecoderState) =

component.widthStride = state.numMcuWide * component.yScale * 8
component.heightStride = state.numMcuHigh * component.xScale * 8
component.channel =
newSeq[uint8](component.widthStride * component.heightStride)
component.channel = newMask(component.widthStride, component.heightStride)

if state.progressive:
component.widthCoeff = component.widthStride div 8
Expand Down Expand Up @@ -813,14 +849,14 @@ proc idctBlock(component: var Component, offset: int, data: array[64, int16]) =
x2 += 65536 + (128 shl 17)
x3 += 65536 + (128 shl 17)

component.channel[outPos + 0] = clampByte((x0 + t3) shr 17)
component.channel[outPos + 7] = clampByte((x0 - t3) shr 17)
component.channel[outPos + 1] = clampByte((x1 + t2) shr 17)
component.channel[outPos + 6] = clampByte((x1 - t2) shr 17)
component.channel[outPos + 2] = clampByte((x2 + t1) shr 17)
component.channel[outPos + 5] = clampByte((x2 - t1) shr 17)
component.channel[outPos + 3] = clampByte((x3 + t0) shr 17)
component.channel[outPos + 4] = clampByte((x3 - t0) shr 17)
component.channel.data[outPos + 0] = clampByte((x0 + t3) shr 17)
component.channel.data[outPos + 7] = clampByte((x0 - t3) shr 17)
component.channel.data[outPos + 1] = clampByte((x1 + t2) shr 17)
component.channel.data[outPos + 6] = clampByte((x1 - t2) shr 17)
component.channel.data[outPos + 2] = clampByte((x2 + t1) shr 17)
component.channel.data[outPos + 5] = clampByte((x2 - t1) shr 17)
component.channel.data[outPos + 3] = clampByte((x3 + t0) shr 17)
component.channel.data[outPos + 4] = clampByte((x3 - t0) shr 17)

{.pop.}

Expand Down Expand Up @@ -902,59 +938,45 @@ proc quantizationAndIDCTPass(state: var DecoderState) =
data
)

template dataIndex(component: var Component, x, y: int): int =
component.widthStride * y + x

template `[]`*(component: Component, x, y: int): uint8 =
component.channel[component.dataIndex(x, y)]

proc magnifyXBy2(component: var Component) =
proc magnifyXBy2(mask: Mask): Mask =
## Smooth magnify by power of 2 only in the X direction.
var magnified =
newSeq[uint8](component.widthStride * 2 * component.heightStride)
for y in 0 ..< component.heightStride:
for x in 0 ..< component.widthStride:
let n = 3 * component[x, y].uint16
result = newMask(mask.width * 2, mask.height)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
let n = 3 * mask.unsafe[x, y].uint16
if x == 0:
magnified[component.dataIndex(x * 2 + 0, y)] = component[x, y]
magnified[component.dataIndex(x * 2 + 1, y)] =
((n + component[x + 1, y].uint16 + 2) div 4).uint8
elif x == component.widthStride - 1:
magnified[component.dataIndex(x * 2 + 0, y)] =
((n + component[x - 1, y].uint16 + 2) div 4).uint8
magnified[component.dataIndex(x * 2 + 1, y)] = component[x, y]
result.unsafe[x * 2 + 0, y] = mask.unsafe[x, y]
result.unsafe[x * 2 + 1, y] =
((n + mask.unsafe[x + 1, y].uint16 + 2) div 4).uint8
elif x == mask.width - 1:
result.unsafe[x * 2 + 0, y] =
((n + mask.unsafe[x - 1, y].uint16 + 2) div 4).uint8
result.unsafe[x * 2 + 1, y] = mask.unsafe[x, y]
else:
magnified[component.dataIndex(x * 2 + 0, y)] =
((n + component[x - 1, y].uint16) div 4).uint8
magnified[component.dataIndex(x * 2 + 1, y)] =
((n + component[x + 1, y].uint16) div 4).uint8
result.unsafe[x * 2 + 0, y] =
((n + mask.unsafe[x - 1, y].uint16) div 4).uint8
result.unsafe[x * 2 + 1, y] =
((n + mask.unsafe[x + 1, y].uint16) div 4).uint8

component.channel = move magnified
component.widthStride *= 2

proc magnifyYBy2(component: var Component) =
proc magnifyYBy2(mask: Mask): Mask =
## Smooth magnify by power of 2 only in the Y direction.
var magnified =
newSeq[uint8](component.widthStride * component.heightStride * 2)
for y in 0 ..< component.heightStride:
for x in 0 ..< component.widthStride:
let n = 3 * component[x, y].uint16
result = newMask(mask.width, mask.height * 2)
for y in 0 ..< mask.height:
for x in 0 ..< mask.width:
let n = 3 * mask.unsafe[x, y].uint16
if y == 0:
magnified[component.dataIndex(x, y * 2 + 0)] = component[x, y]
magnified[component.dataIndex(x, y * 2 + 1)] =
((n + component[x, y + 1].uint16 + 2) div 4).uint8
elif y == component.heightStride - 1:
magnified[component.dataIndex(x, y * 2 + 0)] =
((n + component[x, y - 1].uint16 + 2) div 4).uint8
magnified[component.dataIndex(x, y * 2 + 1)] = component[x, y]
result.unsafe[x, y * 2 + 0] = mask.unsafe[x, y]
result.unsafe[x, y * 2 + 1] =
((n + mask.unsafe[x, y + 1].uint16 + 2) div 4).uint8
elif y == mask.height - 1:
result.unsafe[x, y * 2 + 0] =
((n + mask.unsafe[x, y - 1].uint16 + 2) div 4).uint8
result.unsafe[x, y * 2 + 1] = mask.unsafe[x, y]
else:
magnified[component.dataIndex(x, y * 2 + 0)] =
((n + component[x, y - 1].uint16) div 4).uint8
magnified[component.dataIndex(x, y * 2 + 1)] =
((n + component[x, y + 1].uint16) div 4).uint8

component.channel = move magnified
component.heightStride *= 2
result.unsafe[x, y * 2 + 0] =
((n + mask.unsafe[x, y - 1].uint16) div 4).uint8
result.unsafe[x, y * 2 + 1] =
((n + mask.unsafe[x, y + 1].uint16) div 4).uint8

proc yCbCrToRgbx(py, pcb, pcr: uint8): ColorRGBX =
## Takes a 3 component yCbCr outputs and populates image.
Expand Down Expand Up @@ -989,29 +1011,33 @@ proc buildImage(state: var DecoderState): Image =
of 3:
for component in state.components.mitems:
while component.yScale < state.maxYScale:
component.magnifyXBy2()
component.channel = component.channel.magnifyXBy2()
component.yScale *= 2

while component.xScale < state.maxXScale:
component.magnifyYBy2()
component.channel = component.channel.magnifyYBy2()
component.xScale *= 2

let
cy = state.components[0].channel
cb = state.components[1].channel
cr = state.components[2].channel
for y in 0 ..< state.imageHeight:
var channelIndex = state.components[0].dataIndex(0, y)
var channelIndex = cy.dataIndex(0, y)
for x in 0 ..< state.imageWidth:
result.unsafe[x, y] = yCbCrToRgbx(
state.components[0].channel[channelIndex], # cy
state.components[1].channel[channelIndex], # cb
state.components[2].channel[channelIndex], # cr
cy.data[channelIndex],
cb.data[channelIndex],
cr.data[channelIndex],
)
inc channelIndex

of 1:
let cy = state.components[0].channel
for y in 0 ..< state.imageHeight:
var channelIndex = state.components[0].dataIndex(0, y)
var channelIndex = cy.dataIndex(0, y)
for x in 0 ..< state.imageWidth:
result.unsafe[x, y] =
grayScaleToRgbx(state.components[0].channel[channelIndex]) # cy
result.unsafe[x, y] = grayScaleToRgbx(cy.data[channelIndex])
inc channelIndex

else:
Expand Down