Add JPEG exif orientation.

This commit is contained in:
treeform 2022-05-18 10:57:20 -07:00
parent fe1b1483a1
commit 419801afa2
12 changed files with 110 additions and 5 deletions

View file

@ -9,7 +9,7 @@ requires "nim >= 1.4.8"
requires "vmath >= 1.1.4" requires "vmath >= 1.1.4"
requires "chroma >= 0.2.5" requires "chroma >= 0.2.5"
requires "zippy >= 0.9.7" requires "zippy >= 0.9.7"
requires "flatty >= 0.3.0" requires "flatty >= 0.3.2"
requires "nimsimd >= 1.0.0" requires "nimsimd >= 1.0.0"
requires "bumpy >= 1.1.1" requires "bumpy >= 1.1.1"

View file

@ -1,4 +1,5 @@
import pixie/common, pixie/images, pixie/masks, sequtils, strutils, chroma, std/decls import pixie/common, pixie/images, pixie/masks, sequtils, strutils, chroma,
std/decls, flatty/binny
# This JPEG decoder is loosely based on stb_image which is public domain. # This JPEG decoder is loosely based on stb_image which is public domain.
@ -81,6 +82,7 @@ type
restartInterval: int restartInterval: int
todoBeforeRestart: int todoBeforeRestart: int
eobRun: int eobRun: int
orientation: int
when defined(release): when defined(release):
{.push checks: off.} {.push checks: off.}
@ -108,6 +110,14 @@ proc readUint16be(state: var DecoderState): uint16 =
## Reads uint16 big-endian from the input stream. ## Reads uint16 big-endian from the input stream.
(state.readUint8().uint16 shl 8) or state.readUint8() (state.readUint8().uint16 shl 8) or state.readUint8()
proc readUint32be(state: var DecoderState): uint32 =
## Reads uint32 big-endian from the input stream.
return
(state.readUint8().uint32 shl 24) or
(state.readUint8().uint32 shl 16) or
(state.readUint8().uint32 shl 8) or
state.readUint8().uint32
proc skipBytes(state: var DecoderState, n: int) = proc skipBytes(state: var DecoderState, n: int) =
## Skips a number of bytes. ## Skips a number of bytes.
if state.pos + n > state.buffer.len: if state.pos + n > state.buffer.len:
@ -312,6 +322,57 @@ proc decodeSOF2(state: var DecoderState) =
state.decodeSOF0() state.decodeSOF0()
state.progressive = true state.progressive = true
proc decodeExif(state: var DecoderState) =
## Decode Exif header
let
len = state.readUint16be().int - 2
endOffset = state.pos + len
let exifHeader = state.buffer[state.pos ..< state.pos + 6]
state.skipBytes(6)
if exifHeader != "Exif\0\0":
# Happens with progressive images, just ignore instead of error.
# Skip to the end.
state.pos = endOffset
return
# Read the endianess of the exif header
let
tiffHeader = state.readUint16be().int
littleEndian =
if tiffHeader == 0x4D4D:
false
elif tiffHeader == 0x4949:
true
else:
failInvalid("invalid Tiff header")
# Verify we got the endianess right.
if state.readUint16be().maybeSwap(littleEndian) != 0x002A.uint16:
failInvalid("invalid Tiff header endianess")
# Skip any other tiff header data.
let offsetToFirstIFD = state.readUint32be().maybeSwap(littleEndian).int
state.skipBytes(offsetToFirstIFD - 8)
# Read the IFD0 (main image) tags.
let numTags = state.readUint16be().maybeSwap(littleEndian).int
for i in 0 ..< numTags:
let
tagNumber = state.readUint16be().maybeSwap(littleEndian)
dataFormat = state.readUint16be().maybeSwap(littleEndian)
numberComponents = state.readUint32be().maybeSwap(littleEndian)
dataOffset = state.readUint32be().maybeSwap(littleEndian).int
# For now we only care about orientation tag.
case tagNumber:
of 0x0112: # Orientation
state.orientation = dataOffset shr 16
else:
discard
# Skip all of the data we do not want to read, IFD1, thumbnail, etc.
state.pos = endOffset
proc reset(state: var DecoderState) = proc reset(state: var DecoderState) =
## Rests the decoder state need for restart markers. ## Rests the decoder state need for restart markers.
state.bitBuffer = 0 state.bitBuffer = 0
@ -913,6 +974,32 @@ proc buildImage(state: var DecoderState): Image =
else: else:
failInvalid() failInvalid()
# Do any of the orientation flips from the Exif header.
case state.orientation:
of 0, 1:
discard
of 2:
result.flipHorizontal()
of 3:
result.flipVertical()
result.flipHorizontal()
of 4:
result.flipVertical()
of 5:
result.rotate90()
of 6:
result.rotate90()
result.flipHorizontal()
of 7:
result.rotate90()
result.flipVertical()
result.flipHorizontal()
of 8:
result.rotate90()
result.flipVertical()
else:
failInvalid("invalid orientation")
proc decodeJpeg*(data: string): Image {.raises: [PixieError].} = proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
## Decodes the JPEG into an Image. ## Decodes the JPEG into an Image.
@ -962,9 +1049,8 @@ proc decodeJpeg*(data: string): Image {.raises: [PixieError].} =
# state.decodeAPP0(data, at) # state.decodeAPP0(data, at)
state.skipChunk() state.skipChunk()
of 0xE1: of 0xE1:
# Exif # Exif/APP1
# state.decodeExif(data, at) state.decodeExif()
state.skipChunk()
of 0xE2..0xEF: of 0xE2..0xEF:
# Application-specific # Application-specific
state.skipChunk() state.skipChunk()

View file

@ -170,6 +170,16 @@ proc flipVertical*(image: Image) {.raises: [].} =
image.data[image.dataIndex(x, image.height - y - 1)] image.data[image.dataIndex(x, image.height - y - 1)]
) )
proc rotate90*(image: Image) {.raises: [PixieError].} =
## Rotates the image 90 degrees.
var copy = newImage(image.height, image.width)
for y in 0 ..< copy.height:
for x in 0 ..< copy.width:
copy.data[copy.dataIndex(x, y)] = image.data[image.dataIndex(y, x)]
image.width = copy.width
image.height = copy.height
image.data = copy.data
proc subImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} = proc subImage*(image: Image, x, y, w, h: int): Image {.raises: [PixieError].} =
## Gets a sub image from this image. ## Gets a sub image from this image.
if x < 0 or x + w > image.width: if x < 0 or x + w > image.width:

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 994 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

View file

@ -36,4 +36,13 @@ const jpegSuiteFiles* = [
"tests/fileformats/jpeg/masters/testimgp.jpg", "tests/fileformats/jpeg/masters/testimgp.jpg",
"tests/fileformats/jpeg/masters/testorig.jpg", "tests/fileformats/jpeg/masters/testorig.jpg",
"tests/fileformats/jpeg/masters/testprog.jpg", "tests/fileformats/jpeg/masters/testprog.jpg",
"tests/fileformats/jpeg/masters/f1-exif.jpg",
"tests/fileformats/jpeg/masters/f2-exif.jpg",
"tests/fileformats/jpeg/masters/f3-exif.jpg",
"tests/fileformats/jpeg/masters/f4-exif.jpg",
"tests/fileformats/jpeg/masters/f5-exif.jpg",
"tests/fileformats/jpeg/masters/f6-exif.jpg",
"tests/fileformats/jpeg/masters/f7-exif.jpg",
"tests/fileformats/jpeg/masters/f8-exif.jpg",
] ]