open($uri); // } function __destruct() { return $this->close(); } /// working with file( handle)s /// open file /// return BOOL() success function open( $uri = NULL /// str() path to jpeg file ) { if (!is_string($uri) || !isset($uri[0])) { if (!isset($this->_file)) { $this->_file = FALSE; } return self::fail; } $this->_file = fopen($uri, 'r+b'); return is_resource($this->_file); } /// close filehandle /// return BOOL success function close() { if (!is_resource($this->_file)) { return self::fail; } return fclose($this->_file); } /// read structured data utilizing unpack() /// return HASH() | ::fail function struct_read( $unpack_format = NULL, $bytesize = NULL ) { if (!is_resource($this->_file)) { return self::fail; } $buffer = fread($this->_file, $bytesize); if (!is_string($buffer) || strlen($buffer) < $bytesize) { return self::fail; } $old_error_reporting = error_reporting(-1); ob_start(); $array = unpack($unpack_format, $buffer); $error = ob_get_clean(); error_reporting($old_error_reporting); if (!is_string($error) || isset($error[0])) { echo $error; return self::fail; // unpack failed } // zero align array; array_values() does not work here array_unshift($array, FALSE); array_shift($array); return $array; } /// jpeg // note: marker == SOS signals bitmap data including the trailing EOS segment /// read|skip JIF-segment /// return LIST(marker, data) | ::fail function segment_read() { if (!is_resource($this->_file)) { return self::fail; } // get marker $marker = fread($this->_file, 2); if (!is_string($marker) || strlen($marker) < 2) { return self::fail; // invalid segment } $type = $marker[1]; // get bytesize of raw data if (self::SOS === $type) { // bitmap data $length = NULL; } elseif (self::RST0 <= $type && self::SOS >= $type) { $length = 0; // zero sized segment } else { $length = $this->struct_read('n', 2); if (!is_array($length)) { return self::fail; // file is incomplete } $length = $length[0]; } // read data if (NULL === $length) { // raw compressed bitmap data w/o following EOI segment marker // note: RSTn and "\xff\00" segments stay encoded as they are $eoi_segment = self::JPEG_SEG . self::EOI; for ( $data = array (); is_string($buffer = fread($this->_file, self::$read_buffer)); $data[] = $buffer ) { if (is_int($eoi_pos = strpos($buffer, $eoi_segment))) { // EOI marker found $data[] = substr($buffer, 0, $eoi_pos); if ( 0 !== fseek( $this->_file, $eoi_pos - strlen($buffer), SEEK_CUR ) ) { return self::fail; } break; } elseif (substr_compare($buffer, self::JPEG_SEG, -1) === 0) { // first byte of some marker found at the end // check if it is an EOI if (self::EOI === fread($this->_file, 1)) { $data[] = substr($buffer, 0, -1); if (0 !== fseek($this->_file, -2, SEEK_CUR)) { return self::fail; } break; } } } // $data = implode($data); } elseif ($length > 0) { // segment $length -= 2; $data = fread($this->_file, $length); if (!is_string($data) || strlen($data) < $length) { return self::fail; // end of file before end of segment data } } else { $data = NULL; } return array ($type, $data); } // note: some parts from IJG/jpegsrc.v7.tar.gz/jdmarker.c /// extract quantization tables in zigzag-order /// return LIST() | ::fail function dqt() { if (!is_resource($this->_file)) { return self::fail; } // cache current seek position $old_fpos = ftell($this->_file); if (0 !== fseek($this->_file, 0, SEEK_SET)) { return self::fail; } for ($tables = array (); is_array($seg = $this->segment_read()); ) { if (self::DQT !== $seg[0]) { continue; // skip other segments } if (self::SOS === $seg[0]) { break; // there should be no DQT markers after SOS } // build quantization table(s) $off = 0; $len = strlen($seg[1]); while ($len > 0) { $n = ord($seg[1][0]); // 1st byte in DQT data $off += 1; // "seek" position $len -= 1; // number of remaining bytes $prec = $n >> 4; // quant. table precision: UBYTE or USHORT $n &= 0x0f; // number of quantization tables if ($n >= self::NUM_QUANT_TBLS) { return self::fail; // ERREXIT1(cinfo, JERR_DQT_INDEX, n); } if ($prec) { $up_f = 'n*'; // unsigned short int $up_s = self::DCTSIZE2 + self::DCTSIZE2; } else { $up_f = 'C*'; // unsigned byte $up_s = self::DCTSIZE2; } $tmp = unpack($up_f, substr($seg[1], $off, $up_s)); array_unshift($tmp, FALSE); array_shift($tmp); // zero-align unpacked array $tables[] = $tmp; $off += $up_s; $len -= $up_s; } /// endwhile $len > 0 if ($len < 0) { return self::fail; // ERREXIT(cinfo, JERR_BAD_LENGTH); } } /// endfor $seg = $this->read() // restore previous seek position if (0 !== fseek($this->_file, $old_fpos, SEEK_SET)) { return self::fail; } return $tables; } // note: from gimp 2.6.7 plugins/file-jpeg/jpeg-quality.c/jpeg_detect_quality() /// detect IJG's jpeg quality factor /// return INT() 0: error <0: estimated >0: exact function detect_quality() { if (!is_array($tbl = $this->dqt())){ return self::fail; } // files using CMYK or having 4 quantisation tables are unusual if (count($tbl) > 3) { return 0; } $sum = array (0, 0, 0); // gint sum[3] // Most files use table 0 for luminance divisors (Y) and table 1 for // chrominance divisors (Cb and Cr). Some files use separate tables // for Cb and Cr, so table 2 may also be used. for ($t = 0; $t < 3; ++$t) { $sum[$t] = 0; //if ($cinfo->quant_tbl_ptrs[$t]) { if (isset($tbl[$t])) { for ($i = 0; $i < self::DCTSIZE2; ++$i) { //$sum[$t] += $cinfo->quant_tbl_ptrs[$t]->quantv[$i]; //debug::writefln('$t: %s $i: %s', $t, $i); $sum[$t] += $tbl[$t][$i]; } /// endfor } /// endif } /// endfor $sums = 0; // gint sums //if ($cinfo->output_components > 1) { if (count($tbl) > 1) { if ($sum[0] < 64 || $sum[1] < 64) { return 0; } // compare with the chrominance table having the lowest sum * / if ($sum[1] < $sum[2] || $sum[2] <= 0) { $sums = $sum[0] + $sum[1]; } else { $sums = $sum[0] + $sum[2]; } $q = 100; for (; $sums > self::$std_luminance_sum[$q] + self::$std_chrominance_sum[$q]; --$q ) { } return ( $sum[0] == self::$std_luminance_sum[$q] && $sum[1] == self::$std_chrominance_sum[$q] ) ? $q : -$q; } else { if ($sum[0] < 64) { return 0; } $q = 100; for (; $sum[0] > self::$std_luminance_sum[$q]; --$q) { } if ($sum[0] == self::$std_luminance_sum[$q]) { return $q; } else { return -$q; } } /// endif } } /// endclass jpeg_reader_sa // simple example usage: $file = 'i:/test/jif_test/miez.jpg'; $jrs = new jpeg_reader_sa(); $jrs->open($file); $qf = $jrs->detect_quality(); var_dump($qf); // show the jpeg quality factor or an error $jrs->close(); ?>