This file is indexed.

/usr/share/tcltk/tcllib1.19/jpeg/jpeg.tcl is in tcllib 1.19-dfsg-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

   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
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
# jpeg.tcl --
#
#       Querying and modifying JPEG image files.
#
# Copyright (c) 2004    Aaron Faupell <afaupell@users.sourceforge.net>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# 
# RCS: @(#) $Id: jpeg.tcl,v 1.19 2011/05/06 13:39:27 patthoyts Exp $

# ### ### ### ######### ######### #########
## Requisites

namespace eval ::jpeg {}

# ### ### ### ######### ######### #########
## Notes :: Structure of jpeg files.

# Base types
#
# BYTE    = 1 byte
# SHORT   = 2 bytes, endianess determined by context.
# BESHORT = 2 bytes, big endian
# INT     = 4 bytes, endianess determined by context.

# JPEG types
#
# JPEG = <
#   BYTE     [2] == 0xFF 0xD8 (SOI (Start Of Image))
#   JSEGMENT [.] 1 or more jpeg segments, variadic size
#   BYTE     [2] == 0xFF 0xD9 (EOI (End Of Image))
# >
#
# JSEGMENT = <
#   BYTE    [1]   == 0xFF
#   BYTE    [1]   Segment Tag, type marker
#   BESHORT [1]   Segment Length N
#   BYTE    [N-2] Segment Data, interpreted dependent on tag.
# >
#
# Notable segments, and their structure.
#
# Comment = JSEGMENT (Tag = 0xFE, Data = <
#
# >)


# Type 0xFE (Comment)
# Data BYTE [ ]
# Note: Multiple comment segments are allowed.

# Type 0xC0/0xC1/0xC2/0xC3 (Start of Frame)
# Data BYTE    [1] Precision
#      BESHORT [1] Height
#      BESHORT [1] Width
#      BYTE    [1] Number of color components
#      ...

# Type 0xEx (x=0-9A-F) (App0 - App15)
# Data It is expected that the data starts with a checkable marker, as
#      the app segments can be used by multiple applications for
#      different purposes. I.e. a sub-type is needed before the
#      segment data can be processed.

# App0/JFIF image info
# Type 0xE0
# Data BYTE    [5] 'JFIF\0'	JFIF sub-type marker
#      BYTE    [1] Version1 (major)
#      BYTE    [1] Version2 (minor)
#      BYTE    [1] Units
#      BESHORT [1] X-density (dots per inch ?)
#      BESHORT [1] Y-density
#      BYTE    [1] X-thumb   (Width  of thumbnail, if any, or zero)
#      BYTE    [1] Y-thumb   (Height of thumbnail, if any, or zero)

# App0/JFXX extended image information
# Type 0xE0
# Data BYTE    [5] 'JFXX\0'	JFXX sub-type marker
#      BYTE    [1] Extension code 10 -> JPEG thumbnail
#                                 11 -> Palletized thumbnail
#                                 13 -> RGB thumbnail
#      BYTE    [ ] Data per the extension code.

# App1/EXIF
# Type 0xE1
# Data BYTE  [6] 'Exif\0\0' EXIF sub-type marker. (1)
#      BYTE  [2] Byte Order  0x4d 0x4d = big endian
#                         or 0x49 0x49 = small endian
#      SHORT [1] Magic == 42 under the specified byteorder.
#      INT   [1] Next  == Offset to the first actual EXIF data block.
#
# EXIF data block structure (IFD = Image File Directory)
#
# 1. SHORT [1] Number N of exif entries
# 2. ENTRY [N] Array of exif entries
# 3. INT   [1] Offset to the next EXIF data block, or <0 for the last block.
#

# exif ENTRY structure
#
# 1. SHORT [1] num
# 2. SHORT [1] tag    = exif key
# 3. SHORT [1] format
# 4. INT   [1] component
# 5. INT   [1] value

# The 'value is interpreted dependent on the values of tag, format,
# and component.
#
# A.  Tag in ( 0x8769, 0xA005 )
#     Value is offset to a subordinate exif data block, process recursively.
# B.  Size = components * sizeof(format)
# B1. Size > 4
#     Value is offset to the actual value.
# B2. Size <= 4
#     Value is the actual value.

# Usually a jpeg with exif information has two exif data blocks. The
# first is the main block, the second the thumbnail block.
#
# Note that all the exif data structures are within the app1/exif
# segment.
#
# (1) The offset of the first byte after the exif marker is what all
#     the offsets in exif are relative to.

# Type 0xDA (SOS, Start of Stream/Scan)
# Followed by the JPEG data. Last segment before EOI

# ### ### ### ######### ######### #########

