Skip to content

Commit 4ed5441

Browse files
committed
option to choose between 3 shears and rotsprite
1 parent 5085fcd commit 4ed5441

File tree

6 files changed

+133
-108
lines changed

6 files changed

+133
-108
lines changed

app/components/mappings/new-mapping.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const baseConfig = {
2020
const BottomMenu = observer(() => (
2121
<div className="row">
2222
<Button color="magenta" onClick={mappingState.toggleNewMapping}>
23-
close
23+
Close
2424
</Button>
2525
<div className="autodismiss">
2626
<span onClick={mappingState.toggleAutodismiss}>autodismiss</span>

app/components/mappings/raw-editor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ export const RawEditor = observer(
291291
color="magenta"
292292
onClick={mappingState.toggleRawEditor}
293293
>
294-
close
294+
Close
295295
</Button>
296296
</div>
297297
<SortableMappingList

app/components/mappings/rotate.js

Lines changed: 19 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -2,125 +2,38 @@ import React, { useEffect, useRef } from 'react';
22
import { environment } from '#store/environment';
33
import { observer } from 'mobx-react';
44
import { exportSprite } from '#formats/image';
5-
import { rotateImageData, getRotateDiagonal } from '#util/rotsprite';
6-
import { Input, Slider, Item, Button, Modal } from '#ui';
5+
import { rotsprite, threeShears } from '#util/rotsprite';
6+
import { Input, Slider, Item, Button, Modal, Select } from '#ui';
77
import { mappingState } from './state';
88

99
import { importState } from '../import/state';
1010

11-
function rotateCurrentSprite(canvas, angle) {
11+
function rotateCurrentSprite(canvas, angle, algorithm) {
1212
const spriteCanv = exportSprite(environment.currentSprite);
13-
const spriteCtx = spriteCanv.getContext('2d');
14-
// const { width, height } = spriteCanv;
15-
// const imageData = spriteCtx.getImageData(0, 0, width, height);
16-
// const rotatedData = rotateImageData(imageData, angle, width, height);
1713

18-
// const ctx = canvas.getContext('2d');
19-
// canvas.width = rotatedData.width;
20-
// canvas.height = rotatedData.height;
21-
// ctx.putImageData(rotatedData, 0, 0);
22-
23-
function flipImageData(imageData) {
24-
const { width, height, data } = imageData;
25-
const rotatedImageData = new ImageData(width, height);
26-
const rotatedData = rotatedImageData.data;
27-
28-
for (let y = 0; y < height; y++) {
29-
for (let x = 0; x < width; x++) {
30-
const srcIndex = (y * width + x) * 4;
31-
const dstIndex = ((height - y - 1) * width + (width - x - 1)) * 4;
32-
rotatedData[dstIndex + 0] = data[srcIndex + 0];
33-
rotatedData[dstIndex + 1] = data[srcIndex + 1];
34-
rotatedData[dstIndex + 2] = data[srcIndex + 2];
35-
rotatedData[dstIndex + 3] = data[srcIndex + 3];
36-
}
37-
}
38-
39-
return rotatedImageData;
40-
}
41-
42-
const flipped = angle > 90 && angle < 270;
43-
// TODO: fix flip clipping
44-
// TODO: fix padding clipping
45-
46-
let { diagonal, xMargin, yMargin } = getRotateDiagonal(
47-
spriteCanv.width,
48-
spriteCanv.height,
49-
);
50-
const width = diagonal;
51-
const height = diagonal;
52-
53-
const copy = spriteCtx.getImageData(
54-
0,
55-
0,
56-
spriteCanv.width,
57-
spriteCanv.height,
58-
);
59-
60-
spriteCanv.width = diagonal;
61-
spriteCanv.height = diagonal;
62-
63-
spriteCtx.putImageData(flipped ? flipImageData(copy) : copy, xMargin, yMargin);
64-
65-
const ctx = canvas.getContext('2d');
66-
canvas.width = width;
67-
canvas.height = height;
68-
69-
70-
if (flipped) {
71-
// // flip spriteCanv using ctx as a buffer
72-
// ctx.save();
73-
// ctx.translate(width / 2, height / 2);
74-
// ctx.rotate(Math.PI);
75-
// ctx.drawImage(spriteCanv, -width / 2, -width / 2);
76-
// ctx.restore();
77-
// spriteCtx.clearRect(0, 0, width, height);
78-
// spriteCtx.drawImage(canvas, 0, 0, width, height);
79-
// ctx.clearRect(0, 0, width, height);
14+
if (algorithm === 'rotsprite') {
15+
rotsprite(spriteCanv, canvas, angle);
16+
} else {
17+
threeShears(spriteCanv, canvas, angle);
8018
}
8119

82-
// rotate
83-
84-
const clampedAngle = flipped ? angle - 180 : angle;
85-
const theta = (clampedAngle * Math.PI) / 180;
86-
const alpha = -Math.tan(theta / 2);
87-
const beta = Math.sin(theta);
88-
89-
for (let y = 0; y < height; ++y) {
90-
const shear = Math.round((y - height / 2) * alpha);
91-
ctx.drawImage(spriteCanv, 0, y, width, 1, shear, y, width, 1);
92-
}
93-
spriteCtx.clearRect(0, 0, width, height);
94-
spriteCtx.drawImage(canvas, 0, 0);
95-
ctx.clearRect(0, 0, width, height);
96-
for (let x = 0; x < width; ++x) {
97-
const shear = Math.round((x - width / 2) * beta);
98-
ctx.drawImage(spriteCanv, x, 0, 1, height, x, shear, 1, height);
99-
}
100-
spriteCtx.clearRect(0, 0, width, height);
101-
spriteCtx.drawImage(canvas, 0, 0);
102-
ctx.clearRect(0, 0, width, height);
103-
for (let y = 0; y < height; ++y) {
104-
const shear = Math.round((y - height / 2) * alpha);
105-
ctx.drawImage(spriteCanv, 0, y, width, 1, shear, y, width, 1);
106-
}
10720
}
10821

10922
export const Rotate = observer(() => {
11023
const canvasRef = useRef();
11124

112-
const { active, angle } = mappingState.rotate;
25+
const { active, angle, algorithm } = mappingState.rotate;
11326

11427
useEffect(() => {
11528
if (!canvasRef.current) {
11629
requestAnimationFrame(() => {
11730
canvasRef.current &&
118-
rotateCurrentSprite(canvasRef.current, angle);
31+
rotateCurrentSprite(canvasRef.current, angle, algorithm);
11932
});
12033
return;
12134
}
122-
rotateCurrentSprite(canvasRef.current, angle);
123-
}, [environment.currentSprite, angle, active]);
35+
rotateCurrentSprite(canvasRef.current, angle, algorithm);
36+
}, [environment.currentSprite, angle, active, algorithm]);
12437

12538
const assertInput = (num) => {
12639
const value = Math.max(0, Math.min(360, num));
@@ -142,7 +55,14 @@ export const Rotate = observer(() => {
14255
opacity: active ? 1 : 0,
14356
}}
14457
>
58+
<div className="row">
14559
<Item>Rotate Sprite</Item>
60+
<Select
61+
options={mappingState.rotateAlgOptions}
62+
store={mappingState.rotate}
63+
accessor="algorithm"
64+
/>
65+
</div>
14666
<canvas ref={canvasRef} />
14767
<div className="angles">
14868
<div className="numbers">
@@ -177,7 +97,7 @@ export const Rotate = observer(() => {
17797
</div>
17898
<div className="actions">
17999
<Button color="magenta" onClick={mappingState.toggleRotate}>
180-
close
100+
Close
181101
</Button>
182102
<Button color="red" onClick={reImport}>
183103
Import

app/components/mappings/state/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,16 @@ class MappingState {
175175
}
176176
};
177177

178-
// rotsprite
178+
// rotate
179179

180180
rotate = {
181181
angle: 0,
182182
active: false,
183+
algorithm: '3 shears',
183184
};
184185

186+
rotateAlgOptions = ['3 shears', 'rotsprite'];
187+
185188
toggleRotate = () => {
186189
this.rotate.active = !this.rotate.active;
187190
};

app/util/rotsprite.js

Lines changed: 104 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,93 @@
11
// 3 shears
22

3+
4+
export function threeShears(spriteCanv, canvas, angle) {
5+
const spriteCtx = spriteCanv.getContext('2d');
6+
7+
const flipped = angle > 90 && angle < 270;
8+
// TODO: fix padding clipping
9+
10+
let { diagonal, xMargin, yMargin } = getRotateDiagonal(
11+
spriteCanv.width,
12+
spriteCanv.height,
13+
);
14+
const width = diagonal;
15+
const height = diagonal;
16+
17+
const copy = spriteCtx.getImageData(
18+
0,
19+
0,
20+
spriteCanv.width,
21+
spriteCanv.height,
22+
);
23+
24+
spriteCanv.width = diagonal;
25+
spriteCanv.height = diagonal;
26+
27+
if (flipped) {
28+
spriteCtx.putImageData(flipImageData(copy), xMargin+1, yMargin+1);
29+
} else {
30+
spriteCtx.putImageData(copy, xMargin, yMargin);
31+
32+
}
33+
34+
const ctx = canvas.getContext('2d');
35+
canvas.width = width;
36+
canvas.height = height;
37+
38+
// ctx.drawImage(spriteCanv,0,0);
39+
// return;
40+
41+
// rotate
42+
43+
const clampedAngle = flipped ? angle - 180 : angle;
44+
const theta = (clampedAngle * Math.PI) / 180;
45+
const alpha = -Math.tan(theta / 2);
46+
const beta = Math.sin(theta);
47+
48+
for (let y = 0; y < height; ++y) {
49+
const shear = Math.round((y - height / 2) * alpha);
50+
ctx.drawImage(spriteCanv, 0, y, width, 1, shear, y, width, 1);
51+
}
52+
spriteCtx.clearRect(0, 0, width, height);
53+
spriteCtx.drawImage(canvas, 0, 0);
54+
ctx.clearRect(0, 0, width, height);
55+
for (let x = 0; x < width; ++x) {
56+
const shear = Math.round((x - width / 2) * beta);
57+
ctx.drawImage(spriteCanv, x, 0, 1, height, x, shear, 1, height);
58+
}
59+
spriteCtx.clearRect(0, 0, width, height);
60+
spriteCtx.drawImage(canvas, 0, 0);
61+
ctx.clearRect(0, 0, width, height);
62+
for (let y = 0; y < height; ++y) {
63+
const shear = Math.round((y - height / 2) * alpha);
64+
ctx.drawImage(spriteCanv, 0, y, width, 1, shear, y, width, 1);
65+
}
66+
}
67+
68+
function flipImageData(imageData) {
69+
const { width, height, data } = imageData;
70+
const rotatedImageData = new ImageData(width, height);
71+
const rotatedData = rotatedImageData.data;
72+
73+
for (let y = 0; y < height; y++) {
74+
for (let x = 0; x < width; x++) {
75+
const srcIndex = (y * width + x) * 4;
76+
const dstIndex = ((height - y - 1) * width + (width - x - 1)) * 4;
77+
rotatedData[dstIndex + 0] = data[srcIndex + 0];
78+
rotatedData[dstIndex + 1] = data[srcIndex + 1];
79+
rotatedData[dstIndex + 2] = data[srcIndex + 2];
80+
rotatedData[dstIndex + 3] = data[srcIndex + 3];
81+
}
82+
}
83+
84+
return rotatedImageData;
85+
}
86+
87+
388
// common
489

5-
export function getRotateDiagonal(width, height) {
90+
function getRotateDiagonal(width, height) {
691
const diagonal =
792
1 + (0 | (2 * Math.sqrt(width ** 2 / 4 + height ** 2 / 4)));
893

@@ -18,7 +103,20 @@ export function getRotateDiagonal(width, height) {
18103
// stole code from ChaseMor/pxt-arcade-rotsprite
19104
// some generated with chatGPT
20105

21-
export function rotateImageData(imageData, angle, width, height) {
106+
107+
export function rotsprite(spriteCanv, canvas, angle) {
108+
const spriteCtx = spriteCanv.getContext('2d');
109+
const { width, height } = spriteCanv;
110+
const imageData = spriteCtx.getImageData(0, 0, width, height);
111+
const rotatedData = rotateImageData(imageData, angle, width, height);
112+
113+
const ctx = canvas.getContext('2d');
114+
canvas.width = rotatedData.width;
115+
canvas.height = rotatedData.height;
116+
ctx.putImageData(rotatedData, 0, 0);
117+
}
118+
119+
function rotateImageData(imageData, angle, width, height) {
22120
const { diagonal, xMargin, yMargin } = getRotateDiagonal(width, height);
23121

24122
const spriteData = addMarginToImageData(
@@ -37,7 +135,7 @@ export function rotateImageData(imageData, angle, width, height) {
37135
data[i / 4] = (a << 24) + (r << 16) + (g << 8) + b;
38136
}
39137

40-
const rotated = rotSprite(
138+
const rotated = rotspriteAlg(
41139
new Pixels(diagonal, diagonal, data),
42140
(angle * Math.PI) / 180,
43141
).pixels;
@@ -56,7 +154,7 @@ export function rotateImageData(imageData, angle, width, height) {
56154
return new ImageData(pixelData, diagonal, diagonal);
57155
}
58156

59-
function rotSprite(image, angle) {
157+
function rotspriteAlg(image, angle) {
60158
image = scale2xImage(image);
61159
image = scale2xImage(image);
62160
image = scale2xImage(image);
@@ -129,7 +227,7 @@ function rotateAndReduceImage(original, angle) {
129227
return rotated;
130228
}
131229

132-
export class Pixels {
230+
class Pixels {
133231
// The width and height of the image in pixels
134232
width;
135233
height;
@@ -193,7 +291,7 @@ export class Pixels {
193291
};
194292
}
195293

196-
export function addMarginToImageData(imageData, xMargin, yMargin) {
294+
function addMarginToImageData(imageData, xMargin, yMargin) {
197295
// Create a new ImageData object with the new dimensions, including the margin.
198296
const newImageData = new ImageData(
199297
imageData.width + xMargin * 2,

styles/components/rotsprite.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
height: 400px;
88
margin: 10px 0;
99
}
10+
.Dropdown-menu, .Dropdown-placeholder {
11+
width: 90px;
12+
text-align: center;
13+
}
1014

1115
.angles {
1216
margin: 10px 0;

0 commit comments

Comments
 (0)