Skip to content

Commit 13f7c8d

Browse files
committed
feat(frontend): gem potpourri RPGold (s) slot and unify size pad options
- Add third Gem preset with gempic/rpgold.png preview - Ronin Pro unify size: horizontal/vertical transparent padding selects Made-with: Cursor
1 parent 5819be4 commit 13f7c8d

4 files changed

Lines changed: 107 additions & 16 deletions

File tree

frontend/public/gempic/rpgold.png

34.8 KB
Loading

frontend/src/components/RoninProUnifySize.tsx

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useState } from 'react'
2-
import { Button, InputNumber, message, Space, Typography, Upload } from 'antd'
2+
import { Button, InputNumber, message, Select, Space, Typography, Upload } from 'antd'
33
import { DownloadOutlined } from '@ant-design/icons'
44
import type { UploadFile, UploadProps } from 'antd'
55
import { useLanguage } from '../i18n/context'
@@ -11,13 +11,26 @@ const { Text } = Typography
1111

1212
const IMAGE_ACCEPT = ['.png', '.jpg', '.jpeg', '.webp']
1313

14+
type UnifyPadH = 'left' | 'center' | 'right'
15+
type UnifyPadV = 'top' | 'center' | 'bottom'
16+
17+
function cellInnerOffset(max: number, img: number, align: 'start' | 'center' | 'end'): number {
18+
if (align === 'start') return 0
19+
if (align === 'center') return Math.floor((max - img) / 2)
20+
return max - img
21+
}
22+
1423
export default function RoninProUnifySize() {
1524
const { t } = useLanguage()
1625
const [files, setFiles] = useState<File[]>([])
1726
const [previewUrls, setPreviewUrls] = useState<string[]>([])
1827
const [loading, setLoading] = useState(false)
1928
const [resultUrl, setResultUrl] = useState<string | null>(null)
2029
const [cols, setCols] = useState(4)
30+
/** 水平:透明补在另一侧(靠左=右扩,靠右=左扩,居中=两侧) */
31+
const [padH, setPadH] = useState<UnifyPadH>('center')
32+
/** 垂直:靠下=默认往上扩透明,靠上=往下扩,居中=上下各半 */
33+
const [padV, setPadV] = useState<UnifyPadV>('bottom')
2134
const [dims, setDims] = useState<{ maxW: number; maxH: number } | null>(null)
2235

2336
useEffect(() => {
@@ -96,14 +109,19 @@ export default function RoninProUnifySize() {
96109
canvas.height = outH
97110
const ctx = canvas.getContext('2d')!
98111
ctx.imageSmoothingEnabled = false
112+
ctx.clearRect(0, 0, outW, outH)
113+
114+
const hAlign: 'start' | 'center' | 'end' =
115+
padH === 'left' ? 'start' : padH === 'right' ? 'end' : 'center'
116+
const vAlign: 'start' | 'center' | 'end' =
117+
padV === 'top' ? 'start' : padV === 'bottom' ? 'end' : 'center'
99118

100119
for (let i = 0; i < imgs.length; i++) {
101120
const img = imgs[i]!
102121
const row = Math.floor(i / c)
103122
const col = i % c
104-
// 宽度居中,高度仅往上扩(图在格子底部)
105-
const dx = col * maxW + Math.floor((maxW - img.naturalWidth) / 2)
106-
const dy = row * maxH + (maxH - img.naturalHeight)
123+
const dx = col * maxW + cellInnerOffset(maxW, img.naturalWidth, hAlign)
124+
const dy = row * maxH + cellInnerOffset(maxH, img.naturalHeight, vAlign)
107125
ctx.drawImage(
108126
img,
109127
0,
@@ -174,17 +192,49 @@ export default function RoninProUnifySize() {
174192

175193
{files.length > 0 && (
176194
<>
177-
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 }}>
178-
<span>
179-
<Text type="secondary">{t('roninProUnifySizeCols')}:</Text>
180-
<InputNumber
181-
min={1}
182-
max={64}
183-
value={cols}
184-
onChange={(v) => setCols(v ?? 4)}
185-
style={{ width: 64, marginLeft: 8 }}
186-
/>
187-
</span>
195+
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
196+
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 }}>
197+
<span>
198+
<Text type="secondary">{t('roninProUnifySizeCols')}:</Text>
199+
<InputNumber
200+
min={1}
201+
max={64}
202+
value={cols}
203+
onChange={(v) => setCols(v ?? 4)}
204+
style={{ width: 64, marginLeft: 8 }}
205+
/>
206+
</span>
207+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
208+
<Text type="secondary">{t('roninProUnifySizePadH')}:</Text>
209+
<Select<UnifyPadH>
210+
value={padH}
211+
onChange={setPadH}
212+
style={{ width: 120 }}
213+
options={[
214+
{ value: 'left', label: t('roninProUnifySizePadHLeft') },
215+
{ value: 'center', label: t('roninProUnifySizePadHCenter') },
216+
{ value: 'right', label: t('roninProUnifySizePadHRight') },
217+
]}
218+
/>
219+
</span>
220+
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
221+
<Text type="secondary">{t('roninProUnifySizePadV')}:</Text>
222+
<Select<UnifyPadV>
223+
value={padV}
224+
onChange={setPadV}
225+
style={{ width: 120 }}
226+
options={[
227+
{ value: 'top', label: t('roninProUnifySizePadVTop') },
228+
{ value: 'center', label: t('roninProUnifySizePadVCenter') },
229+
{ value: 'bottom', label: t('roninProUnifySizePadVBottom') },
230+
]}
231+
/>
232+
</span>
233+
</div>
234+
<Text type="secondary" style={{ fontSize: 12, margin: 0 }}>
235+
{t('roninProUnifySizePadExplain')}
236+
</Text>
237+
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 12 }}>
188238
<Button type="primary" loading={loading} onClick={runUnify}>
189239
{t('roninProUnifySizeRun')}
190240
</Button>
@@ -193,6 +243,7 @@ export default function RoninProUnifySize() {
193243
{t('roninProUnifySizeDownload')}
194244
</Button>
195245
)}
246+
</div>
196247
</div>
197248
{dims && (
198249
<Text type="secondary" style={{ fontSize: 12 }}>

frontend/src/i18n/locales.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const locales: Record<Lang, Record<string, string>> = {
6363
gemPixelPotpourriPageHint: '以下为预留位,后续将在此集中放置预设按钮。',
6464
gemPixelPotpourriTunshiTiandiS: '吞食天地(S)',
6565
gemPixelPotpourriRpgHorseQianmo: 'RPG骑马(潜墨千羽)',
66+
gemPixelPotpourriRpgoldS: 'RPGold(s)',
6667
gemPixelPotpourriSlot: '预留 {n}',
6768
aiPixelAnimalsPageHint: '以下为预留位,后续将接入 Gemini 等外部生成链接。',
6869
aiPixelAnimalsGemDog: '狗',
@@ -123,6 +124,16 @@ export const locales: Record<Lang, Record<string, string>> = {
123124
roninProUnifySizeSuccess: '已合成',
124125
roninProUnifySizeFailed: '合成失败',
125126
roninProUnifySizeSizeHint: '每格 {cellW}×{cellH},总 {outW}×{outH}',
127+
roninProUnifySizePadH: '水平对齐',
128+
roninProUnifySizePadV: '垂直对齐',
129+
roninProUnifySizePadHLeft: '靠左(右侧补透明)',
130+
roninProUnifySizePadHCenter: '居中(两侧补透明)',
131+
roninProUnifySizePadHRight: '靠右(左侧补透明)',
132+
roninProUnifySizePadVTop: '靠上(下方补透明)',
133+
roninProUnifySizePadVCenter: '居中(上下补透明)',
134+
roninProUnifySizePadVBottom: '靠下(上方补透明,默认)',
135+
roninProUnifySizePadExplain:
136+
'原图在格子内贴哪一侧,对侧用透明像素填满。默认与原先一致:水平居中、垂直靠下(透明在上方)。',
126137
roninProDupFrames: '切分后寻找重复帧',
127138
roninProDupFramesCardDesc: '按行列均分整图后,逐格比对像素找出完全相同的帧',
128139
roninProDupFramesHint:
@@ -1010,6 +1021,7 @@ export const locales: Record<Lang, Record<string, string>> = {
10101021
gemPixelPotpourriPageHint: 'Placeholder slots; preset buttons will be added here later.',
10111022
gemPixelPotpourriTunshiTiandiS: 'Destiny of an Emperor S (吞食天地(S))',
10121023
gemPixelPotpourriRpgHorseQianmo: 'RPG horse riding (潜墨千羽)',
1024+
gemPixelPotpourriRpgoldS: 'RPGold (s)',
10131025
gemPixelPotpourriSlot: 'Reserved {n}',
10141026
aiPixelAnimalsPageHint: 'Placeholder slots below; Gemini and other links will be added later.',
10151027
aiPixelAnimalsGemDog: 'Dog',
@@ -1070,6 +1082,16 @@ export const locales: Record<Lang, Record<string, string>> = {
10701082
roninProUnifySizeSuccess: 'Composed',
10711083
roninProUnifySizeFailed: 'Compose failed',
10721084
roninProUnifySizeSizeHint: 'Cell {cellW}×{cellH}, total {outW}×{outH}',
1085+
roninProUnifySizePadH: 'Horizontal',
1086+
roninProUnifySizePadV: 'Vertical',
1087+
roninProUnifySizePadHLeft: 'Left (pad right)',
1088+
roninProUnifySizePadHCenter: 'Center (pad both sides)',
1089+
roninProUnifySizePadHRight: 'Right (pad left)',
1090+
roninProUnifySizePadVTop: 'Top (pad below)',
1091+
roninProUnifySizePadVCenter: 'Center (pad top & bottom)',
1092+
roninProUnifySizePadVBottom: 'Bottom (pad above, default)',
1093+
roninProUnifySizePadExplain:
1094+
'Choose which side of the cell the image sits on; the rest is filled with transparency. Default matches before: horizontal center, vertical bottom (padding above).',
10731095
roninProDupFrames: 'Find duplicate frames after split',
10741096
roninProDupFramesCardDesc: 'Split into a grid, then find frames with identical pixels',
10751097
roninProDupFramesHint:
@@ -1954,6 +1976,7 @@ export const locales: Record<Lang, Record<string, string>> = {
19541976
gemPixelPotpourriPageHint: '予定地。後日ここにプリセットボタンを配置します。',
19551977
gemPixelPotpourriTunshiTiandiS: '天地を喰らう S(吞食天地(S))',
19561978
gemPixelPotpourriRpgHorseQianmo: 'RPG騎馬(潜墨千羽)',
1979+
gemPixelPotpourriRpgoldS: 'RPGold(s)',
19571980
gemPixelPotpourriSlot: '予約 {n}',
19581981
aiPixelAnimalsPageHint: '下はプレースホルダーです。後日 Gemini など外部リンクを接続します。',
19591982
aiPixelAnimalsGemDog: '犬',
@@ -2014,6 +2037,16 @@ export const locales: Record<Lang, Record<string, string>> = {
20142037
roninProUnifySizeSuccess: '合成完了',
20152038
roninProUnifySizeFailed: '合成失敗',
20162039
roninProUnifySizeSizeHint: 'セル {cellW}×{cellH}、合計 {outW}×{outH}',
2040+
roninProUnifySizePadH: '水平',
2041+
roninProUnifySizePadV: '垂直',
2042+
roninProUnifySizePadHLeft: '左寄せ(右に透明)',
2043+
roninProUnifySizePadHCenter: '中央(左右に透明)',
2044+
roninProUnifySizePadHRight: '右寄せ(左に透明)',
2045+
roninProUnifySizePadVTop: '上寄せ(下に透明)',
2046+
roninProUnifySizePadVCenter: '中央(上下に透明)',
2047+
roninProUnifySizePadVBottom: '下寄せ(上に透明・既定)',
2048+
roninProUnifySizePadExplain:
2049+
'セル内で画像をどの辺に寄せるか、反対側を透明で埋めます。既定は従来どおり:水平中央・下寄せ(上に透明)。',
20172050
roninProDupFrames: '分割後の重複フレーム検出',
20182051
roninProDupFramesCardDesc: 'グリッド均等分割後、ピクセルが完全一致するコマを検出',
20192052
roninProDupFramesHint:

frontend/src/lib/gemPixelUrls.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ export const GEM_PIXEL_TUNSHITIANDI_S_URL =
4646
/** 生成大杂烩 · RPG骑马(潜墨千羽) */
4747
export const GEM_PIXEL_RPG_HORSE_QIANMO_URL =
4848
'https://gemini.google.com/gem/901be39a347f?usp=sharing'
49+
/** 生成大杂烩 · RPGold(s) */
50+
export const GEM_PIXEL_RPGOLD_S_URL =
51+
'https://gemini.google.com/gem/1XeOiyNUE7oGZBn9rk4Xlk4fEzDGwIbAL?usp=sharing'
4952

5053
export interface GemPixelPotpourriItem {
5154
url: string
@@ -66,7 +69,11 @@ export const GEM_PIXEL_POTPOURRI_HUB_SLOTS: (GemPixelPotpourriItem | null)[] = [
6669
labelKey: 'gemPixelPotpourriRpgHorseQianmo',
6770
previewPublicPath: 'gempic/riderrpg.png',
6871
},
69-
null,
72+
{
73+
url: GEM_PIXEL_RPGOLD_S_URL,
74+
labelKey: 'gemPixelPotpourriRpgoldS',
75+
previewPublicPath: 'gempic/rpgold.png',
76+
},
7077
null,
7178
null,
7279
null,

0 commit comments

Comments
 (0)