| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | ########################################################### | 
| 2 |  |  |  |  |  |  | # A Perl package for showing/modifying JPEG (meta)data.   # | 
| 3 |  |  |  |  |  |  | # Copyright (C) 2004,2005,2006 Stefano Bettelli           # | 
| 4 |  |  |  |  |  |  | # See the COPYING and LICENSE files for license terms.    # | 
| 5 |  |  |  |  |  |  | ########################################################### | 
| 6 | 15 |  |  | 15 |  | 92 | use Image::MetaData::JPEG::data::Tables qw(:TagsAPP0); | 
|  | 15 |  |  |  |  | 34 |  | 
|  | 15 |  |  |  |  | 3204 |  | 
| 7 | 15 |  |  | 15 |  | 146 | no  integer; | 
|  | 15 |  |  |  |  | 31 |  | 
|  | 15 |  |  |  |  | 95 |  | 
| 8 | 15 |  |  | 15 |  | 339 | use strict; | 
|  | 15 |  |  |  |  | 32 |  | 
|  | 15 |  |  |  |  | 437 |  | 
| 9 | 15 |  |  | 15 |  | 81 | use warnings; | 
|  | 15 |  |  |  |  | 31 |  | 
|  | 15 |  |  |  |  | 9243 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | ########################################################### | 
| 12 |  |  |  |  |  |  | # This method parses an APP0 segment. APP0 segments are   # | 
| 13 |  |  |  |  |  |  | # written by older cameras adopting the JFIF (JPEG File   # | 
| 14 |  |  |  |  |  |  | # Interchange Format) for storing images. JFIF uses the   # | 
| 15 |  |  |  |  |  |  | # APP0 application segment for inserting configuration    # | 
| 16 |  |  |  |  |  |  | # data and a thumbnail image. The format is as follows:   # | 
| 17 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 18 |  |  |  |  |  |  | #  5 bytes  identifier ('JFIF\000' = 0x4a46494600)        # | 
| 19 |  |  |  |  |  |  | #  1 byte   major version (e.g. 0x01)                     # | 
| 20 |  |  |  |  |  |  | #  1 byte   minor version (e.g. 0x01 or 0x02)             # | 
| 21 |  |  |  |  |  |  | #  1 byte   units (0: densities give aspect ratio         # | 
| 22 |  |  |  |  |  |  | #                  1: density values are dots per inch    # | 
| 23 |  |  |  |  |  |  | #                  2: density values are dots per cm)     # | 
| 24 |  |  |  |  |  |  | #  2 bytes  Xdensity (Horizontal pixel density)           # | 
| 25 |  |  |  |  |  |  | #  2 bytes  Ydensity (Vertical pixel density)             # | 
| 26 |  |  |  |  |  |  | #  1 byte   Xthumbnail (Thumbnail horizontal pixel count) # | 
| 27 |  |  |  |  |  |  | #  1 byte   Ythumbnail (Thumbnail vertical pixel count)   # | 
| 28 |  |  |  |  |  |  | # 3n bytes  (RGB)n, packed (24-bit) RGB values for the    # | 
| 29 |  |  |  |  |  |  | #           thumbnail pixels, n = Xthumbnail * Ythumbnail # | 
| 30 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 31 |  |  |  |  |  |  | # There is also an "extended" version of JFIF (only pos-  # | 
| 32 |  |  |  |  |  |  | # sible for JFIF versions 1.02 and above). In this case   # | 
| 33 |  |  |  |  |  |  | # the identifier is not 'JFIF' but 'JFXX'. The syntax in  # | 
| 34 |  |  |  |  |  |  | # this case is modified as follows:                       # | 
| 35 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 36 |  |  |  |  |  |  | #  5 bytes  identifier ('JFXX\000' = 0x4a46585800)        # | 
| 37 |  |  |  |  |  |  | #  1 byte   extension  (0x10 Thumbnail coded using JPEG   # | 
| 38 |  |  |  |  |  |  | #                       0x11 Thumbnail using 1 byte/pixel # | 
| 39 |  |  |  |  |  |  | #                       0x13 Thumbnail using 3 bytes/pixel# | 
| 40 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 41 |  |  |  |  |  |  | # The remainder of the segment varies with the extension. # | 
| 42 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 43 |  |  |  |  |  |  | # Thumbnail coded using JPEG: the compressed thumbnail    # | 
| 44 |  |  |  |  |  |  | # immediately follows the extension code in the extension # | 
| 45 |  |  |  |  |  |  | # data field and the length must be included in the JFIF  # | 
| 46 |  |  |  |  |  |  | # extension APP0 marker length field. The extension data  # | 
| 47 |  |  |  |  |  |  | # field conforms to the syntax for a JPEG file (SOI ....  # | 
| 48 |  |  |  |  |  |  | # SOF ... EOI); however, no 'JFIF' or 'JFXX' marker seg-  # | 
| 49 |  |  |  |  |  |  | # ments shall be present.                                 # | 
| 50 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 51 |  |  |  |  |  |  | # Thumbnail stored using one byte per pixel: this must    # | 
| 52 |  |  |  |  |  |  | # include a thumbnail and a colour palette as follows:    # | 
| 53 |  |  |  |  |  |  | #  1 byte   Xthumbnail (Thumbnail horizontal pixel count) # | 
| 54 |  |  |  |  |  |  | #  1 byte   Ythumbnail (Thumbnail vertical pixel count)   # | 
| 55 |  |  |  |  |  |  | # 768 bytes palette (24-bit RGB pixel values for the      # | 
| 56 |  |  |  |  |  |  | #                    colour palette. These values define  # | 
| 57 |  |  |  |  |  |  | #                    the colors represented by each value # | 
| 58 |  |  |  |  |  |  | #                    of an 8-bit binary encoding (0-255)) # | 
| 59 |  |  |  |  |  |  | # n bytes   pixels  (8-bit values for the thumbnail       # | 
| 60 |  |  |  |  |  |  | #                    pixels: n = Xthumbnail * Ythumbnail) # | 
| 61 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 62 |  |  |  |  |  |  | # Thumbnail stored using three bytes per pixel: in this   # | 
| 63 |  |  |  |  |  |  | # case there is no colour palette:                        # | 
| 64 |  |  |  |  |  |  | #  1 byte   Xthumbnail (Thumbnail horizontal pixel count) # | 
| 65 |  |  |  |  |  |  | #  1 byte   Ythumbnail (Thumbnail vertical pixel count)   # | 
| 66 |  |  |  |  |  |  | # 3n bytes  pixels (24-bit RGB values for the thumbnail   # | 
| 67 |  |  |  |  |  |  | #                   pixels, n = Xthumbnail * Ythumbnail)  # | 
| 68 |  |  |  |  |  |  | #---------------------------------------------------------# | 
| 69 |  |  |  |  |  |  | # Ref: http://www.dcs.ed.ac.uk/home/mxr/gfx/2d/JPEG.txt   # | 
| 70 |  |  |  |  |  |  | ########################################################### | 
| 71 |  |  |  |  |  |  | sub parse_app0 { | 
| 72 | 36 |  |  | 36 | 0 | 80 | my ($this) = @_; | 
| 73 | 36 |  |  |  |  | 60 | my $offset = 0; | 
| 74 | 36 |  |  |  |  | 72 | my $thumb_x_dim = 0; my $thumb_y_dim = 0; | 
|  | 36 |  |  |  |  | 57 |  | 
| 75 |  |  |  |  |  |  | # first, decode the identifier. It can be simple | 
| 76 |  |  |  |  |  |  | # (JFIF), or extended (JFXX). We need five bytes | 
| 77 | 36 |  |  |  |  | 218 | my $identifier = $this->store_record | 
| 78 |  |  |  |  |  |  | ('Identifier', $ASCII, $offset, length $APP0_JFIF_TAG)->get_value(); | 
| 79 |  |  |  |  |  |  | # go to the relevant decoding routine depending on it | 
| 80 | 36 | 100 |  |  |  | 182 | goto APP0_simple   if $identifier eq $APP0_JFIF_TAG; | 
| 81 | 3 | 100 |  |  |  | 20 | goto APP0_extended if $identifier eq $APP0_JFXX_TAG; | 
| 82 |  |  |  |  |  |  | # if we are still here, let us die of an unknown identifier | 
| 83 | 1 |  |  |  |  | 8 | $this->die("Unknown identifier ($identifier)"); | 
| 84 | 33 |  |  |  |  | 291 | APP0_simple: | 
| 85 |  |  |  |  |  |  | # as far as I know, in a JFIF APP0 there are always the following | 
| 86 |  |  |  |  |  |  | # seven fields, even if the thumbnail is absent. This means that | 
| 87 |  |  |  |  |  |  | # at least 14 bytes (including the initial identifier) must be there. | 
| 88 |  |  |  |  |  |  | # Do a test size and then read the fields. | 
| 89 |  |  |  |  |  |  | $this->test_size($offset + 9); | 
| 90 | 33 |  |  |  |  | 152 | $this->store_record('MajorVersion', $BYTE , $offset); | 
| 91 | 33 |  |  |  |  | 122 | $this->store_record('MinorVersion', $BYTE , $offset); | 
| 92 | 33 |  |  |  |  | 136 | $this->store_record('Units'       , $BYTE , $offset); | 
| 93 | 33 |  |  |  |  | 134 | $this->store_record('XDensity'    , $SHORT, $offset); | 
| 94 | 33 |  |  |  |  | 149 | $this->store_record('YDensity'    , $SHORT, $offset); | 
| 95 | 33 |  |  |  |  | 126 | $thumb_x_dim =$this->store_record('XThumbnail',$BYTE,$offset)->get_value(); | 
| 96 | 33 |  |  |  |  | 377 | $thumb_y_dim =$this->store_record('YThumbnail',$BYTE,$offset)->get_value(); | 
| 97 |  |  |  |  |  |  | # now calculate the size of the thumbnail data area. This | 
| 98 |  |  |  |  |  |  | # is three times the product of the two previous dimensions. | 
| 99 | 33 |  |  |  |  | 92 | my $thumb_size = 3 * $thumb_x_dim * $thumb_y_dim; | 
| 100 |  |  |  |  |  |  | # issue an error if the thumbnail data area is not there | 
| 101 | 33 |  |  |  |  | 397 | $this->test_size($offset + $thumb_size, "corrupted thumbnail"); | 
| 102 |  |  |  |  |  |  | # if size is positive, get the packed thumbnail as unknown | 
| 103 | 33 | 50 |  |  |  | 110 | $this->store_record('ThumbnailData', $UNDEF, $offset, $thumb_size) | 
| 104 |  |  |  |  |  |  | if $thumb_size > 0; | 
| 105 | 33 |  |  |  |  | 210 | goto APP0_END; | 
| 106 | 2 |  |  |  |  | 10 | APP0_extended: | 
| 107 |  |  |  |  |  |  | # so this is an extended JFIF (JFXX). Get the extension code | 
| 108 |  |  |  |  |  |  | my $ext_code = $this->store_record | 
| 109 |  |  |  |  |  |  | ('ExtensionCode', $BYTE, $offset)->get_value(); | 
| 110 |  |  |  |  |  |  | # now, depending on it, go to another parsing segment | 
| 111 | 2 | 100 |  |  |  | 13 | goto APP0_ext_jpeg   if  $ext_code == $APP0_JFXX_JPG; | 
| 112 | 1 | 50 | 33 |  |  | 58 | goto APP0_ext_bytes  if ($ext_code == $APP0_JFXX_1B || | 
| 113 |  |  |  |  |  |  | $ext_code == $APP0_JFXX_3B); | 
| 114 |  |  |  |  |  |  | # if we are still here, die of unknown extension code | 
| 115 | 0 |  |  |  |  | 0 | $this->die("Unknown extension code ($ext_code)"); | 
| 116 | 1 |  |  |  |  | 6 | APP0_ext_jpeg: | 
| 117 |  |  |  |  |  |  | # in this case, the rest of the data area is a jpeg image | 
| 118 |  |  |  |  |  |  | # which we save as undefined data in a single field. We don't | 
| 119 |  |  |  |  |  |  | # dare to check the syntax of these data and go to the end. | 
| 120 |  |  |  |  |  |  | $this->store_record('JPEGThumbnail',$UNDEF,$offset,$this->size()-$offset); | 
| 121 | 1 |  |  |  |  | 5 | goto APP0_END; | 
| 122 | 1 |  |  |  |  | 7 | APP0_ext_bytes: | 
| 123 |  |  |  |  |  |  | # for the other two extensions, we first make sure that there | 
| 124 |  |  |  |  |  |  | # are two other bytes, then we read the thumbnail size | 
| 125 |  |  |  |  |  |  | $this->test_size($offset + 2, "no thumbnail dimensions"); | 
| 126 | 1 |  |  |  |  | 4 | $thumb_x_dim =$this->store_record('XThumbnail',$BYTE,$offset)->get_value(); | 
| 127 | 1 |  |  |  |  | 7 | $thumb_y_dim =$this->store_record('YThumbnail',$BYTE,$offset)->get_value(); | 
| 128 |  |  |  |  |  |  | # now calculate the number of pixels in the thumbnail data area. | 
| 129 |  |  |  |  |  |  | # This is the product of the two previous dimensions. | 
| 130 | 1 |  |  |  |  | 3 | my $thumb_pixels = $thumb_x_dim * $thumb_y_dim; | 
| 131 |  |  |  |  |  |  | # now, the two extensions take different routes ... | 
| 132 | 1 | 50 |  |  |  | 7 | goto APP0_ext_1byte  if $ext_code eq $APP0_JFXX_1B; | 
| 133 | 0 | 0 |  |  |  | 0 | goto APP0_ext_3bytes if $ext_code eq $APP0_JFXX_3B; | 
| 134 | 1 |  |  |  |  | 8 | APP0_ext_1byte: | 
| 135 |  |  |  |  |  |  | # in this case, there must be 768 bytes for the palette, followed | 
| 136 |  |  |  |  |  |  | # by $thumb_pixels for the thumbnail. Issue an error otherwise | 
| 137 |  |  |  |  |  |  | $this->test_size($offset + $APP0_JFXX_PAL + $thumb_pixels, | 
| 138 |  |  |  |  |  |  | "Incorrect thumbnail data size in JFXX 0x10"); | 
| 139 |  |  |  |  |  |  | # store the colour palette and the thumbnail as | 
| 140 |  |  |  |  |  |  | # undefined data and we have finished. | 
| 141 | 1 |  |  |  |  | 5 | $this->store_record('ColorPalette'  , $UNDEF, $offset, $APP0_JFXX_PAL); | 
| 142 | 1 |  |  |  |  | 4 | $this->store_record('1ByteThumbnail', $UNDEF, $offset, $thumb_pixels); | 
| 143 | 1 |  |  |  |  | 4 | goto APP0_END; | 
| 144 | 0 |  |  |  |  | 0 | APP0_ext_3bytes: | 
| 145 |  |  |  |  |  |  | # in this case, there must be 3 * $thumb_pixels | 
| 146 |  |  |  |  |  |  | # for the thumbnail data. Issue an error otherwise | 
| 147 |  |  |  |  |  |  | $this->test_size($offset + 3 * $thumb_pixels, | 
| 148 |  |  |  |  |  |  | "Incorrect thumbnail data size in JFXX 0x13"); | 
| 149 |  |  |  |  |  |  | # store the thumbnail as undefined data and we have finished. | 
| 150 | 0 |  |  |  |  | 0 | $this->store_record('3BytesThumbnail', $UNDEF, $offset, 3 * $thumb_pixels); | 
| 151 | 0 |  |  |  |  | 0 | goto APP0_END; | 
| 152 | 35 |  |  |  |  | 138 | APP0_END: | 
| 153 |  |  |  |  |  |  | # check that there are no spurious data in the segment | 
| 154 |  |  |  |  |  |  | $this->test_size(-$offset, "unknown data at segment end"); | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | # successful load | 
| 158 |  |  |  |  |  |  | 1; |