# open a file, check jpeg signature, and a return a file handle
# at the start of the first marker
proc ::jpeg::openJFIF {file {mode r}} {
    set fh [open $file $mode]
    fconfigure $fh -encoding binary -translation binary -eofchar {}
    # jpeg sig is FFD8, FF is start of first marker
    if {[read $fh 3] != "\xFF\xD8\xFF"} { close $fh; return -code error "not a jpg file" }
    # rewind to first marker
    seek $fh -1 current
    return $fh
}

# return a boolean indicating if a file starts with the jpeg sig
proc ::jpeg::isJPEG {file} {
    set is [catch {openJFIF $file} fh]
    catch {close $fh}
    return [expr {!$is}]
}

# takes an open filehandle at the start of a jpeg marker, and returns a list
# containing information about the file markers in the jpeg file. each list
# element itself a list of the marker type, offset of the start of its data,
# and the length of its data.
proc ::jpeg::markers {fh} {
    set chunks [list]
    while {[read $fh 1] == "\xFF"} {
        binary scan [read $fh 3] H2S type len
        # convert to unsigned
        set len [expr {$len & 0x0000FFFF}]
        # decrement len to account for marker bytes
        incr len -2
        lappend chunks [list $type [tell $fh] $len]
        seek $fh $len current
    }
    # chunks = list (list (type offset length) ...)
    return $chunks
}

proc ::jpeg::imageInfo {file} {
    set fh [openJFIF $file r]
    set data {}
    if {[set app0 [lsearch -inline [markers $fh] "e0 *"]] != ""} {
        seek $fh [lindex $app0 1] start
        set id [read $fh 5]
        if {$id == "JFIF\x00"} {
            binary scan [read $fh 9] cccSScc ver1 ver2 units xr yr xt yt
            set data [list version $ver1.$ver2 units $units xdensity $xr ydensity $yr xthumb $xt ythumb $yt]
        }
    }
    close $fh
    return $data
}

# return an images dimensions by reading the Start Of Frame marker
proc ::jpeg::dimensions {file} {
    set fh [openJFIF $file]
    set sof [lsearch -inline [markers $fh] {c[0-3] *}]
    seek $fh [lindex $sof 1] start
    binary scan [read $fh 5] cSS precision height width
    close $fh
    return [list $width $height]
}

# returns a list of all comments (FE segments) in the file
proc ::jpeg::getComments {file} {
    set fh [openJFIF $file]
    set comments {}
    foreach x [lsearch -all -inline [markers $fh] "fe *"] {
        seek $fh [lindex $x 1] start
        lappend comments [read $fh [lindex $x 2]]
    }
    close $fh
    return $comments
}

# add a new comment to the file
proc ::jpeg::addComment {file comment args} {
    set fh [openJFIF $file r+]
    # find the SoF and save all data after it
    set sof [lsearch -inline [markers $fh] {c[0-3] *}]
    seek $fh [expr {[lindex $sof 1] - 4}] start
    set data2 [read $fh]
    # seek back to the SoF and write comment(s) segment
    seek $fh [expr {[lindex $sof 1] - 4}] start
    foreach x [linsert $args 0 $comment] {
        if {$x == ""} continue
        puts -nonewline $fh [binary format a2Sa* "\xFF\xFE" [expr {[string length $x] + 2}] $x]
    }
    # write the saved data bac
    puts -nonewline $fh $data2
    close $fh
}

proc ::jpeg::replaceComment {file comment} {
    set com [getComments $file]
    removeComments $file
    eval [list addComment $file] [lreplace $com 0 0 $comment]
}

# removes all comment segments from the file
proc ::jpeg::removeComments {file} {
    set fh [openJFIF $file]
    set data "\xFF\xD8"
    foreach marker [markers $fh] {
        if {[lindex $marker 0] != "fe"} {
            # seek back 4 bytes to include the marker and length bytes
            seek $fh [expr {[lindex $marker 1] - 4}] start
            append data [read $fh [expr {[lindex $marker 2] + 4}]]
        }
    }
    append data [read $fh]
    close $fh
    set fh [open $file w]
    fconfigure $fh -encoding binary -translation binary -eofchar {}
    puts -nonewline $fh $data
    close $fh
}

# rewrites a jpeg file and removes all metadata (comments, exif, photoshop)
proc ::jpeg::stripJPEG {file} {
    set fh [openJFIF $file]
    set data {}
    
    set markers [markers $fh]
    # look for a jfif header segment and save it
    if {[lindex $markers 0 0] == "e0"} {
        seek $fh [lindex $markers 0 1] start
        if {[read $fh 5] == "JFIF\x00"} {
            seek $fh -9 current
            set jfif [read $fh [expr {[lindex $markers 0 2] + 4}]]
        }
    }
    # if we dont have a jfif header (exif files), create a fake one
    if {![info exists jfif]} {
        set jfif [binary format a2Sa5cccSScc "\xFF\xE0" 16 "JFIF\x00" 1 2 1 72 72 0 0]
    }

    # remove all the e* and f* markers (metadata)
    foreach marker $markers {
        if {![string match {[ef]*} [lindex $marker 0]]} {
            seek $fh [expr {[lindex $marker 1] - 4}] start
            append data [read $fh [expr {[lindex $marker 2] + 4}]]
        }
    }
    append data [read $fh]

    close $fh
    set fh [open $file w+]
    fconfigure $fh -encoding binary -translation binary -eofchar {}
    # write a jpeg file sig, a jfif header, and all the remaining data
    puts -nonewline $fh \xFF\xD8$jfif$data
    close $fh
}

