Rotate non square sprites, added rotate icon, unit tests for transforms

This commit is contained in:
jdescottes 2014-11-23 15:03:35 +01:00
parent 796cd4466e
commit ce1a5c4918
14 changed files with 385 additions and 85 deletions

View File

@ -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

View 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.

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -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;
};

View File

@ -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
});
};

View File

@ -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);
};
})();

View File

@ -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);
};
})();

View 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;
}
};
})();

View File

@ -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);
}
});
},

View File

@ -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",

View File

@ -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 -->

View 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]
]);
});
});

View File

@ -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;
// })
});