r80775 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r80774‎ | r80775 | r80776 >
Date:22:34, 22 January 2011
Author:btongminh
Status:ok (Comments)
Tags:
Comment:
Follow-up r79867: Read out EXIF orientation in JavaScript and rotate accordingly. Added JsJpegMeta library by Ben Leslie under the MIT license.

* Added JS variabele wgFileCanRotate. If anybody knows a way to make certain variables only available to certain modules, please tell me, could not find it.
* Added JsJpegMeta as mediawiki.util.jpegmeta
* Made BitmapHandler::getScaler and BitmapHandker::canRotate static
* Bumped style version
Modified paths:
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/media/Bitmap.php (modified) (history)
  • /trunk/phase3/includes/resourceloader/ResourceLoaderStartUpModule.php (modified) (history)
  • /trunk/phase3/resources/Resources.php (modified) (history)
  • /trunk/phase3/resources/mediawiki.special/mediawiki.special.upload.js (modified) (history)
  • /trunk/phase3/resources/mediawiki.util/mediawiki.util.jpegmeta.js (added) (history)

Diff [purge]

Index: trunk/phase3/includes/resourceloader/ResourceLoaderStartUpModule.php
@@ -77,6 +77,9 @@
7878 'wgSiteName' => $wgSitename,
7979 'wgFileExtensions' => array_values( $wgFileExtensions ),
8080 'wgDBname' => $wgDBname,
 81+ // This sucks, it is only needed on Special:Upload, but I could
 82+ // not find a way to add vars only for a certain module
 83+ 'wgFileCanRotate' => BitmapHandler::canRotate(),
