d1f3142013-07-08Henrik Grubbström (Grubba) #charset utf-8
3709192002-03-20Martin Nilsson #pike __REAL_VERSION__
a59c9e2002-01-17Martin Nilsson // Imagedimensionreadermodule for Pike.
cbf7782006-09-13Tor Edvardsson // Created by Johan Sch�n, <js@roxen.com>.
a59c9e2002-01-17Martin Nilsson // // This software is based in part on the work of the Independent JPEG Group. //! @appears Image.Dims
039e252002-09-29Manual system //! Reads the dimensions of images of various image formats without //! decoding the actual image.
a59c9e2002-01-17Martin Nilsson  #define M_SOF0 0xC0 /* Start Of Frame N */ #define M_SOF1 0xC1 /* N indicates which compression process */ #define M_SOF2 0xC2 /* Only SOF0-SOF2 are now in common use */ #define M_SOF3 0xC3 #define M_SOF5 0xC5 /* NB: codes C4 and CC are NOT SOF markers */ #define M_SOF6 0xC6 #define M_SOF7 0xC7 #define M_SOF9 0xC9 #define M_SOF10 0xCA #define M_SOF11 0xCB #define M_SOF13 0xCD #define M_SOF14 0xCE #define M_SOF15 0xCF #define M_SOI 0xD8 /* Start Of Image (beginning of datastream) */ #define M_EOI 0xD9 /* End Of Image (end of datastream) */ #define M_SOS 0xDA /* Start Of Scan (begins compressed data) */ #define M_COM 0xFE /* COMment */
3bdcd02015-08-26Martin Nilsson #define M_APP1 0xE1
a59c9e2002-01-17Martin Nilsson 
9eaf1d2008-06-28Martin Nilsson protected int(0..255) read_1_byte(Stdio.File f)
a59c9e2002-01-17Martin Nilsson { return f->read(1)[0]; }
9eaf1d2008-06-28Martin Nilsson protected int(0..65535) read_2_bytes(Stdio.File f)
a59c9e2002-01-17Martin Nilsson { int c; sscanf( f->read(2), "%2c", c ); return c; } /* * Read the initial marker, which should be SOI. * For a JFIF file, the first two bytes of the file should be literally * 0xFF M_SOI. To be more general, we could use next_marker, but if the * input file weren't actually JPEG at all, next_marker might read the whole * file and then return a misleading error message... */
9eaf1d2008-06-28Martin Nilsson protected int first_marker(Stdio.File f)
a59c9e2002-01-17Martin Nilsson { int c1, c2;
3524712015-05-26Martin Nilsson 
58eee32004-05-23Martin Nilsson  sscanf(f->read(2), "%c%c", c1, c2);
e567fd2004-05-23Martin Nilsson  if (c1==0xFF||c2==M_SOI) return 1; return 0;
a59c9e2002-01-17Martin Nilsson } /* * Find the next JPEG marker and return its marker code. * We expect at least one FF byte, possibly more if the compressor used FFs * to pad the file. * There could also be non-FF garbage between markers. The treatment of such * garbage is unspecified; we choose to skip over it but emit a warning msg. * NB: this routine must not be used after seeing SOS marker, since it will * not deal correctly with FF/00 sequences in the compressed image data... */
9eaf1d2008-06-28Martin Nilsson protected int next_marker(Stdio.File f)
a59c9e2002-01-17Martin Nilsson {
32cd2b2005-10-05Martin Nilsson  // Find 0xFF byte; count and skip any non-FFs. int c = read_1_byte(f); while (c != 0xFF)
a59c9e2002-01-17Martin Nilsson  c = read_1_byte(f);
32cd2b2005-10-05Martin Nilsson  // Get marker code byte, swallowing any duplicate FF bytes. Extra FFs // are legal as pad bytes.
a59c9e2002-01-17Martin Nilsson  do { c = read_1_byte(f); } while (c == 0xFF); return c; } /* Skip over an unknown or uninteresting variable-length marker */
9eaf1d2008-06-28Martin Nilsson protected int skip_variable(Stdio.File f)
a59c9e2002-01-17Martin Nilsson { int length = read_2_bytes(f);
58eee32004-05-23Martin Nilsson  if (length < 2) return 0; // Length includes itself, so must be at least 2.
a59c9e2002-01-17Martin Nilsson  length -= 2;
d262792015-08-31Martin Nilsson  f->seek(length, Stdio.SEEK_CUR);
a59c9e2002-01-17Martin Nilsson  return 1; }
039e252002-09-29Manual system //! Reads the dimensions from a JPEG file and returns an array with //! width and height, or if the file isn't a valid image, 0.
a59c9e2002-01-17Martin Nilsson array(int) get_JPEG(Stdio.File f) {
58eee32004-05-23Martin Nilsson  if (!first_marker(f))
a59c9e2002-01-17Martin Nilsson  return 0;
3524712015-05-26Martin Nilsson 
3bdcd02015-08-26Martin Nilsson  mapping exif;
a59c9e2002-01-17Martin Nilsson  /* Scan miscellaneous markers until we reach SOS. */ for (;;) {
32cd2b2005-10-05Martin Nilsson  switch (next_marker(f)) {
a59c9e2002-01-17Martin Nilsson  case M_SOF0: /* Baseline */ case M_SOF1: /* Extended sequential, Huffman */ case M_SOF2: /* Progressive, Huffman */ case M_SOF3: /* Lossless, Huffman */ case M_SOF5: /* Differential sequential, Huffman */ case M_SOF6: /* Differential progressive, Huffman */ case M_SOF7: /* Differential lossless, Huffman */ case M_SOF9: /* Extended sequential, arithmetic */ case M_SOF10: /* Progressive, arithmetic */ case M_SOF11: /* Lossless, arithmetic */ case M_SOF13: /* Differential sequential, arithmetic */ case M_SOF14: /* Differential progressive, arithmetic */ case M_SOF15: /* Differential lossless, arithmetic */
58eee32004-05-23Martin Nilsson  int image_height, image_width;
7438972004-05-23Martin Nilsson  sscanf(f->read(7), "%*3s%2c%2c", image_height, image_width);
3bdcd02015-08-26Martin Nilsson  if( exif && has_value(exif, "Orientation") && (< "5", "6", "7", "8" >)[exif->Orientation] )
a142c22013-02-07Leif Stensson  {
3bdcd02015-08-26Martin Nilsson  // Picture has been rotated/flipped 90 or 270 degrees, so // exchange width and height to reflect that. int tmp = image_height; image_height = image_width; image_width = tmp;
a142c22013-02-07Leif Stensson  }
a59c9e2002-01-17Martin Nilsson  return ({ image_width,image_height }); break;
3524712015-05-26Martin Nilsson 
3bdcd02015-08-26Martin Nilsson  case M_APP1: int pos = f->tell(); f->seek(pos-2); catch { exif = Standards.EXIF.get_properties(f, ([ 0x0112 : ({ "Orientation" }) ])); }; f->seek(pos); if(!skip_variable(f)) return 0; break;
a59c9e2002-01-17Martin Nilsson  case M_SOS: /* stop before hitting compressed data */ return 0; case M_EOI: /* in case it's a tables-only JPEG stream */ return 0;
3524712015-05-26Martin Nilsson 
a59c9e2002-01-17Martin Nilsson  default: /* Anything else just gets skipped */
58eee32004-05-23Martin Nilsson  if(!skip_variable(f)) return 0; // we assume it has a parameter count...
a59c9e2002-01-17Martin Nilsson  break; } } } // GIF-header: // typedef struct _GifHeader // { // // Header // BYTE Signature[3]; /* Header Signature (always "GIF") */ // BYTE Version[3]; /* GIF format version("87a" or "89a") */ // // Logical Screen Descriptor // WORD ScreenWidth; /* Width of Display Screen in Pixels */ // WORD ScreenHeight; /* Height of Display Screen in Pixels */ // BYTE Packed; /* Screen and Color Map Information */ // BYTE BackgroundColor; /* Background Color Index */ // BYTE AspectRatio; /* Pixel Aspect Ratio */ // } GIFHEAD;
039e252002-09-29Manual system //! Reads the dimensions from a GIF file and returns an array with //! width and height, or if the file isn't a valid image, 0.
a59c9e2002-01-17Martin Nilsson array(int) get_GIF(Stdio.File f) { if(f->read(3)!="GIF") return 0;
d262792015-08-31Martin Nilsson  f->seek(3, Stdio.SEEK_CUR);
7438972004-05-23Martin Nilsson  return array_sscanf(f->read(4), "%-2c%-2c");
a59c9e2002-01-17Martin Nilsson }
039e252002-09-29Manual system //! Reads the dimensions from a PNG file and returns an array with //! width and height, or if the file isn't a valid image, 0.
a59c9e2002-01-17Martin Nilsson array(int) get_PNG(Stdio.File f) { int offs=f->tell();
d262792015-08-31Martin Nilsson  if(f->read(6)!="\211PNG\r\n") return 0; f->seek(6, Stdio.SEEK_CUR);
a59c9e2002-01-17Martin Nilsson  if(f->read(4)!="IHDR") return 0;
7438972004-05-23Martin Nilsson  return array_sscanf(f->read(8), "%4c%4c");
a59c9e2002-01-17Martin Nilsson }
cbf7782006-09-13Tor Edvardsson //! Reads the dimensions from a TIFF file and returns an array with //! width and height, or if the file isn't a valid image, 0. array(int) get_TIFF(Stdio.File f) { int|string buf; int entries; int val = 0; string bo2b; string bo4b;
3524712015-05-26Martin Nilsson 
cbf7782006-09-13Tor Edvardsson  buf = f->read(2);
3524712015-05-26Martin Nilsson  if(buf == "II")
cbf7782006-09-13Tor Edvardsson  { /* Byte order for Little endian */ bo2b = "%2-c"; bo4b = "%4-c"; } else if(buf == "MM") { /* Byte order for Big endian */ bo2b = "%2c"; bo4b = "%4c"; }
3524712015-05-26Martin Nilsson  else
cbf7782006-09-13Tor Edvardsson  { /* Not a TIFF */ return 0; }
3524712015-05-26Martin Nilsson 
cbf7782006-09-13Tor Edvardsson  sscanf(f->read(2), bo2b, buf); if(buf != 42) { /* Wrong magic number */ return 0; }
3524712015-05-26Martin Nilsson 
cbf7782006-09-13Tor Edvardsson  /* offset to first IFD */ sscanf(f->read(4), bo4b, buf); f->seek(buf);
3524712015-05-26Martin Nilsson 
cbf7782006-09-13Tor Edvardsson  /* number of entries */ sscanf(f->read(2), bo2b, entries);
3524712015-05-26Martin Nilsson  for(int i = 0; i < entries; i++)
cbf7782006-09-13Tor Edvardsson  { sscanf(f->read(2), bo2b, int tag);
3524712015-05-26Martin Nilsson  if(tag == 256 || tag == 257)
cbf7782006-09-13Tor Edvardsson  { sscanf(f->read(2), bo2b, buf); /* Count value must be one(1) */ sscanf(f->read(4), bo4b, int count);
3524712015-05-26Martin Nilsson  if(count == 1)
cbf7782006-09-13Tor Edvardsson  {
3524712015-05-26Martin Nilsson  if(buf == 3)
cbf7782006-09-13Tor Edvardsson  { /* Type short */ sscanf(f->read(2), bo2b, buf); /* Skip to the end of the entry */
d262792015-08-31Martin Nilsson  f->seek(2, Stdio.SEEK_CUR);
cbf7782006-09-13Tor Edvardsson  } else if(buf == 4) { /* Type long */ sscanf(f->read(4), bo4b, buf); } else { /* Wrong type */ return 0;
3524712015-05-26Martin Nilsson  }
cbf7782006-09-13Tor Edvardsson  if(tag == 256) { /* ImageWidth */ if(val == 0) val = buf; else return ({buf, val}); } else { /* ImageLength */ if(val == 0) val = buf; else return ({val, buf}); } } else { /* Wrong count value */ return 0; } }
3524712015-05-26Martin Nilsson  else
cbf7782006-09-13Tor Edvardsson  { /* Skip to next entry */
d262792015-08-31Martin Nilsson  f->seek(10, Stdio.SEEK_CUR);
cbf7782006-09-13Tor Edvardsson  } } /* ImageWidth and ImageLength not found */ return 0; }
b503be2010-06-02Jonas Wallden //! Reads the dimensions from a PSD file and returns an array with //! width and height, or if the file isn't a valid image, 0. array(int) get_PSD(Stdio.File f) { // 4 bytes signature + 2 bytes version if (f->read(6) != "8BPS\0\1") return 0;
3524712015-05-26Martin Nilsson 
b503be2010-06-02Jonas Wallden  // 6 bytes reserved // 2 bytes channel count f->read(8);
3524712015-05-26Martin Nilsson 
b503be2010-06-02Jonas Wallden  // 4 bytes height, 4 bytes width (big-endian) return reverse(array_sscanf(f->read(8), "%4c%4c")); }
0770cf2015-08-08Martin Nilsson //! Reads the dimensions from a WebP file and returns an array with //! width and height, or if the file isn't a valid image, 0. array(int) get_WebP(Stdio.File f) { if (f->read(4) != "RIFF") return 0; f->read(4); if (f->read(4) != "WEBP") return 0; switch(f->read(4)) { case "VP8 ": /* Lossy coding */ f->read(4); // Size of chunk. Not in Google documentation. f->read(3); // Frame tag. if(f->read(3)!="\x9d\x01\x2a") return 0; // Ignore the scaling factor, as it is upscaling and not actual // image information. return array_sscanf(f->read(4), "%-2c%-2c")[*] & 0x3fff; break; case "VP8L": /* Lossless coding */ f->read(4); if( f->read(1) != "/" ) return 0; string data = f->read(4); int width = (data[0] | (data[1] & 0x3f)<<8) + 1; int height = (data[1]>>6 | data[2]<<2 | (data[3] & 0x0f)<<10) + 1; return ({ width, height }); break; case "VP8X": /* Extended VP8 */
5892122015-08-08Martin Nilsson  f->read(4);
0770cf2015-08-08Martin Nilsson  f->read(4); // flags and reserved
5892122015-08-08Martin Nilsson  return array_sscanf(f->read(6), "%-3c%-3c")[*]+1;
0770cf2015-08-08Martin Nilsson  break; } return 0; } //! Read dimensions from a JPEG, GIF, PNG, WebP, TIFF or PSD file and //! return an array with width and height, or if the file isn't a //! valid image, @expr{0@}. The argument @[file] should be file object //! or the data from a file. The offset pointer will be assumed to be //! at the start of the file data and will be modified by the //! function.
2ef6022004-08-26Martin Nilsson //!
e567fd2004-05-23Martin Nilsson //! @returns //! @array //! @elem int 0 //! Image width. //! @elem int 1 //! Image height. //! @elem string 2
b503be2010-06-02Jonas Wallden //! Image type. Any of @expr{"gif"@}, @expr{"png"@}, @expr{"tiff"@},
0770cf2015-08-08Martin Nilsson //! @expr{"jpeg"@}, @expr{"webp"@} and @expr{"psd"@}.
e567fd2004-05-23Martin Nilsson //! @endarray
78a9fc2015-08-27Martin Nilsson array(int|string) get(string|Stdio.File file) {
c0e0972015-08-08Martin Nilsson  if(stringp(file))
2ef6022004-08-26Martin Nilsson  file = Stdio.FakeFile(file);
58eee32004-05-23Martin Nilsson 
2905ba2015-08-08Martin Nilsson  array ret; switch(file->read(2)) { case "GI": if( (< "F87a", "F89a" >)[file->read(4)] ) return array_sscanf(file->read(4), "%-2c%-2c") + ({ "gif" }); break; case "\x89P": if(file->read(4)=="NG\r\n") { file->read(6+4); return array_sscanf(file->read(8), "%4c%4c") + ({ "png" }); } break;
3524712015-05-26Martin Nilsson 
2905ba2015-08-08Martin Nilsson  case "8B": if(file->read(4)=="PS\0\1") { file->read(6+2); return reverse(array_sscanf(file->read(8), "%4c%4c")) + ({ "psd" }); } break; case "II": case "MM":
d262792015-08-31Martin Nilsson  file->seek(-2, Stdio.SEEK_CUR);
2905ba2015-08-08Martin Nilsson  ret = get_TIFF(file); if(ret) return ret+({ "tiff" }); break; case "\xff\xd8":
d262792015-08-31Martin Nilsson  file->seek(-2, Stdio.SEEK_CUR);
2905ba2015-08-08Martin Nilsson  ret = get_JPEG(file); if(ret) return ret+({ "jpeg" }); break;
0770cf2015-08-08Martin Nilsson  case "RI":
d262792015-08-31Martin Nilsson  file->seek(-2, Stdio.SEEK_CUR);
0770cf2015-08-08Martin Nilsson  ret = get_WebP(file); if(ret) return ret+({ "webp" }); break;
58eee32004-05-23Martin Nilsson  }
2905ba2015-08-08Martin Nilsson 
0770cf2015-08-08Martin Nilsson  return 0;
a59c9e2002-01-17Martin Nilsson }