# if file contains a jpeg thumbnail return it. the returned data is the actual
# jpeg data, it can be written directly to a file
proc ::jpeg::getThumbnail {file} {
    # check if the exif information contains a thumbnail
    array set exif [getExif $file thumbnail]
    if {[info exists exif(Compression)] && \
             $exif(Compression) == 6 && \
             [info exists exif(JPEGInterchangeFormat)] && \
             [info exists exif(JPEGInterchangeFormatLength)]} {
        set fh [openJFIF $file]
        seek $fh [expr {$exif(ExifOffset) + $exif(JPEGInterchangeFormat)}] start
        set thumb [read $fh $exif(JPEGInterchangeFormatLength)]
        close $fh
        return $thumb
    }
    # check for a JFXX segment which contains a thumbnail
    set fh [openJFIF $file]
    foreach x [lsearch -inline -all [markers $fh] "e0 *"] {
        seek $fh [lindex $x 1] start
        binary scan [read $fh 6] a5H2 id excode
        # excode 10 is jpeg encoding, we cant interpret the other types
        if {$id == "JFXX\x00" && $excode == "10"} {
            set thumb [read $fh [expr {[lindex $x 2] - 6}]]
            close $fh
            return $thumb
        }
    }
    close $fh
}


# takes key-value pairs returned by getExif and converts their values into
# human readable format
proc ::jpeg::formatExif {exif} {
    variable exif_values
    set out {}
    foreach {tag val} $exif {
        if {[info exists exif_values($tag,$val)]} {
            set val $exif_values($tag,$val)
        } elseif {[info exists exif_values($tag,)]} {
            set val $exif_values($tag,)
        } else {
            switch -exact -- $tag {
                UserComment {set val [string trim [string range $val 8 end] \x00]}
                ComponentsConfiguration {binary scan $val cccc a b c d; set val $a,$b,$c,$d}
                ExifVersion {set val [expr [string range $val 0 1].[string range $val 2 3]]}
                FNumber {set val [format %2.1f $val]}
                MaxApertureValue -
                ApertureValue {
                    if {$val > 0} {
                        set val [format %2.1f [expr {2 * (log($val) / log(2))}]]
                    }
                }
                ShutterSpeedValue {
                    set val [expr {pow(2, $val)}]
                    if {abs(round($val) - $val) < 0.2} {set val [expr {round($val)}]}
                    set val 1/[string trimright [string trimright [format %.2f $val] 0] .]
                }
                ExposureTime {
                    set val 1/[string trimright [string trimright [format %.4f [expr {1 / $val}]] 0] .]
                }
            }
        }
        lappend out $tag $val
    }
    return $out
}

# returns a list of all known exif keys
proc ::jpeg::exifKeys {} {
    variable exif_tags
    set ret {}
    foreach {x y} [array get exif_tags] {lappend ret $y}
    return $ret
}

proc ::jpeg::getExif {file {type main}} {
    set fh [openJFIF $file]
    set r [catch {getExifFromChannel $fh $type} err]
    close $fh
    return -code $r $err
}

proc ::jpeg::getExifFromChannel {chan {type main}} {
    # foreach because file may have multiple e1 markers
    foreach app1 [lsearch -inline -all [markers $chan] "e1 *"] {
        seek $chan [lindex $app1 1] start
        # check that this e1 is really an Exif segment
        if {[read $chan 6] != "Exif\x00\x00"} continue
        # save offset because exif offsets are relative to this
        set start [tell $chan]
        # next 2 bytes determine byte order
        binary scan [read $chan 2] H4 byteOrder
        if {$byteOrder == "4d4d"} {
            set byteOrder big
        } elseif {$byteOrder == "4949"} {
            set byteOrder little
        } else {
            return -code error "invalid byte order magic"
        }
        # the answer is 42, if we have our byte order correct
        _scan $byteOrder [read $chan 6] si magic next
        if {$magic != 42} { return -code error "invalid byte order"}

        seek $chan [expr {$start + $next}] start
        if {$type != "thumbnail"} {
	    if {$type != "main"} {
		return -code error "Bad type \"$type\", expected one of \"main\", or \"thumbnail\""
	    }
            set data [_exif $chan $byteOrder $start]
        } else {
            # number of entries in this exif block
            _scan $byteOrder [read $chan 2] s num
            # each entry is 12 bytes
            seek $chan [expr {$num * 12}] current
            # offset of next exif block (for thumbnail)
            _scan $byteOrder [read $chan 4] i next
            if {$next <= 0} { return }
            # but its relative to start
            seek $chan [expr {$start + $next}] start
            set data [_exif $chan $byteOrder $start]
        }
        lappend data ExifOffset $start ExifByteOrder $byteOrder
        return $data
    }
    return
}