8184 );
8285 if ( $wgContLang->hasVariants() ) {
8386 $vars['wgUserVariant'] = $wgContLang->getPreferredVariant();
Index: trunk/phase3/includes/media/Bitmap.php
@@ -22,10 +22,10 @@
2323 $srcWidth = $image->getWidth( $params['page'] );
2424 $srcHeight = $image->getHeight( $params['page'] );
2525
26 - if ( $this->canRotate() ) {
 26+ if ( self::canRotate() ) {
2727 $rotation = $this->getRotation( $image );
2828 if ( $rotation == 90 || $rotation == 270 ) {
29 - wfDebug( __METHOD__ . ": Swapping width and height because the file will be rotation $rotation degrees\n" );
 29+ wfDebug( __METHOD__ . ": Swapping width and height because the file will be rotated $rotation degrees\n" );
3030
3131 $width = $params['width'];
3232 $params['width'] = $params['height'];
@@ -101,7 +101,7 @@
102102 }
103103
104104 # Determine scaler type
105 - $scaler = $this->getScalerType( $dstPath );
 105+ $scaler = self::getScalerType( $dstPath );
106106 wfDebug( __METHOD__ . ": scaler $scaler\n" );
107107
108108 if ( $scaler == 'client' ) {
@@ -156,7 +156,7 @@
157157 *
158158 * @return string client,im,custom,gd
159159 */
160 - protected function getScalerType( $dstPath, $checkDstPath = true ) {
 160+ protected static function getScalerType( $dstPath, $checkDstPath = true ) {
161161 global $wgUseImageResize, $wgUseImageMagick, $wgCustomConvertCommand;
162162
163163 if ( !$dstPath && $checkDstPath ) {
@@ -678,8 +678,8 @@
679679 *
680680 * @return bool
681681 */
682 - public function canRotate() {
683 - $scaler = $this->getScalerType( null, false );
 682+ public static function canRotate() {
 683+ $scaler = self::getScalerType( null, false );
684684 return $scaler == 'im' || $scaler == 'gd';
685685 }
686686
@@ -691,6 +691,6 @@
692692 * @return bool
693693 */
694694 public function mustRender( $file ) {
695 - return $this->canRotate() && $this->getRotation( $file ) != 0;
 695+ return self::canRotate() && $this->getRotation( $file ) != 0;
696696 }
697697 }
Index: trunk/phase3/includes/DefaultSettings.php
@@ -1599,7 +1599,7 @@
16001600 * to ensure that client-side caches do not keep obsolete copies of global
16011601 * styles.
16021602 */
1603 -$wgStyleVersion = '301';
 1603+$wgStyleVersion = '302';
16041604
16051605 /**
16061606 * This will cache static pages for non-logged-in users to reduce
Index: trunk/phase3/resources/mediawiki.util/mediawiki.util.jpegmeta.js
@@ -0,0 +1,738 @@
 2+/* This is JsJpegMeta 1.0, ported to MediaWiki ResourceLoader by Bryan Tong Minh */
 3+/* The following lines where changed with respect to the original: 54, 625-627 */
 4+
 5+( function( $, mw ) {
 6+
 7+ /* JsJpegMeta starts here */
 8+
 9+ /*
 10+ Copyright (c) 2009 Ben Leslie
 11+
 12+ Permission is hereby granted, free of charge, to any person obtaining a copy
 13+ of this software and associated documentation files (the "Software"), to deal
 14+ in the Software without restriction, including without limitation the rights
 15+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 16+ copies of the Software, and to permit persons to whom the Software is
 17+ furnished to do so, subject to the following conditions:
 18+
 19+ The above copyright notice and this permission notice shall be included in
 20+ all copies or substantial portions of the Software.
 21+
 22+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 23+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 24+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 25+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 26+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 27+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 28+ THE SOFTWARE.
 29+ */
 30+
 31+ /*
 32+ This JavaScript library is used to parse meta-data from files
 33+ with mime-type image/jpeg.
 34+
 35+ Include it with something like:
 36+
 37+ <script type="text/javascript" src="jpegmeta.js"></script>
 38+
 39+ This adds a single 'module' object called 'JpegMeta' to the global
 40+ namespace.
 41+
 42+ Public Functions
 43+ ----------------
 44+ JpegMeta.parseNum - parse unsigned integers from binary data
 45+ JpegMeta.parseSnum - parse signed integers from binary data
 46+
 47+ Public Classes
 48+ --------------
 49+ JpegMeta.Rational - A rational number class
 50+ JpegMeta.JfifSegment
 51+ JpegMeta.ExifSegment
 52+ JpegMeta.JpegFile - Primary class for Javascript parsing
 53+ */
 54+
 55+ var JpegMeta = {};
 56+ this.JpegMeta = JpegMeta; // I have no clue why I need this magic... -- Bryan
 57+
 58+ /*
 59+ parse an unsigned number of size bytes at offset in some binary string data.
 60+ If endian
 61+ is "<" parse the data as little endian, if endian
 62+ is ">" parse as big-endian.
 63+ */
 64+ JpegMeta.parseNum = function parseNum(endian, data, offset, size) {
 65+ var i;
 66+ var ret;
 67+ var big_endian = (endian === ">");
 68+ if (offset === undefined) offset = 0;
 69+ if (size === undefined) size = data.length - offset;
 70+ for (big_endian ? i = offset : i = offset + size - 1;
 71+ big_endian ? i < offset + size : i >= offset;
 72+ big_endian ? i++ : i--) {
 73+ ret <<= 8;
 74+ ret += data.charCodeAt(i);
 75+ }
 76+ return ret;
 77+ }
 78+
 79+ /*
 80+ parse an signed number of size bytes at offset in some binary string data.
 81+ If endian
 82+ is "<" parse the data as little endian, if endian
 83+ is ">" parse as big-endian.
 84+ */
 85+ JpegMeta.parseSnum = function parseSnum(endian, data, offset, size) {
 86+ var i;
 87+ var ret;
 88+ var neg;
 89+ var big_endian = (endian === ">");
 90+ if (offset === undefined) offset = 0;
 91+ if (size === undefined) size = data.length - offset;
 92+ for (big_endian ? i = offset : i = offset + size - 1;
 93+ big_endian ? i < offset + size : i >= offset;
 94+ big_endian ? i++ : i--) {
 95+ if (neg === undefined) {
 96+ /* Negative if top bit is set */
 97+ neg = (data.charCodeAt(i) & 0x80) === 0x80;
 98+ }
 99+ ret <<= 8;
 100+ /* If it is negative we invert the bits */
 101+ ret += neg ? ~data.charCodeAt(i) & 0xff: data.charCodeAt(i);
 102+ }
 103+ if (neg) {
 104+ /* If it is negative we do two's complement */
 105+ ret += 1;
 106+ ret *= -1;
 107+ }
 108+ return ret;
 109+ }
 110+
 111+ /* Rational number class */
 112+ JpegMeta.Rational = function Rational(num, den)
 113+ {
 114+ this.num = num;
 115+ this.den = den || 1;
 116+ return this;
 117+ }
 118+
 119+ /* Rational number methods */
 120+ JpegMeta.Rational.prototype.toString = function toString() {
 121+ if (this.num === 0) {
 122+ return "" + this.num
 123+ }
 124+ if (this.den === 1) {
 125+ return "" + this.num;
 126+ }
 127+ if (this.num === 1) {
 128+ return this.num + " / " + this.den;
 129+ }
 130+ return this.num / this.den; // + "/" + this.den;
 131+ }
 132+
 133+ JpegMeta.Rational.prototype.asFloat = function asFloat() {
 134+ return this.num / this.den;
 135+ }
 136+
 137+
 138+ /* MetaGroup class */
 139+ JpegMeta.MetaGroup = function MetaGroup(fieldName, description) {
 140+ this.fieldName = fieldName;
 141+ this.description = description;
 142+ this.metaProps = {};
 143+ return this;
 144+ }
 145+
 146+ JpegMeta.MetaGroup.prototype._addProperty = function _addProperty(fieldName, description, value) {
 147+ var property = new JpegMeta.MetaProp(fieldName, description, value);
 148+ this[property.fieldName] = property;
 149+ this.metaProps[property.fieldName] = property;
 150+ }
 151+
 152+ JpegMeta.MetaGroup.prototype.toString = function toString() {
 153+ return "[MetaGroup " + this.description + "]";
 154+ }
 155+
 156+
 157+ /* MetaProp class */
 158+ JpegMeta.MetaProp = function MetaProp(fieldName, description, value) {
 159+ this.fieldName = fieldName;
 160+ this.description = description;
 161+ this.value = value;
 162+ return this;
 163+ }
 164+
 165+ JpegMeta.MetaProp.prototype.toString = function toString() {
 166+ return "" + this.value;
 167+ }
 168+
 169+
 170+
 171+ /* JpegFile class */
 172+ JpegMeta.JpegFile = function JpegFile(binary_data, filename) {
 173+ /* Change this to EOI if we want to parse. */
 174+ var break_segment = this._SOS;
 175+
 176+ this.metaGroups = {};
 177+ this._binary_data = binary_data;
 178+ this.filename = filename;
 179+
 180+ /* Go through and parse. */
 181+ var pos = 0;
 182+ var pos_start_of_segment = 0;
 183+ var delim;
 184+ var mark;
 185+ var _mark;
 186+ var segsize;
 187+ var headersize;
 188+ var mark_code;
 189+ var mark_fn;
 190+
 191+ /* Check to see if this looks like a JPEG file */
 192+ if (this._binary_data.slice(0, 2) !== this._SOI_MARKER) {
 193+ throw new Error("Doesn't look like a JPEG file. First two bytes are " +
 194+ this._binary_data.charCodeAt(0) + "," +
 195+ this._binary_data.charCodeAt(1) + ".");
 196+ }
 197+
 198+ pos += 2;
 199+
 200+ while (pos < this._binary_data.length) {
 201+ delim = this._binary_data.charCodeAt(pos++);
 202+ mark = this._binary_data.charCodeAt(pos++);
 203+
 204+ pos_start_of_segment = pos;
 205+
 206+ if (delim != this._DELIM) {
 207+ break;
 208+ }
 209+
 210+ if (mark === break_segment) {
 211+ break;
 212+ }
 213+
 214+ headersize = JpegMeta.parseNum(">", this._binary_data, pos, 2);
 215+
 216+ /* Find the end */
 217+ pos += headersize;
 218+ while (pos < this._binary_data.length) {
 219+ delim = this._binary_data.charCodeAt(pos++);
 220+ if (delim == this._DELIM) {
 221+ _mark = this._binary_data.charCodeAt(pos++);
 222+ if (_mark != 0x0) {
 223+ pos -= 2;
 224+ break;
 225+ }
 226+ }
 227+ }
 228+
 229+ segsize = pos - pos_start_of_segment;
 230+
 231+ if (this._markers[mark]) {
 232+ mark_code = this._markers[mark][0];
 233+ mark_fn = this._markers[mark][1];
 234+ } else {
 235+ mark_code = "UNKN";
 236+ mark_fn = undefined;
 237+ }
 238+
 239+ if (mark_fn) {
 240+ this[mark_fn](mark, pos_start_of_segment + 2);
 241+ }
 242+
 243+ }
 244+
 245+ if (this.general === undefined) {
 246+ throw Error("Invalid JPEG file.");
 247+ }
 248+
 249+ return this;
 250+ }
 251+
 252+ this.JpegMeta.JpegFile.prototype.toString = function () {
 253+ return "[JpegFile " + this.filename + " " +
 254+ this.general.type + " " +
 255+ this.general.pixelWidth + "x" +
 256+ this.general.pixelHeight +
 257+ " Depth: " + this.general.depth + "]";
 258+ }
 259+
 260+ /* Some useful constants */
 261+ this.JpegMeta.JpegFile.prototype._SOI_MARKER = '\xff\xd8';
 262+ this.JpegMeta.JpegFile.prototype._DELIM = 0xff;
 263+ this.JpegMeta.JpegFile.prototype._EOI = 0xd9;
 264+ this.JpegMeta.JpegFile.prototype._SOS = 0xda;
 265+
 266+ this.JpegMeta.JpegFile.prototype._sofHandler = function _sofHandler (mark, pos) {
 267+ if (this.general !== undefined) {
 268+ throw Error("Unexpected multiple-frame image");
 269+ }
 270+
 271+ this._addMetaGroup("general", "General");
 272+ this.general._addProperty("depth", "Depth", JpegMeta.parseNum(">", this._binary_data, pos, 1));
 273+ this.general._addProperty("pixelHeight", "Pixel Height", JpegMeta.parseNum(">", this._binary_data, pos + 1, 2));
 274+ this.general._addProperty("pixelWidth", "Pixel Width",JpegMeta.parseNum(">", this._binary_data, pos + 3, 2));
 275+ this.general._addProperty("type", "Type", this._markers[mark][2]);
 276+ }
 277+
 278+ /* JFIF idents */
 279+ this.JpegMeta.JpegFile.prototype._JFIF_IDENT = "JFIF\x00";
 280+ this.JpegMeta.JpegFile.prototype._JFXX_IDENT = "JFXX\x00";
 281+
 282+ /* EXIF idents */
 283+ this.JpegMeta.JpegFile.prototype._EXIF_IDENT = "Exif\x00";
 284+
 285+ /* TIFF types */
 286+ this.JpegMeta.JpegFile.prototype._types = {
 287+ /* The format is identifier : ["type name", type_size_in_bytes ] */
 288+ 1 : ["BYTE", 1],
 289+ 2 : ["ASCII", 1],
 290+ 3 : ["SHORT", 2],
 291+ 4 : ["LONG", 4],
 292+ 5 : ["RATIONAL", 8],
 293+ 6 : ["SBYTE", 1],
 294+ 7 : ["UNDEFINED", 1],
 295+ 8 : ["SSHORT", 2],
 296+ 9 : ["SLONG", 4],
 297+ 10 : ["SRATIONAL", 8],
 298+ 11 : ["FLOAT", 4],
 299+ 12 : ["DOUBLE", 8],
 300+ };
 301+
 302+ this.JpegMeta.JpegFile.prototype._tifftags = {
 303+ /* A. Tags relating to image data structure */
 304+ 256 : ["Image width", "ImageWidth"],
 305+ 257 : ["Image height", "ImageLength"],
 306+ 258 : ["Number of bits per component", "BitsPerSample"],
 307+ 259 : ["Compression scheme", "Compression",
 308+ {1 : "uncompressed", 6 : "JPEG compression" }],
 309+ 262 : ["Pixel composition", "PhotmetricInerpretation",
 310+ {2 : "RGB", 6 : "YCbCr"}],
 311+ 274 : ["Orientation of image", "Orientation",
 312+ /* FIXME: Check the mirror-image / reverse encoding and rotation */
 313+ {1 : "Normal", 2 : "Reverse?",
 314+ 3 : "Upside-down", 4 : "Upside-down Reverse",
 315+ 5 : "90 degree CW", 6 : "90 degree CW reverse",
 316+ 7 : "90 degree CCW", 8 : "90 degree CCW reverse",}],
 317+ 277 : ["Number of components", "SamplesPerPixel"],
 318+ 284 : ["Image data arrangement", "PlanarConfiguration",
 319+ {1 : "chunky format", 2 : "planar format"}],
 320+ 530 : ["Subsampling ratio of Y to C", "YCbCrSubSampling"],
 321+ 531 : ["Y and C positioning", "YCbCrPositioning",
 322+ {1 : "centered", 2 : "co-sited"}],
 323+ 282 : ["X Resolution", "XResolution"],
 324+ 283 : ["Y Resolution", "YResolution"],
 325+ 296 : ["Resolution Unit", "ResolutionUnit",
 326+ {2 : "inches", 3 : "centimeters"}],
 327+ /* B. Tags realting to recording offset */
 328+ 273 : ["Image data location", "StripOffsets"],
 329+ 278 : ["Number of rows per strip", "RowsPerStrip"],
 330+ 279 : ["Bytes per compressed strip", "StripByteCounts"],
 331+ 513 : ["Offset to JPEG SOI", "JPEGInterchangeFormat"],
 332+ 514 : ["Bytes of JPEG Data", "JPEGInterchangeFormatLength"],
 333+ /* C. Tags relating to image data characteristics */
 334+ 301 : ["Transfer function", "TransferFunction"],
 335+ 318 : ["White point chromaticity", "WhitePoint"],
 336+ 319 : ["Chromaticities of primaries", "PrimaryChromaticities"],
 337+ 529 : ["Color space transformation matrix coefficients", "YCbCrCoefficients"],
 338+ 532 : ["Pair of black and white reference values", "ReferenceBlackWhite"],
 339+ /* D. Other tags */
 340+ 306 : ["Date and time", "DateTime"],
 341+ 270 : ["Image title", "ImageDescription"],
 342+ 271 : ["Make", "Make"],
 343+ 272 : ["Model", "Model"],
 344+ 305 : ["Software", "Software"],
 345+ 315 : ["Person who created the image", "Artist"],
 346+ 316 : ["Host Computer", "HostComputer"],
 347+ 33432 : ["Copyright holder", "Copyright"],
 348+
 349+ 34665 : ["Exif tag", "ExifIfdPointer"],
 350+ 34853 : ["GPS tag", "GPSInfoIfdPointer"],
 351+ };
 352+
 353+ this.JpegMeta.JpegFile.prototype._exiftags = {
 354+ /* Tag Support Levels (2) - 0th IFX Exif Private Tags */
 355+ /* A. Tags Relating to Version */
 356+ 36864 : ["Exif Version", "ExifVersion"],
 357+ 40960 : ["FlashPix Version", "FlashpixVersion"],
 358+
 359+ /* B. Tag Relating to Image Data Characteristics */
 360+ 40961 : ["Color Space", "ColorSpace"],
 361+
 362+ /* C. Tags Relating to Image Configuration */
 363+ 37121 : ["Meaning of each component", "ComponentsConfiguration"],
 364+ 37122 : ["Compressed Bits Per Pixel", "CompressedBitsPerPixel"],
 365+ 40962 : ["Pixel X Dimension", "PixelXDimension"],
 366+ 40963 : ["Pixel Y Dimension", "PixelYDimension"],
 367+
 368+ /* D. Tags Relating to User Information */
 369+ 37500 : ["Manufacturer notes", "MakerNote"],
 370+ 37510 : ["User comments", "UserComment"],
 371+
 372+ /* E. Tag Relating to Related File Information */
 373+ 40964 : ["Related audio file", "RelatedSoundFile"],
 374+
 375+ /* F. Tags Relating to Date and Time */
 376+ 36867 : ["Date Time Original", "DateTimeOriginal"],
 377+ 36868 : ["Date Time Digitized", "DateTimeDigitized"],
 378+ 37520 : ["DateTime subseconds", "SubSecTime"],
 379+ 37521 : ["DateTimeOriginal subseconds", "SubSecTimeOriginal"],
 380+ 37522 : ["DateTimeDigitized subseconds", "SubSecTimeDigitized"],
 381+
 382+ /* G. Tags Relating to Picture-Taking Conditions */
 383+ 33434 : ["Exposure time", "ExposureTime"],
 384+ 33437 : ["FNumber", "FNumber"],
 385+ 34850 : ["Exposure program", "ExposureProgram"],
 386+ 34852 : ["Spectral sensitivity", "SpectralSensitivity"],
 387+ 34855 : ["ISO Speed Ratings", "ISOSpeedRatings"],
 388+ 34856 : ["Optoelectric coefficient", "OECF"],
 389+ 37377 : ["Shutter Speed", "ShutterSpeedValue"],
 390+ 37378 : ["Aperture Value", "ApertureValue"],
 391+ 37379 : ["Brightness", "BrightnessValue"],
 392+ 37380 : ["Exposure Bias Value", "ExposureBiasValue"],
 393+ 37381 : ["Max Aperture Value", "MaxApertureValue"],
 394+ 37382 : ["Subject Distance", "SubjectDistance"],
 395+ 37383 : ["Metering Mode", "MeteringMode"],
 396+ 37384 : ["Light Source", "LightSource"],
 397+ 37385 : ["Flash", "Flash"],
 398+ 37386 : ["Focal Length", "FocalLength"],
 399+ 37396 : ["Subject Area", "SubjectArea"],
 400+ 41483 : ["Flash Energy", "FlashEnergy"],
 401+ 41484 : ["Spatial Frequency Response", "SpatialFrequencyResponse"],
 402+ 41486 : ["Focal Plane X Resolution", "FocalPlaneXResolution"],
 403+ 41487 : ["Focal Plane Y Resolution", "FocalPlaneYResolution"],
 404+ 41488 : ["Focal Plane Resolution Unit", "FocalPlaneResolutionUnit"],
 405+ 41492 : ["Subject Location", "SubjectLocation"],
 406+ 41493 : ["Exposure Index", "ExposureIndex"],
 407+ 41495 : ["Sensing Method", "SensingMethod"],
 408+ 41728 : ["File Source", "FileSource"],
 409+ 41729 : ["Scene Type", "SceneType"],
 410+ 41730 : ["CFA Pattern", "CFAPattern"],
 411+ 41985 : ["Custom Rendered", "CustomRendered"],
 412+ 41986 : ["Exposure Mode", "Exposure Mode"],
 413+ 41987 : ["White Balance", "WhiteBalance"],
 414+ 41988 : ["Digital Zoom Ratio", "DigitalZoomRatio"],
 415+ 41990 : ["Scene Capture Type", "SceneCaptureType"],
 416+ 41991 : ["Gain Control", "GainControl"],
 417+ 41992 : ["Contrast", "Contrast"],
 418+ 41993 : ["Saturation", "Saturation"],
 419+ 41994 : ["Sharpness", "Sharpness"],
 420+ 41995 : ["Device settings description", "DeviceSettingDescription"],
 421+ 41996 : ["Subject distance range", "SubjectDistanceRange"],
 422+
 423+ /* H. Other Tags */
 424+ 42016 : ["Unique image ID", "ImageUniqueID"],
 425+
 426+ 40965 : ["Interoperability tag", "InteroperabilityIFDPointer"],
 427+ }
 428+
 429+ this.JpegMeta.JpegFile.prototype._gpstags = {
 430+ /* A. Tags Relating to GPS */
 431+ 0 : ["GPS tag version", "GPSVersionID"],
 432+ 1 : ["North or South Latitude", "GPSLatitudeRef"],
 433+ 2 : ["Latitude", "GPSLatitude"],
 434+ 3 : ["East or West Longitude", "GPSLongitudeRef"],
 435+ 4 : ["Longitude", "GPSLongitude"],
 436+ 5 : ["Altitude reference", "GPSAltitudeRef"],
 437+ 6 : ["Altitude", "GPSAltitude"],
 438+ 7 : ["GPS time (atomic clock)", "GPSTimeStamp"],
 439+ 8 : ["GPS satellites usedd for measurement", "GPSSatellites"],
 440+ 9 : ["GPS receiver status", "GPSStatus"],
 441+ 10 : ["GPS mesaurement mode", "GPSMeasureMode"],
 442+ 11 : ["Measurement precision", "GPSDOP"],
 443+ 12 : ["Speed unit", "GPSSpeedRef"],
 444+ 13 : ["Speed of GPS receiver", "GPSSpeed"],
 445+ 14 : ["Reference for direction of movement", "GPSTrackRef"],
 446+ 15 : ["Direction of movement", "GPSTrack"],
 447+ 16 : ["Reference for direction of image", "GPSImgDirectionRef"],
 448+ 17 : ["Direction of image", "GPSImgDirection"],
 449+ 18 : ["Geodetic survey data used", "GPSMapDatum"],
 450+ 19 : ["Reference for latitude of destination", "GPSDestLatitudeRef"],
 451+ 20 : ["Latitude of destination", "GPSDestLatitude"],
 452+ 21 : ["Reference for longitude of destination", "GPSDestLongitudeRef"],
 453+ 22 : ["Longitude of destination", "GPSDestLongitude"],
 454+ 23 : ["Reference for bearing of destination", "GPSDestBearingRef"],
 455+ 24 : ["Bearing of destination", "GPSDestBearing"],
 456+ 25 : ["Reference for distance to destination", "GPSDestDistanceRef"],
 457+ 26 : ["Distance to destination", "GPSDestDistance"],
 458+ 27 : ["Name of GPS processing method", "GPSProcessingMethod"],
 459+ 28 : ["Name of GPS area", "GPSAreaInformation"],
 460+ 29 : ["GPS Date", "GPSDateStamp"],
 461+ 30 : ["GPS differential correction", "GPSDifferential"],
 462+ }
 463+
 464+
 465+ this.JpegMeta.JpegFile.prototype._markers = {
 466+ /* Start Of Frame markers, non-differential, Huffman coding */
 467+ 0xc0: ["SOF0", "_sofHandler", "Baseline DCT"],
 468+ 0xc1: ["SOF1", "_sofHandler", "Extended sequential DCT"],
 469+ 0xc2: ["SOF2", "_sofHandler", "Progressive DCT"],
 470+ 0xc3: ["SOF3", "_sofHandler", "Lossless (sequential)"],
 471+
 472+ /* Start Of Frame markers, differential, Huffman coding */
 473+ 0xc5: ["SOF5", "_sofHandler", "Differential sequential DCT"],
 474+ 0xc6: ["SOF6", "_sofHandler", "Differential progressive DCT"],
 475+ 0xc7: ["SOF7", "_sofHandler", "Differential lossless (sequential)"],
 476+
 477+ /* Start Of Frame markers, non-differential, arithmetic coding */
 478+ 0xc8: ["JPG", null, "Reserved for JPEG extensions"],
 479+ 0xc9: ["SOF9", "_sofHandler", "Extended sequential DCT"],
 480+ 0xca: ["SOF10", "_sofHandler", "Progressive DCT"],
 481+ 0xcb: ["SOF11", "_sofHandler", "Lossless (sequential)"],
 482+
 483+ /* Start Of Frame markers, differential, arithmetic coding */
 484+ 0xcd: ["SOF13", "_sofHandler", "Differential sequential DCT"],
 485+ 0xce: ["SOF14", "_sofHandler", "Differential progressive DCT"],
 486+ 0xcf: ["SOF15", "_sofHandler", "Differential lossless (sequential)"],
 487+
 488+ /* Huffman table specification */
 489+ 0xc4: ["DHT", null, "Define Huffman table(s)"],
 490+ 0xcc: ["DAC", null, "Define arithmetic coding conditioning(s)"],
 491+
 492+ /* Restart interval termination" */
 493+ 0xd0: ["RST0", null, "Restart with modulo 8 count “0”"],
 494+ 0xd1: ["RST1", null, "Restart with modulo 8 count “1”"],
 495+ 0xd2: ["RST2", null, "Restart with modulo 8 count “2”"],
 496+ 0xd3: ["RST3", null, "Restart with modulo 8 count “3”"],
 497+ 0xd4: ["RST4", null, "Restart with modulo 8 count “4”"],
 498+ 0xd5: ["RST5", null, "Restart with modulo 8 count “5”"],
 499+ 0xd6: ["RST6", null, "Restart with modulo 8 count “6”"],
 500+ 0xd7: ["RST7", null, "Restart with modulo 8 count “7”"],
 501+
 502+ /* Other markers */
 503+ 0xd8: ["SOI", null, "Start of image"],
 504+ 0xd9: ["EOI", null, "End of image"],
 505+ 0xda: ["SOS", null, "Start of scan"],
 506+ 0xdb: ["DQT", null, "Define quantization table(s)"],
 507+ 0xdc: ["DNL", null, "Define number of lines"],
 508+ 0xdd: ["DRI", null, "Define restart interval"],
 509+ 0xde: ["DHP", null, "Define hierarchical progression"],
 510+ 0xdf: ["EXP", null, "Expand reference component(s)"],
 511+ 0xe0: ["APP0", "_app0Handler", "Reserved for application segments"],
 512+ 0xe1: ["APP1", "_app1Handler"],
 513+ 0xe2: ["APP2", null],
 514+ 0xe3: ["APP3", null],
 515+ 0xe4: ["APP4", null],
 516+ 0xe5: ["APP5", null],
 517+ 0xe6: ["APP6", null],
 518+ 0xe7: ["APP7", null],
 519+ 0xe8: ["APP8", null],
 520+ 0xe9: ["APP9", null],
 521+ 0xea: ["APP10", null],
 522+ 0xeb: ["APP11", null],
 523+ 0xec: ["APP12", null],
 524+ 0xed: ["APP13", null],
 525+ 0xee: ["APP14", null],
 526+ 0xef: ["APP15", null],
 527+ 0xf0: ["JPG0", null], /* Reserved for JPEG extensions */
 528+ 0xf1: ["JPG1", null],
 529+ 0xf2: ["JPG2", null],
 530+ 0xf3: ["JPG3", null],
 531+ 0xf4: ["JPG4", null],
 532+ 0xf5: ["JPG5", null],
 533+ 0xf6: ["JPG6", null],
 534+ 0xf7: ["JPG7", null],
 535+ 0xf8: ["JPG8", null],
 536+ 0xf9: ["JPG9", null],
 537+ 0xfa: ["JPG10", null],
 538+ 0xfb: ["JPG11", null],
 539+ 0xfc: ["JPG12", null],
 540+ 0xfd: ["JPG13", null],
 541+ 0xfe: ["COM", null], /* Comment */
 542+
 543+ /* Reserved markers */
 544+ 0x01: ["JPG13", null], /* For temporary private use in arithmetic coding */
 545+ /* 02 -> bf are reserverd */
 546+ }
 547+
 548+ /* Private methods */
 549+ this.JpegMeta.JpegFile.prototype._addMetaGroup = function _addMetaGroup(name, description) {
 550+ var group = new JpegMeta.MetaGroup(name, description);
 551+ this[group.fieldName] = group;
 552+ this.metaGroups[group.fieldName] = group;
 553+ return group;
 554+ }
 555+
 556+ this.JpegMeta.JpegFile.prototype._parseIfd = function _parseIfd(endian, _binary_data, base, ifd_offset, tags, name, description) {
 557+ var num_fields = JpegMeta.parseNum(endian, _binary_data, base + ifd_offset, 2);
 558+ /* Per tag variables */
 559+ var i, j;
 560+ var tag_base;
 561+ var tag_field;
 562+ var type, type_field, type_size;
 563+ var num_values;
 564+ var value_offset;
 565+ var value;
 566+ var _val;
 567+ var num;
 568+ var den;
 569+
 570+ var group;
 571+
 572+ group = this._addMetaGroup(name, description);
 573+
 574+ for (var i = 0; i < num_fields; i++) {
 575+ /* parse the field */
 576+ tag_base = base + ifd_offset + 2 + (i * 12);
 577+ tag_field = JpegMeta.parseNum(endian, _binary_data, tag_base, 2);
 578+ type_field = JpegMeta.parseNum(endian, _binary_data, tag_base + 2, 2);
 579+ num_values = JpegMeta.parseNum(endian, _binary_data, tag_base + 4, 4);
 580+ value_offset = JpegMeta.parseNum(endian, _binary_data, tag_base + 8, 4);
 581+ if (this._types[type_field] === undefined) {
 582+ continue;
 583+ }
 584+ type = this._types[type_field][0];
 585+ type_size = this._types[type_field][1];
 586+
 587+ if (type_size * num_values <= 4) {
 588+ /* Data is in-line */
 589+ value_offset = tag_base + 8;
 590+ } else {
 591+ value_offset = base + value_offset;
 592+ }
 593+
 594+ /* Read the value */
 595+ if (type == "UNDEFINED") {
 596+ value = _binary_data.slice(value_offset, value_offset + num_values);
 597+ } else if (type == "ASCII") {
 598+ value = _binary_data.slice(value_offset, value_offset + num_values);
 599+ value = value.split('\x00')[0]
 600+ /* strip trail nul */
 601+ } else {
 602+ value = new Array();
 603+ for (j = 0; j < num_values; j++, value_offset += type_size) {
 604+ if (type == "BYTE" || type == "SHORT" || type == "LONG") {
 605+ value.push(JpegMeta.parseNum(endian, _binary_data, value_offset, type_size));
 606+ }
 607+ if (type == "SBYTE" || type == "SSHORT" || type == "SLONG") {
 608+ value.push(JpegMeta.parseSnum(endian, _binary_data, value_offset, type_size));
 609+ }
 610+ if (type == "RATIONAL") {
 611+ num = JpegMeta.parseNum(endian, _binary_data, value_offset, 4);
 612+ den = JpegMeta.parseNum(endian, _binary_data, value_offset + 4, 4);
 613+ value.push(new JpegMeta.Rational(num, den));
 614+ }
 615+ if (type == "SRATIONAL") {
 616+ num = JpegMeta.parseSnum(endian, _binary_data, value_offset, 4);
 617+ den = JpegMeta.parseSnum(endian, _binary_data, value_offset + 4, 4);
 618+ value.push(new JpegMeta.Rational(num, den));
 619+ }
 620+ value.push();
 621+ }
 622+ if (num_values === 1) {
 623+ value = value[0];
 624+ }
 625+ }
 626+ if (tags[tag_field] !== undefined) {
 627+ group._addProperty(tags[tag_field][1], tags[tag_field][0], value);
 628+ }
 629+ }
 630+ }
 631+
 632+ this.JpegMeta.JpegFile.prototype._jfifHandler = function _jfifHandler(mark, pos) {
 633+ if (this.jfif !== undefined) {
 634+ throw Error("Multiple JFIF segments found");
 635+ }
 636+ this._addMetaGroup("jfif", "JFIF");
 637+ this.jfif._addProperty("version_major", "Version Major", this._binary_data.charCodeAt(pos + 5));
 638+ this.jfif._addProperty("version_minor", "Version Minor", this._binary_data.charCodeAt(pos + 6));
 639+ this.jfif._addProperty("version", "JFIF Version", this.jfif.version_major.value + "." + this.jfif.version_minor.value);
 640+ this.jfif._addProperty("units", "Density Unit", this._binary_data.charCodeAt(pos + 7));
 641+ this.jfif._addProperty("Xdensity", "X density", JpegMeta.parseNum(">", this._binary_data, pos + 8, 2));
 642+ this.jfif._addProperty("Ydensity", "Y Density", JpegMeta.parseNum(">", this._binary_data, pos + 10, 2));
 643+ this.jfif._addProperty("Xthumbnail", "X Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 12, 1));
 644+ this.jfif._addProperty("Ythumbnail", "Y Thumbnail", JpegMeta.parseNum(">", this._binary_data, pos + 13, 1));
 645+ }
 646+
 647+
 648+ /* Handle app0 segments */
 649+ this.JpegMeta.JpegFile.prototype._app0Handler = function app0Handler(mark, pos) {
 650+ var ident = this._binary_data.slice(pos, pos + 5);
 651+ if (ident == this._JFIF_IDENT) {
 652+ this._jfifHandler(mark, pos);
 653+ } else if (ident == this._JFXX_IDENT) {
 654+ /* Don't handle JFXX Ident yet */
 655+ } else {
 656+ /* Don't know about other idents */
 657+ }
 658+ }
 659+
 660+
 661+ /* Handle app1 segments */
 662+ this.JpegMeta.JpegFile.prototype._app1Handler = function _app1Handler(mark, pos) {
 663+ var ident = this._binary_data.slice(pos, pos + 5);
 664+ if (ident == this._EXIF_IDENT) {
 665+ this._exifHandler(mark, pos + 6);
 666+ } else {
 667+ /* Don't know about other idents */
 668+ }
 669+ }
 670+
 671+ /* Handle exif segments */
 672+ JpegMeta.JpegFile.prototype._exifHandler = function _exifHandler(mark, pos) {
 673+ if (this.exif !== undefined) {
 674+ throw new Error("Multiple JFIF segments found");
 675+ }
 676+
 677+ /* Parse this TIFF header */
 678+ var endian;
 679+ var magic_field;
 680+ var ifd_offset;
 681+ var primary_ifd, exif_ifd, gps_ifd;
 682+ var endian_field = this._binary_data.slice(pos, pos + 2);
 683+
 684+ /* Trivia: This 'I' is for Intel, the 'M' is for Motorola */
 685+ if (endian_field === "II") {
 686+ endian = "<";
 687+ } else if (endian_field === "MM") {
 688+ endian = ">";
 689+ } else {
 690+ throw new Error("Malformed TIFF meta-data. Unknown endianess: " + endian_field);
 691+ }
 692+
 693+ magic_field = JpegMeta.parseNum(endian, this._binary_data, pos + 2, 2);
 694+
 695+ if (magic_field !== 42) {
 696+ throw new Error("Malformed TIFF meta-data. Bad magic: " + magic_field);
 697+ }
 698+
 699+ ifd_offset = JpegMeta.parseNum(endian, this._binary_data, pos + 4, 4);
 700+
 701+ /* Parse 0th IFD */
 702+ this._parseIfd(endian, this._binary_data, pos, ifd_offset, this._tifftags, "tiff", "TIFF");
 703+
 704+ if (this.tiff.ExifIfdPointer) {
 705+ this._parseIfd(endian, this._binary_data, pos, this.tiff.ExifIfdPointer.value, this._exiftags, "exif", "Exif");
 706+ }
 707+
 708+ if (this.tiff.GPSInfoIfdPointer) {
 709+ this._parseIfd(endian, this._binary_data, pos, this.tiff.GPSInfoIfdPointer.value, this._gpstags, "gps", "GPS");
 710+ if (this.gps.GPSLatitude) {
 711+ var latitude;
 712+ latitude = this.gps.GPSLatitude.value[0].asFloat() +
 713+ (1 / 60) * this.gps.GPSLatitude.value[1].asFloat() +
 714+ (1 / 3600) * this.gps.GPSLatitude.value[2].asFloat();
 715+ if (this.gps.GPSLatitudeRef.value === "S") {
 716+ latitude = -latitude;
 717+ }
 718+ this.gps._addProperty("latitude", "Dec. Latitude", latitude);
 719+ }
 720+ if (this.gps.GPSLongitude) {
 721+ var longitude;
 722+ longitude = this.gps.GPSLongitude.value[0].asFloat() +
 723+ (1 / 60) * this.gps.GPSLongitude.value[1].asFloat() +
 724+ (1 / 3600) * this.gps.GPSLongitude.value[2].asFloat();
 725+ if (this.gps.GPSLongitudeRef.value === "W") {
 726+ longitude = -longitude;
 727+ }
 728+ this.gps._addProperty("longitude", "Dec. Longitude", longitude);
 729+ }
 730+ }
 731+ };
 732+
 733+ /* JsJpegMeta ends here */
 734+
 735+ mw.util.jpegmeta = function( fileReaderResult, fileName ) {
 736+ return new JpegMeta.JpegFile( fileReaderResult, fileName );
 737+ };
 738+
 739+} )( jQuery, mediaWiki );
Property changes on: trunk/phase3/resources/mediawiki.util/mediawiki.util.jpegmeta.js
___________________________________________________________________
Added: svn:eol-style
1740 + native
Index: trunk/phase3/resources/Resources.php
@@ -355,6 +355,9 @@
356356 'dependencies' => array( 'jquery.checkboxShiftClick', 'jquery.client', 'jquery.cookie', 'jquery.placeholder', 'jquery.makeCollapsible' ),
357357 'debugScripts' => 'resources/mediawiki.util/mediawiki.util.test.js',
358358 ),
 359+ 'mediawiki.util.jpegmeta' => array(
 360+ 'scripts' => 'resources/mediawiki.util/mediawiki.util.jpegmeta.js',
 361+ ),
359362 'mediawiki.action.history' => array(
360363 'scripts' => 'resources/mediawiki.action/mediawiki.action.history.js',
361364 'dependencies' => 'mediawiki.legacy.history',
@@ -385,6 +388,7 @@
386389 // @todo: merge in remainder of mediawiki.legacy.upload
387390 'scripts' => 'resources/mediawiki.special/mediawiki.special.upload.js',
388391 'messages' => array( 'widthheight', 'size-bytes', 'size-kilobytes', 'size-megabytes', 'size-gigabytes' ),
 392+ 'dependencies' => array( 'mediawiki.util.jpegmeta' ),
389393 ),
390394 'mediawiki.language' => array(
391395 'scripts' => 'resources/mediawiki.language/mediawiki.language.js',
Index: trunk/phase3/resources/mediawiki.special/mediawiki.special.upload.js
@@ -58,9 +58,27 @@
5959 spinner.src = mw.config.get( 'wgScriptPath' ) + '/skins/common/images/spinner.gif';
6060 $( '#mw-htmlform-source' ).parent().prepend( thumb );
6161
62 - fetchPreview( file, function( dataURL ) {
 62+ var meta;
 63+ fetchPreview( file, function( dataURL ) {
6364 var img = new Image(),
6465 rotation = 0;
 66+
 67+ if ( meta && meta.tiff.Orientation ) {
 68+ rotation = (360 - function () {
 69+ // See includes/media/Bitmap.php
 70+ switch ( meta.tiff.Orientation.value ) {
 71+ case 8:
 72+ return 90;
 73+ case 3:
 74+ return 180;
 75+ case 6:
 76+ return 270;
 77+ default:
 78+ return 0;
 79+ }
 80+ }() ) % 360;
 81+ }
 82+
6583 img.onload = function() {
6684 // Fit the image within the previewSizexpreviewSize box
6785 if ( img.width > img.height ) {
@@ -83,6 +101,7 @@
84102 y = dy;
85103 break;
86104 case 90:
 105+
87106 x = dx;
88107 y = dy - previewSize;
89108 break;
@@ -106,22 +125,44 @@
107126 $( '#mw-upload-thumbnail .fileinfo' ).text( info );
108127 };
109128 img.src = dataURL;
110 - } );
 129+ }, mediaWiki.config.get( 'wgFileCanRotate' ) ? function ( data ) {
 130+ try {
 131+ meta = mediaWiki.util.jpegmeta( data, file.fileName );
 132+ meta._binary_data = null;
 133+ } catch ( e ) {
 134+ meta = null;
 135+ }
 136+ } : null );
111137 }
112138
113139 /**
114140 * Start loading a file into memory; when complete, pass it as a
115 - * data URL to the callback function.
 141+ * data URL to the callback function. If the callbackBinary is set it will
 142+ * first be read as binary and afterwards as data URL. Useful if you want
 143+ * to do preprocessing on the binary data first.
116144 *
117145 * @param {File} file
118146 * @param {function} callback
 147+ * @param {function} callbackBinary
119148 */
120 - function fetchPreview( file, callback ) {
 149+ function fetchPreview( file, callback, callbackBinary ) {
121150 var reader = new FileReader();
122151 reader.onload = function() {
123 - callback( reader.result );
 152+ if ( callbackBinary ) {
 153+ callbackBinary( reader.result );
 154+ reader.onload = function() {
 155+ callback( reader.result );
 156+ }
 157+ reader.readAsDataURL( file );
 158+ } else {
 159+ callback( reader.result );
 160+ }
124161 };
125 - reader.readAsDataURL( file );
 162+ if ( callbackBinary ) {
 163+ reader.readAsBinaryString( file );
 164+ } else {
 165+ reader.readAsDataURL( file );
 166+ }
126167 }
127168
128169 /**

Follow-up revisions

RevisionCommit summaryAuthorDate
r81208Follow-up r80775: Check for meta.tiff as well....btongminh14:26, 30 January 2011

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r79867Follow-up r79845: Add rotation support to the upload preview using the <canva...btongminh16:24, 8 January 2011

Comments

#Comment by Platonides (talk | contribs)   23:49, 29 January 2011

Error inside the if ( meta && meta.tiff.Orientation ) because there's no meta.tiff (it probably needs to be meta && meta.tiff && meta.tiff.Orientation).

#Comment by Bryan (talk | contribs)   12:45, 2 February 2011
#Comment by Krinkle (talk | contribs)   19:08, 24 August 2011

+ this.JpegMeta = JpegMeta; // I have no clue why I need this magic... -- Bryan

I stumbled across this today, here's the answer:

  • The simple answer, later in the code it refers to this.JpegMeta when declaring new properties. So that's why it's needed there. That need could be removed by replacing uses of this.JpegMeta with JpegMeta (the local variable)
  • So what's "this" referring to here anyway ? It's referring to the global window object. So it's actually declaring window.JpegMeta (at least for us that's what it's doing). We probably don't want that, since we're putting it into mw.libs.jpegmeta.

I would suggest that we:

  • Remove this.JpegMeta, so that we no longer have it as a global variable
  • Keep the scrip functional by replacing uses of this.JpegMeta with JpegMeta (which is a general good practice and should be offered upstream)

Status & tagging log