이미지 편집 수정 중

main
이범준 1 year ago
parent 05769e3684
commit b49948c681

@ -39,6 +39,7 @@
<link rel="stylesheet" href="<c:url value="/resources/3rd-party/jquery-ui/1.13.2/themes/redmond/jquery-ui.css"/>" /> <link rel="stylesheet" href="<c:url value="/resources/3rd-party/jquery-ui/1.13.2/themes/redmond/jquery-ui.css"/>" />
<link rel="stylesheet" href="<c:url value="/resources/lib/fims/framework/datepicker/datepicker.css"/>" /> <link rel="stylesheet" href="<c:url value="/resources/lib/fims/framework/datepicker/datepicker.css"/>" />
<link rel="stylesheet" href="<c:url value="/resources/lib/fims/biz/paintweb/style/style.css"/>?${ver}" />
</head> </head>
<body> <body>
<c:set var="pageKorName" scope="request">사진 편집</c:set> <c:set var="pageKorName" scope="request">사진 편집</c:set>
@ -61,9 +62,14 @@
<script src="<c:url value='/resources/3rd-party/sneat/js/config.js'/>"></script> <script src="<c:url value='/resources/3rd-party/sneat/js/config.js'/>"></script>
<script src="<c:url value='/resources/3rd-party/sneat/libs/chartjs/chartjs.js'/>"></script> <script src="<c:url value='/resources/3rd-party/sneat/libs/chartjs/chartjs.js'/>"></script>
<!-- json, xhtml -->
<script src="<c:url value='/resources/lib/fims/biz/paintweb/lang/ko.js'/>?${ver}"></script> <script src="<c:url value='/resources/lib/fims/biz/paintweb/korean.js'/>?${ver}"></script>
<script src="<c:url value='/resources/lib/fims/biz/paintweb/paintweb-config.js'/>?${ver}"></script> <script src="<c:url value='/resources/lib/fims/biz/paintweb/paintweb-config.js'/>?${ver}"></script>
<script src="<c:url value='/resources/lib/fims/biz/paintweb/layout.js'/>?${ver}"></script>
<!-- init -->
<script src="<c:url value='/resources/lib/fims/biz/paintweb/pwlib.js'/>?${ver}"></script>
<script src="<c:url value='/resources/lib/fims/biz/paintweb/guiScript.js'/>?${ver}"></script>
<script src="<c:url value='/resources/lib/fims/biz/paintweb/paintweb.js'/>?${ver}"></script> <script src="<c:url value='/resources/lib/fims/biz/paintweb/paintweb.js'/>?${ver}"></script>
<script src="<c:url value='/resources/lib/fims/biz/paintweb/html2canvas.min.js'/>?${ver}"></script> <script src="<c:url value='/resources/lib/fims/biz/paintweb/html2canvas.min.js'/>?${ver}"></script>
@ -74,14 +80,14 @@
//FIXME: pw instance 변수명 변경시 paintweb.js의 imageSaveTo() 내의 2564 line if(pw.image.modified) 변경 필요 //FIXME: pw instance 변수명 변경시 paintweb.js의 imageSaveTo() 내의 2564 line if(pw.image.modified) 변경 필요
var pw; var pw;
var originalInfomation = {}; var originalInfomation = {};
let editableImage = null; var editableImage = null;
var isNumberPlate = false;
const initEditor = () => { const initEditor = () => {
pw = new PaintWeb(); pw = new PaintWeb();
pw.config.imageLoad = editableImage; pw.config.imageLoad = editableImage;
pw.config.guiPlaceholder = document.getElementById('paintWebTarget'); pw.config.guiPlaceholder = document.getElementById('paintWebTarget');
pw.config.configFile = 'paintweb-config.json?${ver}';
pw.config.imageSave = fnImageSaveTo; pw.config.imageSave = fnImageSaveTo;
pw.config.afterImageSave = fnAfterImageSave; pw.config.afterImageSave = fnAfterImageSave;
@ -93,10 +99,8 @@
console.log(doc); console.log(doc);
console.log(_self); console.log(_self);
const isDelete = imgWidth === 0; if(isNumberPlate){
if(!confirm('번호판 이미지를 추가하시겠습니까?')) return;
if(isDelete){
if(!confirm('단속이미지를 정말로 삭제 하시겠습니까?')) return;
}else{ }else{
if(!confirm('편집한 단속이미지를 저장하시겠습니까?')) return; if(!confirm('편집한 단속이미지를 저장하시겠습니까?')) return;
} }
@ -105,42 +109,24 @@
// var param = originalInfomation 파일아이디,단속아이디,inpoType.. // var param = originalInfomation 파일아이디,단속아이디,inpoType..
// 삭제일때 // 번호판일때
if(isDelete){ if(isNumberPlate){
//cmmAjax({
// url: '/somePath/removeFile.do',
// data: $.param(param),
// success: (res) => {
// window.opener.callbackReloadImage();
// window.close();
// }
//})
return true;
}
return true;
} else {
// 저장일때 // 저장일때
const formData = new FormData(); const formData = new FormData();
//formData.append('fileId', fileId); //formData.append('fileId', fileId);
//formData.append('file', file); //formData.append('file', file);
//cmmAjax({
// type: 'post',
// url: '/somePath/save.do',
// processData: false,
// contentType: false,
// data: formData,
// success: (res) => {
// window.opener.callbackReloadImage();
// window.close();
// }
//});
return true; return true;
}; };
function fnAfterImageSave(){ function fnAfterImageSave(){
console.log('after...');
window.close(); window.close();
}; };

@ -0,0 +1,81 @@
/**
* 전달받은 이미지를 base64로 인코딩한다
* @param file - 이미지 파일 또는 이미지 URL
* @param maxWidth - 인코딩 시의 이미지 max width 사이즈
* @returns {Promise<any>}
*/
var readImage = () => {
let image = this.$refs.image.src
this.toBlob(image)
.then(res => {
console.log(res)
this.image = {
filename: res.name,
size: res.size,
type: res.type,
lastModified: res.lastModified
}
})
};
/**
* 이미지 url을 blob 파일로 변환하여 전달한다
* @param url
* @returns {Promise<any>}
*/
var toBlob = (url) => {
return new Promise((resolve, reject) => {
this.base64Encode(url)
.then(res => {
let byteString = atob(res.dataUrl)
let ab = new ArrayBuffer(byteString.length)
let ia = new Uint8Array(ab)
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i)
}
// write the array buffer to blob
let blob = new Blob([ab], {type: 'image/' + res.type})
let formData = new FormData()
formData.append('file', blob, res.name)
resolve(formData.get('file'))
})
})
};
/**
* 전달받은 이미지를 base64로 인코딩한다
*/
var base64Encode = (url) => {
const re = new RegExp('.(gif|jpg|jpeg|tiff|png|ico)$', 'i')
let name = (/[^(/|\\)]*$/).exec(url)[0]
let type = re.test(name) ? re.exec(name)[0].replace('.', '') : 'jpg'
return new Promise((resolve, reject) => {
let image = new Image()
image.onload = function (event) {
let canvas = document.createElement('canvas')
// draw canvas
canvas.width = image.naturalWidth
canvas.height = image.naturalHeight
canvas.getContext('2d').drawImage(image, 0, 0)
let dataUrl = canvas.toDataURL('image/' + type)
resolve({
name: name,
type: type,
dataUrl: dataUrl.split(',')[1]
})
}
image.onerror = function () {
let msg = `"${file}"을 로딩하는 데 오류가 발생하였습니다. 이미지 파일을 확인해주세요.`
alert(msg)
console.error(msg)
}
image.crossOrigin = 'anonymous';
image.src = url
})
}