proc ::jpeg::removeExif {file} {
    set fh [openJFIF $file]
    set data {}
    set markers [markers $fh]
    if {[lsearch $markers "e1 *"] < 0} { close $fh; return }
    foreach marker $markers {
        if {[lindex $marker 0] != "e1"} {
            seek $fh [expr {[lindex $marker 1] - 4}] start
            append data [read $fh [expr {[lindex $marker 2] + 4}]]
        } else {
            seek $fh [lindex $marker 1] start
            if {[read $fh 6] == "Exif\x00\x00"} continue
            seek $fh -10 current
            append data [read $fh [expr {[lindex $marker 2] + 4}]]
        }
    }
    append data [read $fh]
    close $fh
    set fh [open $file w]
    fconfigure $fh -encoding binary -translation binary -eofchar {}
    puts -nonewline $fh "\xFF\xD8"
    if {[lindex $markers 0 0] != "e0"} {
        puts -nonewline $fh [binary format a2Sa5cccSScc "\xFF\xE0" 16 "JFIF\x00" 1 2 1 72 72 0 0]
    }
    puts -nonewline $fh $data
    close $fh
}

proc ::jpeg::_exif2 {data} {
    variable exif_tags
    set byteOrder little
    set start 0
    set i 2
    for {_scan $byteOrder $data @0s num} {$num > 0} {incr num -1} {
        binary scan $data @${i}H2H2 t1 t2
        if {$byteOrder == "big"} {
            set tag $t1$t2
        } else {
            set tag $t2$t1
        }
        incr i 2
        _scan $byteOrder $data @${i}si format components
        incr i 6
        set value [string range $data $i [expr {$i + 3}]]
        if {$tag == "8769" || $tag == "a005"} {
            _scan $byteOrder $value i next
            #set pos [tell $fh]
            #seek $fh [expr {$offset + $next}] start
            #eval lappend return [_exif $fh $byteOrder $offset]
            #seek $fh $pos start
            continue
        }
        if {![info exists exif_formats($format)]} continue
        if {[info exists exif_tags($tag)]} { set tag $exif_tags($tag) }
        set size [expr {$exif_formats($format) * $components}]
        if {$size > 4} {
            _scan $byteOrder $value i value
            #puts "$value"
            #set value [string range $data [expr {$i + $offset + $value}] [expr {$size - 1}]]
        }
        lappend ret $tag [_format $byteOrder $value $format $components]
    }
}

# reads an exif block and returns key-value pairs
proc ::jpeg::_exif {fh byteOrder offset {tag_info exif_tags}} {
    variable exif_formats
    variable exif_tags
    variable gps_tags
    set return {}
    for {_scan $byteOrder [read $fh 2] s num} {$num > 0} {incr num -1} {
        binary scan [read $fh 2] H2H2 t1 t2
        _scan $byteOrder [read $fh 6] si format components
        if {$byteOrder == "big"} {
            set tag $t1$t2
        } else {
            set tag $t2$t1
        }
        set value [read $fh 4]
        # special tags, they point to more exif blocks
        if {$tag == "8769" || $tag == "a005"} {
            _scan $byteOrder $value i next
            set pos [tell $fh]
            seek $fh [expr {$offset + $next}] start
            eval lappend return [_exif $fh $byteOrder $offset]
            seek $fh $pos start
            continue
        }
	# special tag, another exif block holding GPS/location information.
	if {$tag == "8825"} {
            _scan $byteOrder $value i next
            set pos [tell $fh]
            seek $fh [expr {$offset + $next}] start
            eval lappend return [_exif $fh $byteOrder $offset gps_tags]
            seek $fh $pos start
            continue
	}
        if {![info exists exif_formats($format)]} continue
	upvar 0 $tag_info thetags
        if {[info exists thetags($tag)]} { set tag $thetags($tag) }
        set size [expr {$exif_formats($format) * $components}]
        # if the data is over 4 bytes, its stored later in the file, with the
        # data being the offset relative to the exif header
        if {$size > 4} {
            set pos [tell $fh]
            _scan $byteOrder $value i value
            seek $fh [expr {$offset + $value}] start
            set value [read $fh $size]
            seek $fh $pos start
        }
        lappend return $tag [_format $byteOrder $value $format $components]
    }
    return $return
}

