Merge pull request #20 from unsettledgames/master
Added canvas resizing and sprite scaling
31
README.md
@ -13,13 +13,10 @@ The next version is mostly focused on adding missing essential features and port
|
||||
Suggestions / Planned features:
|
||||
|
||||
- Line tool
|
||||
- Resize canvas
|
||||
- Snap brush preview to pixel grid
|
||||
- Move selection with arrows
|
||||
- Tiled mode
|
||||
- Load palette from LPE file
|
||||
- Move colours in palette editor
|
||||
- Duplicate layer
|
||||
- Hide non-hovered layers
|
||||
- Move colours in (advanced) palette editor
|
||||
- Symmetry options
|
||||
|
||||
- Custom color picker
|
||||
- custom code without dependencies
|
||||
@ -31,17 +28,19 @@ Suggestions / Planned features:
|
||||
- Maybe rearrange UI on portrait
|
||||
- Stack colors when too many
|
||||
- Fix popups
|
||||
|
||||
- Show colors which would need to be added to palette
|
||||
|
||||
- Palette option remove unused colors
|
||||
- Pixel Grid
|
||||
- Another currentLayer.canvas
|
||||
- Must be rescaled each zoom
|
||||
|
||||
|
||||
- Possibly add collaborate function
|
||||
- Bug fix
|
||||
- Alt + scroll broken
|
||||
|
||||
- Polish:
|
||||
- ctrl + a to select everything / selection -> all, same for deselection
|
||||
- Show colors which would need to be added to palette
|
||||
- Warning windows for wrong inputs
|
||||
- Palette option remove unused colors
|
||||
- Move selection with arrows
|
||||
- Update pivot buttons when resizing canvas
|
||||
- Update borders by dragging the canvas' edges with the mouse when resizing canvas
|
||||
- Move the canvases so they're centered after resizing the canvas (maybe a .center() method in layer class)
|
||||
- Trim canvas
|
||||
|
||||
## How to Contribute
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
function getValue(elementId) {
|
||||
var element = (typeof elementId == 'string' ? document.getElementById(elementId) : elementId);
|
||||
console.log("setting: " + elementId + ": " + element.value);
|
||||
return element.value;
|
||||
}
|
||||
|
||||
|
13
_ext/svg/arrows/bottom.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512.171 512.171" style="enable-background:new 0 0 512.171 512.171;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M479.046,283.925c-1.664-3.989-5.547-6.592-9.856-6.592H352.305V10.667C352.305,4.779,347.526,0,341.638,0H170.971
|
||||
c-5.888,0-10.667,4.779-10.667,10.667v266.667H42.971c-4.309,0-8.192,2.603-9.856,6.571c-1.643,3.989-0.747,8.576,2.304,11.627
|
||||
l212.8,213.504c2.005,2.005,4.715,3.136,7.552,3.136s5.547-1.131,7.552-3.115l213.419-213.504
|
||||
C479.793,292.501,480.71,287.915,479.046,283.925z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 793 B |
13
_ext/svg/arrows/bottomleft.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 426.672 426.672" style="enable-background:new 0 0 426.672 426.672;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M423.553,131.12l-128-128c-4.16-4.16-10.923-4.16-15.083,0L96.001,187.589L18.22,109.787
|
||||
c-3.051-3.051-7.616-3.947-11.627-2.304c-3.989,1.643-6.592,5.547-6.592,9.856v298.667c0,5.888,4.779,10.667,10.667,10.667
|
||||
h298.667c4.309,0,8.213-2.603,9.856-6.592c1.664-3.989,0.747-8.576-2.304-11.627l-77.803-77.781l184.448-184.448
|
||||
C427.713,142.043,427.713,135.301,423.553,131.12z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 782 B |
13
_ext/svg/arrows/bottomright.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 426.672 426.672" style="enable-background:new 0 0 426.672 426.672;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M420.069,107.483c-3.968-1.643-8.576-0.747-11.627,2.304l-77.781,77.803L146.213,3.12c-4.16-4.16-10.923-4.16-15.083,0
|
||||
l-128,128c-4.16,4.16-4.16,10.923,0,15.083l184.448,184.448l-77.781,77.781c-3.051,3.051-3.968,7.637-2.304,11.627
|
||||
s5.525,6.613,9.835,6.613h298.667c5.888,0,10.667-4.779,10.667-10.667V117.339C426.661,113.029,424.059,109.125,420.069,107.483z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 771 B |
13
_ext/svg/arrows/left.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512.171 512.171" style="enable-background:new 0 0 512.171 512.171;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M501.504,160.047H234.837V42.714c0-4.309-2.603-8.192-6.592-9.856c-3.989-1.664-8.576-0.747-11.627,2.304L3.115,248.495
|
||||
C1.109,250.501,0,253.21,0,256.047c0,2.837,1.131,5.547,3.115,7.552l213.504,213.419c2.048,2.048,4.779,3.115,7.552,3.115
|
||||
c1.365,0,2.752-0.256,4.075-0.811c3.989-1.664,6.592-5.547,6.592-9.856V352.047h266.667c5.888,0,10.667-4.779,10.667-10.667
|
||||
V170.714C512.171,164.826,507.413,160.047,501.504,160.047z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 831 B |
1
_ext/svg/arrows/middle.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg id="Layer_1" enable-background="new 0 0 506.1 506.1" height="512" viewBox="0 0 506.1 506.1" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m489.609 0h-473.118c-9.108 0-16.491 7.383-16.491 16.491v473.118c0 9.107 7.383 16.491 16.491 16.491h473.119c9.107 0 16.49-7.383 16.49-16.491v-473.118c0-9.108-7.383-16.491-16.491-16.491z"/></svg>
|
After Width: | Height: | Size: 346 B |
13
_ext/svg/arrows/right.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512.171 512.171" style="enable-background:new 0 0 512.171 512.171;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M509.035,248.212l-213.504-212.8c-3.051-3.029-7.595-3.904-11.627-2.304c-3.989,1.664-6.571,5.547-6.571,9.856v117.333
|
||||
H10.667C4.779,160.298,0,165.076,0,170.964v170.667c0,5.888,4.779,10.667,10.667,10.667h266.667v116.885
|
||||
c0,4.309,2.603,8.192,6.592,9.856c1.323,0.555,2.709,0.811,4.075,0.811c2.773,0,5.504-1.088,7.552-3.115l213.504-213.419
|
||||
c2.005-2.005,3.115-4.715,3.115-7.552C512.171,252.927,511.04,250.218,509.035,248.212z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 837 B |
13
_ext/svg/arrows/top.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512.171 512.171" style="enable-background:new 0 0 512.171 512.171;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M476.723,216.64L263.305,3.115C261.299,1.109,258.59,0,255.753,0c-2.837,0-5.547,1.131-7.552,3.136L35.422,216.64
|
||||
c-3.051,3.051-3.947,7.637-2.304,11.627c1.664,3.989,5.547,6.571,9.856,6.571h117.333v266.667c0,5.888,4.779,10.667,10.667,10.667
|
||||
h170.667c5.888,0,10.667-4.779,10.667-10.667V234.837h116.885c4.309,0,8.192-2.603,9.856-6.592
|
||||
C480.713,224.256,479.774,219.691,476.723,216.64z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 796 B |
13
_ext/svg/arrows/topleft.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 426.672 426.672" style="enable-background:new 0 0 426.672 426.672;" xml:space="preserve" transform = "scale(1, -1)">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M423.553,131.12l-128-128c-4.16-4.16-10.923-4.16-15.083,0L96.001,187.589L18.22,109.787
|
||||
c-3.051-3.051-7.616-3.947-11.627-2.304c-3.989,1.643-6.592,5.547-6.592,9.856v298.667c0,5.888,4.779,10.667,10.667,10.667
|
||||
h298.667c4.309,0,8.213-2.603,9.856-6.592c1.664-3.989,0.747-8.576-2.304-11.627l-77.803-77.781l184.448-184.448
|
||||
C427.713,142.043,427.713,135.301,423.553,131.12z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 809 B |
13
_ext/svg/arrows/topright.svg
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 426.672 426.672" style="enable-background:new 0 0 426.672 426.672;" xml:space="preserve" transform = "scale(1, -1)">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M420.069,107.483c-3.968-1.643-8.576-0.747-11.627,2.304l-77.781,77.803L146.213,3.12c-4.16-4.16-10.923-4.16-15.083,0
|
||||
l-128,128c-4.16,4.16-4.16,10.923,0,15.083l184.448,184.448l-77.781,77.781c-3.051,3.051-3.968,7.637-2.304,11.627
|
||||
s5.525,6.613,9.835,6.613h298.667c5.888,0,10.667-4.779,10.667-10.667V117.339C426.661,113.029,424.059,109.125,420.069,107.483z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 798 B |
@ -305,11 +305,17 @@ svg {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#tmp-canvas {
|
||||
z-index: 3;
|
||||
#pixel-grid {
|
||||
z-index: 5000;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
#tmp-canvas {
|
||||
z-index: 4;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
|
||||
#vfx-canvas {
|
||||
z-index: -5000;
|
||||
background: transparent;
|
||||
@ -855,9 +861,11 @@ svg {
|
||||
color: #c0bfc1;
|
||||
}
|
||||
|
||||
#settings-container {
|
||||
.settings-entry {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top:10px;
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
}
|
||||
@ -865,6 +873,7 @@ svg {
|
||||
width: 90px !important;
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
float:right;
|
||||
}
|
||||
}
|
||||
|
||||
@ -947,6 +956,211 @@ svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#resize-canvas, #resize-sprite {
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-wrap:wrap;
|
||||
}
|
||||
|
||||
#pivot-menu {
|
||||
position: relative;
|
||||
display:inline-flex;
|
||||
flex-wrap:wrap;
|
||||
vertical-align:middle;
|
||||
text-align:center;
|
||||
width:130px;
|
||||
float:left;
|
||||
|
||||
button {
|
||||
margin-right:10px;
|
||||
margin-bottom:10px;
|
||||
position:relative;
|
||||
width:32px;
|
||||
height:32px;
|
||||
background:$basehover;
|
||||
border:none;
|
||||
|
||||
path {
|
||||
fill:$basehovericon;
|
||||
}
|
||||
transition: background 100ms ease-in-out,
|
||||
transform 100ms ease;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus,
|
||||
button.rc-selected-pivot {
|
||||
cursor:pointer;
|
||||
background-color: $baseicon;
|
||||
path {
|
||||
fill:$basehovericonhover;
|
||||
}
|
||||
border: 2px solid color(base, foreground);
|
||||
}
|
||||
button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
#borders-menu, #rc-size-menu, #rs-size-menu, #rs-percentage-menu {
|
||||
display:flex;
|
||||
position:relative;
|
||||
flex-wrap: wrap;
|
||||
width:250px;
|
||||
font-size:15px;
|
||||
left:10px;
|
||||
text-align:center;
|
||||
|
||||
button {
|
||||
background:$basehover;
|
||||
border:none;
|
||||
|
||||
color: $basehovericon;
|
||||
transition: background 100ms ease-in-out,
|
||||
transform 100ms ease;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button:focus {
|
||||
cursor:pointer;
|
||||
background-color: $baseicon;
|
||||
color:$basehovericonhover;
|
||||
border: 2px solid color(base, foreground);
|
||||
}
|
||||
button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
input[type=number] {
|
||||
position:relative;
|
||||
margin-left:10px;
|
||||
height:15px;
|
||||
width:40px;
|
||||
padding:8px;
|
||||
}
|
||||
|
||||
input[type=number]::-webkit-outer-spin-button,
|
||||
input[type=number]::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
/* Firefox */
|
||||
input[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
span {
|
||||
padding-right:10px;
|
||||
float:left;
|
||||
position:relative;
|
||||
vertical-align:middle;
|
||||
height:40px;
|
||||
width:100px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
#rs-percentage-menu {
|
||||
width: 400px;
|
||||
span {
|
||||
width:150px;
|
||||
}
|
||||
}
|
||||
|
||||
#rs-size-menu {
|
||||
width: 400px;
|
||||
span {
|
||||
width:150px;
|
||||
}
|
||||
}
|
||||
|
||||
#rs-percentage-menu, #rs-size-menu {
|
||||
justify-content: center;
|
||||
|
||||
div {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
#borders-menu {
|
||||
width:400px;
|
||||
justify-content: center;
|
||||
|
||||
div {
|
||||
float: none;
|
||||
width: 330px;
|
||||
padding-left:50px;
|
||||
|
||||
span {
|
||||
padding-right:20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#rs-ratio-div {
|
||||
width:400px;
|
||||
justify-content: center;
|
||||
padding-left:20px;
|
||||
|
||||
span {
|
||||
width:400px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
select {
|
||||
height:30px;
|
||||
background-color: $basehover;
|
||||
color: $basehovericon;
|
||||
border:none;
|
||||
position:relative;
|
||||
left:10px;
|
||||
|
||||
option {
|
||||
background-color: $basehover;
|
||||
color:$basehovericon;
|
||||
padding:5px;
|
||||
}
|
||||
|
||||
option:checked, option:hover {
|
||||
box-shadow: 0 0 10px 100px $basehovericon inset;
|
||||
color: $basehovericonhover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#rs-keep-ratio {
|
||||
background: color(button);
|
||||
border: none;
|
||||
font-size:14px;
|
||||
color: color(button, foreground);
|
||||
padding: 10px 20px;
|
||||
margin: 0 auto;
|
||||
position:relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#resize-canvas-confirm, #resize-sprite-confirm {
|
||||
background: color(button);
|
||||
border: none;
|
||||
font-size:18px;
|
||||
border-radius: 4px;
|
||||
color: color(button, foreground);
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
margin: 0 auto;
|
||||
position:relative;
|
||||
top:10px;
|
||||
display: block;
|
||||
&:hover {
|
||||
background: color(button, background, hover);
|
||||
}
|
||||
}
|
||||
|
||||
#compatibility-warning {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
@ -4,7 +4,6 @@ let currentPalette = [];
|
||||
//input hex color string
|
||||
//returns list item element
|
||||
function addColor (newColor) {
|
||||
|
||||
//add # at beginning if not present
|
||||
if (newColor.charAt(0) != '#')
|
||||
newColor = '#' + newColor;
|
||||
@ -19,13 +18,6 @@ function addColor (newColor) {
|
||||
button.addEventListener('mouseup', clickedColor);
|
||||
listItem.appendChild(button);
|
||||
|
||||
/*
|
||||
//create input to hold color value
|
||||
var colorValue = document.createElement("input");
|
||||
colorValue.classList.add("color-value");
|
||||
listItem.appendChild(colorValue);
|
||||
*/
|
||||
|
||||
//insert new listItem element at the end of the colors menu (right before add button)
|
||||
colorsMenu.insertBefore(listItem, colorsMenu.children[colorsMenu.children.length-1]);
|
||||
|
||||
|
@ -44,7 +44,7 @@ on('click', 'add-color-button', function(){
|
||||
//add new color and make it selected
|
||||
var addedColor = addColor(newColor);
|
||||
addedColor.classList.add('selected');
|
||||
context.fillStyle = '#' + newColor;
|
||||
currentLayer.context.fillStyle = '#' + newColor;
|
||||
|
||||
//add history state
|
||||
//saveHistoryState({type: 'addcolor', colorValue: addedColor.firstElementChild.jscolor.toString()});
|
||||
|
@ -11,7 +11,7 @@ function changeZoom (layer, direction, cursorLocation) {
|
||||
newHeight = canvasSize[1] * zoom;
|
||||
|
||||
//adjust canvas position
|
||||
setCanvasOffset(layer.canvas, layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth)
|
||||
layer.setCanvasOffset(layer.canvas.offsetLeft + (oldWidth - newWidth) *cursorLocation[0]/oldWidth, layer.canvas.offsetTop + (oldHeight - newHeight) *cursorLocation[1]/oldWidth);
|
||||
}
|
||||
//if you want to zoom in
|
||||
else if (direction == 'in' && zoom + Math.ceil(zoom/10) < window.innerHeight/4){
|
||||
@ -20,7 +20,7 @@ function changeZoom (layer, direction, cursorLocation) {
|
||||
newHeight = canvasSize[1] * zoom;
|
||||
|
||||
//adjust canvas position
|
||||
setCanvasOffset(layer.canvas, layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight))
|
||||
layer.setCanvasOffset(layer.canvas.offsetLeft - Math.round((newWidth - oldWidth)*cursorLocation[0]/oldWidth), layer.canvas.offsetTop - Math.round((newHeight - oldHeight)*cursorLocation[1]/oldHeight));
|
||||
}
|
||||
|
||||
//resize canvas
|
||||
|
@ -1,10 +1,19 @@
|
||||
// This script contains all the functions used to manage the checkboard
|
||||
// Checkerboard color 1
|
||||
var firstCheckerBoardColor = 'rgba(179, 173, 182, 1)';
|
||||
// Checkerboard color 2
|
||||
var secondCheckerBoardColor = 'rgba(204, 200, 206, 1)';
|
||||
// Square size for the checkerboard
|
||||
var checkerBoardSquareSize = 16;
|
||||
// Checkerboard canvas
|
||||
var checkerBoardCanvas = document.getElementById('checkerboard');
|
||||
|
||||
// Setting current colour (each square has a different colour
|
||||
var currentColor = firstCheckerBoardColor;
|
||||
// Saving number of squares filled until now
|
||||
var nSquaresFilled = 0;
|
||||
|
||||
|
||||
function fillCheckerboard() {
|
||||
// Getting checkerboard context
|
||||
var context = checkerBoard.context;
|
||||
|
@ -8,7 +8,7 @@ function clickedColor (e){
|
||||
if (selectedColor) selectedColor.classList.remove('selected');
|
||||
|
||||
//set current color
|
||||
for (let i=1; i<layers.length - 2; i++) {
|
||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||
layers[i].context.fillStyle = this.style.backgroundColor;
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ function colorChanged(e) {
|
||||
|
||||
//if this is the current color, update the drawing color
|
||||
if (e.target.colorElement.parentElement.classList.contains('selected')) {
|
||||
for (let i=1; i<layers.length - 2; i++) {
|
||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||
layers[i].context.fillStyle = '#'+ rgbToHex(newColor.r,newColor.g,newColor.b);
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,6 @@ let copiedStartY;
|
||||
let copiedEndX;
|
||||
let copiedEndY;
|
||||
|
||||
// BUG: when merging tmp layer to currentLayer there are offset problems
|
||||
// FIX: maybe copy the entire tmp layer and paste it so that the merging happens at 0,0
|
||||
|
||||
function copySelection() {
|
||||
copiedEndX = endX;
|
||||
copiedEndY = endY;
|
||||
@ -65,9 +62,4 @@ function cutSelectionTool() {
|
||||
clipboardData = currentLayer.context.getImageData(startX, startY, endX - startX + 1, endY - startY + 1);
|
||||
currentLayer.context.clearRect(startX - 0.5, startY - 0.5, endX - startX + 1, endY - startY + 1);
|
||||
}
|
||||
|
||||
// Moving those pixels from the current layer to the tmp layer
|
||||
//TMPLayer.context.putImageData(imageDataToMove, startX + 1, startY);
|
||||
|
||||
//originalDataPosition = [currentPos[0], currentPos[1]];
|
||||
}
|
@ -30,7 +30,5 @@ function line(x0,y0,x1,y1, brushSize) {
|
||||
err +=dx;
|
||||
y0+=sy;
|
||||
}
|
||||
|
||||
console.log(x0 + ", " + x1);
|
||||
}
|
||||
}
|
@ -26,11 +26,8 @@ function switchMode(currentMode, mustConfirm = true) {
|
||||
|
||||
//switch to basic mode
|
||||
else {
|
||||
|
||||
|
||||
//if there is a current layer (a document is active)
|
||||
if (currentLayer) {
|
||||
|
||||
//confirm with user before flattening image
|
||||
if (mustConfirm ) {
|
||||
if (!confirm('Switching to basic mode will flatten all the visible layers. Are you sure you want to continue?')) {
|
||||
|
@ -2,7 +2,7 @@
|
||||
function getCursorPosition(e) {
|
||||
var x;
|
||||
var y;
|
||||
|
||||
|
||||
if (e.pageX != undefined && e.pageY != undefined) {
|
||||
x = e.pageX;
|
||||
y = e.pageY;
|
||||
@ -12,28 +12,8 @@ function getCursorPosition(e) {
|
||||
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
}
|
||||
|
||||
x -= canvas.offsetLeft;
|
||||
y -= canvas.offsetTop;
|
||||
x -= currentLayer.canvas.offsetLeft;
|
||||
y -= currentLayer.canvas.offsetTop;
|
||||
|
||||
return [x,y];
|
||||
}
|
||||
|
||||
//get cursor position relative to canvas
|
||||
function getCursorPositionRelative(e, layer) {
|
||||
var x;
|
||||
var y;
|
||||
|
||||
if (e.pageX != undefined && e.pageY != undefined) {
|
||||
x = e.pageX;
|
||||
y = e.pageY;
|
||||
}
|
||||
else {
|
||||
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
}
|
||||
|
||||
x -= layer.canvas.offsetLeft;
|
||||
y -= layer.canvas.offsetTop;
|
||||
|
||||
return [x,y];
|
||||
}
|
||||
return [Math.round(x), Math.round(y)];
|
||||
}
|
105
js/_history.js
@ -3,6 +3,76 @@ var redoStates = [];
|
||||
|
||||
const undoLogStyle = 'background: #87ff1c; color: black; padding: 5px;';
|
||||
|
||||
function HistoryStateResizeSprite(xRatio, yRatio, algo, oldData) {
|
||||
this.xRatio = xRatio;
|
||||
this.yRatio = yRatio;
|
||||
this.algo = algo;
|
||||
this.oldData = oldData;
|
||||
|
||||
this.undo = function() {
|
||||
let layerIndex = 0;
|
||||
|
||||
currentAlgo = algo;
|
||||
resizeSprite(null, [1 / this.xRatio, 1 / this.yRatio]);
|
||||
|
||||
// Also putting the old data
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
layers[i].context.putImageData(this.oldData[layerIndex], 0, 0);
|
||||
layerIndex++;
|
||||
layers[i].updateLayerPreview();
|
||||
}
|
||||
}
|
||||
|
||||
redoStates.push(this);
|
||||
};
|
||||
|
||||
this.redo = function() {
|
||||
currentAlgo = algo;
|
||||
resizeSprite(null, [this.xRatio, this.yRatio]);
|
||||
undoStates.push(this);
|
||||
};
|
||||
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
function HistoryStateResizeCanvas(newSize, oldSize, imageDatas, trim) {
|
||||
this.oldSize = oldSize;
|
||||
this.newSize = newSize;
|
||||
this.imageDatas = imageDatas;
|
||||
this.trim = trim;
|
||||
|
||||
this.undo = function() {
|
||||
let dataIndex = 0;
|
||||
console.log("breakpoint");
|
||||
// Resizing the canvas
|
||||
resizeCanvas(null, oldSize, null, false);
|
||||
// Putting the image datas
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
layers[i].context.putImageData(this.imageDatas[dataIndex], 0, 0);
|
||||
dataIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
redoStates.push(this);
|
||||
};
|
||||
|
||||
this.redo = function() {
|
||||
console.log("trim: " + this.trim);
|
||||
if (!this.trim) {
|
||||
resizeCanvas(null, newSize, null, false);
|
||||
}
|
||||
else {
|
||||
trimCanvas(null, false);
|
||||
}
|
||||
|
||||
undoStates.push(this);
|
||||
};
|
||||
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
function HistoryStateFlattenVisible(flattened) {
|
||||
this.nFlattened = flattened;
|
||||
|
||||
@ -62,7 +132,7 @@ function HistoryStateFlattenAll(nFlattened) {
|
||||
this.nFlattened = nFlattened;
|
||||
|
||||
this.undo = function() {
|
||||
for (let i=0; i<this.nFlattened - 2; i++) {
|
||||
for (let i=0; i<this.nFlattened - nAppLayers; i++) {
|
||||
undo();
|
||||
}
|
||||
|
||||
@ -70,7 +140,7 @@ function HistoryStateFlattenAll(nFlattened) {
|
||||
};
|
||||
|
||||
this.redo = function() {
|
||||
for (let i=0; i<this.nFlattened - 2; i++) {
|
||||
for (let i=0; i<this.nFlattened - nAppLayers; i++) {
|
||||
redo();
|
||||
}
|
||||
|
||||
@ -129,6 +199,27 @@ function HistoryStateRenameLayer(oldName, newName, layer) {
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
function HistoryStateDuplicateLayer(addedLayer, copiedLayer) {
|
||||
this.addedLayer = addedLayer;
|
||||
this.copiedLayer = copiedLayer;
|
||||
|
||||
this.undo = function() {
|
||||
addedLayer.selectLayer();
|
||||
deleteLayer(false);
|
||||
|
||||
redoStates.push(this);
|
||||
};
|
||||
|
||||
this.redo = function() {
|
||||
copiedLayer.selectLayer();
|
||||
duplicateLayer(null, false);
|
||||
|
||||
undoStates.push(this);
|
||||
};
|
||||
|
||||
saveHistoryState(this);
|
||||
}
|
||||
|
||||
function HistoryStateDeleteLayer(layerData, before, index) {
|
||||
this.deleted = layerData;
|
||||
this.before = before;
|
||||
@ -209,10 +300,20 @@ function HistoryStateAddLayer(layerData, index) {
|
||||
this.index = index;
|
||||
|
||||
this.undo = function() {
|
||||
console.log("uo");
|
||||
|
||||
redoStates.push(this);
|
||||
if (layers.length - nAppLayers > this.index + 1) {
|
||||
layers[this.index + 1].selectLayer();
|
||||
}
|
||||
else {
|
||||
layers[this.index - 1].selectLayer();
|
||||
}
|
||||
|
||||
|
||||
this.added.canvas.remove();
|
||||
this.added.menuEntry.remove();
|
||||
|
||||
layers.splice(index, 1);
|
||||
};
|
||||
|
||||
|
127
js/_layer.js
@ -57,6 +57,8 @@ class Layer {
|
||||
if (menuEntry != null) {
|
||||
this.name = menuEntry.getElementsByTagName("p")[0].innerHTML;
|
||||
menuEntry.id = "layer" + id;
|
||||
menuEntry.onmouseover = () => this.hover();
|
||||
menuEntry.onmouseout = () => this.unhover();
|
||||
menuEntry.onclick = () => this.selectLayer();
|
||||
menuEntry.getElementsByTagName("button")[0].onclick = () => this.toggleLock();
|
||||
menuEntry.getElementsByTagName("button")[1].onclick = () => this.toggleVisibility();
|
||||
@ -76,12 +78,13 @@ class Layer {
|
||||
|
||||
// Initializes the canvas
|
||||
initialize() {
|
||||
/*
|
||||
var maxHorizontalZoom = Math.floor(window.innerWidth/this.canvasSize[0]*0.75);
|
||||
var maxVerticalZoom = Math.floor(window.innerHeight/this.canvasSize[1]*0.75);
|
||||
|
||||
zoom = Math.min(maxHorizontalZoom,maxVerticalZoom);
|
||||
if (zoom < 1) zoom = 1;
|
||||
|
||||
*/
|
||||
//resize canvas
|
||||
this.canvas.width = this.canvasSize[0];
|
||||
this.canvas.height = this.canvasSize[1];
|
||||
@ -99,6 +102,24 @@ class Layer {
|
||||
this.context.mozImageSmoothingEnabled = false;
|
||||
}
|
||||
|
||||
hover() {
|
||||
// Hide all the layers but the current one
|
||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||
if (layers[i] !== this) {
|
||||
layers[i].canvas.style.opacity = 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unhover() {
|
||||
// Show all the layers again
|
||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||
if (layers[i] !== this) {
|
||||
layers[i].canvas.style.opacity = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setID(id) {
|
||||
this.id = id;
|
||||
if (this.menuEntry != null) {
|
||||
@ -160,13 +181,38 @@ class Layer {
|
||||
this.canvas.style.width = newWidth;
|
||||
this.canvas.style.height = newHeight;
|
||||
}
|
||||
// Copies the otherCanvas' position and size
|
||||
copyData(otherCanvas) {
|
||||
this.canvas.style.width = otherCanvas.canvas.style.width;
|
||||
this.canvas.style.height = otherCanvas.canvas.style.height;
|
||||
|
||||
this.canvas.style.left = otherCanvas.canvas.style.left;
|
||||
this.canvas.style.top = otherCanvas.canvas.style.top;
|
||||
setCanvasOffset (offsetLeft, offsetTop) {
|
||||
//horizontal offset
|
||||
var minXOffset = -this.canvasSize[0] * zoom;
|
||||
var maxXOffset = window.innerWidth - 300;
|
||||
|
||||
if (offsetLeft < minXOffset)
|
||||
this.canvas.style.left = minXOffset +'px';
|
||||
else if (offsetLeft > maxXOffset)
|
||||
this.canvas.style.left = maxXOffset +'px';
|
||||
else
|
||||
this.canvas.style.left = offsetLeft +'px';
|
||||
|
||||
//vertical offset
|
||||
var minYOffset = -this.canvasSize[1] * zoom + 164;
|
||||
var maxYOffset = window.innerHeight - 100;
|
||||
|
||||
if (offsetTop < minYOffset)
|
||||
this.canvas.style.top = minYOffset +'px';
|
||||
else if (offsetTop > maxYOffset)
|
||||
this.canvas.style.top = maxYOffset +'px';
|
||||
else
|
||||
this.canvas.style.top = offsetTop +'px';
|
||||
}
|
||||
|
||||
// Copies the otherLayer's position and size
|
||||
copyData(otherLayer) {
|
||||
this.canvas.style.width = otherLayer.canvas.style.width;
|
||||
this.canvas.style.height = otherLayer.canvas.style.height;
|
||||
|
||||
this.canvas.style.left = otherLayer.canvas.style.left;
|
||||
this.canvas.style.top = otherLayer.canvas.style.top;
|
||||
}
|
||||
|
||||
openOptionsMenu(event) {
|
||||
@ -219,9 +265,10 @@ class Layer {
|
||||
layer.menuEntry.classList.add("selected-layer");
|
||||
currentLayer = layer;
|
||||
}
|
||||
|
||||
/*
|
||||
canvas = currentLayer.canvas;
|
||||
context = currentLayer.context;
|
||||
*/
|
||||
}
|
||||
|
||||
toggleLock() {
|
||||
@ -433,6 +480,55 @@ function deleteLayer(saveHistory = true) {
|
||||
currentLayer.closeOptionsMenu();
|
||||
}
|
||||
|
||||
function duplicateLayer(event, saveHistory = true) {
|
||||
let layerIndex = layers.indexOf(currentLayer);
|
||||
let toDuplicate = currentLayer;
|
||||
let menuEntries = layerList.children;
|
||||
|
||||
// Increasing z-indexes of the layers above
|
||||
for (let i=getMenuEntryIndex(menuEntries, toDuplicate.menuEntry) - 1; i>=0; i--) {
|
||||
getLayerByID(menuEntries[i].id).canvas.style.zIndex++;
|
||||
}
|
||||
maxZIndex++;
|
||||
|
||||
// Creating a new canvas
|
||||
let newCanvas = document.createElement("canvas");
|
||||
// Setting up the new canvas
|
||||
canvasView.append(newCanvas);
|
||||
newCanvas.style.zIndex = parseInt(currentLayer.canvas.style.zIndex) + 1;
|
||||
newCanvas.classList.add("drawingCanvas");
|
||||
|
||||
if (!layerListEntry) return console.warn('skipping adding layer because no document');
|
||||
|
||||
// Clone the default layer
|
||||
let toAppend = currentLayer.menuEntry.cloneNode(true);
|
||||
// Setting the default name for the layer
|
||||
toAppend.getElementsByTagName('p')[0].innerHTML += " copy";
|
||||
// Removing the selected class
|
||||
toAppend.classList.remove("selected-layer");
|
||||
// Adding the layer to the list
|
||||
layerCount++;
|
||||
|
||||
// Creating a layer object
|
||||
let newLayer = new Layer(currentLayer.canvasSize[0], currentLayer.canvasSize[1], newCanvas, toAppend);
|
||||
newLayer.context.fillStyle = currentLayer.context.fillStyle;
|
||||
newLayer.copyData(currentLayer);
|
||||
|
||||
layers.splice(layerIndex, 0, newLayer);
|
||||
|
||||
// Insert it before the Add layer button
|
||||
layerList.insertBefore(toAppend, currentLayer.menuEntry);
|
||||
|
||||
// Copy the layer content
|
||||
newLayer.context.putImageData(currentLayer.context.getImageData(
|
||||
0, 0, currentLayer.canvasSize[0], currentLayer.canvasSize[1]), 0, 0);
|
||||
newLayer.updateLayerPreview();
|
||||
// Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history
|
||||
if (saveHistory) {
|
||||
new HistoryStateDuplicateLayer(newLayer, currentLayer);
|
||||
}
|
||||
}
|
||||
|
||||
function renameLayer(event) {
|
||||
let layerIndex = layers.indexOf(currentLayer);
|
||||
let toRename = currentLayer;
|
||||
@ -521,6 +617,16 @@ function moveLayers(toDropLayer, staticLayer, saveHistory = true) {
|
||||
}
|
||||
}
|
||||
|
||||
function getMenuEntryIndex(list, entry) {
|
||||
for (let i=0; i<list.length; i++) {
|
||||
if (list[i] === entry) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Finds a layer given its name
|
||||
function getLayerByName(name) {
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
@ -558,8 +664,6 @@ function addLayer(id, saveHistory = true) {
|
||||
newCanvas.style.zIndex = maxZIndex;
|
||||
newCanvas.classList.add("drawingCanvas");
|
||||
|
||||
console.log("Tela creata: " + newCanvas);
|
||||
|
||||
if (!layerListEntry) return console.warn('skipping adding layer because no document');
|
||||
|
||||
// Clone the default layer
|
||||
@ -575,8 +679,9 @@ function addLayer(id, saveHistory = true) {
|
||||
let newLayer = new Layer(currentLayer.canvasSize[0], currentLayer.canvasSize[1], newCanvas, toAppend);
|
||||
newLayer.context.fillStyle = currentLayer.context.fillStyle;
|
||||
newLayer.copyData(currentLayer);
|
||||
layers.splice(index, 0, newLayer);
|
||||
|
||||
layers.splice(index, 0, newLayer);
|
||||
|
||||
// Insert it before the Add layer button
|
||||
layerList.insertBefore(toAppend, layerList.childNodes[0]);
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
var currentMouseEvent;
|
||||
var lastMousePos;
|
||||
var lastMouseMovePos;
|
||||
|
||||
//mousedown - start drawing
|
||||
window.addEventListener("mousedown", function (mouseEvent) {
|
||||
@ -12,7 +12,7 @@ window.addEventListener("mousedown", function (mouseEvent) {
|
||||
//prevent right mouse clicks and such, which will open unwanted menus
|
||||
//mouseEvent.preventDefault();
|
||||
|
||||
lastPos = getCursorPosition(mouseEvent);
|
||||
lastMouseClickPos = getCursorPosition(mouseEvent);
|
||||
|
||||
dragging = true;
|
||||
//left or right click ?
|
||||
@ -157,16 +157,23 @@ window.addEventListener("mouseup", function (mouseEvent) {
|
||||
}, false);
|
||||
|
||||
// TODO: Make it snap to the pixel grid
|
||||
function setPreviewPosition(preview, cursor, size){
|
||||
function setPreviewPosition(preview, size){
|
||||
let toAdd = 0;
|
||||
|
||||
// This prevents the brush to be placed in the middle of pixels
|
||||
if (size % 2 == 0) {
|
||||
toAdd = 0.5;
|
||||
}
|
||||
|
||||
preview.style.left = (
|
||||
currentLayer.canvas.offsetLeft
|
||||
+ Math.floor(cursor[0]/zoom) * zoom
|
||||
- Math.floor(size / 2) * zoom
|
||||
- Math.floor(size / 2) * zoom + toAdd
|
||||
) + 'px';
|
||||
preview.style.top = (
|
||||
currentLayer.canvas.offsetTop
|
||||
+ Math.floor(cursor[1]/zoom) * zoom
|
||||
- Math.floor(size / 2) * zoom
|
||||
- Math.floor(size / 2) * zoom + toAdd
|
||||
) + 'px';
|
||||
}
|
||||
|
||||
@ -176,205 +183,187 @@ function setPreviewPosition(preview, cursor, size){
|
||||
//mouse is moving on canvas
|
||||
window.addEventListener("mousemove", draw, false);
|
||||
function draw (mouseEvent) {
|
||||
lastMousePos = getCursorPosition(mouseEvent);
|
||||
// Saving the event in case something else needs it
|
||||
currentMouseEvent = mouseEvent;
|
||||
if (!dialogueOpen)
|
||||
{
|
||||
lastMouseMovePos = getCursorPosition(mouseEvent);
|
||||
// Saving the event in case something else needs it
|
||||
currentMouseEvent = mouseEvent;
|
||||
|
||||
var cursorLocation = lastMousePos;
|
||||
var cursorLocation = lastMouseMovePos;
|
||||
|
||||
//if a document hasnt yet been created or the current layer is locked, exit this function
|
||||
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
|
||||
//if a document hasnt yet been created or the current layer is locked, exit this function
|
||||
if (!documentCreated || dialogueOpen || !currentLayer.isVisible || currentLayer.isLocked) return;
|
||||
|
||||
// Moving brush preview
|
||||
currentTool.moveBrushPreview(cursorLocation);
|
||||
// Hiding eyedropper, will be shown if it's needed
|
||||
eyedropperPreview.style.display = 'none';
|
||||
|
||||
eyedropperPreview.style.display = 'none';
|
||||
if (currentTool.name == 'pencil') {
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas')
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
|
||||
if (currentTool.name == 'pencil') {
|
||||
//move the brush preview
|
||||
brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - tool.pencil.brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - tool.pencil.brushSize * zoom / 2 + 'px';
|
||||
//draw line to current pixel
|
||||
if (dragging) {
|
||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
||||
line(Math.floor(lastMouseClickPos[0]/zoom),Math.floor(lastMouseClickPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), tool.pencil.brushSize);
|
||||
lastMouseClickPos = cursorLocation;
|
||||
}
|
||||
}
|
||||
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas')
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
//get lightness value of color
|
||||
var selectedColor = currentLayer.context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
|
||||
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2])
|
||||
|
||||
//draw line to current pixel
|
||||
if (dragging) {
|
||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
||||
line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), tool.pencil.brushSize);
|
||||
lastPos = cursorLocation;
|
||||
//for the darkest 50% of colors, change the brush preview to dark mode
|
||||
if (colorLightness>127) brushPreview.classList.remove('dark');
|
||||
else brushPreview.classList.add('dark');
|
||||
|
||||
currentLayer.updateLayerPreview();
|
||||
}
|
||||
// Decided to write a different implementation in case of differences between the brush and the eraser tool
|
||||
else if (currentTool.name == 'eraser') {
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas')
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
|
||||
//draw line to current pixel
|
||||
if (dragging) {
|
||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
||||
line(Math.floor(lastMouseClickPos[0]/zoom),Math.floor(lastMouseClickPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), currentTool.brushSize);
|
||||
lastMouseClickPos = cursorLocation;
|
||||
}
|
||||
}
|
||||
|
||||
currentLayer.updateLayerPreview();
|
||||
}
|
||||
else if (currentTool.name == 'rectangle')
|
||||
{
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas')
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
|
||||
if (!isDrawingRect && dragging) {
|
||||
startRectDrawing(mouseEvent);
|
||||
}
|
||||
else if (dragging){
|
||||
updateRectDrawing(mouseEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//get lightness value of color
|
||||
var selectedColor = context.getImageData(Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom),1,1).data;
|
||||
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2])
|
||||
|
||||
//for the darkest 50% of colors, change the brush preview to dark mode
|
||||
if (colorLightness>127) brushPreview.classList.remove('dark');
|
||||
else brushPreview.classList.add('dark');
|
||||
|
||||
currentLayer.updateLayerPreview();
|
||||
}
|
||||
// Decided to write a different implementation in case of differences between the brush and the eraser tool
|
||||
else if (currentTool.name == 'eraser') {
|
||||
// Uses the same preview as the brush
|
||||
//move the brush preview
|
||||
brushPreview.style.left = cursorLocation[0] + canvas.offsetLeft - currentTool.brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = cursorLocation[1] + canvas.offsetTop - currentTool.brushSize * zoom / 2 + 'px';
|
||||
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas')
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
|
||||
//draw line to current pixel
|
||||
if (dragging) {
|
||||
if (mouseEvent.target.className == 'drawingCanvas' || mouseEvent.target.className == 'drawingCanvas') {
|
||||
line(Math.floor(lastPos[0]/zoom),Math.floor(lastPos[1]/zoom),Math.floor(cursorLocation[0]/zoom),Math.floor(cursorLocation[1]/zoom), currentTool.brushSize);
|
||||
lastPos = cursorLocation;
|
||||
}
|
||||
}
|
||||
|
||||
currentLayer.updateLayerPreview();
|
||||
}
|
||||
else if (currentTool.name == 'rectangle')
|
||||
{
|
||||
//move the brush preview
|
||||
brushPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - currentTool.brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - currentTool.brushSize * zoom / 2 + 'px';
|
||||
|
||||
//hide brush preview outside of canvas / canvas view
|
||||
if (mouseEvent.target.className == 'drawingCanvas'|| mouseEvent.target.className == 'drawingCanvas')
|
||||
brushPreview.style.visibility = 'visible';
|
||||
else
|
||||
brushPreview.style.visibility = 'hidden';
|
||||
|
||||
if (!isDrawingRect && dragging) {
|
||||
startRectDrawing(mouseEvent);
|
||||
else if (currentTool.name == 'pan' && dragging) {
|
||||
// Setting first layer position
|
||||
layers[0].setCanvasOffset(layers[0].canvas.offsetLeft + (cursorLocation[0] - lastMouseClickPos[0]), layers[0].canvas.offsetTop + (cursorLocation[1] - lastMouseClickPos[1]));
|
||||
// Copying that position to the other layers
|
||||
for (let i=1; i<layers.length; i++) {
|
||||
layers[i].copyData(layers[0]);
|
||||
}
|
||||
}
|
||||
else if (dragging){
|
||||
updateRectDrawing(mouseEvent);
|
||||
else if (currentTool.name == 'eyedropper' && dragging && mouseEvent.target.className == 'drawingCanvas') {
|
||||
let selectedColor = getEyedropperColor(cursorLocation);
|
||||
|
||||
eyedropperPreview.style.borderColor = '#'+rgbToHex(selectedColor[0],selectedColor[1],selectedColor[2]);
|
||||
eyedropperPreview.style.display = 'block';
|
||||
|
||||
eyedropperPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - 30 + 'px';
|
||||
eyedropperPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - 30 + 'px';
|
||||
|
||||
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2]);
|
||||
|
||||
//for the darkest 50% of colors, change the eyedropper preview to dark mode
|
||||
if (colorLightness>127) eyedropperPreview.classList.remove('dark');
|
||||
else eyedropperPreview.classList.add('dark');
|
||||
}
|
||||
else if (currentTool.name == 'resizebrush' && dragging) {
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0];
|
||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||
var brushSizeChange = Math.round(distanceFromClick/10);
|
||||
var newBrushSize = tool.pencil.previousBrushSize + brushSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
tool.pencil.brushSize = Math.max(1,newBrushSize);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
tool.pencil.moveBrushPreview(lastMouseClickPos);
|
||||
currentTool.updateCursor();
|
||||
}
|
||||
else if (currentTool.name == 'resizeeraser' && dragging) {
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0];
|
||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||
var eraserSizeChange = Math.round(distanceFromClick/10);
|
||||
var newEraserSizeChange = tool.eraser.previousBrushSize + eraserSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
tool.eraser.brushSize = Math.max(1,newEraserSizeChange);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
tool.eraser.moveBrushPreview(lastMouseClickPos);
|
||||
currentTool.updateCursor();
|
||||
}
|
||||
else if (currentTool.name == 'resizerectangle' && dragging) {
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastMouseClickPos[0];
|
||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||
var rectangleSizeChange = Math.round(distanceFromClick/10);
|
||||
var newRectangleSize = tool.rectangle.previousBrushSize + rectangleSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
tool.rectangle.brushSize = Math.max(1,newRectangleSize);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
tool.rectangle.moveBrushPreview(lastMouseClickPos);
|
||||
currentTool.updateCursor();
|
||||
}
|
||||
else if (currentTool.name == 'rectselect') {
|
||||
if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') {
|
||||
isRectSelecting = true;
|
||||
startRectSelection(mouseEvent);
|
||||
}
|
||||
else if (dragging && isRectSelecting) {
|
||||
updateRectSelection(mouseEvent);
|
||||
}
|
||||
else if (isRectSelecting) {
|
||||
endRectSelection();
|
||||
}
|
||||
}
|
||||
else if (currentTool.name == 'moveselection') {
|
||||
// Updating the cursor (move if inside rect, cross if not)
|
||||
currentTool.updateCursor();
|
||||
|
||||
// If I'm dragging, I move the preview
|
||||
if (dragging && cursorInSelectedArea()) {
|
||||
updateMovePreview(getCursorPosition(mouseEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (currentTool.name == 'pan' && dragging) {
|
||||
// Setting first layer position
|
||||
setCanvasOffset(layers[0].canvas, layers[0].canvas.offsetLeft + (cursorLocation[0] - lastPos[0]), layers[0].canvas.offsetTop + (cursorLocation[1] - lastPos[1]));
|
||||
// Copying that position to the other layers
|
||||
for (let i=1; i<layers.length; i++) {
|
||||
layers[i].copyData(layers[0]);
|
||||
}
|
||||
}
|
||||
else if (currentTool.name == 'eyedropper' && dragging && mouseEvent.target.className == 'drawingCanvas') {
|
||||
let selectedColor = getEyedropperColor(cursorLocation);
|
||||
|
||||
eyedropperPreview.style.borderColor = '#'+rgbToHex(selectedColor[0],selectedColor[1],selectedColor[2]);
|
||||
eyedropperPreview.style.display = 'block';
|
||||
|
||||
eyedropperPreview.style.left = cursorLocation[0] + currentLayer.canvas.offsetLeft - 30 + 'px';
|
||||
eyedropperPreview.style.top = cursorLocation[1] + currentLayer.canvas.offsetTop - 30 + 'px';
|
||||
|
||||
var colorLightness = Math.max(selectedColor[0],selectedColor[1],selectedColor[2]);
|
||||
|
||||
//for the darkest 50% of colors, change the eyedropper preview to dark mode
|
||||
if (colorLightness>127) eyedropperPreview.classList.remove('dark');
|
||||
else eyedropperPreview.classList.add('dark');
|
||||
}
|
||||
else if (currentTool.name == 'resizebrush' && dragging) {
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastPos[0];
|
||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||
var brushSizeChange = Math.round(distanceFromClick/10);
|
||||
var newBrushSize = tool.pencil.previousBrushSize + brushSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
tool.pencil.brushSize = Math.max(1,newBrushSize);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - tool.pencil.brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - tool.pencil.brushSize * zoom / 2 + 'px';
|
||||
|
||||
currentTool.updateCursor();
|
||||
}
|
||||
else if (currentTool.name == 'resizeeraser' && dragging) {
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastPos[0];
|
||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||
var eraserSizeChange = Math.round(distanceFromClick/10);
|
||||
var newEraserSizeChange = tool.eraser.previousBrushSize + eraserSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
tool.eraser.brushSize = Math.max(1,newEraserSizeChange);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - tool.eraser.brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - tool.eraser.brushSize * zoom / 2 + 'px';
|
||||
|
||||
currentTool.updateCursor();
|
||||
}
|
||||
else if (currentTool.name == 'resizerectangle' && dragging) {
|
||||
//get new brush size based on x distance from original clicking location
|
||||
var distanceFromClick = cursorLocation[0] - lastPos[0];
|
||||
//var roundingAmount = 20 - Math.round(distanceFromClick/10);
|
||||
//this doesnt work in reverse... because... it's not basing it off of the brush size which it should be
|
||||
var rectangleSizeChange = Math.round(distanceFromClick/10);
|
||||
var newRectangleSize = tool.rectangle.previousBrushSize + rectangleSizeChange;
|
||||
|
||||
//set the brush to the new size as long as its bigger than 1
|
||||
tool.rectangle.brushSize = Math.max(1,newRectangleSize);
|
||||
|
||||
//fix offset so the cursor stays centered
|
||||
brushPreview.style.left = lastPos[0] + currentLayer.canvas.offsetLeft - tool.rectangle.brushSize * zoom / 2 + 'px';
|
||||
brushPreview.style.top = lastPos[1] + currentLayer.canvas.offsetTop - tool.rectangle.brushSize * zoom / 2 + 'px';
|
||||
|
||||
currentTool.updateCursor();
|
||||
}
|
||||
else if (currentTool.name == 'rectselect') {
|
||||
if (dragging && !isRectSelecting && mouseEvent.target.className == 'drawingCanvas') {
|
||||
isRectSelecting = true;
|
||||
startRectSelection(mouseEvent);
|
||||
}
|
||||
else if (dragging && isRectSelecting) {
|
||||
updateRectSelection(mouseEvent);
|
||||
}
|
||||
else if (isRectSelecting) {
|
||||
endRectSelection();
|
||||
}
|
||||
}
|
||||
else if (currentTool.name == 'moveselection') {
|
||||
// Updating the cursor (move if inside rect, cross if not)
|
||||
currentTool.updateCursor();
|
||||
|
||||
// If I'm dragging, I move the preview
|
||||
if (dragging && cursorInSelectedArea()) {
|
||||
updateMovePreview(getCursorPosition(mouseEvent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//mousewheel scrroll
|
||||
//mousewheel scroll
|
||||
canvasView.addEventListener("wheel", function(mouseEvent){
|
||||
|
||||
if (currentTool.name == 'zoom' || mouseEvent.altKey) {
|
||||
let mode;
|
||||
if (mouseEvent.deltaY < 0){
|
||||
mode = 'in';
|
||||
}
|
||||
else if (mouseEvent.deltaY > 0) {
|
||||
mode = 'out';
|
||||
}
|
||||
|
||||
// Changing zoom and position of the first layer
|
||||
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
|
||||
|
||||
for (let i=1; i<layers.length; i++) {
|
||||
// Copying first layer's data into the other layers
|
||||
layers[i].copyData(layers[0]);
|
||||
}
|
||||
let mode;
|
||||
if (mouseEvent.deltaY < 0){
|
||||
mode = 'in';
|
||||
}
|
||||
else if (mouseEvent.deltaY > 0) {
|
||||
mode = 'out';
|
||||
}
|
||||
|
||||
// Changing zoom and position of the first layer
|
||||
changeZoom(layers[0], mode, getCursorPosition(mouseEvent));
|
||||
|
||||
for (let i=1; i<layers.length; i++) {
|
||||
// Copying first layer's data into the other layers
|
||||
layers[i].copyData(layers[0]);
|
||||
}
|
||||
});
|
@ -7,13 +7,12 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||
if (firstPixel) {
|
||||
layerListEntry = layerList.firstElementChild;
|
||||
|
||||
// Setting up the current layer
|
||||
currentLayer = new Layer(width, height, canvas, layerListEntry);
|
||||
canvas.style.zIndex = 2;
|
||||
currentLayer.canvas.style.zIndex = 2;
|
||||
}
|
||||
else {
|
||||
let nLayers = layers.length;
|
||||
for (let i=2; i < layers.length - 2; i++) {
|
||||
for (let i=2; i < layers.length - nAppLayers; i++) {
|
||||
let currentEntry = layers[i].menuEntry;
|
||||
let associatedLayer;
|
||||
|
||||
@ -32,7 +31,7 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||
}
|
||||
|
||||
// Removing the old layers from the list
|
||||
for (let i=2; i<nLayers - 2; i++) {
|
||||
for (let i=2; i<nLayers - nAppLayers; i++) {
|
||||
layers.splice(2, 1);
|
||||
}
|
||||
|
||||
@ -40,9 +39,12 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||
layers[1] = new Layer(width, height, layers[1].canvas, layers[1].menuEntry);
|
||||
currentLayer = layers[1];
|
||||
|
||||
canvas = currentLayer.canvas;
|
||||
context = currentLayer.context;
|
||||
canvas.style.zIndex = 2;
|
||||
currentLayer.canvas.style.zIndex = 2;
|
||||
|
||||
// Updating canvas size
|
||||
for (let i=0; i<nLayers; i++) {
|
||||
layers[i].canvasSize = [width, height];
|
||||
}
|
||||
}
|
||||
|
||||
// Adding the checkerboard behind it
|
||||
@ -52,7 +54,10 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||
VFXLayer = new Layer(width, height, VFXCanvas);
|
||||
|
||||
// Tmp layer to draw previews on
|
||||
TMPLayer = new Layer(width, height, TMPCanvas);
|
||||
TMPLayer = new Layer(width, height, TMPCanvas);
|
||||
|
||||
// Pixel grid
|
||||
pixelGrid = new Layer(width, height, pixelGridCanvas);
|
||||
|
||||
canvasSize = currentLayer.canvasSize;
|
||||
|
||||
@ -65,6 +70,7 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||
layers.push(currentLayer);
|
||||
layers.push(VFXLayer);
|
||||
layers.push(TMPLayer);
|
||||
layers.push(pixelGrid);
|
||||
}
|
||||
|
||||
//remove current palette
|
||||
@ -108,6 +114,7 @@ function newPixel (width, height, editorMode, fileContent = null) {
|
||||
|
||||
//fill background of canvas with bg color
|
||||
fillCheckerboard();
|
||||
fillPixelGrid();
|
||||
|
||||
//reset undo and redo states
|
||||
undoStates = [];
|
||||
|
@ -105,4 +105,89 @@ function getElementAbsolutePosition(element) {
|
||||
}
|
||||
|
||||
return [curleft,curtop];
|
||||
}
|
||||
|
||||
function nearestNeighbor (src, dst) {
|
||||
let pos = 0
|
||||
|
||||
for (let y = 0; y < dst.height; y++) {
|
||||
for (let x = 0; x < dst.width; x++) {
|
||||
const srcX = Math.floor(x * src.width / dst.width)
|
||||
const srcY = Math.floor(y * src.height / dst.height)
|
||||
|
||||
let srcPos = ((srcY * src.width) + srcX) * 4
|
||||
|
||||
dst.data[pos++] = src.data[srcPos++] // R
|
||||
dst.data[pos++] = src.data[srcPos++] // G
|
||||
dst.data[pos++] = src.data[srcPos++] // B
|
||||
dst.data[pos++] = src.data[srcPos++] // A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bilinearInterpolation (src, dst) {
|
||||
function interpolate (k, kMin, kMax, vMin, vMax) {
|
||||
return Math.round((k - kMin) * vMax + (kMax - k) * vMin)
|
||||
}
|
||||
|
||||
function interpolateHorizontal (offset, x, y, xMin, xMax) {
|
||||
const vMin = src.data[((y * src.width + xMin) * 4) + offset]
|
||||
if (xMin === xMax) return vMin
|
||||
|
||||
const vMax = src.data[((y * src.width + xMax) * 4) + offset]
|
||||
return interpolate(x, xMin, xMax, vMin, vMax)
|
||||
}
|
||||
|
||||
function interpolateVertical (offset, x, xMin, xMax, y, yMin, yMax) {
|
||||
const vMin = interpolateHorizontal(offset, x, yMin, xMin, xMax)
|
||||
if (yMin === yMax) return vMin
|
||||
|
||||
const vMax = interpolateHorizontal(offset, x, yMax, xMin, xMax)
|
||||
return interpolate(y, yMin, yMax, vMin, vMax)
|
||||
}
|
||||
|
||||
let pos = 0
|
||||
|
||||
for (let y = 0; y < dst.height; y++) {
|
||||
for (let x = 0; x < dst.width; x++) {
|
||||
const srcX = x * src.width / dst.width
|
||||
const srcY = y * src.height / dst.height
|
||||
|
||||
const xMin = Math.floor(srcX)
|
||||
const yMin = Math.floor(srcY)
|
||||
|
||||
const xMax = Math.min(Math.ceil(srcX), src.width - 1)
|
||||
const yMax = Math.min(Math.ceil(srcY), src.height - 1)
|
||||
|
||||
dst.data[pos++] = interpolateVertical(0, srcX, xMin, xMax, srcY, yMin, yMax) // R
|
||||
dst.data[pos++] = interpolateVertical(1, srcX, xMin, xMax, srcY, yMin, yMax) // G
|
||||
dst.data[pos++] = interpolateVertical(2, srcX, xMin, xMax, srcY, yMin, yMax) // B
|
||||
dst.data[pos++] = interpolateVertical(3, srcX, xMin, xMax, srcY, yMin, yMax) // A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resizeImageData (image, width, height, algorithm) {
|
||||
algorithm = algorithm || 'bilinear-interpolation'
|
||||
|
||||
let resize
|
||||
switch (algorithm) {
|
||||
case 'nearest-neighbor': resize = nearestNeighbor; break
|
||||
case 'bilinear-interpolation': resize = bilinearInterpolation; break
|
||||
default: throw new Error(`Unknown algorithm: ${algorithm}`)
|
||||
}
|
||||
|
||||
const result = new ImageData(width, height)
|
||||
|
||||
resize(image, result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function getPixelPosition(index) {
|
||||
let linearIndex = index / 4;
|
||||
let x = linearIndex % layers[0].canvasSize[0];
|
||||
let y = Math.floor(linearIndex / layers[0].canvasSize[0]);
|
||||
|
||||
return [Math.ceil(x), Math.ceil(y)];
|
||||
}
|
52
js/_pixelGrid.js
Normal file
@ -0,0 +1,52 @@
|
||||
let pixelGridColor = "#0000FF";
|
||||
let lineDistance = 12;
|
||||
let pixelGridVisible = false;
|
||||
pixelGridCanvas = document.getElementById("pixel-grid");
|
||||
|
||||
function togglePixelGrid(event) {
|
||||
let button = document.getElementById("toggle-pixelgrid-button");
|
||||
|
||||
pixelGridVisible = !pixelGridVisible;
|
||||
|
||||
if (pixelGridVisible) {
|
||||
button.innerHTML = "Hide pixel grid";
|
||||
pixelGridCanvas.style.display = "inline-block";
|
||||
}
|
||||
else {
|
||||
button.innerHTML = "Show pixel grid";
|
||||
pixelGridCanvas.style.display = "none";
|
||||
}
|
||||
}
|
||||
|
||||
function fillPixelGrid() {
|
||||
let context = pixelGridCanvas.getContext("2d");
|
||||
let originalSize = layers[0].canvasSize;
|
||||
|
||||
pixelGridCanvas.width = originalSize[0] * lineDistance;
|
||||
pixelGridCanvas.height = originalSize[1] * lineDistance;
|
||||
|
||||
// OPTIMIZABLE, could probably be a bit more elegant
|
||||
// Draw horizontal lines
|
||||
for (let i=0; i<pixelGridCanvas.width / lineDistance; i++) {
|
||||
context.strokeStyle = settings.pixelGridColour;
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(i * lineDistance + 0.5, 0);
|
||||
context.lineTo(i * lineDistance + 0.5, originalSize[1] * lineDistance);
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
// Draw vertical lines
|
||||
for (let i=0; i<pixelGridCanvas.height / lineDistance; i++) {
|
||||
context.beginPath();
|
||||
context.moveTo(0, i * lineDistance + 0.5);
|
||||
context.lineTo(originalSize[0] * lineDistance, i * lineDistance + 0.5);
|
||||
context.stroke();
|
||||
context.closePath();
|
||||
}
|
||||
|
||||
if (!pixelGridVisible) {
|
||||
pixelGridCanvas.style.display = 'none';
|
||||
}
|
||||
}
|
@ -53,8 +53,6 @@ function endRectDrawing(mouseEvent) {
|
||||
startRectY = tmp;
|
||||
}
|
||||
|
||||
let hexColor = hexToRgb(currentLayer.context.fillStyle);
|
||||
|
||||
// Resetting this
|
||||
isDrawingRect = false;
|
||||
// Drawing the rect
|
||||
|
276
js/_resizeCanvas.js
Normal file
@ -0,0 +1,276 @@
|
||||
let resizeCanvasContainer = document.getElementById("resize-canvas");
|
||||
let rcPivot = "middle";
|
||||
let currentPivotObject;
|
||||
let borders = {left: 0, right: 0, top: 0, bottom: 0};
|
||||
|
||||
function openResizeCanvasWindow() {
|
||||
initResizeCanvasInputs();
|
||||
showDialogue('resize-canvas');
|
||||
}
|
||||
|
||||
function initResizeCanvasInputs() {
|
||||
let buttons = document.getElementsByClassName("pivot-button");
|
||||
|
||||
for (let i=0; i<buttons.length; i++) {
|
||||
buttons[i].addEventListener("click", changePivot);
|
||||
if (buttons[i].getAttribute("value").includes("middle")) {
|
||||
currentPivotObject = buttons[i];
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("rc-width").value = layers[0].canvasSize[0];
|
||||
document.getElementById("rc-height").value = layers[0].canvasSize[1];
|
||||
|
||||
document.getElementById("rc-border-left").addEventListener("change", rcChangedBorder);
|
||||
document.getElementById("rc-border-right").addEventListener("change", rcChangedBorder);
|
||||
document.getElementById("rc-border-top").addEventListener("change", rcChangedBorder);
|
||||
document.getElementById("rc-border-bottom").addEventListener("change", rcChangedBorder);
|
||||
|
||||
document.getElementById("rc-width").addEventListener("change", rcChangedSize);
|
||||
document.getElementById("rc-height").addEventListener("change", rcChangedSize);
|
||||
|
||||
document.getElementById("resize-canvas-confirm").addEventListener("click", resizeCanvas);
|
||||
console.log("Pivot selezionato: " + currentPivotObject);
|
||||
}
|
||||
|
||||
function rcChangedBorder(event) {
|
||||
rcUpdateBorders();
|
||||
|
||||
document.getElementById("rc-width").value = parseInt(layers[0].canvasSize[0]) + borders.left + borders.right;
|
||||
document.getElementById("rc-height").value = parseInt(layers[0].canvasSize[1]) + borders.top + borders.bottom;
|
||||
}
|
||||
|
||||
function rcChangedSize(event) {
|
||||
let widthOffset = Math.abs(document.getElementById("rc-width").value) - layers[0].canvasSize[0];
|
||||
let heightOffset = Math.abs(document.getElementById("rc-height").value) - layers[0].canvasSize[1];
|
||||
|
||||
let left = Math.round(widthOffset / 2);
|
||||
let right = widthOffset - left;
|
||||
let top = Math.round(heightOffset / 2);
|
||||
let bottom = heightOffset - top;
|
||||
|
||||
document.getElementById("rc-border-left").value = left;
|
||||
document.getElementById("rc-border-right").value = right;
|
||||
document.getElementById("rc-border-top").value = top;
|
||||
document.getElementById("rc-border-bottom").value = bottom;
|
||||
|
||||
borders.left = left;
|
||||
borders.right = right;
|
||||
borders.top = top;
|
||||
borders.bottom = bottom;
|
||||
}
|
||||
|
||||
function resizeCanvas(event, size, customData, saveHistory = true) {
|
||||
let imageDatas = [];
|
||||
let leftOffset = 0;
|
||||
let topOffset = 0;
|
||||
let copiedDataIndex = 0;
|
||||
|
||||
// If I'm undoing and I'm not trimming, I manually put the values in the window
|
||||
if (size != null && customData == null) {
|
||||
document.getElementById("rc-width").value = size.x;
|
||||
document.getElementById("rc-height").value = size.y;
|
||||
|
||||
rcChangedSize();
|
||||
}
|
||||
|
||||
rcUpdateBorders();
|
||||
|
||||
// Save all imageDatas
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
imageDatas.push(layers[i].context.getImageData(0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1]));
|
||||
}
|
||||
}
|
||||
|
||||
// Saving the history only if I'm not already undoing or redoing
|
||||
if (saveHistory) {
|
||||
// Saving history
|
||||
new HistoryStateResizeCanvas(
|
||||
{x: parseInt(layers[0].canvasSize[0]) + borders.left + borders.right,
|
||||
y: parseInt(layers[0].canvasSize[1]) + borders.top + borders.bottom},
|
||||
|
||||
{x: layers[0].canvasSize[0],
|
||||
y: layers[0].canvasSize[1]},
|
||||
imageDatas.slice(), customData != null && saveHistory
|
||||
);
|
||||
|
||||
console.log("salvata");
|
||||
}
|
||||
|
||||
// Resize the canvases
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
layers[i].canvasSize[0] = parseInt(layers[i].canvasSize[0]) + borders.left + borders.right;
|
||||
layers[i].canvasSize[1] = parseInt(layers[i].canvasSize[1]) + borders.top + borders.bottom;
|
||||
|
||||
layers[i].canvas.width = layers[i].canvasSize[0];
|
||||
layers[i].canvas.height = layers[i].canvasSize[1];
|
||||
|
||||
layers[i].resize();
|
||||
layers[i].context.fillStyle = currentGlobalColor;
|
||||
}
|
||||
|
||||
// Regenerate the checkerboard
|
||||
fillCheckerboard();
|
||||
fillPixelGrid();
|
||||
// Put the imageDatas in the right position
|
||||
switch (rcPivot)
|
||||
{
|
||||
case 'topleft':
|
||||
leftOffset = 0;
|
||||
topOffset = 0;
|
||||
break;
|
||||
case 'top':
|
||||
leftOffset = (borders.left + borders.right) / 2;
|
||||
topOffset = 0;
|
||||
break;
|
||||
case 'topright':
|
||||
leftOffset = borders.left + borders.right;
|
||||
topOffset = 0;
|
||||
break;
|
||||
case 'left':
|
||||
leftOffset = 0;
|
||||
topOffset = (borders.top + borders.bottom) / 2;
|
||||
break;
|
||||
case 'middle':
|
||||
leftOffset = (borders.left + borders.right) / 2;
|
||||
topOffset = (borders.top + borders.bottom) / 2;
|
||||
break;
|
||||
case 'right':
|
||||
leftOffset = borders.left + borders.right;
|
||||
topOffset = (borders.top + borders.bottom) / 2;
|
||||
break;
|
||||
case 'bottomleft':
|
||||
leftOffset = 0;
|
||||
topOffset = borders.top + borders.bottom;
|
||||
break;
|
||||
case 'bottom':
|
||||
leftOffset = (borders.left + borders.right) / 2;
|
||||
topOffset = borders.top + borders.bottom;
|
||||
break;
|
||||
case 'bottomright':
|
||||
leftOffset = borders.left + borders.right;
|
||||
topOffset = borders.top + borders.bottom;
|
||||
break;
|
||||
default:
|
||||
console.log('Pivot does not exist, please report an issue at https://github.com/lospec/pixel-editor');
|
||||
break;
|
||||
}
|
||||
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
if (customData == undefined) {
|
||||
layers[i].context.putImageData(imageDatas[copiedDataIndex], leftOffset, topOffset);
|
||||
}
|
||||
else {
|
||||
console.log("sgancio " + layers[i].canvasSize + ", [" +
|
||||
customData[copiedDataIndex].width + "," + customData[copiedDataIndex].height
|
||||
+ "]");
|
||||
layers[i].context.putImageData(customData[copiedDataIndex], 0, 0);
|
||||
}
|
||||
layers[i].updateLayerPreview();
|
||||
copiedDataIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
closeDialogue();
|
||||
}
|
||||
|
||||
function trimCanvas(event, saveHistory) {
|
||||
let minY = Infinity;
|
||||
let minX = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let maxY = -Infinity;
|
||||
let tmp;
|
||||
let imageDatas = [];
|
||||
let historySave = saveHistory == null;
|
||||
let prevPivot = rcPivot;
|
||||
|
||||
rcPivot = "topleft";
|
||||
console.log("debug");
|
||||
|
||||
for (let i=1; i<layers.length - nAppLayers; i++) {
|
||||
let imageData = layers[i].context.getImageData(0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1]);
|
||||
let pixelPosition;
|
||||
|
||||
for (let i=imageData.data.length - 1; i>= 0; i-=4) {
|
||||
if (!isPixelEmpty(
|
||||
[imageData.data[i - 3], imageData.data[i - 2],
|
||||
-imageData.data[i - 1], imageData.data[i]])) {
|
||||
pixelPosition = getPixelPosition(i);
|
||||
|
||||
// max x
|
||||
if (pixelPosition[0] > maxX) {
|
||||
maxX = pixelPosition[0];
|
||||
}
|
||||
// min x
|
||||
if (pixelPosition[0] < minX) {
|
||||
minX = pixelPosition[0];
|
||||
}
|
||||
// max y
|
||||
if (pixelPosition[1] > maxY) {
|
||||
maxY = pixelPosition[1];
|
||||
}
|
||||
// min y
|
||||
if (pixelPosition[1] < minY) {
|
||||
minY = pixelPosition[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tmp = minY;
|
||||
minY = maxY;
|
||||
maxY = tmp;
|
||||
|
||||
minY = layers[0].canvasSize[1] - minY;
|
||||
maxY = layers[0].canvasSize[1] - maxY;
|
||||
|
||||
borders.right = (maxX - layers[0].canvasSize[0]) + 1;
|
||||
borders.left = -minX;
|
||||
borders.top = maxY - layers[0].canvasSize[1] + 1;
|
||||
borders.bottom = -minY;
|
||||
|
||||
// Saving the data
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
imageDatas.push(layers[i].context.getImageData(minX - 1, layers[i].canvasSize[1] - maxY, maxX-minX + 1, maxY-minY + 1));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(imageDatas);
|
||||
//console.log("sx: " + borders.left + "dx: " + borders.right + "top: " + borders.top + "btm: " + borders.bottom);
|
||||
|
||||
document.getElementById("rc-border-left").value = borders.left;
|
||||
document.getElementById("rc-border-right").value = borders.right;
|
||||
document.getElementById("rc-border-top").value = borders.top;
|
||||
document.getElementById("rc-border-bottom").value = borders.bottom;
|
||||
|
||||
resizeCanvas(null, null, imageDatas.slice(), historySave);
|
||||
// Resetting the previous pivot
|
||||
rcPivot = prevPivot;
|
||||
}
|
||||
|
||||
function rcUpdateBorders() {
|
||||
// Getting input
|
||||
borders.left = document.getElementById("rc-border-left").value;
|
||||
borders.right = document.getElementById("rc-border-right").value;
|
||||
borders.top = document.getElementById("rc-border-top").value;
|
||||
borders.bottom = document.getElementById("rc-border-bottom").value;
|
||||
|
||||
// Validating input
|
||||
borders.left == "" ? borders.left = 0 : borders.left = Math.round(parseInt(borders.left));
|
||||
borders.right == "" ? borders.right = 0 : borders.right = Math.round(parseInt(borders.right));
|
||||
borders.top == "" ? borders.top = 0 : borders.top = Math.round(parseInt(borders.top));
|
||||
borders.bottom == "" ? borders.bottom = 0 : borders.bottom = Math.round(parseInt(borders.bottom));
|
||||
}
|
||||
|
||||
function changePivot(event) {
|
||||
rcPivot = event.target.getAttribute("value");
|
||||
|
||||
// Setting the selected class
|
||||
currentPivotObject.classList.remove("rc-selected-pivot");
|
||||
currentPivotObject = event.target;
|
||||
currentPivotObject.classList.add("rc-selected-pivot");
|
||||
}
|
||||
|
239
js/_resizeSprite.js
Normal file
@ -0,0 +1,239 @@
|
||||
|
||||
let keepRatio = true;
|
||||
let currentRatio;
|
||||
let currentAlgo = 'nearest-neighbor';
|
||||
let data = {width: 0, height: 0, widthPercentage: 100, heightPercentage: 100};
|
||||
let startData = {width: 0, height:0, widthPercentage: 100, heightPercentage: 100};
|
||||
|
||||
function openResizeSpriteWindow() {
|
||||
initResizeSpriteInputs();
|
||||
|
||||
currentRatio = layers[0].canvasSize[0] / layers[0].canvasSize[1];
|
||||
|
||||
data.width = layers[0].canvasSize[0];
|
||||
data.height = layers[1].canvasSize[1];
|
||||
|
||||
startData.width = parseInt(data.width);
|
||||
startData.height = parseInt(data.height);
|
||||
startData.heightPercentage = 100;
|
||||
startData.widthPercentage = 100;
|
||||
|
||||
showDialogue('resize-sprite');
|
||||
}
|
||||
|
||||
function initResizeSpriteInputs() {
|
||||
document.getElementById("rs-width").value = layers[0].canvasSize[0];
|
||||
document.getElementById("rs-height").value = layers[0].canvasSize[1];
|
||||
|
||||
document.getElementById("rs-width-percentage").value = 100;
|
||||
document.getElementById("rs-height-percentage").value = 100;
|
||||
|
||||
document.getElementById("rs-keep-ratio").checked = true;
|
||||
|
||||
document.getElementById("rs-width").addEventListener("change", changedWidth);
|
||||
document.getElementById("rs-height").addEventListener("change", changedHeight);
|
||||
document.getElementById("rs-width-percentage").addEventListener("change", changedWidthPercentage);
|
||||
document.getElementById("rs-height-percentage").addEventListener("change", changedHeightPercentage);
|
||||
|
||||
document.getElementById("resize-sprite-confirm").addEventListener("click", resizeSprite);
|
||||
document.getElementById("rs-keep-ratio").addEventListener("click", toggleRatio);
|
||||
document.getElementById("resize-algorithm-combobox").addEventListener("change", changedAlgorithm);
|
||||
}
|
||||
|
||||
function resizeSprite(event, ratio) {
|
||||
let oldWidth, oldHeight;
|
||||
let newWidth, newHeight;
|
||||
let rsImageDatas = [];
|
||||
let layerIndex = 0;
|
||||
let imageDatasCopy = [];
|
||||
|
||||
oldWidth = layers[0].canvasSize[0];
|
||||
oldHeight = layers[1].canvasSize[1];
|
||||
rcPivot = "middle";
|
||||
|
||||
// Updating values if the user didn't press enter
|
||||
switch (document.activeElement.id) {
|
||||
case "rs-width-percentage":
|
||||
changedWidthPercentage();
|
||||
break;
|
||||
case "rs-width":
|
||||
changedWidth();
|
||||
break;
|
||||
case "rs-height-percentage":
|
||||
changedHeightPercentage();
|
||||
break;
|
||||
case "rs-height":
|
||||
changedHeight();
|
||||
break;
|
||||
default:
|
||||
// In this case everything has been updated correctly
|
||||
break;
|
||||
}
|
||||
|
||||
if (ratio == null) {
|
||||
newWidth = data.width;
|
||||
newHeight = data.height;
|
||||
}
|
||||
else {
|
||||
newWidth = layers[0].canvasSize[0] * ratio[0];
|
||||
newHeight = layers[1].canvasSize[1] * ratio[1];
|
||||
}
|
||||
|
||||
// Get all the image datas
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
rsImageDatas.push(layers[i].context.getImageData(
|
||||
0, 0, layers[0].canvasSize[0], layers[0].canvasSize[1])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (ratio == null) {
|
||||
imageDatasCopy = rsImageDatas.slice();
|
||||
new HistoryStateResizeSprite(newWidth / oldWidth, newHeight / oldHeight, currentAlgo, imageDatasCopy);
|
||||
}
|
||||
|
||||
// Resizing the canvas
|
||||
resizeCanvas(null, {x: newWidth, y: newHeight});
|
||||
|
||||
// Put the image datas on the new canvases
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
if (layers[i].menuEntry != null) {
|
||||
layers[i].context.putImageData(
|
||||
resizeImageData(rsImageDatas[layerIndex], newWidth, newHeight, currentAlgo), 0, 0
|
||||
);
|
||||
layers[i].updateLayerPreview();
|
||||
layerIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Updating start values when I finish scaling the sprite
|
||||
// OPTIMIZABLE? Can't I just assign data to startData? Is js smart enough to understand?
|
||||
if (ratio == null) {
|
||||
startData.width = data.width;
|
||||
startData.height = data.height;
|
||||
}
|
||||
else {
|
||||
startData.width = layers[0].canvasSize[0];
|
||||
startData.height = layers[0].canvasSize[1];
|
||||
}
|
||||
|
||||
startData.widthPercentage = 100;
|
||||
startData.heightPercentage = 100;
|
||||
|
||||
closeDialogue();
|
||||
}
|
||||
|
||||
function changedWidth(event) {
|
||||
let oldValue = data.width;
|
||||
let ratio;
|
||||
let percentageRatio;
|
||||
let newHeight, newHeightPerc, newWidthPerc;
|
||||
|
||||
data.width = event.target.value;
|
||||
delta = data.width - oldValue;
|
||||
|
||||
ratio = data.width / oldValue;
|
||||
|
||||
newHeight = data.width / currentRatio;
|
||||
newHeightPerc = (newHeight * 100) / startData.height;
|
||||
newWidthPerc = (data.width * 100) / startData.width;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-height").value = newHeight;
|
||||
data.height = newHeight;
|
||||
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
data.heightPercentage = newHeightPerc;
|
||||
}
|
||||
|
||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||
}
|
||||
|
||||
function changedHeight(event) {
|
||||
let oldValue = 100;
|
||||
let ratio;
|
||||
let newWidth, newWidthPerc, newHeightPerc;
|
||||
|
||||
data.height = event.target.value;
|
||||
delta = data.height - oldValue;
|
||||
|
||||
ratio = data.height / oldValue;
|
||||
|
||||
newWidth = data.height * currentRatio;
|
||||
newWidthPerc = (newWidth * 100) / startData.width;
|
||||
newHeightPerc = (data.height * 100) / startData.height;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
data.width = newWidth;
|
||||
|
||||
document.getElementById("rs-width-percentage").value = newWidthPerc;
|
||||
data.widthPercentage = newWidthPerc;
|
||||
}
|
||||
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
data.heightPercentage = newHeightPerc;
|
||||
}
|
||||
|
||||
function changedWidthPercentage(event) {
|
||||
let oldValue = 100;
|
||||
let ratio;
|
||||
let newWidth, newHeight, newHeightPerc;
|
||||
|
||||
data.widthPercentage = event.target.value;
|
||||
delta = data.widthPercentage - oldValue;
|
||||
|
||||
ratio = data.widthPercentage / oldValue;
|
||||
|
||||
console.log("old value: " + oldValue + ", ratio: " + ratio);
|
||||
|
||||
newHeight = startData.height * ratio;
|
||||
newHeightPerc = data.widthPercentage / currentRatio;
|
||||
newWidth = startData.width * ratio;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-height-percentage").value = newHeightPerc;
|
||||
data.heightPercentage = newHeightPerc;
|
||||
|
||||
document.getElementById("rs-height").value = newHeight
|
||||
data.height = newHeight;
|
||||
}
|
||||
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
data.width = newWidth;
|
||||
}
|
||||
|
||||
function changedHeightPercentage(event) {
|
||||
let oldValue = data.heightPercentage;
|
||||
let ratio;
|
||||
let newHeight, newWidth, newWidthPerc;
|
||||
|
||||
data.heightPercentage = event.target.value;
|
||||
delta = data.heightPercentage - oldValue;
|
||||
|
||||
ratio = data.heightPercentage / oldValue;
|
||||
|
||||
newWidth = startData.width * ratio;
|
||||
newWidthPerc = data.heightPercentage * currentRatio;
|
||||
newHeight = startData.height * ratio;
|
||||
|
||||
if (keepRatio) {
|
||||
document.getElementById("rs-width-percentage").value = data.heightPercentage * currentRatio;
|
||||
data.widthPercentage = newWidthPerc;
|
||||
|
||||
document.getElementById("rs-width").value = newWidth;
|
||||
data.width = newWidth;
|
||||
}
|
||||
|
||||
document.getElementById("rs-height").value = newHeight;
|
||||
data.height = newHeight;
|
||||
}
|
||||
|
||||
function toggleRatio(event) {
|
||||
keepRatio = !keepRatio;
|
||||
}
|
||||
|
||||
function changedAlgorithm(event) {
|
||||
currentAlgo = event.target.value;
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
function setCanvasOffset (canvas, offsetLeft, offsetTop) {
|
||||
//horizontal offset
|
||||
var minXOffset = -canvasSize[0]*zoom+ 164;
|
||||
var maxXOffset = window.innerWidth - 300;
|
||||
|
||||
if (offsetLeft < minXOffset)
|
||||
canvas.style.left = minXOffset +'px';
|
||||
else if (offsetLeft > maxXOffset)
|
||||
canvas.style.left = maxXOffset +'px';
|
||||
else
|
||||
canvas.style.left = offsetLeft +'px';
|
||||
|
||||
//vertical offset
|
||||
var minYOffset = -canvasSize[1]*zoom + 164;
|
||||
var maxYOffset = window.innerHeight-100;
|
||||
|
||||
if (offsetTop < minYOffset)
|
||||
canvas.style.top = minYOffset +'px';
|
||||
else if (offsetTop > maxYOffset)
|
||||
canvas.style.top = maxYOffset +'px';
|
||||
else
|
||||
canvas.style.top = offsetTop +'px';
|
||||
}
|
@ -14,7 +14,8 @@ if(!settingsFromCookie) {
|
||||
enableBrushPreview: true, //unused - performance
|
||||
enableEyedropperPreview: true, //unused - performance
|
||||
numberOfHistoryStates: 20,
|
||||
maxColorsOnImportedImage: 128
|
||||
maxColorsOnImportedImage: 128,
|
||||
pixelGridColour: '#0000FF'
|
||||
};
|
||||
}
|
||||
else{
|
||||
@ -35,6 +36,9 @@ on('click', 'save-settings', function (){
|
||||
|
||||
//save new settings to settings object
|
||||
settings.numberOfHistoryStates = getValue('setting-numberOfHistoryStates');
|
||||
settings.pixelGridColour = getValue('setting-pixelGridColour');
|
||||
// Filling pixel grid again if colour changed
|
||||
fillPixelGrid();
|
||||
|
||||
//save settings object to cookie
|
||||
var cookieValue = JSON.stringify(settings);
|
||||
|
40
js/_tools.js
@ -64,6 +64,46 @@ class Tool {
|
||||
//change cursor
|
||||
this.updateCursor();
|
||||
}
|
||||
|
||||
updateCursor () {
|
||||
//switch to that tools cursor
|
||||
canvasView.style.cursor = this.cursor || 'default';
|
||||
|
||||
//if the tool uses a brush preview, make it visible and update the size
|
||||
if (this.brushPreview) {
|
||||
//console.log('brush size',this.currentBrushSize)
|
||||
brushPreview.style.display = 'block';
|
||||
brushPreview.style.width = this.currentBrushSize * zoom + 'px';
|
||||
brushPreview.style.height = this.currentBrushSize * zoom + 'px';
|
||||
}
|
||||
|
||||
//show / hide eyedropper color preview
|
||||
if (this.eyedropperPreview) eyedropperPreview.style.display = 'block';
|
||||
else eyedropperPreview.style.display = 'none';
|
||||
|
||||
//moveSelection
|
||||
if (currentTool.name == 'moveselection') {
|
||||
if (cursorInSelectedArea()) {
|
||||
canMoveSelection = true;
|
||||
canvasView.style.cursor = 'move';
|
||||
brushPreview.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
canvasView.style.cursor = 'crosshair';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
moveBrushPreview(cursorLocation) {
|
||||
let toSub = 0;
|
||||
// Prevents the brush to be put in the middle of pixels
|
||||
if (this.currentBrushSize % 2 == 0) {
|
||||
toSub = 0.5;
|
||||
}
|
||||
|
||||
brushPreview.style.left = (Math.ceil(cursorLocation[0] / zoom) * zoom + currentLayer.canvas.offsetLeft - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px';
|
||||
brushPreview.style.top = (Math.ceil(cursorLocation[1] / zoom) * zoom + currentLayer.canvas.offsetTop - this.currentBrushSize * zoom / 2 - zoom / 2 - toSub * zoom) + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,35 +0,0 @@
|
||||
|
||||
|
||||
//set the correct cursor for the current tool
|
||||
Tool.prototype.updateCursor = function () {
|
||||
//console.log('updateCursor()', currentTool)
|
||||
|
||||
//switch to that tools cursor
|
||||
canvasView.style.cursor = this.cursor || 'default';
|
||||
|
||||
//if the tool uses a brush preview, make it visible and update the size
|
||||
if (this.brushPreview) {
|
||||
//console.log('brush size',this.currentBrushSize)
|
||||
brushPreview.style.display = 'block';
|
||||
brushPreview.style.width = this.currentBrushSize * zoom + 'px';
|
||||
brushPreview.style.height = this.currentBrushSize * zoom + 'px';
|
||||
}
|
||||
|
||||
//show / hide eyedropper color preview
|
||||
if (this.eyedropperPreview) eyedropperPreview.style.display = 'block';
|
||||
else eyedropperPreview.style.display = 'none';
|
||||
|
||||
//moveSelection
|
||||
if (currentTool.name == 'moveselection') {
|
||||
if (cursorInSelectedArea()) {
|
||||
canMoveSelection = true;
|
||||
canvasView.style.cursor = 'move';
|
||||
brushPreview.style.display = 'none';
|
||||
}
|
||||
else {
|
||||
canvasView.style.cursor = 'crosshair';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*global Tool, dragging, canvasView, brushPreview, canMoveSelection, cursorInSelectedArea, eyedropperPreview, zoom, currentTool */
|
@ -1,21 +1,12 @@
|
||||
//init variables
|
||||
var canvasSize,zoom;
|
||||
var canvasSize;
|
||||
var zoom = 7;
|
||||
var dragging = false;
|
||||
var lastPos = [0,0];
|
||||
var lastMouseClickPos = [0,0];
|
||||
var dialogueOpen = false;
|
||||
var documentCreated = false;
|
||||
var pixelEditorMode;
|
||||
|
||||
// Checkerboard management
|
||||
// Checkerboard color 1
|
||||
var firstCheckerBoardColor = 'rgba(179, 173, 182, 1)';
|
||||
// Checkerboard color 2
|
||||
var secondCheckerBoardColor = 'rgba(204, 200, 206, 1)';
|
||||
// Square size for the checkerboard
|
||||
var checkerBoardSquareSize = 16;
|
||||
// Checkerboard canvas
|
||||
var checkerBoardCanvas = document.getElementById('checkerboard');
|
||||
|
||||
//common elements
|
||||
var brushPreview = document.getElementById("brush-preview");
|
||||
var eyedropperPreview = document.getElementById("eyedropper-preview");
|
||||
@ -26,7 +17,6 @@ var popUpContainer = document.getElementById("pop-up-container");
|
||||
|
||||
// main canvas
|
||||
var canvas = document.getElementById('pixel-canvas');
|
||||
var context = canvas.getContext('2d');
|
||||
var currentGlobalColor;
|
||||
|
||||
// Layers
|
||||
@ -43,3 +33,13 @@ var VFXCanvas = document.getElementById('vfx-canvas');
|
||||
var TMPLayer;
|
||||
// TMP canvas
|
||||
var TMPCanvas = document.getElementById('tmp-canvas');
|
||||
|
||||
// Pixel grid layer
|
||||
var pixelGrid;
|
||||
// Pixel grid canvas
|
||||
var pixelGridCanvas;
|
||||
|
||||
// Index of the first layer the user can use in the layers array
|
||||
var firstUserLayerIndex = 2;
|
||||
// Number of layers that are only used by the editor
|
||||
var nAppLayers = 3;
|
@ -42,8 +42,11 @@
|
||||
//=include _deleteColor.js
|
||||
//=include _replaceAllOfColor.js
|
||||
//=include _checkerboard.js
|
||||
//=include _pixelGrid.js
|
||||
//=include _layer.js
|
||||
//=include _copyPaste.js
|
||||
//=include _resizeCanvas.js
|
||||
//=include _resizeSprite.js
|
||||
|
||||
/**load file**/
|
||||
//=include _loadImage.js
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
new Tool('rectselect', {
|
||||
cursor: 'crosshair',
|
||||
brushPreview: true,
|
||||
});
|
||||
|
||||
|
||||
|
@ -52,14 +52,24 @@
|
||||
<li>
|
||||
<button>Edit</button>
|
||||
<ul>
|
||||
<li><button id="resize-canvas-button" onclick = "openResizeCanvasWindow()">Resize canvas</button></li>
|
||||
<li><button id="resize-sprite-button" onclick = "openResizeSpriteWindow()">Scale sprite</button></li>
|
||||
<li><button onclick = "trimCanvas()">Trim canvas</button></li>
|
||||
<li><button id="undo-button" class="disabled">Undo</button></li>
|
||||
<li><button id="redo-button" class="disabled">Redo</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<button>View</button>
|
||||
<ul>
|
||||
<li><button id="toggle-pixelgrid-button" onclick="togglePixelGrid()">Show pixel grid</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<button id = "layer-button">Layer</button>
|
||||
<ul>
|
||||
<li><button onclick = "addLayer()">New layer</button></li>
|
||||
<li><button onclick = "duplicateLayer()">Duplicate</button></li>
|
||||
<li><button onclick = "renameLayer()">Rename</button></li>
|
||||
<li><button onclick = "deleteLayer()">Delete</button></li>
|
||||
<li><button onclick = "merge()">Merge below</button></li>
|
||||
@ -81,13 +91,13 @@
|
||||
<button>Editor</button>
|
||||
<ul>
|
||||
<li><button id="switch-mode-button">Switch to basic mode</button></li>
|
||||
<li><button>Settings</button></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<button>Help</button>
|
||||
<ul>
|
||||
<li><button>Settings</button></li>
|
||||
<li><button>Help</button></li>
|
||||
<li><button>About</button></li>
|
||||
<li><button>Changelog</button></li>
|
||||
@ -171,10 +181,13 @@
|
||||
|
||||
<ul id = "layer-properties-menu">
|
||||
<li>
|
||||
<button onclick = "deleteLayer()">Delete</button>
|
||||
<button onclick = "renameLayer()">Rename</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onclick = "renameLayer()">Rename</button>
|
||||
<button onclick = "duplicateLayer()">Duplicate</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onclick = "deleteLayer()">Delete</button>
|
||||
</li>
|
||||
<li>
|
||||
<button onclick = "merge()">Merge below</button>
|
||||
@ -187,6 +200,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- TOOL PREVIEWS -->
|
||||
<div id="eyedropper-preview"></div>
|
||||
<div id="brush-preview"></div>
|
||||
|
||||
@ -196,6 +210,7 @@
|
||||
<canvas id = "tmp-canvas" class = "drawingCanvas"></canvas>
|
||||
<canvas id="pixel-canvas" class = "drawingCanvas"></canvas>
|
||||
<canvas id="checkerboard" class = "drawingCanvas"></canvas>
|
||||
<canvas id="pixel-grid" class = "drawingCanvas"></canvas>
|
||||
</div>
|
||||
<div id="canvas-view-shadow"></div>
|
||||
|
||||
@ -217,7 +232,8 @@
|
||||
{{svg "adjust.svg" width="20" height="20" }}
|
||||
</div>
|
||||
|
||||
<div id="pop-up-container">
|
||||
<div id="pop-up-container" id = "new-pixel-container">
|
||||
<!-- NEW PIXEL -->
|
||||
<div id="new-pixel">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>New Pixel</h1>
|
||||
@ -245,6 +261,116 @@
|
||||
<button id="create-button" class="default">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--SPRITE RESIZE-->
|
||||
<div id = "resize-sprite">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Scale sprite</h1>
|
||||
<!-- SIZE-->
|
||||
<h2>New size</h2>
|
||||
<span id = "rs-size-menu">
|
||||
<div>
|
||||
<span>
|
||||
Width: <input id="rs-width" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Height: <input id="rs-height" default="0" step="1" type="number"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<!--BORDERS-->
|
||||
<h2>Resize percentages</h2>
|
||||
<span id = "rs-percentage-menu">
|
||||
<div>
|
||||
<span>
|
||||
Width <input id="rs-width-percentage" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/> %
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Height <input id="rs-height-percentage" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/> %
|
||||
</span>
|
||||
</div>
|
||||
<div id = "rs-ratio-div">
|
||||
<span>
|
||||
Keep current ratio <input type = "checkbox" id = "rs-keep-ratio"/>
|
||||
</span>
|
||||
<span>
|
||||
Scaling algorithm:
|
||||
<select name = "resize-algorithm" id = "resize-algorithm-combobox">
|
||||
<option value = "nearest-neighbor">Nearest neighbour</option>
|
||||
<option value = "bilinear-interpolation">Bilinear</option>
|
||||
</select>
|
||||
</span>
|
||||
</br>
|
||||
<button id = "resize-sprite-confirm">Scale sprite</button>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!--CANVAS RESIZE-->
|
||||
<div id = "resize-canvas">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Resize canvas</h1>
|
||||
|
||||
<!--PIVOTS-->
|
||||
<span id = "pivot-menu">
|
||||
<button class="pivot-button" value="topleft">{{svg "arrows/topleft.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="top">{{svg "arrows/top.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="topright">{{svg "arrows/topright.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="left">{{svg "arrows/left.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button rc-selected-pivot" value="middle">{{svg "arrows/middle.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="right">{{svg "arrows/right.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="bottomleft">{{svg "arrows/bottomleft.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="bottom">{{svg "arrows/bottom.svg" width="20" height="20"}}</button>
|
||||
<button class="pivot-button" value="bottomright">{{svg "arrows/bottomright.svg" width="20" height="20"}}</button>
|
||||
</span>
|
||||
<!-- SIZE-->
|
||||
<span id = "rc-size-menu">
|
||||
<h2>Size</h2>
|
||||
<div>
|
||||
<span>
|
||||
Width: <input id="rc-width" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Height: <input id="rc-height" default="0" step="1" type="number"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
</div>
|
||||
</span>
|
||||
<!--BORDERS-->
|
||||
<span id = "borders-menu">
|
||||
<h2>Borders offsets</h2>
|
||||
<div>
|
||||
<span>
|
||||
Left: <input id="rc-border-left" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Right: <input id="rc-border-right" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Top: <input id="rc-border-top" type="number" default="0" step="1"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
Bottom: <input id="rc-border-bottom" default="0" step="1" type="number"
|
||||
value="{{#if border}}{{border}}{{else}}0{{/if}}" autocomplete="off"/>
|
||||
</span>
|
||||
</div>
|
||||
<button id = "resize-canvas-confirm">Resize canvas</button>
|
||||
</span>
|
||||
</div>
|
||||
<div id="help">
|
||||
<button class="close-button">{{svg "x.svg" width="20" height="20"}}</button>
|
||||
<h1>Help</h1>
|
||||
@ -306,7 +432,15 @@
|
||||
<h1>Settings</h1>
|
||||
|
||||
<div id="settings-container">
|
||||
<label for="setting-numberOfHistoryStates">Number of History States</label> <input id="setting-numberOfHistoryStates" value="20" autocomplete="off" />
|
||||
<h2>History</h2>
|
||||
<div class = "settings-entry">
|
||||
<label for="setting-numberOfHistoryStates">Number of History States</label> <input id="setting-numberOfHistoryStates" value="20" autocomplete="off" />
|
||||
</div>
|
||||
|
||||
<h2>Pixel grid</h2>
|
||||
<div class = "settings-entry">
|
||||
<label for="setting-pixelGridColour">Colour of the pixel grid</label><input id="setting-pixelGridColour" value = "#0000FF" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p id="cookies-disabled-warning">Your browsers cookies are disabled, settings will be lost upon closing this page.</p>
|
||||
|