summaryrefslogtreecommitdiffstats
path: root/jpg.nim
blob: 57d3ee46a2c05255cc71f760cb8972ce0f08a523 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import os
import strutils
import asyncfile
import asyncdispatch
import exifnim/libexif
import exifnim/helpers
import tables
import parseutils
import options
import utils

var buf {.threadvar.}: pointer

# MUST be called once per thread
proc init_jpg*() =
  init_exif()
  buf = alloc(65536)

proc deinit_jpg*() =
  deinit_exif()
  dealloc(buf)

const
  MARKER_START = parseHexStr("FF")

  # 0xCn SOFn

  SOS   = parseHexStr("DA") # Start Of Scan (begins compressed data)
  SOI   = parseHexStr("D8") # Start Of Image (beginning of datastream)
  EOI   = parseHexStr("D9") # End Of Image (end of datastream)

  EXIF  = parseHexStr("E1")
  JFIF  = parseHexStr("E0")

type SofData* = object
  components*: uint8
  precision*: uint8
  height*: uint16
  width*: uint16

type JpgInfo* = object
  exifData*: Option[Table[string, string]]
  sofData*: Option[SofData]

proc getSectionSize(file: AsyncFile): Future[uint16] {.async.} =
  # FIXME consider endianness
  var size: uint16
  let val = toHex(file.read(2).await)
  discard parseHex(val, size)
  return size - uint16(2)

proc skipSection(file: AsyncFile): Future[void] {.async.} =
  let size = int64(file.getSectionSize().await)
  debug("skipping ", size)
  file.setFilePos(file.getFilePos() + size)

proc expect(file: AsyncFile, expected: string): Future[void] {.async.} =
  let byte = file.read(1).await
  if byte != expected:
    error("expected ", toHex(expected),", got ", toHex(byte))

proc process_sof*(file: AsyncFile): Future[SofData] {.gcsafe,async.} =
  discard parseHex(toHex(file.read(1).await), result.precision)
  discard parseHex(toHex(file.read(2).await), result.height)
  discard parseHex(toHex(file.read(2).await), result.width)
  discard parseHex(toHex(file.read(1).await), result.components)

proc collect_jpg*(file: AsyncFile): Future[JpgInfo] {.gcsafe,async.} =
  var done = false
  var byte: string

  file.expect(MARKER_START).await
  file.expect(SOI).await

  while not done:
    file.expect(MARKER_START).await

    let marker = file.read(1).await

    case marker:
    of "":
      error("unexpected end of file")
    of SOS:
      # Beginning of compressed data
      done = true
    of EOI:
      # No compressed data? Okay...
      done = true
    of EXIF:
      debug "found EXIF"
      let size = int(file.getSectionSize().await)
      discard file.readBuffer(buf, size).await
      let ed = exif_data_new_from_data(cast[ptr[cuchar]](buf), cuint(size))
      let ed_table = ed.collect_exif_data()
      if result.exifData.isNone:
        result.exifData = some(ed_table)
      debug ed_table
    of JFIF:
      debug "found JFIF"
      file.skipSection().await
    else:
      if toHex(marker).startsWith("C"):
        debug "found SOF"
        let section_start = file.getFilePos() + 2
        let section_end = section_start + int64(file.getSectionSize().await)
        let sd = file.process_sof().await
        if result.sofData.isNone:
          result.sofData = some(sd)
        debug sd
        file.setFilePos(section_end)
        continue

      debug("unknown section: ", toHex(marker))
      file.skipSection().await