proc ::jpeg::MakerNote {offset byteOrder Make data} {
    if {$Make == "Canon"} {
        set data [MakerNoteCanon $offset $byteOrder $data]
    } elseif {[string match Nikon* $data] || $Make == "NIKON"} {
        set data [MakerNoteNikon $offset $byteOrder $data]
    } elseif {[string match FUJIFILM* $data]} {
        set data [MakerNoteFuji $offset $byteOrder $data]
    } elseif {[string match OLYMP* $data]} {
        set data [MakerNoteOlympus $offset $byteOrder $data]
    }
    return $data
}

proc ::jpeg::MakerNoteNikon {offset byteOrder data} {
    variable exif_formats
    set return {}
    if {[string match Nikon* $data]} {
        set i 8
    } else {
        set i 0
    }
    binary scan $data @8s num
    incr i 2
    puts [expr {($num * 12) + $i}]
    puts [string range $data 142 150]
    #exit
    for {} {$num > 0} {incr num -1} {
        binary scan $data @${i}H2H2 t1 t2
        if {$byteOrder == "big"} {
            set tag $t1$t2
        } else {
            set tag $t2$t1
        }
        incr i 2
        _scan $byteOrder $data @${i}si format components
        incr i 6
        set value [string range $data $i [expr {$i + 3}]]
        if {![info exists exif_formats($format)]} continue
        #if {[info exists exif_tags($tag)]} { set tag $exif_tags($tag) }
        set size [expr {$exif_formats($format) * $components}]
        if {$size > 4} {
            _scan $byteOrder $value i value
            puts "$value"
            set value 1
            #set value [string range $data [expr {$i + $offset + $value}] [expr {$size - 1}]]
        } else {
        
        lappend ret $tag [_format $byteOrder $value $format $components]
        }
        puts "$tag $format $components $value"
    }
    return $return
}

proc ::jpeg::debug {file} {
    set fh [openJFIF $file]

    puts "marker: d8 length: 0"
    puts "  SOI (Start Of Image)"

    foreach marker [markers $fh] {
        seek $fh [lindex $marker 1] 
        puts "marker: [lindex $marker 0] length: [lindex $marker 2]"
        switch -glob -- [lindex $marker 0] {
            c[0-3] {
                binary scan [read $fh 6] cSSc precision height width color
                puts "  SOF (Start Of Frame) [string map {c0 "Baseline" c1 "Non-baseline" c2 "Progressive" c3 "Lossless"} [lindex $marker 0]]"
                puts "    Image dimensions: $width $height"
                puts "    Precision: $precision"
                puts "    Color Components: $color"
            }
            c4 {
                puts "  DHT (Define Huffman Table)"
                binary scan [read $fh 17] cS bits symbols
                puts "    $symbols symbols"
            }
            da {
                puts "  SOS (Start Of Scan)"
                binary scan [read $fh 2] c num
                puts "    Components: $num"
            }
            db {
                puts "  DQT (Define Quantization Table)"
            }
            dd {
                puts "  DRI (Define Restart Interval)"
                binary scan [read $fh 2] S num
                puts "    Interval: $num blocks"
            }
            e0 {
                set id [read $fh 5]
                if {$id == "JFIF\x00"} {
                    puts "  JFIF"
                    binary scan [read $fh 9] cccSScc ver1 ver2 units xr vr xt yt
                    puts "    Header: $ver1.$ver2 $units $xr $vr $xt $yt"
                } elseif {$id == "JFXX\x00"} {
                    puts "  JFXX (JFIF Extension)"
                    binary scan [read $fh 1] H2 excode
                    if {$excode == "10"} { set excode "10 (JPEG thumbnail)" }
                    if {$excode == "11"} { set excode "11 (Palletized thumbnail)" }
                    if {$excode == "13"} { set excode "13 (RGB thumbnail)" }
                    puts "    Extension code: 0x$excode"
                } else {
                    puts "  Unknown APP0 segment: $id"
                }
            }
            e1 {
                if {[read $fh 6] == "Exif\x00\x00"} {
                    puts "  EXIF data"
                    puts "    MAIN EXIF"
                    foreach {x y} [getExif $file] {
                        puts "    $x $y"
                    }
                    puts "    THUMBNAIL EXIF"
                    foreach {x y} [getExif $file thumbnail] {
                        puts "    $x $y"
                    }
                } else {
                    puts "  APP1 (unknown)"
                }
            }
            e2 {
                if {[read $fh 12] == "ICC_PROFILE\x00"} {
                    puts "  ICC profile"
                } else {
                    puts "  APP2 (unknown)"
                }
            }
            ed {
                if {[read $fh 18] == "Photoshop 3.0\0008BIM"} {
                    puts "  Photoshop 8BIM data"
                } else {
                    puts "  APP13 (unknown)"
                }
            }
            ee {
                if {[read $fh 5] == "Adobe"} {
                    puts "  Adobe metadata"
                } else {
                    puts "  APP14 (unknown)"
                }
            }
            e[3456789abcf] {
                puts [format "  %s%d %s" APP 0x[string index [lindex $marker 0] 1] (unknown)]
            }
            fe {
                puts "  Comment: [read $fh [lindex $marker 2]]"
            }
            default {
                puts "  Unknown"
            }
        }
    }
}

# for mapping the exif format types to byte lengths
array set ::jpeg::exif_formats [list 1 1 2 1 3 2 4 4 5 8 6 1 7 1 8 2 9 4 10 8 11 4 12 8]

# list of recognized exif tags. if a tag is not listed here it will show up as its raw hex value
array set ::jpeg::exif_tags {
    0100 ImageWidth
    0101 ImageLength
    0102 BitsPerSample
    0103 Compression
    0106 PhotometricInterpretation
    0112 Orientation
    0115 SamplesPerPixel
    011c PlanarConfiguration
    0212 YCbCrSubSampling
    0213 YCbCrPositioning
    011a XResolution
    011b YResolution
    0128 ResolutionUnit

    0111 StripOffsets
    0116 RowsPerStrip
    0117 StripByteCounts
    0201 JPEGInterchangeFormat
    0202 JPEGInterchangeFormatLength

    012d TransferFunction
    013e WhitePoint
    013f PrimaryChromaticities
    0211 YCbCrCoefficients
    0213 YCbCrPositioning
    0214 ReferenceBlackWhite

    0132 DateTime
    010e ImageDescription
    010f Make
    0110 Model
    0131 Software  
    013b Artist
    8298 Copyright
    
    9000 ExifVersion  
    a000 FlashpixVersion

    a001 ColorSpace

    9101 ComponentsConfiguration
    9102 CompressedBitsPerPixel
    a002 ExifImageWidth
    a003 ExifImageHeight

    927c MakerNote
    9286 UserComment

    a004 RelatedSoundFile

    9003 DateTimeOriginal
    9004 DateTimeDigitized
    9290 SubsecTime
    9291 SubsecTimeOriginal
    9292 SubsecTimeDigitized

    829a ExposureTime
    829d FNumber
    8822 ExposureProgram
    8824 SpectralSensitivity
    8827 ISOSpeedRatings
    8828 OECF
    9201 ShutterSpeedValue
    9202 ApertureValue
    9203 BrightnessValue
    9204 ExposureBiasValue
    9205 MaxApertureValue
    9206 SubjectDistance
    9207 MeteringMode
    9208 LightSource
    9209 Flash
    920a FocalLength
    9214 SubjectArea
    a20b FlashEnergy
    a20c SpatialFrequencyResponse
    a20e FocalPlaneXResolution
    a20f FocalPlaneYResolution
    a210 FocalPlaneResolutionUnit
    a214 SubjectLocation
    a215 ExposureIndex
    a217 SensingMethod
    a300 FileSource
    a301 SceneType
    a302 CFAPattern
    a401 CustomRendered
    a402 ExposureMode
    a403 WhiteBalance
    a404 DigitalZoomRatio
    a405 FocalLengthIn35mmFilm
    a406 SceneCaptureType
    a407 GainControl
    a408 Contrast
    a409 Saturation
    a40a Sharpness
    a40b DeviceSettingDescription
    a40c SubjectDistanceRange
    a420 ImageUniqueID

    
    0001 InteroperabilityIndex
    0002 InteroperabilityVersion
    1000 RelatedImageFileFormat
    1001 RelatedImageWidth
    1002 RelatedImageLength
    
    00fe NewSubfileType
    00ff SubfileType
    013d Predictor
    0142 TileWidth
    0143 TileLength
    0144 TileOffsets
    0145 TileByteCounts
    014a SubIFDs
    015b JPEGTables
    828d CFARepeatPatternDim
    828e CFAPattern
    828f BatteryLevel
    83bb IPTC/NAA
    8773 InterColorProfile
    8825 GPSInfo
    8829 Interlace
    882a TimeZoneOffset
    882b SelfTimerMode
    920c SpatialFrequencyResponse
    920d Noise
    9211 ImageNumber
    9212 SecurityClassification
    9213 ImageHistory
    9215 ExposureIndex
    9216 TIFF/EPStandardID
}

# list of recognized exif tags for the GPSInfo section--added by mdp 6/5/2009
array set ::jpeg::gps_tags {
    0000 GPSVersionID
    0001 GPSLatitudeRef
    0002 GPSLatitude
    0003 GPSLongitudeRef
    0004 GPSLongitude
    0005 GPSAltitudeRef
    0006 GPSAltitude
    0007 GPSTimeStamp
    0008 GPSSatellites
    0009 GPSStatus
    000a GPSMeasureMode
    000b GPSDOP
    000c GPSSpeedRef
    000d GPSSpeed
    000e GPSTrackRef
    000f GPSTrack
    0010 GPSImgDirectionRef
    0011 GPSImgDirection
    0012 GPSMapDatum
    0013 GPSDestLatitudeRef
    0014 GPSDestLatitude
    0015 GPSDestLongitudeRef
    0016 GPSDestLongitude
    0017 GPSDestBearingRef
    0018 GPSDestBearing
    0019 GPSDestDistanceRef
    001a GPSDestDistance
    001b GPSProcessingMethod
    001c GPSAreaInformation
    001d GPSDateStamp
    001e GPSDifferential
}

