Skip to content

Instantly share code, notes, and snippets.

@zwilias
Last active July 22, 2019 06:35
Show Gist options
  • Save zwilias/a40702ecab0451088b2a1d34568ec69f to your computer and use it in GitHub Desktop.
Save zwilias/a40702ecab0451088b2a1d34568ec69f to your computer and use it in GitHub Desktop.
Bytes decoder to extract orientation info from EXIF metadata in JPEG images
module Exif exposing (Orientation, orientation)
import Bytes
import Bytes.Decode as Decode exposing (Decoder)
type Orientation
= Normal
| MirrorHorizontal
| Rotate180
| MirrorVertical
| MirrorHorizontalRotate270CW
| Rotate90CW
| MirrorHorizontalRotate90CW
| Rotate270CW
orientation : Decoder Orientation
orientation =
Decode.succeed identity
|> skip startOfImage
|> andMap marker
|> Decode.andThen handleMarker
handleMarker : Int -> Decoder Orientation
handleMarker m =
if m == 0xE1 then
Decode.succeed identity
|> skip (Decode.unsignedInt16 Bytes.BE)
|> smatch "Exif\u{0000}\u{0000}" (Decode.string 6)
|> andMap (Decode.string 2)
|> Decode.andThen toEndianness
|> Decode.andThen decodeExif
else
Decode.succeed identity
|> skip block
|> andMap marker
|> Decode.andThen handleMarker
decodeExif : Bytes.Endianness -> Decoder Orientation
decodeExif bo =
Decode.succeed identity
|> smatch 0x2A (Decode.unsignedInt16 bo)
|> andMap (Decode.unsignedInt32 bo)
|> Decode.map (\x -> x - 8)
|> Decode.andThen skipBytes
|> Decode.andThen
(\_ ->
Decode.unsignedInt16 bo
|> Decode.andThen (\x -> Decode.loop x (decodeDirEntry bo))
)
decodeDirEntry : Bytes.Endianness -> Int -> Decoder (Decode.Step Int Orientation)
decodeDirEntry bo entries =
if entries <= 0 then
Decode.fail
else
Decode.unsignedInt16 bo
|> Decode.andThen
(\tag ->
if tag == 0x0112 then
Decode.succeed Decode.Done
|> skip (Decode.unsignedInt16 bo)
|> skip (Decode.unsignedInt32 bo)
|> andMap (orientationFromUi16 bo)
else
skipBytes 10
|> Decode.map (\_ -> Decode.Loop (entries - 1))
)
orientationFromUi16 : Bytes.Endianness -> Decoder Orientation
orientationFromUi16 bo =
Decode.unsignedInt16 bo
|> Decode.andThen
(\v ->
case v of
1 ->
Decode.succeed Normal
2 ->
Decode.succeed MirrorHorizontal
3 ->
Decode.succeed Rotate180
4 ->
Decode.succeed MirrorVertical
5 ->
Decode.succeed MirrorHorizontalRotate270CW
6 ->
Decode.succeed Rotate90CW
7 ->
Decode.succeed MirrorHorizontalRotate90CW
8 ->
Decode.succeed Rotate270CW
_ ->
Decode.fail
)
toEndianness : String -> Decoder Bytes.Endianness
toEndianness mark =
if mark == "II" then
Decode.succeed Bytes.LE
else if mark == "MM" then
Decode.succeed Bytes.BE
else
Decode.fail
block : Decoder ()
block =
Decode.unsignedInt16 Bytes.BE
|> Decode.map (\x -> x - 2)
|> Decode.andThen skipBytes
skipBytes : Int -> Decoder ()
skipBytes x =
Decode.loop x skipBytesStep
skipBytesStep : Int -> Decoder (Decode.Step Int ())
skipBytesStep n =
if n <= 0 then
Decode.succeed (Decode.Done ())
else
Decode.map (always (Decode.Loop (n - 1))) ui8
startOfImage : Decoder ()
startOfImage =
Decode.succeed ()
|> smatch 0xD8 marker
marker : Decoder Int
marker =
Decode.succeed identity
|> smatch 0xFF ui8
|> andMap ui8
smatch : v -> Decoder v -> Decoder a -> Decoder a
smatch v dec =
skip (match v dec)
match : v -> Decoder v -> Decoder ()
match v dec =
Decode.andThen
(\d ->
if d == v then
Decode.succeed ()
else
Decode.fail
)
dec
andMap : Decoder a -> Decoder (a -> b) -> Decoder b
andMap val pipe =
Decode.map2 (<|) pipe val
skip : Decoder a -> Decoder b -> Decoder b
skip s k =
Decode.map2 always k s
ui8 : Decoder Int
ui8 =
Decode.unsignedInt8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment