diff -rupN --exclude=.svn /root/paintweb/src/config-example.json paintweb/src/config-example.json --- /root/paintweb/src/config-example.json 2009-11-08 17:54:46.000000000 +0000 +++ paintweb/src/config-example.json 2010-09-29 16:02:55.000000000 +0000 @@ -388,6 +388,14 @@ "borderWidth": 3, /** + * How much to rotate (in degrees) the selection. + * + * @type Number + * @default 0 + */ + "rotation": 0, + + /** * Keyboard shortcuts for several selection-related commands. * @type Object */ diff -rupN --exclude=.svn /root/paintweb/src/config.json paintweb/src/config.json --- /root/paintweb/src/config.json 1970-01-01 00:00:00.000000000 +0000 +++ paintweb/src/config.json 2010-09-29 14:55:27.000000000 +0000 @@ -0,0 +1,670 @@ +{ + // $Date: 2009-11-08 19:54:46 +0200 $ + // TODO: try jsdoc-toolkit on this json (!) + + /** + * The list of available languages. This associated language IDs to their + * language titles. + * + * @type Object + */ + "languages": { + // Besides the language title, you may also tell the file name. The 'file' + // property needs to be relative to the PaintWeb baseFolder. If no 'file' is + // given, then the 'en.json' file name is used, and the file will be loaded + // from the 'langFolder' you specify below. + "en": { "title": "English" } + }, + + /** + * The default language. + * + * @type String + * @default "en" + */ + "lang": "en", + + /** + * The folder which holds the language files. + * + * @type String + * @default "lang" + */ + "langFolder": "lang", + + /** + * The graphical user interface you want to use. + * + * @type String + * @default "default" + */ + "gui": "default", + + /** + * The folder contains all the interfaces. + * + * @type String + * @default "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%", + * "450px" or "30em". + * + *
Note: the GUI implementation might ignore this value. + * + * @type String + * @default "100%" + */ + "viewportWidth": "100%", + + /** + * The image viewport height. Make sure the value is a CSS length, like "50%", + * "450px" or "30em". + * + *
Note: the GUI implementation might ignore this value. + * + * @type String + * @default "400px" + */ + "viewportHeight": "400px", + + /** + * Image save quality for the JPEG format. + * + * @type Number + * @default 0.9 + */ + "jpegSaveQuality": 0.9, + + /** + * The default image width. + * + * @type Number + * @default 400 + */ + "imageWidth": 400, + + /** + * The default image width. + * + * @type Number + * @default 300 + */ + "imageHeight": 300, + + /** + * Image preload. The image you want to display when PaintWeb loads. This must + * be a reference to an HTML Image element. + * + * @type HTMLImageElement + * @default null + */ + "imagePreload": null, + + /** + * Default background color. + * + * @type CSS3Color + * @default "#fff" + */ + "backgroundColor": "#fff", + + /** + * Default fill style. + * + * @type CSS3Color-rgba functional notation + * @default "rgba(0,0,0,1)" + */ + "fillStyle": "rgba(0,0,0,1)", + + /** + * Default stroke style. + * + * @type CSS3Color-rgba functional notation + * @default "rgba(0,0,255,1)" + */ + "strokeStyle": "rgba(0,0,255,1)", + + /** + * Enable checkers background. This tells the user interface to render + * checkers in the image background. These are visible only when parts of + * the image being edited are transparent. + * + *
If the device you are running PaintWeb on has limited resources, + * disabling the checkers background should improve the drawing performance. + * + * @type Boolean + * @default true + */ + "checkersBackground": true, + + /** + * GUI placeholder element. This element will hold all the PaintWeb interface + * elements. + * + *
For a successful initialization of PaintWeb, you must define this + * configuration value programatically from your scripts. + * + * @type Element + * @default null + */ + "guiPlaceholder": null, + + /** + * Shape drawing "type": filled, only stroke, or both. Possible values: + * "filled", "stroke" or "both". + * + * @type String + * @default "both" + */ + "shapeType": "both", + + /** + * Number of available history steps. + * + * @type Number + * @default 10 + */ + "historyLimit": 10, + + /** + * Zoom factor when the user increases/decreases the image zoom level. + * + * @type Number + * @default 0.05 + */ + // 0.05 means 5% zoom. + "imageZoomStep": 0.05, + + /** + * The maximum allowed image zoom level. + * + * @type Number + * @default 4 + */ + // 4 means 400% zoom. + "imageZoomMax": 4, + + /** + * The minimum allowed image zoom level. + * + * @type Number + * @default 0.2 + */ + // 0.2 means 20% zoom. + "imageZoomMin": 0.2, + + /** + * The image zoom control keys, for zoom in, zoom out and zoom reset. + * @type Object + */ + "imageZoomKeys": { + "in": "+", + "out": "-", + "reset": "*" + }, + + /** + * Holds the list of drawing tools you want to load. + * @type Array + */ + "tools": ["bcurve", "cbucket", "cpicker", "ellipse", "eraser", "hand", "insertimg", "line", "pencil", "polygon", "rectangle", "selection", "text"], + + /** + * Tools folder. + * @type String + * @default "tools" + */ + "toolsFolder": "tools", + + /** + * The default tool ID. + * + * @type String + * @default "line" + * @see this.tools The array holding the list of drawing tools you want + * loaded. + */ + "toolDefault": "line", + + /** + * Tool drawing delay (milliseconds). Some tools delay their drawing + * operations for performance reasons. + * + * @type Number + * @default 80 + */ + "toolDrawDelay": 80, + + /** + * Holds the list of extensions you want to load. + * @type Array + */ + "extensions": ["colormixer", "mousekeys"], + + /** + * Extensions folder. + * + * @type String + * @default "extensions" + */ + "extensionsFolder": "extensions", + + /** + * @namespace Line tool options. + */ + "line": { + /** + * Line cap. Possible values: "butt", "round", "square". + * + * @type String + * @default "round" + */ + "lineCap": "round", + + /** + * Line join. Possible values: "round", "bevel", "miter". + * + * @type String + * @default "round" + */ + "lineJoin": "round", + + /** + * Line width. + * + * @type Number + * @default 1 + */ + "lineWidth": 1, + + /** + * Miter limit. + * + * @type Number + * @default 10 + */ + "miterLimit": 10 + }, + + /** + * @namespace Shadow options. + */ + "shadow": { + /** + * Tells if a shadow should render or not. + * + * @type Boolean + * @default false + */ + "enable": false, + + /** + * Shadow color + * + * @type CSS3Color-rgba() functional notation + * @default "rgba(0,0,0,1)" + */ + "shadowColor": "rgba(0,0,0,1)", + + /** + * Shadow blur. + * + * @type Number + * @default 5 + */ + "shadowBlur": 5, + + /** + * Shadow offset X. + * + * @type Number + * @default 5 + */ + "shadowOffsetX": 5, + + /** + * Shadow offset %. + * + * @type Number + * @default 5 + */ + "shadowOffsetY": 5 + }, + + /** + * @namespace Selection tool options. + */ + "selection": { + /** + * Selection transformation mode. This tells if any drag/resize would also + * affect the selected pixels or not. + * + * @type Boolean + * @default false + */ + "transform": false, + + /** + * Transparent selection. + * + * @type Boolean + * @default true + */ + "transparent": true, + + /** + * Selection marquee border width. + * + * @type Number + * @default 3 + */ + "borderWidth": 3, + + /** + * How much to rotate (in degrees) the selection. + * + * @type Number + * @default 0 + */ + "rotation": 0, + + /** + * Keyboard shortcuts for several selection-related commands. + * @type Object + */ + "keys": { + "selectionCrop": "Control K", + "selectionDelete": "Delete", + "selectionDrop": "Escape", + "selectionFill": "Alt Backspace", + "transformToggle": "Enter" + } + }, + + /** + * Text tool options. + * @type Object + */ + "text": { + "bold": false, + "italic": false, + + /** + * The default list of font families available in font family drop-down. + * @type Array + */ + "fontFamilies": ["sans-serif", "serif", "cursive", "fantasy", "monospace"], + + /** + * The font family used for rendering the text. + * @type String + * @default "sans-serif" + */ + "fontFamily": "sans-serif", + + "fontSize": 36, + + /** + * Horizontal text alignment. Possible values: "left", "center", "right". + * + *
Note that the Canvas Text API also defines the "start" and "end" + * values, which are not "supported" by PaintWeb. + * + * @type String + * @default "left" + */ + "textAlign": "left", + + /** + * Vertical text alignment. Possible values: "top", "hanging", "middle", + * "alphabetic", "ideographic", or "bottom". + * + * @type String + * @default "alphabetic" + */ + "textBaseline": "top" + }, + + /** + * @namespace Color Mixer extension configuration. + */ + "colormixer": { + /** + * Holds the minimum and maximum value for each color channel input field. + * The value incrementation step is also included - this is used the user + * presses the up/down arrow keys in the input of the color channel. + * + * @type Object + */ + "inputValues": { + // RGB + // order: minimum, maximum, step + "red": [0, 255, 1], + "green": [0, 255, 1], + "blue": [0, 255, 1], + + // HSV + // Hue - degrees + "hue": [0, 360, 1], + "sat": [0, 255, 1], + "val": [0, 255, 1], + + // CMYK - all are percentages + "cyan": [0, 100, 1], + "magenta": [0, 100, 1], + "yellow": [0, 100, 1], + "black": [0, 100, 1], + + // CIE Lab + // cie_l = Lightness, it's a percentage value + // cie_a and cie_b are the color-opponent dimensions + "cie_l": [ 0, 100, 1], + "cie_a": [ -86, 98, 1], + "cie_b": [-107, 94, 1], + + "alpha": [0, 100, 1] + }, + + /** + * CIE Lab configuration. + * @type Object + */ + "lab": { + // The RGB working space is sRGB which has the reference white point of + // D65. + // These are the chromaticity coordinates for the red, green and blue + // primaries. + "x_r": 0.64, + "y_r": 0.33, + "x_g": 0.3, + "y_g": 0.6, + "x_b": 0.13, + "y_b": 0.06, + + // Standard observer: D65 (daylight), 2° (CIE 1931). + // Chromaticity coordinates. + "ref_x": 0.31271, + "ref_y": 0.32902, + + // This is the calculated reference white point (xyY to XYZ) for D65, also + // known as the reference illuminant tristimulus. + // These values are updated based on chromaticity coordinates, during + // initialization. + "w_x": 0.95047, + "w_y": 1, + "w_z": 1.08883, + + // The 3x3 matrix used for multiplying the RGB values when converting RGB + // to XYZ. + // These values are updated based on the chromaticity coordinates, during + // initialization. + "m": [ 0.412424, 0.212656, 0.0193324, + 0.357579, 0.715158, 0.119193, + 0.180464, 0.0721856, 0.950444], + + // The same matrix, but inverted. This is used for the XYZ to RGB conversion. + "m_i": [ 3.24071, -0.969258, 0.0556352, + -1.53726, 1.87599, -0.203996, + -0.498571, 0.0415557, 1.05707] + }, + + /** + * Slider width. This value must be relative to the color space + * visualisation canvas element: 1 means full width, 0.5 means half width, + * etc. + * + * @type Number + * @default 0.10 (which is 10% of the canvas element width) + */ + "sliderWidth": 0.10, + + /** + * Spacing between the slider and the color chart. + * + * @type Number + * @default 0.03 (which is 3% of the canvas element width) + */ + "sliderSpacing": 0.03, + + /** + * Holds the list of color palettes. + * @type Object + */ + "colorPalettes": { + "_saved" : { + // Color values are: red, green, blue. All three channels have values + // ranging between 0 and 1. + "colors" : [[1,1,1], [1,1,0], [1,0,1], [0,1,1], [1,0,0], [0,1,0], [0,0,1], [0,0,0]] + }, + "windows" : { + "file" : "colors/windows.json" + }, + "macos" : { + "file" : "colors/macos.json" + }, + "web" : { + "file" : "colors/web.json" + } + }, + + "paletteDefault": "windows" + }, + + /** + * @namespace Holds the MouseKeys extension options. + */ + "mousekeys": { + /** + * The mouse keys movement acceleration. + * + * @type Number + * @default 0.1 + * @see PaintMouseKeys The MouseKeys extension. + */ + "accel": 0.1, + + /** + * Holds the list of actions, associated to keyboard shortcuts. + * + * @type Object + */ + // We make sure the number keys on the NumPad also work when the Shift key + // is down. + "actions": { + "ButtonToggle": [0], + "SouthWest": [1], + "South": [2], + "SouthEast": [3], + "West": [4], + "ButtonClick": [5], + "East": [6], + "NorthWest": [7], + "North": [8], + "NorthEast": [9] + + /* + You might want Shift+NumPad keys as well ... + Shift+Arrows breaks spatial navigation in Opera. + "ButtonToggle": [0, "Shift Insert"], + "SouthWest": [1, "Shift End"], + "South": [2, "Shift Down"], + "SouthEast": [3, "Shift PageDown"], + "West": [4, "Shift Left"], + "ButtonClick": [5, "Shift Clear"], + "East": [6, "Shift Right"], + "NorthWest": [7, "Shift Home"], + "North": [8, "Shift Up"], + "NorthEast": [9, "Shift PageUp"] + */ + } + }, + + /** + * Keyboard shortcuts associated to drawing tools and other actions. + * + * @type Object + * @see PaintTools The object holding all the drawing tools. + */ + "keys": { + // Use "command": "id" to execute some command. + "Control Z": { "command": "historyUndo" }, + "Control Y": { "command": "historyRedo" }, + + "Control N": { "command": "imageClear" }, + "Control S": { "command": "imageSave" }, + + "Control A": { "command": "selectAll" }, + "Control X": { "command": "selectionCut" }, + "Shift Delete": { "command": "selectionCut" }, + "Control C": { "command": "selectionCopy" }, + "Control V": { "command": "clipboardPaste" }, + + // Use "toolActivate": "id" to activate the tool with the given ID. + "C": { "toolActivate": "cpicker" }, + "E": { "toolActivate": "ellipse" }, + "F": { "toolActivate": "cbucket" }, + "G": { "toolActivate": "polygon" }, + "H": { "toolActivate": "hand" }, + "I": { "toolActivate": "insertimg" }, + "L": { "toolActivate": "line" }, + "O": { "toolActivate": "eraser" }, + "P": { "toolActivate": "pencil" }, + "R": { "toolActivate": "rectangle" }, + "S": { "toolActivate": "selection" }, + "T": { "toolActivate": "text" }, + "V": { "toolActivate": "bcurve" }, + + // Miscellaneous commands. + "X": { "command": "swapFillStroke" }, + "F1": { "command": "about" } + } + + // vim:set spell spl=en fo=wan1croql tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix ft=javascript: +} Binary files /root/paintweb/src/interfaces/default/icons/selectionHorizontalInvert.png and paintweb/src/interfaces/default/icons/selectionHorizontalInvert.png differ Binary files /root/paintweb/src/interfaces/default/icons/selectionRotate.png and paintweb/src/interfaces/default/icons/selectionRotate.png differ Binary files /root/paintweb/src/interfaces/default/icons/selectionVerticalInvert.png and paintweb/src/interfaces/default/icons/selectionVerticalInvert.png differ diff -rupN --exclude=.svn /root/paintweb/src/interfaces/default/layout.xhtml paintweb/src/interfaces/default/layout.xhtml --- /root/paintweb/src/interfaces/default/layout.xhtml 2009-11-08 11:19:52.000000000 +0000 +++ paintweb/src/interfaces/default/layout.xhtml 2010-09-30 14:18:42.000000000 +0000 @@ -146,6 +146,8 @@
Cut selection
Copy selection
Clipboard paste
+Horizontally invert selection
+Vertically invert selection
Crop selection
Delete selection
@@ -159,6 +161,11 @@ + ++ +
diff -rupN --exclude=.svn /root/paintweb/src/interfaces/default/script.js paintweb/src/interfaces/default/script.js --- /root/paintweb/src/interfaces/default/script.js 2010-06-26 18:47:30.000000000 +0000 +++ paintweb/src/interfaces/default/script.js 2010-09-20 00:18:44.000000000 +0000 @@ -277,6 +277,10 @@ pwlib.gui = function (app) { this.canvasResizer.events.add('guiResizeStart', this.canvasResizeStart); this.canvasResizer.events.add('guiResizeEnd', this.canvasResizeEnd); + if (!config.imageResizeable) { + resizeHandle.style.display = 'none'; + } + // Setup the viewport resizer. var resizeHandle = this.elems.viewportResizer; if (!resizeHandle) { @@ -297,6 +301,10 @@ pwlib.gui = function (app) { this.viewportResizeMouseMove); this.viewportResizer.events.add('guiResizeEnd', this.viewportResizeEnd); + if (!config.viewportResizeable) { + resizeHandle.style.display = 'none'; + } + if ('statusMessage' in this.elems) { this.elems.statusMessage._prevText = false; } @@ -853,11 +861,15 @@ pwlib.gui = function (app) { var selCrop = this.commands.selectionCrop, selFill = this.commands.selectionFill, - selDelete = this.commands.selectionDelete; + selDelete = this.commands.selectionDelete, + selHorizontalInvert = this.commands.selectionHorizontalInvert, + selVerticalInvert = this.commands.selectionVerticalInvert; selCrop.className += classDisabled; selFill.className += classDisabled; selDelete.className += classDisabled; + selHorizontalInvert.className += classDisabled; + selVerticalInvert.className += classDisabled; return true; }; @@ -1785,7 +1797,8 @@ pwlib.gui = function (app) { elems = [_self.commands.selectionCut, _self.commands.selectionCopy, _self.elems.selTab_selectionCut, _self.elems.selTab_selectionCopy, _self.commands.selectionDelete, _self.commands.selectionFill, - _self.commands.selectionCrop]; + _self.commands.selectionCrop, _self.commands.selectionHorizontalInvert, + _self.commands.selectionVerticalInvert]; for (var i = 0, n = elems.length; i < n; i++) { elem = elems[i]; @@ -1798,7 +1811,10 @@ pwlib.gui = function (app) { if (ev.state === ev.STATE_NONE && elemEnabled) { elem.className += classDisabled; } else if (ev.state === ev.STATE_SELECTED && !elemEnabled) { - elem.className = elem.className.replace(classDisabled, ''); + if (elems[i] !== _self.commands.selectionCrop && + !config.imageResizeable) { + elem.className = elem.className.replace(classDisabled, ''); + } } } }; diff -rupN --exclude=.svn /root/paintweb/src/interfaces/default/style.css paintweb/src/interfaces/default/style.css --- /root/paintweb/src/interfaces/default/style.css 2009-11-08 11:15:04.000000000 +0000 +++ paintweb/src/interfaces/default/style.css 2010-09-30 14:20:10.000000000 +0000 @@ -501,7 +501,9 @@ } .paintweb_main_selection .paintweb_cmd_selectionCopy, -.paintweb_main_selection .paintweb_cmd_clipboardPaste { border-left: 0 } +.paintweb_main_selection .paintweb_cmd_clipboardPaste, +.paintweb_main_selection .paintweb_cmd_selectionHorizontalInvert, +.paintweb_main_selection .paintweb_cmd_selectionVerticalInvert { border-left: 0 } .paintweb_cmd_selectionCrop { border-top: 0; @@ -516,14 +518,14 @@ .paintweb_opt_selectionTransparent, .paintweb_opt_selectionTransform { background: #f2f2f2; border: 1px solid #d6d6d6; - left: 110px; + left: 170px; + position: absolute; line-height: 31px; margin: 0; padding: 0 5px; - position: absolute; top: 0; vertical-align: middle; - width: 15em + width: 150px; /* 15em */ } .paintweb_opt_selectionTransform { @@ -531,6 +533,23 @@ top: 33px } +.paintweb_opt_selectionRotate { + background: #f2f2f2; + border: 1px solid #d6d6d6; + left: 340px; + position: absolute; + line-height: 31px; + margin: 0; + padding: 0 5px; + top: 0; + vertical-align: middle; + width: 160px; /* 15em */ +} + +.paintweb_cfg_selection_rotation { + width: 4em; +} + .paintweb_opt_fontFamily label, .paintweb_opt_fontSize label { display: none } .paintweb_opt_fontFamily, .paintweb_opt_fontSize { @@ -971,6 +990,10 @@ .paintweb_cmd_selectionFill a { background-image: url('icons/selectionFill.png') } +.paintweb_cmd_selectionHorizontalInvert a { background-image: url('icons/selectionHorizontalInvert.png') } + +.paintweb_cmd_selectionVerticalInvert a { background-image: url('icons/selectionVerticalInvert.png') } + .paintweb_cmd_imageSave a { background-image: url('icons/imageSave.png') } .paintweb_cmd_imageClear a { background-image: url('icons/imageClear.png') } diff -rupN --exclude=.svn /root/paintweb/src/lang/en.json paintweb/src/lang/en.json --- /root/paintweb/src/lang/en.json 2009-11-16 16:13:48.000000000 +0000 +++ paintweb/src/lang/en.json 2010-09-30 14:19:25.000000000 +0000 @@ -29,6 +29,7 @@ "errorCpickerUnsupported": "Error: your browser does not implement the get/putImageData methods! The color picker tool cannot be used.", "errorCbucketUnsupported": "Error: your browser does not implement the get/putImageData methods! The color bucket tool cannot be used.", "errorClipboardUnsupported": "Error: your browser does not support get/putImageData! Clipboard operations like cut/copy/paste cannot be used.", + "errorInvertingUnsupported": "Error: your browser does not support get/putImageData! Image inversion cannot be used.", "errorTextUnsupported": "Error: your browser does not implement the Canvas Text API! The text tool cannot be used.", "errorInsertimg": "The image could not be inserted. Maybe the address does not point to an image.", "errorInsertimgHost": "The URL you provided points to a different host. The image cannot be added for security reasons.", @@ -112,7 +113,9 @@ "selectionCrop": "Crop selection", "selectionCut": "Cut selection", "selectionDelete": "Delete selection", - "selectionFill": "Fill the selection" + "selectionFill": "Fill the selection", + "selectionHorizontalInvert": "Invert selection horizontally", + "selectionVerticalInvert": "Invert selection vertically" }, "inputs": { @@ -141,7 +144,9 @@ "transform": "Image manipulation", "transformTitle": "If checked, the selected pixels will also be dragged/resized when you make changes to the selection. If unchecked, only the selection marquee will be dragged/resized - pixels will remain unaffected by any such changes.", "transparent": "Transparent selection", - "transparentTitle": "If checked, the background will remain transparent. If unchecked, the background will be filled with the current fill color." + "transparentTitle": "If checked, the background will remain transparent. If unchecked, the background will be filled with the current fill color.", + "rotation": "Rotate ", + "rotationTitle": "Will rotate any pasted content in the amount of degrees specified." }, "text": { "bold": "Bold", diff -rupN --exclude=.svn /root/paintweb/src/tools/selection.js paintweb/src/tools/selection.js --- /root/paintweb/src/tools/selection.js 2009-07-02 13:07:14.000000000 +0000 +++ paintweb/src/tools/selection.js 2010-09-30 14:20:53.000000000 +0000 @@ -346,6 +346,10 @@ pwlib.tools.selection = function (app) { app.commandRegister('selectionCrop', _self.selectionCrop); app.commandRegister('selectionDelete', _self.selectionDelete); app.commandRegister('selectionFill', _self.selectionFill); + app.commandRegister('selectionHorizontalInvert', + _self.selectionHorizontalInvert); + app.commandRegister('selectionVerticalInvert', + _self.selectionVerticalInvert); if (!timer) { timer = setInterval(timerFn, app.config.toolDrawDelay); @@ -390,6 +394,12 @@ pwlib.tools.selection = function (app) { app.commandUnregister('selectionCrop'); app.commandUnregister('selectionDelete'); app.commandUnregister('selectionFill'); + app.commandUnregister('selectionHorizontalInvert'); + app.commandUnregister('selectionVerticalInvert'); + + // Make sure layer context is not feeling the wrath of + // the rotation + layerContext.setTransform(1, 0, 0, 1, 0, 0); return true; }; @@ -1053,6 +1063,54 @@ pwlib.tools.selection = function (app) { }; /** + * Determines the new dimensions for a rotation when given a specific + * height and width. + * + * @param {Number} width The width for which we are rotating. + * @param {Number} height The height for which we are rotating. + * + * @returns {JSON} An array containing width and height. + */ + function rotationDimensions(width, height) { + var returnValue = {"width": 0, "height": 0}; + var radians = config.rotation * Math.PI/180; + + // To help determine the new width and new height, + // divide the width and height by 2 to emulate + // a rotation around the origin. + width = width/2; + height = height/2; + + // Select two adjacent points to rotate. This will + // help determine the furthest x points and furthest y + // points. Allowing determination of the new height and width. + var pointOne = {"x": width, "y": height}; + var pointTwo = {"x": -width, "y": height}; + + // Calculate the points once rotated. + var rotatedPointOne = { + "x": (pointOne.x * Math.cos(radians)) - (pointOne.y * Math.sin(radians)), + "y": (pointOne.x * Math.sin(radians)) + (pointOne.y * Math.cos(radians)) + }; + var rotatedPointTwo = { + "x": (pointTwo.x * Math.cos(radians)) - (pointTwo.y * Math.sin(radians)), + "y": (pointTwo.y * Math.sin(radians)) - (pointTwo.y * Math.cos(radians)) + }; + + // Determine the width and height to use, so the rotation + // can be fit within a square. + var widthOne = Math.floor(Math.abs(rotatedPointOne.x)*2); + var widthTwo = Math.floor(Math.abs(rotatedPointTwo.x)*2); + returnValue.width = (widthOne > widthTwo) ? widthOne : widthTwo; + + var heightOne = Math.floor(Math.abs(rotatedPointOne.y)*2); + var heightTwo = Math.floor(Math.abs(rotatedPointTwo.y)*2); + returnValue.height = (heightOne > heightTwo) ? heightOne : heightTwo; + + return returnValue; + } + + /** * Merge the selection buffer onto the current image layer. * *This method dispatches the {@link pwlib.appEvent.selectionChange}
@@ -1232,13 +1290,29 @@ pwlib.tools.selection = function (app) {
// The default position for the pasted image is the top left corner of the
// visible area, taking into consideration the zoom level.
var x = MathRound(gui.elems.viewport.scrollLeft / image.canvasScale),
- y = MathRound(gui.elems.viewport.scrollTop / image.canvasScale),
- w = app.clipboard.width,
- h = app.clipboard.height;
+ y = MathRound(gui.elems.viewport.scrollTop / image.canvasScale);
+
+ // Take into account rotation
+ var rotatedDimensions = rotationDimensions(app.clipboard.width,
+ app.clipboard.height);
+ var w = rotatedDimensions.width,
+ h = rotatedDimensions.height;
sel.canvas.width = w;
sel.canvas.height = h;
- sel.context.putImageData(app.clipboard, 0, 0);
+
+ var tmpCanvas = app.doc.createElement('canvas'),
+ radians = config.rotation * Math.PI/180;
+ tmpCanvas.getContext('2d').putImageData(app.clipboard, 0, 0);
+ sel.context.save();
+ sel.context.translate(x+(w/2), y+(h/2));
+ sel.context.rotate(radians);
+ sel.context.translate(-(x+(w/2)), -(y+(h/2)));
+ sel.context.drawImage(tmpCanvas, 0, 0);
+ sel.context.restore();
+// sel.context.putImageData(app.clipboard, 0, 0);
+
+ delete tmpCanvas;
if (_self.state === _self.STATE_SELECTED) {
bufferContext.clearRect(sel.x, sel.y, sel.width, sel.height);
@@ -1380,7 +1454,7 @@ pwlib.tools.selection = function (app) {
* @returns {Boolean} True if the operation was successful, or false if not.
*/
this.selectionCrop = function () {
- if (_self.state !== _self.STATE_SELECTED) {
+ if (_self.state !== _self.STATE_SELECTED || !config.imageResizeable) {
return false;
}
@@ -1404,6 +1478,150 @@ pwlib.tools.selection = function (app) {
};
/**
+ * This method is used to invert an image horizontally within a selected
+ * area.
+ *
+ * @returns {Boolean} True if the operation was successful, or false if not.
+ */
+ this.selectionHorizontalInvert = function() {
+ var contextToManipulate = sel.layerCleared ? bufferContext : layerContext;
+
+ if (!contextToManipulate.getImageData ||
+ !contextToManipulate.putImageData) {
+ alert(lang.errorInvertingUnsupported);
+ return false;
+ }
+
+ var imageData = contextToManipulate.getImageData(sel.x, sel.y,
+ sel.width, sel.height);
+ var imagePixels = imageData.data;
+
+ var imageDataReplace = contextToManipulate.createImageData(
+ sel.width, sel.height);
+ var imagePixelsReplace = imageDataReplace.data;
+
+ for (var iy = 0; iy < imageData.height; iy++) {
+ for (var ix = 0; ix < imageData.width; ix++) {
+ var from = (iy*imageData.width*4) + (ix*4);
+ var to = ((iy*imageData.width*4) + (imageData.width*4)) - (ix*4);
+
+
+ imagePixelsReplace[to] = imagePixels[from];
+ imagePixelsReplace[to+1] = imagePixels[from+1];
+ imagePixelsReplace[to+2] = imagePixels[from+2];
+ imagePixelsReplace[to+3] = imagePixels[from+3];
+ }
+ }
+
+ contextToManipulate.clearRect(sel.x, sel.y, sel.width, sel.height);
+ contextToManipulate.putImageData(imageDataReplace, sel.x, sel.y);
+
+ if (!sel.layerCleared) {
+ app.historyAdd();
+ } else {
+ sel.context.drawImage(contextToManipulate, sel.x, sel.y,
+ sel.width, sel.height, 0, 0, widthOriginal, heightOriginal);
+ }
+
+ return true;
+ }
+
+ /**
+ * This method is used to invert an image vertically within a selected
+ * area.
+ *
+ * @returns {Boolean} True if the operation was successful, or false if not.
+ */
+ this.selectionVerticalInvert = function() {
+ var contextToManipulate = sel.layerCleared ? bufferContext : layerContext;
+
+ if (!contextToManipulate.getImageData ||
+ !contextToManipulate.putImageData) {
+ alert(lang.errorInvertingUnsupported);
+ return false;
+ }
+
+ var imageData = contextToManipulate.getImageData(sel.x, sel.y,
+ sel.width, sel.height);
+ var imagePixels = imageData.data;
+
+ var imageDataReplace = contextToManipulate.createImageData(
+ sel.width, sel.height);
+ var imagePixelsReplace = imageDataReplace.data;
+
+ for (var iy = 0; iy < imageData.height; iy++) {
+ for (var ix = 0; ix < imageData.width; ix++) {
+ var from = (iy*imageData.width*4) + (ix*4);
+ var to = ((imageData.height - iy)*imageData.width*4) + (ix*4);
+
+ imagePixelsReplace[to] = imagePixels[from];
+ imagePixelsReplace[to+1] = imagePixels[from+1];
+ imagePixelsReplace[to+2] = imagePixels[from+2];
+ imagePixelsReplace[to+3] = imagePixels[from+3];
+ }
+ }
+
+ contextToManipulate.clearRect(sel.x, sel.y, sel.width, sel.height);
+ contextToManipulate.putImageData(imageDataReplace, sel.x, sel.y);
+
+ if (!sel.layerCleared) {
+ app.historyAdd();
+ } else {
+ sel.context.drawImage(contextToManipulate, sel.x, sel.y,
+ sel.width, sel.height, 0, 0, widthOriginal, heightOriginal);
+ }
+
+ return true;
+ }
+
+ /**
+ * This method rotates the selected content.
+ *
+ * @returns {Boolean} True if the operation was successful, or false if not.
+ */
+ this.selectionRotate = function() {
+ // create new canvas
+ var tempCanvas = app.doc.createElement('canvas');
+ if (!tempCanvas) {
+ alert(lang.errorToolActivate);
+ return false;
+ }
+
+ // Calculate the radians
+ var radians = config.rotation * Math.PI/180;
+
+ // Set canvas width/height
+ tempCanvas.width = sel.width;
+ tempCanvas.height = sel.height;
+
+ // get image data from main canvas
+ var rotateAroundX = Math.floor(sel.width/2);
+ var rotateAroundY = Math.floor(sel.height/2);
+
+ var tmpContext = tempCanvas.getContext('2d');
+ tmpContext.save();
+ tmpContext.translate(rotateAroundX, rotateAroundY);
+ tmpContext.rotate(radians);
+ tmpContext.translate(-rotateAroundX, -rotateAroundY);
+ tmpContext.drawImage(layerCanvas, sel.x, sel.y, sel.width, sel.height,
+ 0, 0, sel.width, sel.height);
+ tmpContext.restore();
+
+ // clear content in selected area of main canvas
+ layerContext.clearRect(sel.x, sel.y, sel.width, sel.height);
+
+ // draw image back on canvas at new coordinates
+ layerContext.drawImage(tempCanvas, sel.x, sel.y, sel.width, sel.height);
+
+ // delete temporary data
+ delete tempCanvas;
+
+ app.historyAdd();
+
+ return true;
+ }
+
+ /**
* The keydown
event handler. This method calls selection-related
* commands associated to keyboard shortcuts.
*