# for mapping exif values to plain english by [formatExif]
array set ::jpeg::exif_values {
    Compression,1 none
    Compression,6 JPEG
    Compression,  unknown

    PhotometricInterpretation,2 RGB
    PhotometricInterpretation,6 YCbCr
    PhotometricInterpretation,  unknown

    Orientation,1 normal
    Orientation,2 mirrored
    Orientation,3 "180 degrees"
    Orientation,4 "180 degrees, mirrored"
    Orientation,5 "90 degrees ccw, mirrored"
    Orientation,6 "90 degrees cw"
    Orientation,7 "90 degrees cw, mirrored"
    Orientation,8 "90 degrees ccw"
    Orientation,  unknown

    PlanarConfiguration,1 chunky
    PlanarConfiguration,2 planar
    PlanarConfiguration,  unknown

    YCbCrSubSampling,2,1 YCbCr4:2:2
    YCbCrSubSampling,2,2 YCbCr4:2:0
    YCbCrSubSampling,    unknown

    YCbCrPositioning,1 centered
    YCbCrPositioning,2 co-sited
    YCbCrPositioning,  unknown

    FlashpixVersion,0100 "Flashpix Format Version 1.0"
    FlashpixVersion,     unknown

    ColorSpace,1     sRGB
    ColorSpace,32768 uncalibrated
    ColorSpace,      unknown

    ExposureProgram,0 undefined
    ExposureProgram,1 manual
    ExposureProgram,2 normal
    ExposureProgram,3 "aperture priority"
    ExposureProgram,4 "shutter priority"
    ExposureProgram,5 creative
    ExposureProgram,6 action
    ExposureProgram,7 portrait
    ExposureProgram,8 landscape
    ExposureProgram,  unknown

    LightSource,0   unknown
    LightSource,1   daylight
    LightSource,2   flourescent
    LightSource,3   tungsten
    LightSource,4   flash
    LightSource,9   "fine weather"
    LightSource,10  "cloudy weather"
    LightSource,11  shade
    LightSource,12  "daylight flourescent"
    LightSource,13  "day white flourescent"
    LightSource,14  "cool white flourescent"
    LightSource,15  "white flourescent"
    LightSource,17  "standard light A"
    LightSource,18  "standard light B"
    LightSource,19  "standard light C"
    LightSource,20  D55
    LightSource,21  D65
    LightSource,22  D75
    LightSource,23  D50
    LightSource,24  "ISO studio tungsten"
    LightSource,255 other
    LightSource,    unknown

    Flash,0  "no flash"
    Flash,1  "flash fired"
    Flash,5  "strobe return light not detected"
    Flash,7  "strobe return light detected"
    Flash,9  "flash fired, compulsory flash mode"
    Flash,13 "flash fired, compulsory flash mode, return light not detected"
    Flash,15 "flash fired, compulsory flash mode, return light detected"
    Flash,16 "flash did not fire, compulsory flash mode"
    Flash,24 "flash did not fire, auto mode"
    Flash,25 "flash fired, auto mode"
    Flash,29 "flash fired, auto mode, return light not detected"
    Flash,31 "flash fired, auto mode, return light detected"
    Flash,32 "no flash function"
    Flash,65 "flash fired, red-eye reduction mode"
    Flash,69 "flash fired, red-eye reduction mode, return light not detected"
    Flash,71 "flash fired, red-eye reduction mode, return light detected"
    Flash,73 "flash fired, compulsory mode, red-eye reduction mode"
    Flash,77 "flash fired, compulsory mode, red-eye reduction mode, return light not detected"
    Flash,79 "flash fired, compulsory mode, red-eye reduction mode, return light detected"
    Flash,89 "flash fired, auto mode, red-eye reduction mode"
    Flash,93 "flash fired, auto mode, return light not detected, red-eye reduction mode"
    Flash,95 "flash fired, auto mode, return light detected, red-eye reduction mode"
    Flash,   unknown

    ResolutionUnit,2 inch
    ResolutionUnit,3 centimeter
    ResolutionUnit,  unknown

    SensingMethod,1 undefined
    SensingMethod,2 "one chip color area sensor"
    SensingMethod,3 "two chip color area sensor"
    SensingMethod,4 "three chip color area sensor"
    SensingMethod,5 "color sequential area sensor"
    SensingMethod,7 "trilinear sensor"
    SensingMethod,8 "color sequential linear sensor"
    SensingMethod,  unknown

    SceneType,\x01\x00\x00\x00 "directly photographed image"
    SceneType,                 unknown

    CustomRendered,0 normal
    CustomRendered,1 custom

    ExposureMode,0 auto
    ExposureMode,1 manual
    ExposureMode,2 "auto bracket"
    ExposureMode,  unknown

    WhiteBalance,0 auto
    WhiteBlanace,1 manual
    WhiteBlanace,  unknown

    SceneCaptureType,0 standard
    SceneCaptureType,1 landscape
    SceneCaptureType,2 portrait
    SceneCaptureType,3 night
    SceneCaptureType,  unknown

    GainControl,0 none
    GainControl,1 "low gain up"
    GainControl,2 "high gain up"
    GainControl,3 "low gain down"
    GainControl,4 "high gain down"
    GainControl,  unknown

    Contrast,0 normal
    Contrast,1 soft
    Contrast,2 hard
    Contrast,  unknown

    Saturation,0 normal
    Saturation,1 low
    Saturation,2 high
    Saturation,  unknown

    Sharpness,0 normal
    Sharpness,1 soft
    Sharpness,2 hard
    Sharpness,  unknown

    SubjectDistanceRange,0 unknown
    SubjectDistanceRange,1 macro
    SubjectDistanceRange,2 close
    SubjectDistanceRange,3 distant
    SubjectDistanceRange,  unknown
    
    MeteringMode,0   unknown
    MeteringMode,1   average
    MeteringMode,2   "center weighted average"
    MeteringMode,3   spot
    MeteringMode,4   multi-spot
    MeteringMode,5   multi-segment
    MeteringMode,6   partial
    MeteringMode,255 other
    MeteringMode,    unknown
    
    FocalPlaneResolutionUnit,2 inch
    FocalPlaneResolutionUnit,3 centimeter
    FocalPlaneResolutionUnit,  none
    
    DigitalZoomRatio,0 "not used"
    
    FileSource,\x03\x00\x00\x00 "digital still camera"
    FileSource,                 unknown
}

