@@ -187,34 +187,55 @@ <h1 class="text-center mb-4" data-text="title">Animation Frame Combiner</h1>
187
187
try {
188
188
const zip = await JSZip . loadAsync ( file ) ;
189
189
const imageFiles = { } ;
190
+ const tempGroups = { } ;
190
191
191
- // First pass: Group files by animation name
192
+ // First pass: Collect all PNG files and their potential base names
192
193
for ( const [ filename , zipEntry ] of Object . entries ( zip . files ) ) {
193
194
if ( ! filename . endsWith ( '.png' ) ) continue ;
194
195
195
- const animName = filename . replace ( / \d + \. p n g $ / , '' ) ;
196
- if ( ! imageFiles [ animName ] ) {
197
- imageFiles [ animName ] = [ ] ;
196
+ // Extract name without extension
197
+ const nameWithoutExt = filename . slice ( 0 , - 4 ) ;
198
+
199
+ // Try to find a number at the end
200
+ const numberMatch = nameWithoutExt . match ( / ^ ( .* ?) ( \d + ) ? $ / ) ;
201
+ if ( ! numberMatch ) continue ;
202
+
203
+ const [ _ , baseName , frameNumber ] = numberMatch ;
204
+ const normalizedBaseName = baseName . endsWith ( '_' ) || baseName . endsWith ( '-' )
205
+ ? baseName . slice ( 0 , - 1 )
206
+ : baseName ;
207
+
208
+ // Store in temporary groups to help identify base names
209
+ if ( ! tempGroups [ normalizedBaseName ] ) {
210
+ tempGroups [ normalizedBaseName ] = [ ] ;
198
211
}
199
- imageFiles [ animName ] . push ( {
212
+
213
+ tempGroups [ normalizedBaseName ] . push ( {
200
214
name : filename ,
215
+ frameNumber : frameNumber ? parseInt ( frameNumber ) : 0 ,
201
216
entry : zipEntry
202
217
} ) ;
203
218
}
204
219
220
+ // Second pass: Process groups and create final imageFiles object
221
+ for ( const [ baseName , files ] of Object . entries ( tempGroups ) ) {
222
+ // If there's only one file and it has no number, skip grouping
223
+ if ( files . length === 1 && files [ 0 ] . frameNumber === 0 ) {
224
+ continue ;
225
+ }
226
+
227
+ imageFiles [ baseName ] = files ;
228
+ }
229
+
205
230
const newZip = new JSZip ( ) ;
206
231
let processedCount = 0 ;
207
232
const totalAnimations = Object . keys ( imageFiles ) . length ;
208
233
209
234
for ( const [ animName , files ] of Object . entries ( imageFiles ) ) {
210
235
statusText . textContent = getText ( 'processingAnimation' ) + animName ;
211
236
212
- // Sort files numerically
213
- files . sort ( ( a , b ) => {
214
- const numA = parseInt ( a . name . match ( / \d + / g) . pop ( ) ) ;
215
- const numB = parseInt ( b . name . match ( / \d + / g) . pop ( ) ) ;
216
- return numA - numB ;
217
- } ) ;
237
+ // Sort files by frame number
238
+ files . sort ( ( a , b ) => a . frameNumber - b . frameNumber ) ;
218
239
219
240
// Load all images first
220
241
const images = await Promise . all ( files . map ( async file => {
@@ -252,10 +273,6 @@ <h1 class="text-center mb-4" data-text="title">Animation Frame Combiner</h1>
252
273
img . width ,
253
274
img . height
254
275
) ;
255
-
256
- // Optional: Draw frame boundary for debugging
257
- // ctx.strokeStyle = '#ff0000';
258
- // ctx.strokeRect(x, 0, maxWidth, maxHeight);
259
276
} ) ;
260
277
261
278
const blob = await new Promise ( resolve => canvas . toBlob ( resolve ) ) ;
@@ -294,4 +311,9 @@ <h1 class="text-center mb-4" data-text="title">Animation Frame Combiner</h1>
294
311
updateLanguage ( 'en' ) ;
295
312
</ script >
296
313
</ body >
314
+ </ html >
315
+
316
+ updateLanguage('en');
317
+ </ script>
318
+ </ body>
297
319
</ html>
0 commit comments