Skip to content

Commit e7a80fe

Browse files
committed
rotsprite WIP implementation
1 parent 131c83a commit e7a80fe

File tree

10 files changed

+185
-27
lines changed

10 files changed

+185
-27
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Changelog
22

33
## [unreleased]
4+
- Add zoom slider in sprites tab
45

56
## [1.1.3]
67
- Upgrade to electron 12

TODO

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
<--- Last few GCs --->
2-
3-
[13424:0x16b81c0b9000] 4101829 ms: Mark-sweep 2045.5 (2052.8) -> 2043.7 (2053.0) MB, 255.8 / 2.0 ms (average mu = 0.098, current mu = 0.010) allocation failure scavenge might not succeed
4-
[13424:0x16b81c0b9000] 4102148 ms: Mark-sweep 2045.7 (2053.0) -> 2043.9 (2053.0) MB, 316.8 / 2.0 ms (average mu = 0.053, current mu = 0.008) allocation failure scavenge might not succeed
5-
6-
7-
<--- JS stacktrace --->
8-
9-
==== JS stack trace =========================================
10-
11-
0: ExitFrame [pc: 0x55a61ac44019]
12-
Security context: 0x08f21517ef51 <String[#39]: file://CD552D653EC98D0FCB75745E2A65E2F4>
13-
1: e(aka e) [0x86cab96f1f1] [file:///home/dustin/Desktop/Tools/Flex2/resources/app.asar/bundles/main.js:~15] [pc=0x3b5492b0376c](this=0x2899d8a80469 <undefined>,2,0x065e2fb79cd1 <Object map = 0x24f968842f81>,0x067399efffd9 <Map map = 0x4f2b0400ca1>)
14-
2: /* anonymous */(aka /* anonymous */) [0xb47f01db931] [f...
151
// formats to support
162

173
// crackers
@@ -27,6 +13,7 @@ Security context: 0x08f21517ef51 <String[#39]: file://CD552D653EC98D0FCB75745E2A
2713
ROADMAP
2814
look at issues related to crashes
2915
rotsprite
16+
drawing mode bugs
3017
improve undo/redo when drawing pixels / moving things
3118
animation editor
3219
export to gif

app/components/mappings/commands.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ export const Commands = observer(class Commands extends Component {
1616
<Masonry
1717
className="commands"
1818
style={{ width }}
19-
ref={(node) => {
20-
window.NN=node
21-
}}
2219
>
2320
{commands.map((group, i) => (
2421
<div key={i} className="group">

app/components/mappings/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { PaletteHUD } from './hud-palette';
1212
import { Guidelines } from './guidelines';
1313
import { NewMapping } from './new-mapping';
1414
import { RawEditor } from './raw-editor';
15+
import { Rotate } from './rotate';
1516
import { DragSelect, attachDragSelectToNode } from './drag-select';
1617
import { attachDragMoveToNode } from './drag-move';
1718
import { Commands } from './commands';
@@ -57,6 +58,7 @@ export const Mappings = observer(class Mappings extends Component {
5758

5859
return (
5960
<div className="mappings" ref={this.onRef}>
61+
<Rotate />
6062
<div
6163
ref={this.mappingRef}
6264
className="mappingContainer"

app/components/mappings/rotate.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import React, { useState, useEffect, useRef } from 'react';
2+
import { environment } from '#store/environment';
3+
import { exportSprite } from '#formats/image';
4+
import rotSprite, { Pixels } from '#util/rotsprite';
5+
6+
export function Rotate() {
7+
return null;
8+
const canvasRef = useRef();
9+
const [angle, setAngle] = useState(45);
10+
11+
useEffect(() => {
12+
const canvas = canvasRef.current;
13+
const ctx = canvas.getContext('2d');
14+
15+
const spriteCanv = exportSprite(environment.currentSprite);
16+
const spriteCtx = spriteCanv.getContext('2d');
17+
18+
const imageData = spriteCtx.getImageData(0, 0, canvas.width, canvas.height);
19+
const data = new Uint32Array(imageData.width * imageData.height);
20+
21+
for (let i = 0; i < imageData.data.length; i += 4) {
22+
const r = imageData.data[i];
23+
const g = imageData.data[i + 1];
24+
const b = imageData.data[i + 2];
25+
data[i / 4] = (r << 16) + (g << 8) + b;
26+
}
27+
28+
const rotated = rotSprite(
29+
new Pixels(imageData.width, imageData.height, data),
30+
angle
31+
).pixels;
32+
33+
const pixelData = new Uint8ClampedArray(data.length * 4);
34+
35+
for (let i = 0; i < data.length; i++) {
36+
const value = rotated[i];
37+
38+
pixelData[i * 4] = (value >> 16) & 0xFF;
39+
pixelData[i * 4 + 1] = (value >> 8) & 0xFF;
40+
pixelData[i * 4 + 2] = value & 0xFF;
41+
pixelData[i * 4 + 3] = 255;
42+
}
43+
44+
const imageData2 = new ImageData(pixelData, canvas.width, canvas.height);
45+
46+
canvas.width = spriteCanv.width;
47+
canvas.height = spriteCanv.height;
48+
ctx.putImageData(imageData2, 0, 0);
49+
canvas.style.width = '200px';
50+
canvas.style.imageRendering = 'pixelated';
51+
52+
53+
}, [environment.currentSprite, angle]);
54+
return <>
55+
<canvas ref={canvasRef} />
56+
<input className="slider" type="range" min="0" step="0.01" max="6.5" value={angle} onChange={e => setAngle(e.target.value)} />
57+
</>
58+
}

app/components/sprites/sprite.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import { getBounds } from '#components/mappings/state/bounds';
66
import SVARS from 'sass-variables';
77
import { spriteState } from './state';
88

9-
const { max, min, abs, floor } = Math;
10-
119
export const Sprite = observer(class Sprite extends Component {
1210

1311
render() {
@@ -16,8 +14,6 @@ export const Sprite = observer(class Sprite extends Component {
1614

1715
const { index, mappings, buffer } = this.props.data;
1816

19-
// const scale = min(5, max(floor(100 / spriteState.zoom), 1));
20-
const scale = spriteState.zoom;
2117
return <div
2218
className="sprite"
2319
style={{

app/formats/image.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { writeFile } from 'fs';
44
import { errorMsg } from '#util/dialog';
55
import { colorMatch } from '#components/import/color-match';
66

7-
function exportSprite({ buffer, mappings }) {
7+
export function exportSprite({ buffer, mappings }) {
88

99
const { palettesRGB } = environment;
1010

app/util/rotsprite.js

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// original algorithm Xenowhirl
2+
// stole code from ChaseMor/pxt-arcade-rotsprite
3+
// generated the rest with chatGPT
4+
5+
export default function rotSprite(image, angle) {
6+
image = scale2xImage(image);
7+
image = scale2xImage(image);
8+
image = scale2xImage(image);
9+
image = rotateAndReduceImage(image, angle);
10+
return image;
11+
}
12+
13+
function scale2xImage(original) {
14+
let scaled = Pixels.create(original.width << 1, original.height << 1);
15+
for (let x = 0; x < original.width; x++) {
16+
for (let y = 0; y < original.height; y++) {
17+
const p = original.getPixel(x, y);
18+
const a = original.getPixel(x, y - 1);
19+
const b = original.getPixel(x + 1, y);
20+
const c = original.getPixel(x - 1, y);
21+
const d = original.getPixel(x, y + 1);
22+
if (c == a && c != d && a != b) {
23+
scaled.setPixel(x << 1, y << 1, a);
24+
} else {
25+
scaled.setPixel(x << 1, y << 1, p);
26+
}
27+
if (a == b && a != c && b != d) {
28+
scaled.setPixel((x << 1) + 1, y << 1, b);
29+
} else {
30+
scaled.setPixel((x << 1) + 1, y << 1, p);
31+
}
32+
if (d == c && d != b && c != a) {
33+
scaled.setPixel(x << 1, (y << 1) + 1, c);
34+
} else {
35+
scaled.setPixel(x << 1, (y << 1) + 1, p);
36+
}
37+
if (b == d && b != a && d != c) {
38+
scaled.setPixel((x << 1) + 1, (y << 1) + 1, d);
39+
} else {
40+
scaled.setPixel((x << 1) + 1, (y << 1) + 1, p);
41+
}
42+
}
43+
}
44+
return scaled;
45+
}
46+
function rotateAndReduceImage(original, angle) {
47+
let rotated = Pixels.create(original.width >> 3, original.height >> 3);
48+
49+
const centerX = rotated.width >> 1;
50+
const centerY = rotated.height >> 1;
51+
52+
for (let x = 0; x < rotated.width; x++) {
53+
for (let y = 0; y < rotated.height; y++) {
54+
let dir = Math.atan2(y - centerY, x - centerX);
55+
let mag = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2) << 3;
56+
57+
dir = dir - angle;
58+
59+
let origX = Math.round((centerX << 3) + mag * Math.cos(dir));
60+
let origY = Math.round((centerY << 3) + mag * Math.sin(dir));
61+
62+
if (
63+
origX >= 0 &&
64+
origX < original.width &&
65+
origY >= 0 &&
66+
origY < original.height
67+
) {
68+
rotated.setPixel(x, y, original.getPixel(origX, origY));
69+
}
70+
}
71+
}
72+
73+
return rotated;
74+
}
75+
76+
export class Pixels {
77+
// The width and height of the image in pixels
78+
width;
79+
height;
80+
// A Uint8Array representing the colors of the pixels in the image
81+
pixels;
82+
83+
// Constructor to create a new Image object with the given dimensions
84+
constructor(width, height, pixels) {
85+
this.width = width;
86+
this.height = height;
87+
this.pixels = pixels || new Uint32Array(width * height);
88+
}
89+
90+
// Returns the color of the pixel at the given coordinates
91+
getPixel = (x, y) => {
92+
return this.pixels[y * this.width + x];
93+
};
94+
95+
// Sets the color of the pixel at the given coordinates to the given color
96+
setPixel = (x, y, color) => {
97+
this.pixels[y * this.width + x] = color;
98+
};
99+
100+
// Creates and returns a new Image object with the given dimensions
101+
static create = (width, height, pixels) => {
102+
return new Pixels(width, height, pixels);
103+
};
104+
105+
// Returns a new Image object with the same dimensions and pixel colors as the original image
106+
clone = () => {
107+
const clonedImage = new Pixels(this.width, this.height);
108+
for (let y = 0; y < this.height; y++) {
109+
for (let x = 0; x < this.width; x++) {
110+
clonedImage.setPixel(x, y, this.getPixel(x, y));
111+
}
112+
}
113+
return clonedImage;
114+
};
115+
}

static/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ function createWindow() {
2424

2525
mainWindow.on('ready-to-show', () => {
2626
mainWindow.show();
27-
mainWindow.focus();
27+
if (!devMode) {
28+
mainWindow.focus();
29+
}
2830
});
2931

3032
if (devMode) {

yarn.lock

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@
3030
global-agent "^3.0.0"
3131
global-tunnel-ng "^2.7.1"
3232

33-
"@electron/remote@^2.0.9":
34-
version "2.0.9"
35-
resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-2.0.9.tgz#092ff085407bc907f45b89a72c36faa773ccf2d9"
36-
integrity sha512-LR0W0ID6WAKHaSs0x5LX9aiG+5pFBNAJL6eQAJfGkCuZPUa6nZz+czZLdlTDETG45CgF/0raSvCtYOYUpr6c+A==
33+
"@electron/remote@^1.1.0":
34+
version "1.2.2"
35+
resolved "https://registry.yarnpkg.com/@electron/remote/-/remote-1.2.2.tgz#4c390a2e669df47af973c09eec106162a296c323"
36+
integrity sha512-PfnXpQGWh4vpX866NNucJRnNOzDRZcsLcLaT32fUth9k0hccsohfxprqEDYLzRg+ZK2xRrtyUN5wYYoHimMCJg==
3737

3838
"@esbuild/[email protected]":
3939
version "0.16.4"

0 commit comments

Comments
 (0)