path:
/nimjpg.nim
3.38 KB | plain
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
123
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)
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 calcRes(sd: SofData): uint64 =
return uint64(sd.height) * uint64(sd.width)
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[uint8]](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 or sd.calcRes() > result.sofData.get.calcRes():
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()