EXIFのタグをJavascriptで読む
MOON GIFTに記載されているexif.jsでは、リトルエンディアンの場合、バグがありましたので、修正しました。
//MOON GIFT(http://www.moongift.jp/2012/05/20120502-2/)を一部改変
Exif = {};
(function () {
var MARKER_SOI = 0xffd8;
var MARKER_APP1 = 0xffe1;
var FMT_SIZE = [
1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8
];
function BinaryReader(arr, endian) {
this.arr = new Uint8Array(arr);
this.endian = endian || 0;
this.cur = 0;
this._stack = ;
}
BinaryReader.LITTLE_ENDIAN = 0;
BinaryReader.BIG_ENDIAN = 1;
BinaryReader.SEEK_ABSOLUTELY = 0;
BinaryReader.SEEK_RELATIVELY = 1;
BinaryReader._makeReadMethod = function (size) {
return function (endian) {
var target;
typeof endian === "undefined" && (endian = this.endian);
target = this.arr.subarray(this.cur, this.cur + size);
this.cur += size;
return BinaryReader.pack(target, endian);
};
};
BinaryReader.pack = function (arr, endian) {
var f = endian === BinaryReader.LITTLE_ENDIAN
? Array.prototype.reduceRight // Little Endian
: Array.prototype.reduce; // Big Endian
return f.call(arr, function (a, b) {
return (a << 8) + b;
}, 0);
};
BinaryReader.pack_signed = function (arr, endian) {
var n = BinaryReader.pack(arr, endian);
if (n & (1 << arr.length * 8 - 1)) {
n = n - (Math.pow(2, arr.length * 8));
}
return n;
}
BinaryReader.packstr = function (arr) {
var res = "";
for (var i=0; i<arr.length; i++)
res += String.fromCharCode(arr[i]);
return res;
//return Array.prototype.map.call(arr, String.fromCharCode).join("");
};
BinaryReader.prototype = {
constructor : BinaryReader,
setEndian : function (endian) {
this.endian = endian;
},
byte : BinaryReader._makeReadMethod(2),
word : BinaryReader._makeReadMethod(4),
dword : BinaryReader._makeReadMethod(8),
skip : function (size) {
this.cur += size;
return this;
},
str : function (size) {
var arr = this.arr.subarray(this.cur, this.cur + size);
this.cur += size;
return BinaryReader.packstr(arr);
},
getarr : function (size) {
var ret = this.arr.subarray(this.cur, this.cur + size);
this.cur += size;
return ret;
},
seek : function (offset, type) {
if (type === BinaryReader.SEEK_RELATIVELY) {
this.cur += offset;
} else {
//default
this.cur = offset;
}
return this;
},
push : function () {
this._stack.push(this.cur);
return this;
},
pop : function () {
this.cur = this._stack.pop();
return this;
}
};
Exif.loadFromArrayBuffer = function (arr) {
var soi, app1 = {}, ifd0, subifd, gpsifd, base, endian;
var reader = new BinaryReader(arr, BinaryReader.BIG_ENDIAN);
//check magic byte
soi = reader.byte();
if (soi !== MARKER_SOI) {
throw "Not JPG file format";
}
//check if file format is Exif
while(1){
app1.marker = reader.byte();
app1.size = reader.byte();
if (app1.marker == MARKER_APP1) {
break;
}else if(app1.marker == 0xfffe || !app1.marker){
throw "Not Exif file format";
}
reader.skip(app1.size-2);
}
app1.exifHeader = reader.str(4);
base = reader.skip(2).cur;
app1.endian = reader.byte();
if (app1.endian === 0x4949) {
reader.setEndian(BinaryReader.LITTLE_ENDIAN);
endian = BinaryReader.LITTLE_ENDIAN;
} else {
endian = BinaryReader.BIG_ENDIAN;
}
app1.ifd0Offset = reader.skip(2).word();
//load IFD0
reader.seek(base + app1.ifd0Offset);
ifd0 = Exif.nameIfd(loadIfd(endian), Exif.IFD0_TABLE);
reader.seek(base + ifd0.exifIFDPointer);
subifd = Exif.nameIfd(loadIfd(endian), Exif.SUBIFD_TABLE);
if ("gpsInfoIFDPointer" in ifd0) {
reader.seek(base + ifd0.gpsInfoIFDPointer);
gpsifd = Exif.nameIfd(loadIfd(endian), Exif.GPSIFD_TABLE);
}
return {
ifd0 : ifd0,
subifd : subifd,
gpsifd : gpsifd
};
function loadIfd(endian) {
var ifd = {}, entries = , i, j, size, bytesLen, data;
ifd.entryLength = reader.byte();
ifd.entries = entries;
for (i = 0; i < ifd.entryLength; i++) {
entries[i] = {};
entries[i].tag = reader.byte();
entries[i].fmt = reader.byte();
entries[i].len = reader.word();
size = FMT_SIZE[entries[i].fmt - 1];
bytesLen = size * entries[i].len;
if (bytesLen <= 4) {
data = reader.getarr(4);
} else {
data = reader.word();
reader.push();
reader.seek(base + data);
data = reader.getarr(bytesLen);
reader.pop();
}
switch (entries[i].fmt) {
case 1: //unsigned byte
case 3: //unsigned short
case 4: //unsigned long
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] = BinaryReader.pack(data.subarray(j * size, (j + 1) * size), endian);
}
entries[i].len === 1 && (entries[i].data = entries[i].data[0]);
break;
case 6: //signed byte
case 8: //signed short
case 9: //signed long
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] = BinaryReader.pack_signed(data.subarray(j * size, (j + 1) * size), endian);
}
entries[i].len === 1 && (entries[i].data = entries[i].data[0]);
break;
case 2: //ascii string
entries[i].data = BinaryReader.packstr(data);
break;
case 5: //unsigned raditional
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] =
BinaryReader.pack(data.subarray(j * 8 , j * 8 + 4), endian) /
BinaryReader.pack(data.subarray(j * 8 + 4, j * 8 + 8), endian)
;
}
break;
case 10: //signed raditional
entries[i].data = ;
for (j = 0; j < entries[i].len; j++) {
entries[i].data[j] =
BinaryReader.pack(data.subarray(j * 8 , j * 8 + 4), endian) /
BinaryReader.pack(data.subarray(j * 8 + 4, j * 8 + 8), endian)
;
}
break;
default:
entries[i].data = data;
}
}
ifd.next = reader.word();
return ifd;
}
};
Exif.nameIfd = function (ifd, table) {
var nifd = {}, entries = ifd.entries, name;
for (var i = 0; i < ifd.entryLength; i++) {
name = table[entries[i].tag];
nifd[name || entries[i].tag] = ifd.entries[i].data;
}
return nifd;
};
Exif.IFD0_TABLE = {
256 : "mageWidth",
257 : "imageLength",
258 : "bitsPerSample",
259 : "compression",
262 : "photometricInterpretation",
274 : "orientation",
277 : "samplesPerPixel",
284 : "planarConfiguration",
530 : "yCbCrSubSampling",
531 : "yCbCrPositioning",
282 : "xResolution",
283 : "yResolution",
296 : "resolutionUnit",
273 : "stripOffsets",
278 : "rowsPerStrip",
279 : "stripByteCounts",
513 : "jPEGInterchangeFormat",
514 : "jPEGInterchangeFormatLength",
301 : "transferFunction",
318 : "whitePoint",
319 : "primaryChromaticities",
529 : "yCbCrCoefficients",
532 : "referenceBlackWhite",
306 : "dateTime",
270 : "imageDescription",
271 : "make",
272 : "model",
305 : "software",
315 : "artist",
3432 : "copyright",
34665 : "exifIFDPointer",
34853 : "gpsInfoIFDPointer"
};
Exif.SUBIFD_TABLE = {
36864 : "exifVersion",
40960 : "flashPixVersion",
40961 : "colorSpace",
37121 : "componentsConfiguration",
37122 : "compressedBitsPerPixel",
40962 : "pixelXDimension",
40963 : "pixelYDimension",
37500 : "makerNote",
37510 : "userComment",
40964 : "relatedSoundFile",
36867 : "dateTimeOriginal",
36868 : "dateTimeDigitized",
37520 : "subSecTime",
37521 : "subSecTimeOriginal",
37522 : "subSecTimeDigitized",
33434 : "exposureTime",
33437 : "fNumber",
34850 : "exposureProgram",
34852 : "spectralSensitivity",
34855 : "iSOSpeedRatings",
34856 : "oECF",
37377 : "shutterSpeedValue",
37378 : "apertureValue",
37379 : "brightnessValue",
37380 : "exposureBiasValue",
37381 : "maxApertureValue",
37382 : "subjectDistance",
37383 : "meteringMode",
37384 : "lightSource",
37385 : "flash",
37386 : "focalLength",
41483 : "flashEnergy",
41484 : "spatialFrequencyResponse",
41486 : "focalPlaneXResolution",
41487 : "focalPlaneYResolution",
41488 : "focalPlaneResolutionUnit",
41492 : "subjectLocation",
41493 : "exposureIndex",
41495 : "sensingMethod",
41728 : "fileSource",
41729 : "sceneType",
41730 : "cFAPattern",
40965 : "interoperabilityIFDPointer"
};
Exif.GPSIFD_TABLE = {
0 : "versionID",
1 : "latitudeRef",
2 : "latitude",
3 : "longitudeRef",
4 : "longitude",
5 : "altitudeRef",
6 : "altitude",
7 : "timeStamp",
8 : "satellites",
9 : "status",
10 : "measureMode",
11 : "dOP",
12 : "speedRef",
13 : "speed",
14 : "trackRef",
15 : "track",
16 : "imgDirectionRef",
17 : "imgDirection",
18 : "mapDatum",
19 : "destLatitudeRef",
20 : "destLatitude",
21 : "destLongitudeRef",
22 : "destLongitude",
23 : "bearingRef",
24 : "bearing",
25 : "destDistanceRef",
26 : "destDistance"
};
})();