summaryrefslogtreecommitdiffstats
path: root/nimjpg.nim
blob: 58ff67b85c7af46ce7cb519dd3ec0f515ac26bda (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
115
116
117
118
119
120
121
122
import os
import streams
import strutils
import asyncfile
import asyncdispatch
import nimexif/libexif
import nimexif/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: Stream | AsyncFile): Future[uint16] {.multisync.} =
  # FIXME consider endianness
  var size: uint16
  let val = toHex(file.read(2).await)
  discard parseHex(val, size)
  echo val
  return size - uint16(2)

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

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

proc process_sof*(file: Stream | AsyncFile): Future[SofData] {.multisync.} =
  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: Stream | AsyncFile): Future[JpgInfo] {.multisync,gcsafe.} =
  var done = false
  var byte: string

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

  while not done:
    discard 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"
      discard 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))
      discard file.skipSection().await

proc collect_jpg*(file: File): JpgInfo =
  let stream = newFileStream(file)
  result = collect_jpg(stream)
  stream.close()