mirror of
https://github.com/piskelapp/piskel.git
synced 2023-08-10 21:12:52 +03:00
Rotate non square sprites, added rotate icon, unit tests for transforms
This commit is contained in:
parent
796cd4466e
commit
ce1a5c4918
@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="89.231px" height="100px" viewBox="0 0 89.231 100" enable-background="new 0 0 89.231 100" xml:space="preserve">
|
||||
<path d="M14.652,64.026c-0.603-2.09-1-4.267-1.15-6.511H0c0.18,3.858,0.86,7.586,1.962,11.13L14.652,64.026z"/>
|
||||
<path d="M14.332,88.192l8.684-10.35c-2.899-2.792-5.273-6.127-6.926-9.857L3.402,72.604C5.9,78.563,9.655,83.87,14.332,88.192z"/>
|
||||
<path d="M42.483,86.499c-6.051-0.409-11.626-2.559-16.245-5.943l-8.681,10.346c6.985,5.336,15.582,8.661,24.926,9.099V86.499z"/>
|
||||
<path d="M44.589,10.768V0L14.266,17.506l30.323,17.506V24.245c17.186,0,31.166,13.98,31.166,31.165 c0,16.477-12.854,29.999-29.061,31.087v13.502C70.337,98.896,89.231,79.32,89.231,55.41C89.231,30.794,69.205,10.768,44.589,10.768z "/>
|
||||
</svg>
|
After Width: | Height: | Size: 843 B |
8
misc/icons/noun-project/undo-kyle_levi_fox/license.txt
Normal file
8
misc/icons/noun-project/undo-kyle_levi_fox/license.txt
Normal file
@ -0,0 +1,8 @@
|
||||
Thank you for using The Noun Project. This icon is licensed under Creative
|
||||
Commons Attribution and must be attributed as:
|
||||
|
||||
Undo by Kyle Levi Fox from The Noun Project
|
||||
|
||||
If you have a Premium Account or have purchased a license for this icon, you
|
||||
don't need to worry about attribution! We will share the profits from your
|
||||
purchase with this icon's designer.
|
@ -98,10 +98,16 @@
|
||||
|
||||
.tool-icon.tool-flip {
|
||||
background-image: url(../img/tools/flip.png);
|
||||
background-position: 7px 10px;
|
||||
background-position: 7px 11px;
|
||||
background-size: 32px;
|
||||
}
|
||||
|
||||
.tool-icon.tool-rotate {
|
||||
background-image: url(../img/tools/rotate.png);
|
||||
background-position: 10px 9px;
|
||||
background-size: 26px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tool cursors:
|
||||
*/
|
||||
|
BIN
src/img/tools/rotate.png
Normal file
BIN
src/img/tools/rotate.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
@ -34,7 +34,7 @@
|
||||
|
||||
ns.TransformationsController.prototype.createToolsDom_ = function() {
|
||||
var html = this.tools.reduce(function (p, tool) {
|
||||
return p + this.toolIconRenderer.render(tool.instance, tool.shortcut);
|
||||
return p + this.toolIconRenderer.render(tool.instance, tool.shortcut, 'left');
|
||||
}.bind(this), '');
|
||||
this.toolsContainer.innerHTML = html;
|
||||
};
|
||||
|
@ -3,12 +3,14 @@
|
||||
|
||||
ns.IconMarkupRenderer = function () {};
|
||||
|
||||
ns.IconMarkupRenderer.prototype.render = function (tool, shortcut) {
|
||||
ns.IconMarkupRenderer.prototype.render = function (tool, shortcut, tooltipPosition) {
|
||||
tooltipPosition = tooltipPosition || 'right';
|
||||
var tpl = pskl.utils.Template.get('drawingTool-item-template');
|
||||
return pskl.utils.Template.replace(tpl, {
|
||||
cssclass : ['tool-icon', tool.toolId].join(' '),
|
||||
toolid : tool.toolId,
|
||||
title : this.getTooltipText(tool, shortcut)
|
||||
title : this.getTooltipText(tool, shortcut),
|
||||
tooltipposition : tooltipPosition
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -3,27 +3,26 @@
|
||||
|
||||
ns.Flip = function () {
|
||||
this.toolId = "tool-flip";
|
||||
this.helpText = "Flip tool";
|
||||
this.tooltipDescriptors = [];
|
||||
this.helpText = "Flip vertically";
|
||||
this.tooltipDescriptors = [
|
||||
{key : 'alt', description : 'Flip horizontally'},
|
||||
{key : 'ctrl', description : 'Apply to all layers'},
|
||||
{key : 'shift', description : 'Apply to all frames'}
|
||||
];
|
||||
};
|
||||
|
||||
pskl.utils.inherit(ns.Flip, ns.Transform);
|
||||
|
||||
ns.Flip.prototype.applyToolOnFrame_ = function (frame, altKey) {
|
||||
var clone = frame.clone();
|
||||
var w = frame.getWidth();
|
||||
var h = frame.getHeight();
|
||||
var axis;
|
||||
|
||||
var isVertical = !altKey;
|
||||
clone.forEachPixel(function (color, x, y) {
|
||||
if (isVertical) {
|
||||
x = w-x-1;
|
||||
} else {
|
||||
y = h-y-1;
|
||||
}
|
||||
frame.pixels[x][y] = color;
|
||||
});
|
||||
frame.version++;
|
||||
if (altKey) {
|
||||
axis = pskl.utils.FrameTransform.HORIZONTAL;
|
||||
} else {
|
||||
axis = pskl.utils.FrameTransform.VERTICAL;
|
||||
}
|
||||
|
||||
pskl.utils.FrameTransform.flip(frame, axis);
|
||||
};
|
||||
|
||||
})();
|
@ -3,30 +3,25 @@
|
||||
|
||||
ns.Rotate = function () {
|
||||
this.toolId = "tool-rotate";
|
||||
this.helpText = "Rotate tool";
|
||||
this.tooltipDescriptors = [];
|
||||
this.helpText = "Counter-clockwise rotation";
|
||||
this.tooltipDescriptors = [
|
||||
{key : 'alt', description : 'Clockwise rotation'},
|
||||
{key : 'ctrl', description : 'Apply to all layers'},
|
||||
{key : 'shift', description : 'Apply to all frames'}];
|
||||
};
|
||||
|
||||
pskl.utils.inherit(ns.Rotate, ns.Transform);
|
||||
|
||||
ns.Rotate.prototype.applyToolOnFrame_ = function (frame, altKey) {
|
||||
var clone = frame.clone();
|
||||
var w = frame.getWidth();
|
||||
var h = frame.getHeight();
|
||||
var direction;
|
||||
|
||||
var isClockwise = altKey;
|
||||
clone.forEachPixel(function (color, x, y) {
|
||||
var _x = x, _y = y;
|
||||
if (isClockwise) {
|
||||
y = x;
|
||||
x = w-_y-1;
|
||||
} else {
|
||||
x = y;
|
||||
y = h-_x-1;
|
||||
}
|
||||
frame.pixels[x][y] = color;
|
||||
});
|
||||
frame.version++;
|
||||
if (altKey) {
|
||||
direction = pskl.utils.FrameTransform.CLOCKWISE;
|
||||
} else {
|
||||
direction = pskl.utils.FrameTransform.COUNTERCLOCKWISE;
|
||||
}
|
||||
|
||||
pskl.utils.FrameTransform.rotate(frame, direction);
|
||||
};
|
||||
|
||||
})();
|
65
src/js/utils/FrameTransform.js
Normal file
65
src/js/utils/FrameTransform.js
Normal file
@ -0,0 +1,65 @@
|
||||
(function () {
|
||||
var ns = $.namespace('pskl.utils');
|
||||
|
||||
ns.FrameTransform = {
|
||||
VERTICAL : 'vertical',
|
||||
HORIZONTAL : 'HORIZONTAL',
|
||||
flip : function (frame, axis) {
|
||||
var clone = frame.clone();
|
||||
var w = frame.getWidth();
|
||||
var h = frame.getHeight();
|
||||
|
||||
clone.forEachPixel(function (color, x, y) {
|
||||
if (axis === ns.FrameTransform.VERTICAL) {
|
||||
x = w-x-1;
|
||||
} else if (axis === ns.FrameTransform.HORIZONTAL) {
|
||||
y = h-y-1;
|
||||
}
|
||||
frame.pixels[x][y] = color;
|
||||
});
|
||||
frame.version++;
|
||||
return frame;
|
||||
},
|
||||
|
||||
CLOCKWISE : 'clockwise',
|
||||
COUNTERCLOCKWISE : 'counterclockwise',
|
||||
rotate : function (frame, direction) {
|
||||
var clone = frame.clone();
|
||||
var w = frame.getWidth();
|
||||
var h = frame.getHeight();
|
||||
|
||||
var max = Math.max(w, h);
|
||||
var xDelta = Math.ceil((max - w)/2);
|
||||
var yDelta = Math.ceil((max - h)/2);
|
||||
|
||||
frame.forEachPixel(function (color, x, y) {
|
||||
var _x = x, _y = y;
|
||||
|
||||
// Convert to square coords
|
||||
x = x + xDelta;
|
||||
y = y + yDelta;
|
||||
|
||||
// Perform the rotation
|
||||
var tmpX = x, tmpY = y;
|
||||
if (direction === ns.FrameTransform.CLOCKWISE) {
|
||||
x = tmpY;
|
||||
y = max - 1 - tmpX;
|
||||
} else if (direction === ns.FrameTransform.COUNTERCLOCKWISE) {
|
||||
y = tmpX;
|
||||
x = max - 1 - tmpY;
|
||||
}
|
||||
|
||||
// Convert the coordinates back to the rectangular grid
|
||||
x = x - xDelta;
|
||||
y = y - yDelta;
|
||||
if (clone.containsPixel(x, y)) {
|
||||
frame.pixels[_x][_y] = clone.getPixel(x, y);
|
||||
} else {
|
||||
frame.pixels[_x][_y] = Constants.TRANSPARENT_COLOR;
|
||||
}
|
||||
});
|
||||
frame.version++;
|
||||
return frame;
|
||||
}
|
||||
};
|
||||
})();
|
@ -2,11 +2,17 @@
|
||||
var ns = $.namespace('pskl.utils');
|
||||
var colorCache = {};
|
||||
ns.FrameUtils = {
|
||||
toImage : function (frame, zoom, bgColor) {
|
||||
/**
|
||||
* Render a Frame object as an image.
|
||||
* Can optionally scale it (zoom)
|
||||
* @param {Frame} frame
|
||||
* @param {Number} zoom
|
||||
* @return {Image}
|
||||
*/
|
||||
toImage : function (frame, zoom) {
|
||||
zoom = zoom || 1;
|
||||
bgColor = bgColor || Constants.TRANSPARENT_COLOR;
|
||||
var canvasRenderer = new pskl.rendering.CanvasRenderer(frame, zoom);
|
||||
canvasRenderer.drawTransparentAs(bgColor);
|
||||
canvasRenderer.drawTransparentAs(Constants.TRANSPARENT_COLOR);
|
||||
return canvasRenderer.render();
|
||||
},
|
||||
|
||||
@ -23,9 +29,9 @@
|
||||
},
|
||||
|
||||
mergeFrames_ : function (frameA, frameB) {
|
||||
frameB.forEachPixel(function (p, col, row) {
|
||||
if (p != Constants.TRANSPARENT_COLOR) {
|
||||
frameA.setPixel(col, row, p);
|
||||
frameB.forEachPixel(function (color, col, row) {
|
||||
if (color != Constants.TRANSPARENT_COLOR) {
|
||||
frameA.setPixel(col, row, color);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -19,6 +19,7 @@
|
||||
"js/utils/Dom.js",
|
||||
"js/utils/Math.js",
|
||||
"js/utils/FileUtils.js",
|
||||
"js/utils/FrameTransform.js",
|
||||
"js/utils/FrameUtils.js",
|
||||
"js/utils/LayerUtils.js",
|
||||
"js/utils/ImageResizer.js",
|
||||
|
@ -35,7 +35,7 @@
|
||||
|
||||
<!-- Drawing tool icon-button -->
|
||||
<script type="text/template" id="drawingTool-item-template">
|
||||
<li rel="tooltip" data-placement="right" class="{{cssclass}}" data-tool-id="{{toolid}}" title="{{title}}"></li>
|
||||
<li rel="tooltip" data-placement="{{tooltipposition}}" class="{{cssclass}}" data-tool-id="{{toolid}}" title="{{title}}"></li>
|
||||
</script>
|
||||
|
||||
<!-- Drawing tool tooltip container -->
|
||||
|
253
test/js/utils/FrameTransformTest.js
Normal file
253
test/js/utils/FrameTransformTest.js
Normal file
@ -0,0 +1,253 @@
|
||||
describe("FrameTransform suite", function() {
|
||||
var A = '#000000';
|
||||
var B = '#ff0000';
|
||||
var O = Constants.TRANSPARENT_COLOR;
|
||||
|
||||
var HORIZONTAL = pskl.utils.FrameTransform.HORIZONTAL;
|
||||
var VERTICAL = pskl.utils.FrameTransform.VERTICAL;
|
||||
|
||||
var CLOCKWISE = pskl.utils.FrameTransform.CLOCKWISE;
|
||||
var COUNTERCLOCKWISE = pskl.utils.FrameTransform.COUNTERCLOCKWISE;
|
||||
|
||||
/**
|
||||
* Check a frame has the pixels as a given grid
|
||||
* @param {Frame} frame [description]
|
||||
* @param {Array<Array<String|Color>>} grid
|
||||
*/
|
||||
var frameEqualsGrid = function (frame, grid) {
|
||||
frame.forEachPixel(function (color, col, row) {
|
||||
expect(color).toBe(grid[row][col]);
|
||||
});
|
||||
};
|
||||
|
||||
var toFrameGrid = function (normalGrid) {
|
||||
var frameGrid = [];
|
||||
var w = normalGrid[0].length;
|
||||
var h = normalGrid.length;
|
||||
for (var x = 0 ; x < w ; x++) {
|
||||
frameGrid[x] = [];
|
||||
for (var y = 0 ; y < h ; y++) {
|
||||
frameGrid[x][y] = normalGrid[y][x];
|
||||
}
|
||||
}
|
||||
return frameGrid;
|
||||
};
|
||||
|
||||
/*******************************/
|
||||
/************ FLIP *************/
|
||||
/*******************************/
|
||||
|
||||
it("flips a frame vertically", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[A, O],
|
||||
[O, B]
|
||||
]));
|
||||
|
||||
// should have flipped
|
||||
pskl.utils.FrameTransform.flip(frame, VERTICAL);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, A],
|
||||
[B, O]
|
||||
]);
|
||||
});
|
||||
|
||||
it("flips a frame horizontally", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[A, O],
|
||||
[O, B]
|
||||
]));
|
||||
|
||||
// should have flipped
|
||||
pskl.utils.FrameTransform.flip(frame, HORIZONTAL);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, B],
|
||||
[A, O]
|
||||
]);
|
||||
});
|
||||
|
||||
it("flips rectangular frame", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[A, O],
|
||||
[A, O],
|
||||
[A, O]
|
||||
]));
|
||||
|
||||
// should have flipped
|
||||
pskl.utils.FrameTransform.flip(frame, VERTICAL);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, A],
|
||||
[O, A],
|
||||
[O, A]
|
||||
]);
|
||||
|
||||
// should be the same
|
||||
pskl.utils.FrameTransform.flip(frame, HORIZONTAL);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, A],
|
||||
[O, A],
|
||||
[O, A]
|
||||
]);
|
||||
});
|
||||
|
||||
/*******************************/
|
||||
/*********** ROTATE ************/
|
||||
/*******************************/
|
||||
|
||||
it("rotates a frame counterclockwise", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[A, O],
|
||||
[O, B]
|
||||
]));
|
||||
|
||||
// rotate once
|
||||
pskl.utils.FrameTransform.rotate(frame, COUNTERCLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, B],
|
||||
[A, O]
|
||||
]);
|
||||
|
||||
// rotate twice
|
||||
pskl.utils.FrameTransform.rotate(frame, COUNTERCLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[B, O],
|
||||
[O, A]
|
||||
]);
|
||||
|
||||
// rotate 3
|
||||
pskl.utils.FrameTransform.rotate(frame, COUNTERCLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, A],
|
||||
[B, O]
|
||||
]);
|
||||
|
||||
// rotate 4 - back to initial state
|
||||
pskl.utils.FrameTransform.rotate(frame, COUNTERCLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[A, O],
|
||||
[O, B]
|
||||
]);
|
||||
});
|
||||
|
||||
it("rotates a frame clockwise", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[A, O],
|
||||
[O, B]
|
||||
]));
|
||||
|
||||
// rotate once
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, A],
|
||||
[B, O]
|
||||
]);
|
||||
|
||||
// rotate twice
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[B, O],
|
||||
[O, A]
|
||||
]);
|
||||
|
||||
// rotate 3
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, B],
|
||||
[A, O]
|
||||
]);
|
||||
|
||||
// rotate 4 - back to initial state
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[A, O],
|
||||
[O, B]
|
||||
]);
|
||||
});
|
||||
|
||||
it("rotates a rectangular frame", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[A, O],
|
||||
[A, O],
|
||||
[B, O],
|
||||
[B, O]
|
||||
]));
|
||||
|
||||
// rotate once
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, O],
|
||||
[B, A],
|
||||
[O, O],
|
||||
[O, O]
|
||||
]);
|
||||
|
||||
// rotate twice
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, O],
|
||||
[O, B],
|
||||
[O, A],
|
||||
[O, O]
|
||||
]);
|
||||
|
||||
// rotate 3
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, O],
|
||||
[O, O],
|
||||
[A, B],
|
||||
[O, O]
|
||||
]);
|
||||
|
||||
// rotate 4
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, O],
|
||||
[A, O],
|
||||
[B, O],
|
||||
[O, O]
|
||||
]);
|
||||
});
|
||||
|
||||
it("rotates a rectangular (horizontal) frame", function () {
|
||||
// create frame
|
||||
var frame = pskl.model.Frame.fromPixelGrid(toFrameGrid([
|
||||
[O, O, O, O],
|
||||
[A, A, B, B]
|
||||
]));
|
||||
|
||||
// rotate once
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, A, O, O],
|
||||
[O, B, O, O]
|
||||
]);
|
||||
|
||||
// rotate twice
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, B, A, O],
|
||||
[O, O, O, O]
|
||||
]);
|
||||
|
||||
// rotate 3
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, O, B, O],
|
||||
[O, O, A, O]
|
||||
]);
|
||||
|
||||
// rotate 4
|
||||
pskl.utils.FrameTransform.rotate(frame, CLOCKWISE);
|
||||
frameEqualsGrid(frame, [
|
||||
[O, O, O, O],
|
||||
[O, A, B, O]
|
||||
]);
|
||||
});
|
||||
|
||||
});
|
@ -101,45 +101,4 @@ describe("FrameUtils suite", function() {
|
||||
expect(frames[3].getPixel(0,1)).toBe(red);
|
||||
|
||||
});
|
||||
|
||||
// it("starts at -1", function() {
|
||||
// historyService = createMockHistoryService();
|
||||
// expect(historyService.currentIndex).toBe(-1);
|
||||
// });
|
||||
|
||||
// it("is at 0 after init", function() {
|
||||
// historyService = createMockHistoryService();
|
||||
// historyService.init();
|
||||
// expect(historyService.currentIndex).toBe(0);
|
||||
// });
|
||||
|
||||
// it("stores a piskel snapshot after 5 SAVE", function () {
|
||||
// // BEFORE
|
||||
// var SNAPSHOT_PERIOD_BACKUP = pskl.service.HistoryService.SNAPSHOT_PERIOD;
|
||||
// pskl.service.HistoryService.SNAPSHOT_PERIOD = 5;
|
||||
|
||||
// historyService = createMockHistoryService();
|
||||
// historyService.init();
|
||||
|
||||
// sendSaveEvents(pskl.service.HistoryService.REPLAY).times(5);
|
||||
|
||||
// expect(historyService.currentIndex).toBe(5);
|
||||
|
||||
// expect(getLastState().piskel).toBe(SERIALIZED_PISKEL);
|
||||
|
||||
// sendSaveEvents(pskl.service.HistoryService.REPLAY).times(4);
|
||||
|
||||
// sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once();
|
||||
// expect(getLastState().piskel).toBeUndefined();
|
||||
|
||||
// sendSaveEvents(pskl.service.HistoryService.REPLAY_NO_SNAPSHOT).once();
|
||||
// expect(getLastState().piskel).toBeUndefined();
|
||||
|
||||
// sendSaveEvents(pskl.service.HistoryService.REPLAY).once();
|
||||
// expect(getLastState().piskel).toBe(SERIALIZED_PISKEL);
|
||||
|
||||
// // AFTER
|
||||
// pskl.service.HistoryService.SNAPSHOT_PERIOD = SNAPSHOT_PERIOD_BACKUP;
|
||||
|
||||
// })
|
||||
});
|
Loading…
Reference in New Issue
Block a user