# [binary scan], in the byte order indicated by $e
proc ::jpeg::_scan {e v f args} {
     foreach x $args { upvar 1 $x $x }
     if {$e == "big"} {
         eval [list binary scan $v [string map {b B h H s S i I} $f]] $args
     } else {
         eval [list binary scan $v $f] $args
     }
}


# formats exif values, the numbers correspond to data types
# values may be either byte order, as indicated by $end
# see the exif spec for more info
proc ::jpeg::_format {end value type num} {
    if {$num > 1 && $type != 2 && $type != 7} {
        variable exif_formats
        set r {}
        for {set i 0} {$i < $num} {incr i} {
            set len $exif_formats($type)
            lappend r [_format $end [string range $value [expr {$len * $i}] [expr {($len * $i) + $len - 1}]] $type 1]
        }
        return [join $r ,]
    }
    switch -exact -- $type {
        1 { _scan $end $value c value }
        2 { set value [string trimright $value \x00] }
        3 {
            _scan $end $value s value
            set value [format %u $value]
        }
        4 {
            _scan $end $value i value
            set value [format %u $value]
        }
        5 {
            _scan $end $value ii n d
            set n [format %u $n]
            set d [format %u $d]
            if {$d == 0} {set d 1}
            #set value [string trimright [string trimright [format %5.4f [expr {double($n) / $d}]] 0] .]
            set value [string trimright [string trimright [expr {double($n) / $d}] 0] .]
            #set value "$n/$d"
        }
        6 { _scan $end $value c value }
        8 { _scan $end $value s value }
        9 { _scan $end $value i value }
        10 {
            _scan $end $value ii n d
            if {$d == 0} {set d 1}
            #set value [string trimright [string trimright [format %5.4f [expr {double($n) / $d}]] 0] .]
            set value [string trimright [string trimright [expr {double($n) / $d}] 0] .]
            #set value "$n/$d"
        }
        11 { _scan $end $value i value }
        12 { _scan $end $value w value }
    }
    return $value
}

# Do a compatibility version of [lassign] for versions of Tcl without
# that command. Not using a version check as special builds may have
# the command even if they are a version which nominally would not.

if {![llength [info commands lassign]]} {
    proc ::jpeg::lassign {sequence v args} {
	set args [linsert $args 0 $v]
	set a [::llength $args]

	# Nothing to assign.
	#if {$a == 0} {return $sequence}

	# Perform assignments
	set i 0
	foreach v $args {
	    upvar 1 $v var
	    set        var [::lindex $sequence $i]
	    incr i
	}

	# Return remainder, if there is any.
	return [::lrange $sequence $a end]
    }
}

# ### ### ### ######### ######### #########
## Ready

package provide jpeg 0.5