@ -35,6 +35,7 @@
* @param {PaintWeb} app Reference to the main paint application object. * @param {PaintWeb} app Reference to the main paint application object.
*/ */
pwlib.extensions.colormixer = function (app) { pwlib.extensions.colormixer = function (app) {
var _self = this, var _self = this,
config = app.config.colormixer, config = app.config.colormixer,
doc = app.doc, doc = app.doc,

@ -1,363 +0,0 @@
/*
* Copyright (C) 2009 Mihai Şucan
*
* This file is part of PaintWeb.
*
* PaintWeb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PaintWeb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PaintWeb. If not, see <http://www.gnu.org/licenses/>.
*
* $URL: http://code.google.com/p/paintweb $
* $Date: 2009-10-29 19:05:49 +0200 $
*/
/**
* @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
* @fileOverview Holds the integration code for PaintWeb inside <a
* href="http://www.moodle.org">Moodle</a>.
*/
/**
* @class The Moodle extension for PaintWeb. This extension handles the Moodle
* integration inside the PaintWeb code.
*
* <p><strong>Note:</strong> This extension is supposed to work with Moodle 1.9
* and Moodle 2.0.
*
* @param {PaintWeb} app Reference to the main paint application object.
*/
pwlib.extensions.moodle = function (app) {
var _self = this,
appEvent = pwlib.appEvent,
config = app.config,
gui = app.gui,
lang = app.lang.moodle,
moodleServer = config.moodleServer,
tinymceEditor = null,
qfErrorShown = false;
// Holds information related to Moodle.
var moodleInfo = {
// Holds the URL of the image the user is saving.
imageURL: null,
// The class name for the element which holds the textarea buttons (toggle
// on/off).
// This element exists only in Moodle 1.9.
textareaButtons: 'textareaicons',
// The image save handler script on the server-side. The path is relative to
// the PaintWeb base folder.
// This used with Moodle 2.0.
imageSaveHandler20: '../ext/moodle/imagesave20.php',
// The image save handler script for Moodle 1.9.
imageSaveHandler19: '../ext/moodle/imagesave19.php',
// This holds the release version of Moodle being used. This should be 1.9
// or 2.0.
release: 0,
// Moodle 2.0 draft item ID used for file storage.
draftitemid: null
};
/**
* The <code>extensionRegister</code> event handler. Setup event listeners,
* determine Moodle version, and more.
*
* @returns {Boolean} True if the extension initialized successfully, or false
* if not.
*/
this.extensionRegister = function () {
// Register application events.
app.events.add('guiShow', this.guiShow);
app.events.add('guiHide', this.guiHide);
app.events.add('imageSave', this.imageSave);
if (moodleServer && moodleServer.release) {
var matches = moodleServer.release.match(/^\s*(\d+\.\d+)/);
if (matches && matches[1]) {
moodleInfo.release = parseFloat(matches[1]);
}
}
if (typeof window.qf_errorHandler === 'function' && config.tinymce &&
!config.tinymce.onSubmitUnsaved) {
config.tinymce.onSubmitUnsaved = this.onSubmitUnsaved;
}
return true;
};
/**
* The <code>submit</code> event handler for the form to which the PaintWeb
* instance is attached to. This method is invoked by the TinyMCE plugin when
* the form is submitted while the user edits an image with unsaved changes.
* @private
*/
this.onSubmitUnsaved = function () {
var tmce = config.tinymceEditor,
textarea = tmce ? tmce.getElement() : null,
guiPlaceholder = config.guiPlaceholder,
prevSibling = guiPlaceholder.previousSibling;
if (tmce && textarea && window.qf_errorHandler) {
try {
qf_errorHandler(textarea, "\n - " + lang.errorSubmitUnsaved);
} catch (err) {
return;
}
qfErrorShown = true;
// Due to the styling of the error shown by Moodle, PaintWeb must have
// clear:right.
if (prevSibling && prevSibling.className &&
prevSibling.className.indexOf('paintweb_tinymce_status') !== -1) {
prevSibling.style.clear = 'right';
} else {
guiPlaceholder.style.clear = 'right';
}
}
};
/**
* The <code>imageSave</code> application event handler. When the user
* attempts to save an image, this extension handles the event by sending the
* image data to the Moodle server, to perform the actual save operation.
*
* @private
* @param {pwlib.appEvent.imageSave} ev The application event object.
*/
this.imageSave = function (ev) {
if (!ev.dataURL) {
return;
}
ev.preventDefault();
moodleInfo.imageURL = config.imageLoad.src;
if (!moodleInfo.imageURL || moodleInfo.imageURL.substr(0, 5) === 'data:') {
moodleInfo.imageURL = '-';
}
if (config.moodleSaveMethod === 'dataURL') {
app.events.dispatch(new appEvent.imageSaveResult(true,
moodleInfo.imageURL, ev.dataURL));
} else {
var handlerURL = PaintWeb.baseFolder,
send = 'url=' + encodeURIComponent(moodleInfo.imageURL) +
'&dataURL=' + encodeURIComponent(ev.dataURL),
headers = {'Content-Type': 'application/x-www-form-urlencoded'};
// In Moodle 2.0 we include the context ID and the draft item ID, such
// that the image save script can properly save the new image inside the
// current draft area of the current textarea element.
if (moodleInfo.release >= 2) {
handlerURL += moodleInfo.imageSaveHandler20;
if (moodleServer.contextid) {
send += '&contextid=' + encodeURIComponent(moodleServer.contextid);
}
if (moodleInfo.draftitemid) {
send += '&draftitemid=' + encodeURIComponent(moodleInfo.draftitemid);
}
} else {
handlerURL += moodleInfo.imageSaveHandler19;
}
pwlib.xhrLoad(handlerURL, imageSaveReady, 'POST', send, headers);
}
};
/**
* The image save <code>onreadystatechange</code> event handler for the
* <code>XMLHttpRequest</code> which performs the image save. This function
* uses the reply to determine if the image save operation is successful or
* not.
*
* <p>The {@link pwlib.appEvent.imageSaveResult} application event is
* dispatched.
*
* <p>The server-side script must reply with a JSON object with the following
* properties:
*
* <ul>
* <li><var>successful</var> which tells if the image save operation was
* successful or not;
*
* <li><var>url</var> which must tell the same URL as the image we just
* saved (sanity/security check);
*
* <li><var>urlNew</var> is optional. This allows the server-side script to
* change the image URL;
*
* <li><var>errorMessage</var> is optional. When the image save was not
* successful, an error message can be displayed.
* </ul>
*
* @private
* @param {XMLHttpRequest} xhr The XMLHttpRequest object.
*/
function imageSaveReady (xhr) {
if (!xhr || xhr.readyState !== 4) {
return;
}
var result = {successful: false, url: moodleInfo.imageURL};
if ((xhr.status !== 304 && xhr.status !== 200) || !xhr.responseText) {
alert(lang.xhrRequestFailed);
app.events.dispatch(new appEvent.imageSaveResult(false, result.url, null,
lang.xhrRequestFailed));
return;
}
try {
result = JSON.parse(xhr.responseText);
} catch (err) {
result.errorMessage = lang.jsonParseFailed + "\n" + err;
alert(result.errorMessage);
}
if (result.successful) {
if (result.url !== moodleInfo.imageURL) {
alert(pwlib.strf(lang.urlMismatch, {
url: moodleInfo.imageURL,
urlServer: result.url || 'null'}));
}
} else {
if (result.errorMessage) {
alert(lang.imageSaveFailed + "\n" + result.errorMessage);
} else {
alert(lang.imageSaveFailed);
}
}
app.events.dispatch(new appEvent.imageSaveResult(result.successful,
result.url, result.urlNew, result.errorMessage));
};
/**
* The <code>guiShow</code> application event handler. When the PaintWeb GUI
* is shown, we must hide the textarea icons for the current textarea element,
* inside a Moodle page.
* @private
*/
this.guiShow = function () {
var pNode = config.guiPlaceholder.parentNode,
textareaButtons
= pNode.getElementsByClassName(moodleInfo.textareaButtons)[0];
// These show in Moodle 1.9.
if (textareaButtons) {
textareaButtons.style.display = 'none';
}
qfErrorShown = false;
// For Moodle 2.0 we must determine the draft item ID in order to properly
// perform the image save operation into the current draft area.
if (moodleInfo.release < 2) {
return;
}
// Typically the TinyMCE editor instance is attached to a textarea element
// which has a name=whatever[text] or similar form. In the same form as the
// textarea, there must be a hidden input element with the
// name=whatever[itemid]. The value of that input holds the draft item ID.
var tmce = config.tinymceEditor,
textarea = tmce ? tmce.getElement() : null,
frm = textarea ? textarea.form : null;
if (!tmce || !textarea || !textarea.name || !frm) {
return;
}
var fieldname = textarea.name.replace(/\[text\]$/, '');
if (!fieldname) {
return;
}
var draftitemid = frm.elements.namedItem(fieldname + '[itemid]'),
format = frm.elements.namedItem(fieldname + '[format]');
if (draftitemid) {
moodleInfo.draftitemid = draftitemid.value;
}
if (format) {
format.style.display = 'none';
}
};
/**
* The <code>guiHide</code> application event handler. When the PaintWeb GUI
* is hidden, we must show again the textarea icons for the current textarea
* element, inside a Moodle page.
* @private
*/
this.guiHide = function () {
var guiPlaceholder = config.guiPlaceholder,
prevSibling = guiPlaceholder.previousSibling;
pNode = guiPlaceholder.parentNode,
textareaButtons
= pNode.getElementsByClassName(moodleInfo.textareaButtons)[0];
// These show in Moodle 1.9.
if (textareaButtons) {
textareaButtons.style.display = '';
}
var tmce = config.tinymceEditor,
textarea = tmce ? tmce.getElement() : null,
frm = textarea ? textarea.form : null;
if (!tmce || !textarea || !textarea.name || !frm) {
return;
}
if (qfErrorShown) {
if (window.qf_errorHandler) {
qf_errorHandler(textarea, '');
}
if (prevSibling && prevSibling.className &&
prevSibling.className.indexOf('paintweb_tinymce_status') !== -1) {
prevSibling.style.clear = '';
} else {
guiPlaceholder.style.clear = '';
}
}
// The format input element only shows in Moodle 2.0.
if (moodleInfo.release >= 2) {
var fieldname = textarea.name.replace(/\[text\]$/, '');
if (!fieldname) {
return;
}
var format = frm.elements.namedItem(fieldname + '[format]');
if (format) {
format.style.display = '';
}
}
};
};
// vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:

@ -32,6 +32,7 @@
* @param {PaintWeb} app Reference to the main paint application object. * @param {PaintWeb} app Reference to the main paint application object.
*/ */
pwlib.extensions.mousekeys = function (app) { pwlib.extensions.mousekeys = function (app) {
var _self = this, var _self = this,
canvas = app.buffer.canvas, canvas = app.buffer.canvas,
config = app.config, config = app.config,

@ -872,10 +872,12 @@ pwlib.gui = function (app) {
var selCrop = this.commands.selectionCrop, var selCrop = this.commands.selectionCrop,
selFill = this.commands.selectionFill, selFill = this.commands.selectionFill,
selForNumPlt = this.commands.selectionForNumberPlate,
selDelete = this.commands.selectionDelete; selDelete = this.commands.selectionDelete;
selCrop.className += classDisabled; selCrop.className += classDisabled;
selFill.className += classDisabled; selFill.className += classDisabled;
selForNumPlt.className += classDisabled;
selDelete.className += classDisabled; selDelete.className += classDisabled;
return true; return true;
@ -1000,6 +1002,9 @@ pwlib.gui = function (app) {
placeholder.focus(); placeholder.focus();
} catch (err) { } } catch (err) { }
this.toolActivate("selection"); //영역 선택
app.events.dispatch(new appEvent.guiShow()); app.events.dispatch(new appEvent.guiShow());
}; };

@ -1,451 +0,0 @@
/*
* Copyright (C) 2008, 2009 Mihai Şucan
*
* This file is part of PaintWeb.
*
* PaintWeb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PaintWeb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PaintWeb. If not, see <http://www.gnu.org/licenses/>.
*
* $URL: http://code.google.com/p/paintweb $
* $Date: 2009-05-11 19:37:56 +0300 $
*/
/**
* @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
* @fileOverview Minimal code used for aiding debugging PaintWeb.
*/
// Opera compatibility
if (!window.console) {
/**
* @namespace Holds a simple method used for debugging. Opera doesn't have the
* window.console API like Firefox+Firebug has.
*/
window.console = {};
}
if (!window.console.log) {
/**
* Display a message in the debugger. If available, opera.postError is used,
* otherwise no message is displayed.
*
* @param {mixed} mixed Any number of arguments, each one is displayed in the
* debugger.
*/
window.console.log = function () {
var msg = '';
for (var i = 0, n = arguments.length; i < n; i++) {
msg += arguments[i] + ' ';
}
if (window.opera && window.opera.postError) {
opera.postError(msg);
}
};
}
if (!window.console.warn) {
/**
* Display a message in the debugger. If available, opera.postError is used,
* otherwise no warning is displayed.
*
* @param {mixed} mixed Any number of arguments, each one is displayed in the
* debugger.
*/
window.console.warn = function () {
console.log.apply(null, arguments);
};
}
/**
* JavaScript code performance profiler.
* <p>Nested timers are accounted for - see the example below.
*
* @example
* <code>// To profile your code just do:
* var profiler = new $.profiler();
*
* profiler.start('long test');
* // ... more code ...
* profiler.start('function 1');
* // ... more code ...
* profiler.stop('function 1');
* // ... more code ...
* profiler.start('function 2');
* // ... more code ...
* profiler.stop('function 2');
* // ... more code ...
* profiler.stop('long test');
*
* // To see the results do:
* profiler.reportText();
* // or ..
* profiler.reportData();</code>
*
* @class JavaScript code performance profiler.
*/
var libProfiler = function () {
/**
* @ignore
* @class Function timer. This is the constructor used for instancing a single
* timer object created by the profiler.
*
* @private
* @param {String} name_ The timer name.
* @param {Boolean} [independent_=false] Tells if the timer is independent.
* Means this timer will not affect the timers execution stack.
*/
function fnTimer (name_, independent_) {
this.avgOwnTimePerCall = 0;
this.avgRunTimePerCall = 0;
this.calls = 0;
this.maxOwnTimePerCall = 0;
this.maxRunTimePerCall = 0;
this.minOwnTimePerCall = 0;
this.minRunTimePerCall = 0;
this.name = name_;
this.ownTimeTotal = 0;
this.runTimeTotal = 0;
this.state = fnTimer.STATE_NONE;
this.independent = independent_;
var startTime_ = 0,
subTimerStart_ = 0,
subTime_ = 0,
stack_ = 0;
/*
* Start timing function execution.
*/
this.start = function () {
if (this.state == fnTimer.STATE_START ||
this.state == fnTimer.STATE_SUB) {
stack_++;
return;
}
startTime_ = (new Date ()).getTime();
this.state = fnTimer.STATE_START;
};
/*
* Stop timing function execution.
*/
this.stop = function () {
if (this.state == fnTimer.STATE_SUB) {
this.subTimerEnd();
}
if (this.state != fnTimer.STATE_START) {
return;
}
this.calls++;
if (stack_) {
stack_--;
return;
}
var runTime = (new Date ()).getTime() - startTime_;
var ownTime = runTime - subTime_;
subTime_ = 0;
this.runTimeTotal += runTime;
this.ownTimeTotal += ownTime;
this.avgRunTimePerCall = this.runTimeTotal / this.calls;
this.avgOwnTimePerCall = this.ownTimeTotal / this.calls;
if (runTime < this.minRunTimePerCall) {
this.minRunTimePerCall = runTime;
}
if (runTime > this.maxRunTimePerCall) {
this.maxRunTimePerCall = runTime;
}
if (ownTime < this.minOwnTimePerCall) {
this.minOwnTimePerCall = ownTime;
}
if (ownTime > this.maxOwnTimePerCall) {
this.maxOwnTimePerCall = ownTime;
}
this.state = fnTimer.STATE_STOP;
};
/*
* Start timing sub-function execution. The sub-function execution timer is
* used for calculating the ownTime (runTime - sub-function execution time).
*/
this.subTimerStart = function () {
if (this.independent || this.state != fnTimer.STATE_START) {
return;
}
subTimerStart_ = (new Date()).getTime();
this.state = fnTimer.STATE_SUB;
};
/*
* Stop timing sub-function execution.
*/
this.subTimerEnd = function () {
if (this.independent || this.state != fnTimer.STATE_SUB) {
return;
}
subTime_ += (new Date()).getTime() - subTimerStart_;
this.state = fnTimer.STATE_START;
};
};
fnTimer.STATE_NONE = 0;
fnTimer.STATE_START = 1;
fnTimer.STATE_SUB = 2;
fnTimer.STATE_STOP = 3;
/**
* Holds the timer objects.
*/
this.timers = {};
var activeTimer_ = null,
timersStack_ = [];
/**
* Start/create a function timer.
*
* @param {String} name The timer name.
* @param {Boolean} [independent=false] Tells if the timer should be
* independent or not. This means that this new function timer is not be
* considered affecting the execution time of existing function timers in the
* call stack.
*/
this.start = function (name, independent) {
var timer = this.timers[name];
if (!timer) {
timer = this.timers[name] = new fnTimer(name, independent);
}
if (!timer.independent && activeTimer_ != name) {
var activeTimer = activeTimer_ ? this.timers[activeTimer_] : null;
if (activeTimer && activeTimer.state == fnTimer.STATE_START) {
timersStack_.push(activeTimer_);
activeTimer.subTimerStart();
}
activeTimer_ = name;
}
timer.start();
};
/**
* Stop a function timer.
*/
this.stop = function (name) {
var timer = this.timers[name];
if (!timer) {
return;
}
timer.stop();
if (timer.independent || name != activeTimer_ ||
name == activeTimer_ && timer.state == fnTimer.STATE_START) {
return;
}
if (timersStack_.length > 0) {
activeTimer_ = timersStack_.pop();
var activeTimer = this.timers[activeTimer_];
activeTimer.subTimerEnd();
} else {
activeTimer_ = null;
}
};
/**
* Generate timers report data.
*
* @returns {Object} Holds all the information gathered by the timers.
*/
this.reportData = function () {
var name, timer, timerDetails,
data = {
avgCallsPerTimer: 0,
avgOwnTimePerCall: 0,
avgOwnTimePerTimer: 0,
avgRunTimePerCall: 0,
avgRunTimePerTimer: 0,
callsTotal: 0,
maxCallsPerTimer: 0,
maxCallsPerTimerName: '',
maxOwnTimePerCall: 0,
maxOwnTimePerCallName: '',
maxRunTimePerCall: 0,
maxRunTimePerCallName: '',
minCallsPerTimer: 0,
minCallsPerTimerName: '',
minOwnTimePerCall: 0,
minOwnTimePerCallName: '',
minRunTimePerCall: 0,
minRunTimePerCallName: '',
ownTimeTotal: 0,
runTimeTotal: 0,
timers: 0,
timerDetails: []
};
for (name in this.timers) {
timer = this.timers[name];
if (timer.state != fnTimer.STATE_STOP) {
continue;
}
timerDetails = {
name: name,
avgOwnTimePerCall: timer.avgOwnTimePerCall,
avgRunTimePerCall: timer.avgRunTimePerCall,
calls: timer.calls,
maxOwnTimePerCall: timer.maxOwnTimePerCall,
maxRunTimePerCall: timer.maxRunTimePerCall,
minOwnTimePerCall: timer.minOwnTimePerCall,
minRunTimePerCall: timer.minRunTimePerCall,
runTimeTotal: timer.runTimeTotal,
ownTimeTotal: timer.ownTimeTotal
};
data.timerDetails.push(timerDetails);
if (timer.calls > data.maxCallsPerTimer || !data.timers) {
data.maxCallsPerTimer = timer.calls;
data.maxCallsPerTimerName = name;
}
if (timer.maxOwnTimePerCall > data.maxOwnTimePerCall || !data.timers) {
data.maxOwnTimePerCall = timer.maxOwnTimePerCall;
data.maxOwnTimePerCallName = name;
}
if (timer.maxRunTimePerCall > data.maxRunTimePerCall || !data.timers) {
data.maxRunTimePerCall = timer.maxRunTimePerCall;
data.maxRunTimePerCallName = name;
}
if (timer.calls < data.minCallsPerTimer || !data.timers) {
data.minCallsPerTimer = timer.calls;
data.minCallsPerTimerName = name;
}
if (timer.minOwnTimePerCall < data.minOwnTimePerCall || !data.timers) {
data.minOwnTimePerCall = timer.minOwnTimePerCall;
data.minOwnTimePerCallName = name;
}
if (timer.minRunTimePerCall < data.minRunTimePerCall || !data.timers) {
data.minRunTimePerCall = timer.minRunTimePerCall;
data.minRunTimePerCallName = name;
}
data.runTimeTotal += timer.runTimeTotal;
data.ownTimeTotal += timer.ownTimeTotal;
data.callsTotal += timer.calls;
data.timers++;
}
if (data.timers == 0) {
return data;
}
data.avgCallsPerTimer = data.callsTotal / data.timers;
data.avgOwnTimePerCall = data.ownTimeTotal / data.callsTotal;
data.avgOwnTimePerTimer = data.ownTimeTotal / data.timers;
data.avgRunTimePerCall = data.runTimeTotal / data.callsTotal;
data.avgRunTimePerTimer = data.runTimeTotal / data.timers;
return data;
};
/**
* Generate a report in text format.
*
* @returns {String} All the information gathered by the timers, as text.
*/
this.reportText = function () {
var data = this.reportData(),
timer, result = '';
if (!data.timers) {
return '';
}
for (var i = 0; i < data.timers; i++) {
timer = data.timerDetails[i];
result += timer.name + ":\n" +
' Avg ownTime / call: ' + timer.avgOwnTimePerCall + " ms\n" +
' Avg runTime / call: ' + timer.avgRunTimePerCall + " ms\n" +
' Calls: ' + timer.calls + "\n"+
' Max ownTime / call: ' + timer.maxOwnTimePerCall + " ms\n" +
' Max runTime / call: ' + timer.maxRunTimePerCall + " ms\n" +
' Min ownTime / call: ' + timer.minOwnTimePerCall + " ms\n" +
' Min runTime / call: ' + timer.minRunTimePerCall + " ms\n" +
' runTime: ' + timer.runTimeTotal + " ms\n" +
' ownTime: ' + timer.ownTimeTotal + " ms\n\n";
}
result += "Overview info:\n" +
' Avg calls / timer: ' + data.avgCallsPerTimer + "\n" +
' Avg ownTime / call: ' + data.avgOwnTimePerCall + " ms\n" +
' Avg ownTime / timer: ' + data.avgOwnTimePerTimer + " ms\n" +
' Avg runTime / call: ' + data.avgRunTimePerCall + " ms\n" +
' Avg runTime / timer: ' + data.avgRunTimePerTimer + " ms\n" +
' Calls total: ' + data.callsTotal + "\n" +
' Max calls / timer: ' + data.maxCallsPerTimer + ' (' +
data.maxCallsPerTimerName + ")\n" +
' Max ownTime / call: ' + data.maxOwnTimePerCall + ' ms (' +
data.maxOwnTimePerCallName + ")\n" +
' Max runTime / call: ' + data.maxRunTimePerCall + ' ms (' +
data.maxRunTimePerCallName + ")\n" +
' Min calls / timer: ' + data.minCallsPerTimer + ' (' +
data.minCallsPerTimerName + ")\n" +
' Min ownTime / call: ' + data.minOwnTimePerCall + ' ms (' +
data.minOwnTimePerCallName + ")\n" +
' Min runTime / call: ' + data.minRunTimePerCall + ' ms (' +
data.minRunTimePerCallName + ")\n" +
' Accumulated ownTime: ' + data.ownTimeTotal + " ms\n" +
' Accumulated runTime: ' + data.runTimeTotal + " ms\n" +
' Timers: ' + data.timers;
return result;
};
/**
* Reset/clear all the timers.
*/
this.reset = function () {
this.timers = {};
activeTimer_ = null;
timersStack_ = [];
};
};
// vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:

@ -1,478 +0,0 @@
/*
http://www.JSON.org/json2.js
2009-04-16
Public Domain.
NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
See http://www.JSON.org/js.html
This file creates a global JSON object containing two methods: stringify
and parse.
JSON.stringify(value, replacer, space)
value any JavaScript value, usually an object or array.
replacer an optional parameter that determines how object
values are stringified for objects. It can be a
function or an array of strings.
space an optional parameter that specifies the indentation
of nested structures. If it is omitted, the text will
be packed without extra whitespace. If it is a number,
it will specify the number of spaces to indent at each
level. If it is a string (such as '\t' or '&nbsp;'),
it contains the characters used to indent at each level.
This method produces a JSON text from a JavaScript value.
When an object value is found, if the object contains a toJSON
method, its toJSON method will be called and the result will be
stringified. A toJSON method does not serialize: it returns the
value represented by the name/value pair that should be serialized,
or undefined if nothing should be serialized. The toJSON method
will be passed the key associated with the value, and this will be
bound to the object holding the key.
For example, this would serialize Dates as ISO strings.
Date.prototype.toJSON = function (key) {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
You can provide an optional replacer method. It will be passed the
key and value of each member, with this bound to the containing
object. The value that is returned from your method will be
serialized. If your method returns undefined, then the member will
be excluded from the serialization.
If the replacer parameter is an array of strings, then it will be
used to select the members to be serialized. It filters the results
such that only members with keys listed in the replacer array are
stringified.
Values that do not have JSON representations, such as undefined or
functions, will not be serialized. Such values in objects will be
dropped; in arrays they will be replaced with null. You can use
a replacer function to replace those with JSON values.
JSON.stringify(undefined) returns undefined.
The optional space parameter produces a stringification of the
value that is filled with line breaks and indentation to make it
easier to read.
If the space parameter is a non-empty string, then that string will
be used for indentation. If the space parameter is a number, then
the indentation will be that many spaces.
Example:
text = JSON.stringify(['e', {pluribus: 'unum'}]);
// text is '["e",{"pluribus":"unum"}]'
text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
// text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
text = JSON.stringify([new Date()], function (key, value) {
return this[key] instanceof Date ?
'Date(' + this[key] + ')' : value;
});
// text is '["Date(---current time---)"]'
JSON.parse(text, reviver)
This method parses a JSON text to produce an object or array.
It can throw a SyntaxError exception.
The optional reviver parameter is a function that can filter and
transform the results. It receives each of the keys and values,
and its return value is used instead of the original value.
If it returns what it received, then the structure is not modified.
If it returns undefined then the member is deleted.
Example:
// Parse the text. Values that look like ISO date strings will
// be converted to Date objects.
myData = JSON.parse(text, function (key, value) {
var a;
if (typeof value === 'string') {
a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
if (a) {
return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+a[5], +a[6]));
}
}
return value;
});
myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
var d;
if (typeof value === 'string' &&
value.slice(0, 5) === 'Date(' &&
value.slice(-1) === ')') {
d = new Date(value.slice(5, -1));
if (d) {
return d;
}
}
return value;
});
This is a reference implementation. You are free to copy, modify, or
redistribute.
This code should be minified before deployment.
See http://javascript.crockford.com/jsmin.html
USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
NOT CONTROL.
*/
/*jslint evil: true */
/*global JSON */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z';
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

@ -114,7 +114,9 @@ let lang_ko = {
"selectionCrop": "자르기", "selectionCrop": "자르기",
"selectionFill": "가림막", "selectionFill": "가림막",
"selectionCopy": "번호판", "selectionForNumberPlate": "번호판",
"selectionCopy": "선택영역 복사",
"selectionCut": "선택영역 잘라내기", "selectionCut": "선택영역 잘라내기",
"selectionDelete": "선택영역 삭제" "selectionDelete": "선택영역 삭제"
}, },

@ -1,4 +1,4 @@
<div xmlns="http://www.w3.org/1999/xhtml"> let xhtml = `<div xmlns="http://www.w3.org/1999/xhtml">
<!-- $Date: 2009-11-08 13:19:52 +0200 $ --> <!-- $Date: 2009-11-08 13:19:52 +0200 $ -->
<h1 class="paintweb_appTitle">PaintWeb</h1> <h1 class="paintweb_appTitle">PaintWeb</h1>
@ -27,7 +27,7 @@
<li class="paintweb_toolSeparator">&#160;</li> <li class="paintweb_toolSeparator">&#160;</li>
<li data-pwTool="selection">Selection</li> <li data-pwTool="selection" hidden>영역 선택</li>
<!--<li data-pwTool="hand">Hand</li>--> <!--<li data-pwTool="hand">Hand</li>-->
<!-- <li data-pwTool="rectangle">Rectangle</li> --> <!-- <li data-pwTool="rectangle">Rectangle</li> -->
@ -36,7 +36,8 @@
<!--<li data-pwCommand="selectionCut">Cut selection</li>--> <!--<li data-pwCommand="selectionCut">Cut selection</li>-->
<li data-pwCommand="selectionCrop">자르기</li> <li data-pwCommand="selectionCrop">자르기</li>
<li data-pwCommand="selectionFill">가림막</li> <li data-pwCommand="selectionFill">가림막</li>
<li data-pwCommand="selectionCopy">번호판</li> <li data-pwCommand="selectionForNumberPlate">번호판</li>
<!-- <li data-pwCommand="selectionCopy">선택영역 복사</li> -->
<!--<li data-pwCommand="clipboardPaste">Clipboard paste</li>--> <!--<li data-pwCommand="clipboardPaste">Clipboard paste</li>-->
<!--<li data-pwTool="insertimg">Insert image</li>--> <!--<li data-pwTool="insertimg">Insert image</li>-->
<!-- <!--
@ -153,9 +154,7 @@
<p data-pwId="selTab_selectionCopy">Copy selection</p> <p data-pwId="selTab_selectionCopy">Copy selection</p>
<p data-pwId="selTab_clipboardPaste">Clipboard paste</p> <p data-pwId="selTab_clipboardPaste">Clipboard paste</p>
<p data-pwCommand="selectionCrop">Crop selection</p>
<p data-pwCommand="selectionDelete">Delete selection</p> <p data-pwCommand="selectionDelete">Delete selection</p>
<p data-pwCommand="selectionFill">Fill selection</p>
<p class="paintweb_opt_selectionTransparent"> <p class="paintweb_opt_selectionTransparent">
<label><input data-pwConfig="selection.transparent" type="checkbox" <label><input data-pwConfig="selection.transparent" type="checkbox"
@ -409,4 +408,4 @@
</div> </div>
<!-- vim:set spell spl=en fo=tcroqwanl1 tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: --> <!-- vim:set spell spl=en fo=tcroqwanl1 tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix: -->
</div> </div>`;

@ -7,14 +7,6 @@ let paintweb_config = {
"lang": "ko", "lang": "ko",
"langFolder": "lang",
/*
The graphical user interface you want to use.
@type String
@default "default"
*/
"gui": "default",
/** /**
* The folder contains all the interfaces. * The folder contains all the interfaces.
@ -24,29 +16,6 @@ let paintweb_config = {
*/ */
"interfacesFolder": "interfaces", "interfacesFolder": "interfaces",
/**
* The interface markup file. The file must be an XHTML valid document.
*
* @type String
* @default "layout.xhtml"
*/
"guiMarkup": "layout.xhtml",
/**
* The interface style file.
*
* @type String
* @default "style.css"
*/
"guiStyle": "style.css",
/**
* The interface script file.
*
* @type String
* @default script.js
*/
"guiScript": "script.js",
/** /**
* The image viewport width. Make sure the value is a CSS length, like "50%", * The image viewport width. Make sure the value is a CSS length, like "50%",
@ -69,7 +38,7 @@ let paintweb_config = {
* @default "400px" * @default "400px"
* 672px * 672px
*/ */
"viewportHeight": "82%", "viewportHeight": "100%",
/** /**
* Image save quality for the JPEG format. * Image save quality for the JPEG format.
@ -77,7 +46,7 @@ let paintweb_config = {
* @type Number * @type Number
* @default 0.9 * @default 0.9
*/ */
"jpegSaveQuality": 0.9, "jpegSaveQuality": 1.0,
/** /**
* The default image width. * The default image width.
@ -611,14 +580,14 @@ let paintweb_config = {
"Control Z": { "command": "historyUndo" }, "Control Z": { "command": "historyUndo" },
"Control Y": { "command": "historyRedo" }, "Control Y": { "command": "historyRedo" },
// "Control N": { "command": "imageClear" }, "Control N": { "command": "imageClear" },
//"Control A": { "command": "selectAll" }, //"Control A": { "command": "selectAll" },
//"Control X": { "command": "selectionCut" }, //"Control X": { "command": "selectionCut" },
//"Shift Delete": { "command": "selectionCut" }, //"Shift Delete": { "command": "selectionCut" },
//"Control C": { "command": "selectionCopy" }, "Control C": { "command": "selectionCopy" },
//"Control V": { "command": "clipboardPaste" }, "Control V": { "command": "clipboardPaste" },
"Control S": { "command": "imageSave" } "Control S": { "command": "imageSave" }
// Use "toolActivate": "id" to activate the tool with the given ID. // Use "toolActivate": "id" to activate the tool with the given ID.

@ -503,29 +503,6 @@ function PaintWeb (win, doc) {
temp_.onInit = handler; temp_.onInit = handler;
// Check Canvas support.
if (!doc.createElement('canvas').getContext) {
this.initError(lang.noCanvasSupport);
return false;
}
// Basic functionality used within the Web application.
if (!window.getComputedStyle) {
try {
if (!win.getComputedStyle(doc.createElement('div'), null)) {
this.initError(lang.noComputedStyle);
return false;
}
} catch (err) {
this.initError(lang.noComputedStyle);
return false;
}
}
if (!window.XMLHttpRequest) {
this.initError(lang.noXMLHttpRequest);
return false;
}
if (typeof this.config.guiPlaceholder !== 'object' || if (typeof this.config.guiPlaceholder !== 'object' ||
this.config.guiPlaceholder.nodeType !== this.ELEMENT_NODE) { this.config.guiPlaceholder.nodeType !== this.ELEMENT_NODE) {
@ -540,42 +517,36 @@ function PaintWeb (win, doc) {
} }
// JSON parser and serializer. // JSON parser and serializer.
if (!window.JSON) { pwlib = window.pwlib;
this.scriptLoad(PaintWeb.baseFolder + 'includes/json2.js', appEvent = pwlib.appEvent;
this.jsonlibReady);
} else { // Create the custom application events object.
this.jsonlibReady(); _self.events = new pwlib.appEvents(_self);
pwlib.extend(_self.config, paintweb_config);
pwlib.extend(_self.lang, lang_ko);
if (!(_self.initCanvas() && _self.initContext())) {
_self.initError(lang.errorInitCanvas);
return;
} }
return true;
};
/** _self.gui = new pwlib.gui(_self);
* The <code>load</code> event handler for the JSON library script.
* @private if (!_self.gui.init(xhtml)) {
*/ _self.initError(lang.errorInitGUI);
this.jsonlibReady = function () { return;
if (window.pwlib) {
_self.pwlibReady();
} else {
_self.scriptLoad(PaintWeb.baseFolder + 'includes/lib.js',
_self.pwlibReady);
} }
};
/** _self.initTools();
* The <code>load</code> event handler for the PaintWeb library script.
* @private
*/
this.pwlibReady = function () {
pwlib = window.pwlib;
appEvent = pwlib.appEvent;
// Create the custom application events object. return true;
_self.events = new pwlib.appEvents(_self);
_self.configLoad();
}; };
/** /**
* Report an initialization error. * Report an initialization error.
* *
@ -622,84 +593,12 @@ function PaintWeb (win, doc) {
} }
}; };
/**
* Asynchronously load the configuration file. This method issues an
* XMLHttpRequest to load the JSON file.
*
* @private
*
* @see PaintWeb.config.configFile The configuration file.
* @see pwlib.xhrLoad The library function being used for creating the
* XMLHttpRequest object.
*/
this.configLoad = function () {
this.configReady();
};
/**
* The configuration reader. This is the event handler for the XMLHttpRequest
* object, for the <code>onreadystatechange</code> event.
*
* @private
*
* @param {XMLHttpRequest} xhr The XMLHttpRequest object being handled.
*
* @see PaintWeb#configLoad The method which issues the XMLHttpRequest request
* for loading the configuration file.
*/
this.configReady = function (xhr) {
/*
* readyState values:
* 0 UNINITIALIZED open() has not been called yet.
* 1 LOADING send() has not been called yet.
* 2 LOADED send() has been called, headers and status are available.
* 3 INTERACTIVE Downloading, responseText holds the partial data.
* 4 COMPLETED Finished with all operations.
*/
var config = paintweb_config;
pwlib.extend(_self.config, config);
_self.langLoad();
};
/**
* Asynchronously load the language file. This method issues an XMLHttpRequest
* to load the JSON file.
*
* @private
*
* @see PaintWeb.config.lang The language you want for the PaintWeb user
* interface.
* @see pwlib.xhrLoad The library function being used for creating the
* XMLHttpRequest object.
*/
this.langLoad = function () {
this.langReady();
};
/**
* The language file reader. This is the event handler for the XMLHttpRequest
* object, for the <code>onreadystatechange</code> event.
*
* @private
*
* @param {XMLHttpRequest} xhr The XMLHttpRequest object being handled.
*
* @see PaintWeb#langLoad The method which issues the XMLHttpRequest request
* for loading the language file.
*/
this.langReady = function () {
pwlib.extend(_self.lang, lang_ko);
if (_self.initCanvas() && _self.initContext()) {
// Start GUI load now.
_self.guiLoad();
} else {
_self.initError(lang.errorInitCanvas);
}
};
/** /**
* Initialize the PaintWeb commands. * Initialize the PaintWeb commands.
@ -711,21 +610,24 @@ function PaintWeb (win, doc) {
this.initCommands = function () { this.initCommands = function () {
if (this.commandRegister('historyUndo', this.historyUndo) && if (this.commandRegister('historyUndo', this.historyUndo) &&
this.commandRegister('historyRedo', this.historyRedo) && this.commandRegister('historyRedo', this.historyRedo) &&
this.commandRegister('selectAll', this.selectAll) &&
this.commandRegister('selectionCrop', this.selectionCrop) && this.commandRegister('selectionCrop', this.selectionCrop) &&
this.commandRegister('selectionFill', this.selectionFill) && this.commandRegister('selectionFill', this.selectionFill) &&
//this.commandRegister('selectionCut', this.selectionCut) && this.commandRegister('selectionForNumberPlate', this.selectionForNumberPlate) &&
this.commandRegister('selectionCopy', this.selectionCopy) &&
//this.commandRegister('clipboardPaste', this.clipboardPaste) &&
this.commandRegister('imageSave', this.imageSave) &&
this.commandRegister('imageRotate', this.imageRotate) && this.commandRegister('imageRotate', this.imageRotate) &&
this.commandRegister('imageBright', this.imageBright) && this.commandRegister('imageBright', this.imageBright) &&
this.commandRegister('imageDark', this.imageDark) && this.commandRegister('imageDark', this.imageDark) &&
//this.commandRegister('imageClear', this.imageClear) && this.commandRegister('imageSave', this.imageSave) &&
this.commandRegister('swapFillStroke', this.swapFillStroke) &&
this.commandRegister('imageZoomIn', this.imageZoomIn) && this.commandRegister('imageZoomIn', this.imageZoomIn) &&
this.commandRegister('imageZoomOut', this.imageZoomOut) && this.commandRegister('imageZoomOut', this.imageZoomOut) &&
this.commandRegister('imageZoomReset', this.imageZoomReset)) { this.commandRegister('imageZoomReset', this.imageZoomReset) &&
this.commandRegister('selectAll', this.selectAll) &&
this.commandRegister('selectionCut', this.selectionCut) &&
this.commandRegister('selectionCopy', this.selectionCopy) &&
this.commandRegister('clipboardPaste', this.clipboardPaste) &&
this.commandRegister('imageClear', this.imageClear) &&
this.commandRegister('swapFillStroke', this.swapFillStroke)) {
return true; return true;
} else { } else {
this.initError(lang.errorInitCommands); this.initError(lang.errorInitCommands);
@ -733,103 +635,8 @@ function PaintWeb (win, doc) {
} }
}; };
/**
* Load th PaintWeb GUI. This method loads the GUI markup file, the stylesheet
* and the script.
*
* @private
*
* @see PaintWeb.config.guiStyle The interface style file.
* @see PaintWeb.config.guiScript The interface script file.
* @see pwlib.gui The interface object.
*/
this.guiLoad = function () {
var cfg = this.config,
gui = this.config.gui,
base = PaintWeb.baseFolder + cfg.interfacesFolder + '/' + gui + '/',
style = base + cfg.guiStyle,
script = base + cfg.guiScript;
this.styleLoad(gui + 'style', style);
if (pwlib.gui) {
this.guiScriptReady();
} else {
this.scriptLoad(script, this.guiScriptReady);
}
};
/**
* The <code>load</code> event handler for the PaintWeb GUI script. This
* method creates an instance of the GUI object that just loaded and starts
* loading the GUI markup.
*
* @private
*
* @see PaintWeb.config.guiScript The interface script file.
* @see PaintWeb.config.guiMarkup The interface markup file.
* @see pwlib.gui The interface object.
* @see pwlib.xhrLoad The library function being used for creating the
* XMLHttpRequest object.
*/
this.guiScriptReady = function () {
var cfg = _self.config,
gui = _self.config.gui,
base = cfg.interfacesFolder + '/' + gui + '/',
markup = base + cfg.guiMarkup;
_self.gui = new pwlib.gui(_self);
// Check if the interface markup is cached already.
if (markup in pwlib.fileCache) {
if (_self.gui.init(pwlib.fileCache[markup])) {
_self.initTools();
} else {
_self.initError(lang.errorInitGUI);
}
} else {
pwlib.xhrLoad(PaintWeb.baseFolder + markup, _self.guiMarkupReady);
}
};
/**
* The GUI markup reader. This is the event handler for the XMLHttpRequest
* object, for the <code>onreadystatechange</code> event.
*
* @private
*
* @param {XMLHttpRequest} xhr The XMLHttpRequest object being handled.
*
* @see PaintWeb#guiScriptReady The method which issues the XMLHttpRequest
* request for loading the interface markup file.
*/
this.guiMarkupReady = function (xhr) {
if (!xhr || xhr.readyState !== 4) {
return;
}
if (xhr.status !== 304 && xhr.status !== 200) {
_self.initError(lang.failedMarkupLoad);
return;
}
var param;
/*if (xhr.responseXML && xhr.responseXML.documentElement) { // by gu
param = xhr.responseXML;
} else */if (xhr.responseText) {
param = xhr.responseText;
} else {
_self.initError(lang.failedMarkupLoad);
return;
}
if (_self.gui.init(param)) {
_self.initTools();
} else {
_self.initError(lang.errorInitGUI);
}
};
/** /**
* Initialize the Canvas elements. This method creates the elements and * Initialize the Canvas elements. This method creates the elements and
@ -1085,7 +892,10 @@ function PaintWeb (win, doc) {
this.initialized = PaintWeb.INIT_DONE; this.initialized = PaintWeb.INIT_DONE;
//this.toolActivate("selection");
this.events.dispatch(new appEvent.appInit(this.initialized)); this.events.dispatch(new appEvent.appInit(this.initialized));
}; };
/** /**
@ -2424,41 +2234,6 @@ function PaintWeb (win, doc) {
xhr.send(''); xhr.send('');
}; };
/**
* Insert a stylesheet into the document.
*
* @param {String} id The stylesheet ID. This is used to avoid inserting the
* same style in the document.
* @param {String} url The URL of the stylesheet you want to insert.
* @param {String} [media='screen, projection'] The media attribute.
* @param {Function} [handler] The <code>load</code> event handler.
*/
this.styleLoad = function (id, url, media, handler) {
id = 'paintweb_style_' + id;
var elem = doc.getElementById(id);
if (elem) {
return;
}
if (!media) {
media = 'screen, projection';
}
elem = doc.createElement('link');
if (handler) {
elem.addEventListener('load', handler, false);
}
elem.id = id;
elem.rel = 'stylesheet';
elem.type = 'text/css';
elem.media = media;
elem.href = url;
this.elems.head.appendChild(elem);
};
/** /**
* Perform action undo. * Perform action undo.
@ -2740,8 +2515,9 @@ function PaintWeb (win, doc) {
bufferCanvas = _self.buffer.canvas, bufferCanvas = _self.buffer.canvas,
bufferCtx = _self.buffer.context, bufferCtx = _self.buffer.context,
img = _self.image; img = _self.image;
var bufferStyle = _self.buffer.canvas.style,
layerStyle = layerCanvas.style; var bufferStyle = _self.buffer.canvas.style;
var layerStyle = layerCanvas.style;
var srcCanvas = doc.createElement('canvas'); var srcCanvas = doc.createElement('canvas');
@ -2766,16 +2542,11 @@ function PaintWeb (win, doc) {
img.width = layerCanvas.width; img.width = layerCanvas.width;
img.height = layerCanvas.height; img.height = layerCanvas.height;
_self.events.dispatch(new appEvent.imageSizeChange(layerCanvas.width, layerCanvas.height));
bufferStyle.width = layerStyle.width = layerCanvas.width + 'px'; bufferStyle.width = layerStyle.width = (layerCanvas.width * img.canvasScale) + 'px';
bufferStyle.height = layerStyle.height = layerCanvas.height + 'px'; bufferStyle.height = layerStyle.height = (layerCanvas.height * img.canvasScale) + 'px';
_self.events.dispatch(new appEvent.canvasSizeChange(layerCanvas.width, layerCanvas.height, img.canvasScale)); _self.events.dispatch(new appEvent.canvasSizeChange(bufferCanvas.width * img.canvasScale, bufferCanvas.height * img.canvasScale, img.canvasScale));
var zoom = _self.image.zoom;
_self.image.zoom=1;
_self.imageZoomTo(zoom);
return true; return true;
}; };
@ -2965,6 +2736,14 @@ function PaintWeb (win, doc) {
} }
}; };
this.selectionForNumberPlate = function (ev) {
if (!_self.tool || _self.tool._id !== 'selection') {
return false;
} else {
return _self.tool.selectionForNumberPlate(ev);
}
};
/** /**
* Paste the current clipboard image. This only works when some ImageData is * Paste the current clipboard image. This only works when some ImageData is
* available in {@link PaintWeb#clipboard}. * available in {@link PaintWeb#clipboard}.

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

@ -40,7 +40,7 @@
background: #999; background: #999;
border: 1px solid #000; border: 1px solid #000;
height: 400px; height: 400px;
min-height: 150px; min-height: 500px;
min-width: 535px; min-width: 535px;
overflow: auto; overflow: auto;
position: relative position: relative
@ -191,11 +191,13 @@
display: block; display: block;
height: 31px; height: 31px;
overflow: hidden; overflow: hidden;
text-indent: 33px; text-indent: 31px;
width: 31px; width:31px;
white-space: nowrap white-space: nowrap
} }
.paintweb_strokeFillStyles:hover, .paintweb_strokeFillStyles:focus, .paintweb_strokeFillStyles:hover, .paintweb_strokeFillStyles:focus,
.paintweb_tool:hover, .paintweb_tool:focus, .paintweb_tool:hover, .paintweb_tool:focus,
.paintweb_command:hover, .paintweb_command:focus, .paintweb_command:hover, .paintweb_command:focus,
@ -516,7 +518,7 @@
clear: left clear: left
} }
.paintweb_cmd_selectionDelete, .paintweb_cmd_selectionFill { .paintweb_cmd_selectionDelete, .paintweb_cmd_selectionFill, .paintweb_cmd_selectionForNumberPlate {
border-left: 0; border-left: 0;
border-top: 0 border-top: 0
} }
@ -984,6 +986,13 @@
.paintweb_cmd_selectionCrop a { background-image: url('icons/i_crop.png') } .paintweb_cmd_selectionCrop a { background-image: url('icons/i_crop.png') }
.paintweb_cmd_selectionForNumberPlate a {
background-image: url('icons/i_carnumber.png');
background-size: contain;
text-indent: 150px;
width : 150px;
}
.paintweb_cmd_selectionDelete a { background-image: url('icons/i_block.png') } .paintweb_cmd_selectionDelete a { background-image: url('icons/i_block.png') }
.paintweb_cmd_selectionFill a { background-image: url('icons/i_colorfill.png') } .paintweb_cmd_selectionFill a { background-image: url('icons/i_colorfill.png') }

@ -344,8 +344,10 @@ pwlib.tools.selection = function (app) {
// Register selection-related commands // Register selection-related commands
app.commandRegister('selectionCrop', _self.selectionCrop); app.commandRegister('selectionCrop', _self.selectionCrop);
app.commandRegister('selectionDelete', _self.selectionDelete); app.commandRegister('selectionForNumberPlate', _self.selectionForNumberPlate);
app.commandRegister('selectionFill', _self.selectionFill); app.commandRegister('selectionFill', _self.selectionFill);
app.commandRegister('selectionDelete', _self.selectionDelete);
if (!timer) { if (!timer) {
timer = setInterval(timerFn, app.config.toolDrawDelay); timer = setInterval(timerFn, app.config.toolDrawDelay);
@ -388,8 +390,11 @@ pwlib.tools.selection = function (app) {
// Unregister selection-related commands // Unregister selection-related commands
app.commandUnregister('selectionCrop'); app.commandUnregister('selectionCrop');
app.commandUnregister('selectionDelete');
app.commandUnregister('selectionFill'); app.commandUnregister('selectionFill');
app.commandUnregister('selectionForNumberPlate');
app.commandUnregister('selectionDelete');
return true; return true;
}; };
@ -1360,11 +1365,12 @@ pwlib.tools.selection = function (app) {
sel.context.fillStyle = bufferContext.fillStyle; sel.context.fillStyle = bufferContext.fillStyle;
sel.context.fillRect(0, 0, sel.widthOriginal, sel.heightOriginal); sel.context.fillRect(0, 0, sel.widthOriginal, sel.heightOriginal);
bufferContext.fillRect(sel.x, sel.y, sel.width, sel.height); bufferContext.fillRect(sel.x, sel.y, sel.width, sel.height);
} else { } else {
layerContext.fillStyle = bufferContext.fillStyle; layerContext.fillStyle = bufferContext.fillStyle;
layerContext.fillRect(sel.x, sel.y, sel.width, sel.height); layerContext.fillRect(sel.x, sel.y, sel.width, sel.height);
app.historyAdd(); app.historyAdd();
_self.selectionDrop(); //선택영역 해제
} }
return true; return true;
@ -1400,6 +1406,42 @@ pwlib.tools.selection = function (app) {
app.imageCrop(sel.x, sel.y, w, h); app.imageCrop(sel.x, sel.y, w, h);
isNumberPlate = false;
return true;
};
/**
* 번호판 캡쳐
*
* <p>This method invokes the {@link this#selectionMerge} and {@link
* PaintWeb#imageCrop} methods.
*
* @returns {Boolean} True if the operation was successful, or false if not.
*/
this.selectionForNumberPlate = function () {
if (_self.state !== _self.STATE_SELECTED) {
return false;
}
_self.selectionMerge();
var w = sel.width,
h = sel.height,
sumX = sel.x + w,
sumY = sel.y + h;
if (sumX > image.width) {
w -= sumX - image.width;
}
if (sumY > image.height) {
h -= sumY - image.height;
}
app.imageCrop(sel.x, sel.y, w, h);
isNumberPlate = true;
return true; return true;
}; };
@ -1426,16 +1468,13 @@ pwlib.tools.selection = function (app) {
case config.keys.selectionCrop: case config.keys.selectionCrop:
return _self.selectionCrop(ev); return _self.selectionCrop(ev);
case config.keys.selectionFill:
return _self.selectionFill(ev);
case config.keys.selectionDelete: case config.keys.selectionDelete:
return _self.selectionDelete(ev); return _self.selectionDelete(ev);
case config.keys.selectionDrop: case config.keys.selectionDrop:
return _self.selectionDrop(ev); return _self.selectionDrop(ev);
case config.keys.selectionFill:
return _self.selectionFill(ev);
default: default:
return false; return false;
} }

Loading…
Cancel
Save