<apex:component >
|
<script type="text/javascript">
|
;
|
'use strict';
|
angular.module('alImageService', [])
|
|
// 画像情報を保持する
|
.factory('alPhotos', ['$q', 'svcSettings', 'alImageUtil', function($q, svcSettings, alImageUtil) {
|
var photos =[];
|
|
|
var getDecimalFormat = function(val) {
|
if (!angular.isArray(val) || val.length != 3) return 0;
|
return val[0].numerator/val[0].denominator
|
+val[1].numerator/(val[1].denominator * 60)
|
+val[2].numerator/(val[2].denominator * 3600);
|
}
|
|
// 元ファイルの画像の圧縮率 と相応する 設定されたファイルサイズ上限値における 長辺のピクセル数
|
var getLongSidePixcels = function(org_size, org_x, org_y, maxSize) {
|
// alert(org_size + ' ' + org_x + ' ' + org_y + ' ' + maxSize);
|
var ratio = (org_x * org_y)/org_size // 元ファイルの画像の圧縮率
|
// alert('ratio : ' + ratio);
|
var result = Math.sqrt((maxSize * ratio * 4) /3); // 元ファイルの画像の圧縮率 と相応する長辺のピクセル数
|
// alert('result : ' + result);
|
return result;
|
}
|
|
// dataUrl から 縮小した
|
// File から所定のデータを取得 EXIFデータも読む
|
//
|
var createFileObjctEx = function(imageFile, maxSize, longSidePixcel, jpegQuality) {
|
var defer = $q.defer();
|
var file = {"file": null, //imageFile, // オリジナルの File
|
"blob": null, // 縮小したBlob
|
"data": null, // 縮小した dataUrl の予定
|
"name": imageFile.name,
|
"type": imageFile.type,
|
"org_size": imageFile.size + 400000, // iPhoneだと ファイルサイズが実際より小さい値になってるので、これで
|
"height": "*", // ピクセル数
|
"width": "*", //
|
"yoko": true, // 横?
|
"position": {"lat":null, "lng":null},
|
"exif": null}; // EXIF文字列
|
var reader = new FileReader();
|
reader.onload = function () {
|
file.data = reader.result;
|
var image = new Image();
|
image.onload = function() {
|
EXIF.getData(image, function() {
|
file.exif = EXIF.pretty(this);
|
// file.exif = JSON.parse(EXIF.pretty(this));
|
// EXIFがない画像ファイルなら、480☓480
|
// 緯度:35度38分45秒54 経度:139度42分35秒97
|
var longSide = longSidePixcel;
|
var org_x = EXIF.getTag(this, "PixelXDimension");
|
var org_y = EXIF.getTag(this, "PixelYDimension");
|
if (angular.isDefined(org_x) && angular.isDefined(org_y)) {
|
longSide = getLongSidePixcels(file.org_size, org_x, org_y, maxSize);
|
}
|
file.position.lat = file.exif.length == 0 ? 0 : getDecimalFormat( EXIF.getTag(this, "GPSLatitude"));
|
file.position.lng = file.exif.length == 0 ? 0 : getDecimalFormat( EXIF.getTag(this, "GPSLongitude"));
|
file.yoko = file.width > file.height;
|
alImageUtil.getResizedData(image.src, longSide, jpegQuality, function(res) {
|
file.blob = res.blob;
|
file.data = res.data;
|
file.height = res.height;
|
file.width = res.width;
|
defer.resolve(file);
|
});
|
});
|
};
|
image.src = reader.result;
|
};
|
reader.readAsDataURL(imageFile);
|
return defer.promise;
|
};
|
|
return {
|
all: function() {
|
return photos;
|
},
|
get: function(index) {
|
if (index >=0 && index < photos.length)
|
return photos[index];
|
else
|
return null;
|
|
},
|
// 写真の削除
|
remove: function(photo) {
|
photos.splice(photos.indexOf(photo), 1);
|
},
|
// 写真の追加
|
appendFile: function(file, config, callbackSuccess, callbackFailure) {
|
// ファイル情報の取得、縮小して、保持するオブジェクトを作成
|
createFileObjctEx(file, config.maxFileSize, config.longSidePixcel, config.jpegQuality).then(function(res) {
|
photos.push(res);
|
callbackSuccess(res);
|
},
|
function() {
|
callbackFailure(null);
|
});
|
},
|
|
clear: function() {
|
photos.splice(0, photos.length);
|
},
|
size: function() {
|
return photos.length;
|
}
|
|
};
|
}])
|
// ローカルセッション使うよう
|
.factory('svLocalstorage', ['$window', function($window) {
|
return {
|
set: function(key, value) {
|
$window.localStorage['sv-'+key] = value;
|
},
|
get: function(key, defaultValue) {
|
return $window.localStorage['sv-'+key] || defaultValue;
|
},
|
setObject: function(key, value) {
|
$window.localStorage['sv-'+key] = JSON.stringify(value);
|
},
|
getObject: function(key) {
|
return JSON.parse($window.localStorage['sv-'+key] || '{}');
|
}
|
}
|
}])
|
// 設定の取得・保存
|
.factory('svcSettings', ['$window', function($window) {
|
return {
|
set: function(value) {
|
$window.localStorage['sv-settings'] = JSON.stringify(value);
|
},
|
get: function() {
|
return JSON.parse($window.localStorage['sv-settings'] ||
|
// 設定のデフォルト値
|
'{ "doubleSide": true, ' + // 初期設定 撮影ごと変更可能
|
' "viewMode": "list", ' +
|
' "imageSize": "600", ' + // 長辺のピクセル数
|
' "repeatExchangeDate": true, ' +
|
' "repeatMemo": true, ' +
|
' "rememberListName": true, ' +
|
' "previousListName": "", ' +
|
' "freehandMemo": false, ' +
|
' "frontOnly": false, ' + //
|
' "geoLocation": true}');
|
}
|
}
|
}])
|
|
.factory('alImageUtil', ['$q', function($q) {
|
return {
|
createBlobFromDataUrl: createBlobFromDataUrl,
|
getResizedData: getResizedData,
|
};
|
|
function createBlobFromDataUrl(dataurl, type, name) {
|
var barr, bin, i, len, type = type || 'image/jpeg';
|
bin = atob(dataurl.split("base64,")[1]);
|
len = bin.length;
|
barr = new Uint8Array(len);
|
i = 0;
|
while (i < len) {
|
barr[i] = bin.charCodeAt(i);
|
i++;
|
}
|
return new Blob([barr], {
|
// name: name,
|
type: type
|
});
|
}
|
|
function getResizedData(data, size, jpegQuality, callbackSuccess, callbackFailure) {
|
var result ={"blob": null, "data":null, "width":0, "height": 0};
|
var maxWidth = size;
|
var maxHeight = size;
|
var img = new Image();
|
|
img.onload = function() {
|
|
var iw = img.naturalWidth, ih = img.naturalHeight;
|
var width = iw, height = ih;
|
|
var orientation;
|
|
// JPEGの場合には、EXIFからOrientation(回転)情報を取得
|
if (data.split(',')[0].match('jpeg')) {
|
orientation = getOrientation(data);
|
}
|
// JPEG以外や、JPEGでもEXIFが無い場合などには、標準の値に設定
|
orientation = orientation || 1;
|
|
// 90度回転など、縦横が入れ替わる場合には事前に最大幅、高さを入れ替えておく
|
if (orientation > 4) {
|
var tmpMaxWidth = maxWidth;
|
maxWidth = maxHeight;
|
maxHeight = tmpMaxWidth;
|
}
|
|
if(width > maxWidth || height > maxHeight) {
|
var ratio = width/maxWidth;
|
if(ratio <= height/maxHeight) {
|
ratio = height/maxHeight;
|
}
|
width = Math.floor(img.width/ratio);
|
height = Math.floor(img.height/ratio);
|
}
|
|
var canvas = $('<canvas>');
|
var ctx = canvas[0].getContext('2d');
|
ctx.save();
|
|
// EXIFのOrientation情報からCanvasを回転させておく
|
transformCoordinate(canvas, width, height, orientation);
|
|
// iPhoneのサブサンプリング問題の回避
|
// see http://d.hatena.ne.jp/shinichitomita/20120927/1348726674
|
var subsampled = detectSubsampling(img);
|
if (subsampled) {
|
iw /= 2;
|
ih /= 2;
|
}
|
var d = 1024; // size of tiling canvas
|
var tmpCanvas = $('<canvas>');
|
tmpCanvas[0].width = tmpCanvas[0].height = d;
|
var tmpCtx = tmpCanvas[0].getContext('2d');
|
var vertSquashRatio = detectVerticalSquash(img, iw, ih);
|
var dw = Math.ceil(d * width / iw);
|
var dh = Math.ceil(d * height / ih / vertSquashRatio);
|
var sy = 0;
|
var dy = 0;
|
while (sy < ih) {
|
var sx = 0;
|
var dx = 0;
|
while (sx < iw) {
|
tmpCtx.clearRect(0, 0, d, d);
|
tmpCtx.drawImage(img, -sx, -sy);
|
ctx.drawImage(tmpCanvas[0], 0, 0, d, d, dx, dy, dw, dh);
|
sx += d;
|
dx += dw;
|
}
|
sy += d;
|
dy += dh;
|
}
|
ctx.restore();
|
tmpCanvas = tmpCtx = null;
|
|
// プレビューするために<img>用のDataURLを作成
|
// (スマホなどの狭小画面でも画像の全体図が見れるように、解像度を保ったたま縮小表示したいので)
|
var displaySrc = ctx.canvas.toDataURL('image/jpeg', jpegQuality/10); // 2016.07.07 JPEG品質は設定から指定
|
|
// FormDataに縮小後の画像データを追加する
|
// Blob形式にすることで、サーバ側は従来のままのコードで対応可能
|
var blob = dataURLtoBlob(displaySrc);
|
result.data = displaySrc;
|
result.blob = blob;
|
result.width = width;
|
result.height = height;
|
callbackSuccess(result);
|
}
|
img.src = data;
|
|
|
// JPEGのEXIFからOrientationのみを取得する
|
var getOrientation = function(imgDataURL) {
|
var byteString = atob(imgDataURL.split(',')[1]);
|
var orientaion = byteStringToOrientation(byteString);
|
return orientaion;
|
|
function byteStringToOrientation(img){
|
var head = 0;
|
var orientation;
|
while (1){
|
if (img.charCodeAt(head) == 255 & img.charCodeAt(head + 1) == 218) {break;}
|
if (img.charCodeAt(head) == 255 & img.charCodeAt(head + 1) == 216) {
|
head += 2;
|
}
|
else {
|
var length = img.charCodeAt(head + 2) * 256 + img.charCodeAt(head + 3);
|
var endPoint = head + length + 2;
|
if (img.charCodeAt(head) == 255 & img.charCodeAt(head + 1) == 225) {
|
var segment = img.slice(head, endPoint);
|
var bigEndian = segment.charCodeAt(10) == 77;
|
if (bigEndian) {
|
var count = segment.charCodeAt(18) * 256 + segment.charCodeAt(19);
|
} else {
|
var count = segment.charCodeAt(18) + segment.charCodeAt(19) * 256;
|
}
|
for (var i=0;i<count;i++){
|
var field = segment.slice(20 + 12 * i, 32 + 12 * i);
|
if ((bigEndian && field.charCodeAt(1) == 18) || (!bigEndian && field.charCodeAt(0) == 18)) {
|
orientation = bigEndian ? field.charCodeAt(9) : field.charCodeAt(8);
|
}
|
}
|
break;
|
}
|
head = endPoint;
|
}
|
if (head > img.length){break;}
|
}
|
return orientation;
|
}
|
}
|
|
// iPhoneのサブサンプリングを検出
|
var detectSubsampling = function(img) {
|
var iw = img.naturalWidth, ih = img.naturalHeight;
|
if (iw * ih > 1024 * 1024) {
|
var canvas = $('<canvas>');
|
canvas[0].width = canvas[0].height = 1;
|
var ctx = canvas[0].getContext('2d');
|
ctx.drawImage(img, -iw + 1, 0);
|
return ctx.getImageData(0, 0, 1, 1).data[3] === 0;
|
} else {
|
return false;
|
}
|
}
|
|
// iPhoneの縦画像でひしゃげて表示される問題
|
var detectVerticalSquash = function (img, iw, ih) {
|
var canvas = $('<canvas>');
|
canvas[0].width = 1;
|
canvas[0].height = ih;
|
var ctx = canvas[0].getContext('2d');
|
ctx.drawImage(img, 0, 0);
|
var data = ctx.getImageData(0, 0, 1, ih).data;
|
var sy = 0;
|
var ey = ih;
|
var py = ih;
|
while (py > sy) {
|
var alpha = data[(py - 1) * 4 + 3];
|
if (alpha === 0) {
|
ey = py;
|
} else {
|
sy = py;
|
}
|
py = (ey + sy) >> 1;
|
}
|
var ratio = (py / ih);
|
return (ratio===0)?1:ratio;
|
}
|
|
|
var transformCoordinate = function (canvas, width, height, orientation) {
|
if (orientation > 4) {
|
canvas[0].width = height;
|
canvas[0].height = width;
|
} else {
|
canvas[0].width = width;
|
canvas[0].height = height;
|
}
|
var ctx = canvas[0].getContext('2d');
|
switch (orientation) {
|
case 2:
|
// horizontal flip
|
ctx.translate(width, 0);
|
ctx.scale(-1, 1);
|
break;
|
case 3:
|
// 180 rotate left
|
ctx.translate(width, height);
|
ctx.rotate(Math.PI);
|
break;
|
case 4:
|
// vertical flip
|
ctx.translate(0, height);
|
ctx.scale(1, -1);
|
break;
|
case 5:
|
// vertical flip + 90 rotate right
|
ctx.rotate(0.5 * Math.PI);
|
ctx.scale(1, -1);
|
break;
|
case 6:
|
// 90 rotate right
|
ctx.rotate(0.5 * Math.PI);
|
ctx.translate(0, -height);
|
break;
|
case 7:
|
// horizontal flip + 90 rotate right
|
ctx.rotate(0.5 * Math.PI);
|
ctx.translate(width, -height);
|
ctx.scale(-1, 1);
|
break;
|
case 8:
|
// 90 rotate left
|
ctx.rotate(-0.5 * Math.PI);
|
ctx.translate(-width, 0);
|
break;
|
default:
|
break;
|
}
|
}
|
|
var dataURLtoArrayBuffer = function(data) {
|
var byteString = atob(data.split(',')[1]);
|
var ab = new ArrayBuffer(byteString.length);
|
var ia = new Uint8Array(ab);
|
for (var i = 0; i < byteString.length; i++) {
|
ia[i] = byteString.charCodeAt(i);
|
}
|
return ab
|
}
|
|
var dataURLtoBlob = function (data) {
|
var mimeString = data.split(',')[0].split(':')[1].split(';')[0];
|
var ab = dataURLtoArrayBuffer(data);
|
var bb = (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder);
|
if (bb) {
|
bb = new (window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder)();
|
bb.append(ab);
|
return bb.getBlob(mimeString);
|
} else {
|
bb = new Blob([ab], {
|
'type': (mimeString)
|
});
|
return bb;
|
}
|
}
|
// バイナリ化した画像をPOSTで送る関数
|
// http://blog.sarabande.jp/post/30694191998
|
var sendImageBinary = function(blob) {
|
var formData = new FormData();
|
formData.append('acceptImage', blob);
|
$.ajax({
|
type: 'POST',
|
url: 'image-accept2.php',
|
data: formData,
|
contentType: false,
|
processData: false,
|
success:function(date, dataType){
|
var $img = $('img');
|
var imgSrc = $img.attr('src');
|
$img.attr('src', "");
|
$img.attr('src', imgSrc + '?' + (new Date())*1);
|
},
|
error: function(XMLHttpRequest, textStatus, errorThrown) {
|
}
|
});
|
};
|
}
|
// 引数のBase64の文字列をBlob形式にしている
|
var base64ToBlob = function(base64){
|
var base64Data = base64.split(',')[1], // Data URLからBase64のデータ部分のみを取得
|
data = window.atob(base64Data), // base64形式の文字列をデコード
|
buff = new ArrayBuffer(data.length),
|
arr = new Uint8Array(buff),
|
blob, i, dataLen;
|
|
// blobの生成
|
for( i = 0, dataLen = data.length; i < dataLen; i++){
|
arr[i] = data.charCodeAt(i);
|
}
|
blob = new Blob([arr], {type: 'image/png'});
|
return blob;
|
}
|
}])
|
|
// モーダル ダイアログ用
|
.factory('alModal', ['$uibModal', '$log', '$q', function($uibModal, $log, $q) {
|
|
return {
|
openLoading : openLoading, // ファイル開くとき用
|
closeLoading : closeLoading, // ファイル開くとき用のを閉じる
|
openConfirm : openConfirm, // 送信確認
|
openProgress : openProgress, // 送信中
|
openWarning : openWarning, // エラー|警告 表示
|
openImage : openImage
|
}
|
|
var modalLoadingInstance = null;
|
|
function openLoading() {
|
modalLoadingInstance = $uibModal.open({
|
animation: false,
|
template: ' ',
|
// templateUrl: ',
|
// windowClass: 'width: 100px; opacity:0',
|
// windowClass: 'border-width: 0px;',
|
windowTemplateUrl : 'loadingWindow.html',
|
// backdropClass: "background-image: url('images/ajax-loader.gif')",
|
size: 'sm',
|
backdrop: true,
|
});
|
return modalLoadingInstance;
|
}
|
|
function closeLoading() {
|
if (modalLoadingInstance) {
|
modalLoadingInstance.close();
|
modalLoadingInstance = null;
|
}
|
}
|
|
// 処理実行 確認用
|
function openConfirm(scope, templateUrl) {
|
var defer = $q.defer();
|
var modalInstance = $uibModal.open({
|
scope: scope,
|
animation: true,
|
backdrop: 'static',
|
templateUrl: templateUrl,
|
// controller: 'ModalInstanceCtrl',
|
size: 'sm',
|
});
|
|
// modalInstance.result.then(function(res) {
|
modalInstance.result.then(function() {
|
$log.info('Confirm OK at: ' + new Date());
|
// scope.status.showLog = res;
|
modalInstance = null;
|
defer.resolve('OK');
|
}, function () {
|
$log.info('Confirm dismissed at: ' + new Date());
|
modalInstance = null;
|
defer.reject('Cancel');
|
});
|
return defer.promise;
|
}
|
|
// 送信中+結果表示
|
function openProgress(scope) {
|
var defer = $q.defer();
|
var modalInstance = $uibModal.open({
|
scope: scope,
|
animation: true,
|
backdrop: 'static',
|
templateUrl: 'uploading.html',
|
controller: 'redirectCtrl',
|
size: 'lg',
|
});
|
|
modalInstance.result.then(function () {
|
$log.info('Upload completed at: ' + new Date());
|
modalInstance = null;
|
defer.resolve('success');
|
});
|
return defer.promise;
|
}
|
// 警告表示
|
function openWarning(text) {
|
var defer = $q.defer();
|
var modalInstance = $uibModal.open({
|
animation: true
|
, backdrop: 'static'
|
, template: '<div class="modal-body">'
|
+ '<p><i class="fa fa-exclamation-circle fa-lg" style="color:#2aabd2;"></i> '
|
+ text
|
+ '</p></div>'
|
+'<div class="modal-footer" style="background-color :#f8f8ff">'
|
+'<button class="pull-right btn btn-info col-xs-4" ng-click="$close()">OK</button>'
|
+'</div>'
|
, size: 'sm'
|
});
|
modalInstance.result.then(function () {
|
modalInstance = null;
|
defer.resolve('success');
|
}, function () {
|
modalInstance = null;
|
defer.reject('falt');
|
});
|
return defer.promise;
|
}
|
|
function openImage(images, index) {
|
var images = images
|
var modalInstance = $uibModal.open({
|
animation: true,
|
backdrop: true,
|
templateUrl: 'imageWindow.html',
|
controller: ['$scope', function ($scope) {
|
$scope.images = Lightbox;
|
Lightbox.keyboardNavEnabled = true;
|
}],
|
size: 'lg',
|
});
|
|
$scope.preveImage = function() {
|
|
}
|
}
|
|
}])
|
|
// 送信履歴用
|
.factory('alHistory', ['$window', function($window) {
|
return {
|
getTemplate: getTemplate
|
, get: get
|
, append: append
|
, clear: clear
|
, getDailySortedDesc: getDailySortedDesc
|
, getDailyBlocked: getDailyBlocked
|
}
|
function getTemplate(latLng) {
|
var d = new Date();
|
var log = {
|
'date': d.getFullYear() + '/' + ('0' + (d.getMonth() + 1)).slice(-2) + '/' +('0' + d.getDate()).slice(-2) + ' ' + ['日','月','火','水','木','金','土'][d.getDay()]
|
, 'time': ('0' + d.getHours()).slice(-2) +':' + ('0' + d.getMinutes()).slice(-2) + ':' + ('0' + d.getSeconds()).slice(-2)
|
, 'location': {'lat': latLng.lat, 'lng': latLng.lng}
|
, 'name': null
|
, 'landId': null
|
, 'landNumber': null
|
, 'attachmentIds': []
|
, 'success': false
|
}; // 画像数
|
return log;
|
}
|
|
function get() {
|
return $window.localStorage['al-history'] ? JSON.parse($window.localStorage['al-history']) : [];
|
}
|
// getObject: function() {
|
// return JSON.parse($window.localStorage['al-history'] || '{}');
|
// },
|
function append(log) {
|
var logs = $window.localStorage['al-history'] ? JSON.parse($window.localStorage['al-history']) : [];
|
// 100 件に達したら、1件目を削除
|
if (logs.length > 99) {
|
logs.splice(0, 1);
|
}
|
logs.push(log);
|
$window.localStorage['al-history'] = JSON.stringify(logs);
|
}
|
function clear() {
|
window.localStorage.removeItem('al-history');
|
}
|
function getDailySortedDesc() {
|
var logs = $window.localStorage['al-history'] ? JSON.parse($window.localStorage['al-history']) : [];
|
// 降順にソート
|
logs.sort(function(a,b) {
|
if (b.date === a.date) {
|
return (b.time === a.time ? 0 : (b.time > a.time ? 1 : -1));
|
}
|
else {
|
return (b.date > a.date ? 1 : -1);
|
}
|
});
|
return logs;
|
}
|
function getDailyBlocked() {
|
var logs = $window.localStorage['al-history'] ? JSON.parse($window.localStorage['al-history']) : [];
|
// 降順にソート
|
if (logs.length === 0) return [];
|
logs.sort(function(a,b) {
|
if (b.date === a.date) {
|
return (b.time === a.time ? 0 : (b.time > a.time ? 1 : -1));
|
}
|
else {
|
return (b.date > a.date ? 1 : -1);
|
}
|
});
|
// 日単位のオブジェクトの配列を作成
|
var result =[];
|
var date = "";
|
for (var n = 0; n<logs.length; n++) {
|
if (date !== logs[n].date) {
|
var day = {"date": logs[n].date, "actions":[logs[n]]};
|
result.push(day);
|
date = logs[n].date;
|
}
|
else {
|
result[result.length-1].actions.push(logs[n]);
|
}
|
}
|
return result;
|
}
|
}])
|
// GPS使用
|
.factory('alGps', ['$q', '$log', function($q, $log) {
|
return {
|
getCurrentLocation : getCurrentLocation,
|
getCurrentLocationPromise : getCurrentLocationPromise
|
}
|
function getCurrentLocation(callbackSuccess, callbackFailure) {
|
if (navigator.geolocation) {
|
navigator.geolocation.getCurrentPosition(
|
function(position) {
|
var res = {"lat": null, "lng": null}; // これで保存するので名前に注意
|
res["lat"] = position.coords.latitude;
|
res["lng"] = position.coords.longitude;
|
callbackSuccess(res);
|
},
|
function(err) {
|
var res = 'ERROR(' + err.code + '): ' + err.message;
|
$log.info([{"text": res}]);
|
callbackFailure(res);
|
});
|
}
|
else {
|
callbackFailure([{"text": 'geolocation not supported.'}]);
|
}
|
}
|
function getCurrentLocationPromise() {
|
var defer = $q.defer();
|
if (navigator.geolocation) {
|
navigator.geolocation.getCurrentPosition(
|
function(position) {
|
var res = {"lat": null, "lng": null}; // これで保存するので名前に注意
|
res.lat = position.coords.latitude;
|
res.lng = position.coords.longitude;
|
defer.resolve(res);
|
},
|
function(err) {
|
$log.error('ERROR(' + err.code + '): ' + err.message);
|
defer.reject();
|
});
|
}
|
return defer.promise;
|
}
|
|
}])
|
;
|
|
</script>
|
</apex:component>
|