Fraser commited on
Commit
213c234
·
1 Parent(s): 20436ef

export/import

Browse files
public/assets/snap_logo.png ADDED

Git LFS Details

  • SHA256: 9a9fe8fdf4979a2b1fdcdcf1868ecaf7fe7a3c5a3a3fedcaa381f1876ca9aa8c
  • Pointer size: 131 Bytes
  • Size of remote file: 217 kB
src/lib/components/MonsterGenerator/MonsterGenerator.svelte CHANGED
@@ -1,10 +1,13 @@
1
  <script lang="ts">
2
  import type { MonsterGeneratorProps, MonsterWorkflowState, CaptionType, CaptionLength, MonsterStats } from '$lib/types';
 
3
  import UploadStep from './UploadStep.svelte';
4
  import WorkflowProgress from './WorkflowProgress.svelte';
5
  import MonsterResult from './MonsterResult.svelte';
6
  import { makeWhiteTransparent } from '$lib/utils/imageProcessing';
7
  import { saveMonster } from '$lib/db/monsters';
 
 
8
 
9
  interface Props extends MonsterGeneratorProps {}
10
 
@@ -79,6 +82,32 @@ Remember to base the stats on how unique/powerful the original object was. Commo
79
 
80
  Write your response within \`\`\`json\`\`\``;
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  async function handleImageSelected(file: File) {
83
  if (!joyCaptionClient || !rwkvClient || !fluxClient) {
84
  state.error = "Services not connected. Please wait...";
@@ -87,7 +116,16 @@ Write your response within \`\`\`json\`\`\``;
87
 
88
  state.userImage = file;
89
  state.error = null;
90
- startWorkflow();
 
 
 
 
 
 
 
 
 
91
  }
92
 
93
  async function startWorkflow() {
 
1
  <script lang="ts">
2
  import type { MonsterGeneratorProps, MonsterWorkflowState, CaptionType, CaptionLength, MonsterStats } from '$lib/types';
3
+ import type { PicletInstance } from '$lib/db/schema';
4
  import UploadStep from './UploadStep.svelte';
5
  import WorkflowProgress from './WorkflowProgress.svelte';
6
  import MonsterResult from './MonsterResult.svelte';
7
  import { makeWhiteTransparent } from '$lib/utils/imageProcessing';
8
  import { saveMonster } from '$lib/db/monsters';
9
+ import { extractPicletMetadata } from '$lib/services/picletMetadata';
10
+ import { savePicletInstance } from '$lib/db/piclets';
11
 
12
  interface Props extends MonsterGeneratorProps {}
13
 
 
82
 
83
  Write your response within \`\`\`json\`\`\``;
84
 
85
+ async function importPiclet(picletData: PicletInstance) {
86
+ state.isProcessing = true;
87
+ state.currentStep = 'complete';
88
+
89
+ try {
90
+ // Save the imported piclet
91
+ const savedId = await savePicletInstance(picletData);
92
+
93
+ // Create a success state similar to generation
94
+ state.monsterImage = {
95
+ imageUrl: picletData.imageUrl,
96
+ imageData: picletData.imageData
97
+ };
98
+
99
+ // Show import success
100
+ state.isProcessing = false;
101
+ alert(`Successfully imported ${picletData.nickname || picletData.typeId}!`);
102
+
103
+ // Reset to allow another import/generation
104
+ setTimeout(() => reset(), 2000);
105
+ } catch (error) {
106
+ state.error = `Failed to import piclet: ${error}`;
107
+ state.isProcessing = false;
108
+ }
109
+ }
110
+
111
  async function handleImageSelected(file: File) {
112
  if (!joyCaptionClient || !rwkvClient || !fluxClient) {
113
  state.error = "Services not connected. Please wait...";
 
116
 
117
  state.userImage = file;
118
  state.error = null;
119
+
120
+ // Check if this is a piclet card with metadata
121
+ const picletData = await extractPicletMetadata(file);
122
+ if (picletData) {
123
+ // Import existing piclet
124
+ await importPiclet(picletData);
125
+ } else {
126
+ // Generate new piclet
127
+ startWorkflow();
128
+ }
129
  }
130
 
131
  async function startWorkflow() {
src/lib/components/Piclets/PicletDetail.svelte CHANGED
@@ -3,6 +3,7 @@
3
  import type { PicletInstance } from '$lib/db/schema';
4
  import { deletePicletInstance } from '$lib/db/piclets';
5
  import { uiStore } from '$lib/stores/ui';
 
6
 
7
  interface Props {
8
  instance: PicletInstance;
@@ -14,6 +15,7 @@
14
  let showDeleteConfirm = $state(false);
15
  let selectedTab = $state<'about' | 'stats' | 'actions'>('about');
16
  let expandedMoves = $state(new Set<number>());
 
17
 
18
  onMount(() => {
19
  uiStore.openDetailPage();
@@ -53,6 +55,18 @@
53
  }
54
  expandedMoves = new Set(expandedMoves);
55
  }
 
 
 
 
 
 
 
 
 
 
 
 
56
  </script>
57
 
58
  <div class="detail-page">
@@ -228,6 +242,9 @@
228
  <button class="btn btn-danger" onclick={handleDelete}>Yes, Release</button>
229
  <button class="btn btn-secondary" onclick={() => showDeleteConfirm = false}>Cancel</button>
230
  {:else}
 
 
 
231
  <button class="btn btn-danger" onclick={() => showDeleteConfirm = true}>Release Piclet</button>
232
  {/if}
233
  </div>
@@ -587,6 +604,18 @@
587
  transform: scale(0.95);
588
  }
589
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  .btn-danger {
591
  background: #ff3b30;
592
  color: white;
 
3
  import type { PicletInstance } from '$lib/db/schema';
4
  import { deletePicletInstance } from '$lib/db/piclets';
5
  import { uiStore } from '$lib/stores/ui';
6
+ import { downloadPicletCard } from '$lib/services/picletExport';
7
 
8
  interface Props {
9
  instance: PicletInstance;
 
15
  let showDeleteConfirm = $state(false);
16
  let selectedTab = $state<'about' | 'stats' | 'actions'>('about');
17
  let expandedMoves = $state(new Set<number>());
18
+ let isSharing = $state(false);
19
 
20
  onMount(() => {
21
  uiStore.openDetailPage();
 
55
  }
56
  expandedMoves = new Set(expandedMoves);
57
  }
58
+
59
+ async function handleShare() {
60
+ isSharing = true;
61
+ try {
62
+ await downloadPicletCard(instance);
63
+ } catch (err) {
64
+ console.error('Failed to share piclet:', err);
65
+ alert('Failed to create shareable image');
66
+ } finally {
67
+ isSharing = false;
68
+ }
69
+ }
70
  </script>
71
 
72
  <div class="detail-page">
 
242
  <button class="btn btn-danger" onclick={handleDelete}>Yes, Release</button>
243
  <button class="btn btn-secondary" onclick={() => showDeleteConfirm = false}>Cancel</button>
244
  {:else}
245
+ <button class="btn btn-primary" onclick={handleShare} disabled={isSharing}>
246
+ {isSharing ? 'Creating...' : 'Share Piclet'}
247
+ </button>
248
  <button class="btn btn-danger" onclick={() => showDeleteConfirm = true}>Release Piclet</button>
249
  {/if}
250
  </div>
 
604
  transform: scale(0.95);
605
  }
606
 
607
+ .btn-primary {
608
+ background: #007bff;
609
+ color: white;
610
+ width: 100%;
611
+ margin-bottom: 8px;
612
+ }
613
+
614
+ .btn-primary:disabled {
615
+ opacity: 0.7;
616
+ cursor: not-allowed;
617
+ }
618
+
619
  .btn-danger {
620
  background: #ff3b30;
621
  color: white;
src/lib/services/picletExport.ts ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PicletInstance } from '$lib/db/schema';
2
+ import { embedPicletMetadata } from './picletMetadata';
3
+
4
+ /**
5
+ * Generates a shareable image of a piclet with embedded metadata
6
+ */
7
+ export async function generateShareableImage(piclet: PicletInstance): Promise<Blob> {
8
+ // Create canvas
9
+ const canvas = document.createElement('canvas');
10
+ const ctx = canvas.getContext('2d');
11
+ if (!ctx) throw new Error('Could not create canvas context');
12
+
13
+ // Set canvas size (square format for social media sharing)
14
+ const canvasSize = 800;
15
+ canvas.width = canvasSize;
16
+ canvas.height = canvasSize;
17
+
18
+ // Fill background with a gradient
19
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvasSize);
20
+ gradient.addColorStop(0, '#87CEEB'); // Sky blue
21
+ gradient.addColorStop(0.6, '#98D98E'); // Light green
22
+ gradient.addColorStop(1, '#567d46'); // Dark green
23
+ ctx.fillStyle = gradient;
24
+ ctx.fillRect(0, 0, canvasSize, canvasSize);
25
+
26
+ // Load and draw grass platform
27
+ const grassImg = await loadImage('/assets/grass.PNG');
28
+ const platformSize = 400;
29
+ const platformX = (canvasSize - platformSize) / 2;
30
+ const platformY = canvasSize - platformSize + 100;
31
+ ctx.drawImage(grassImg, platformX, platformY, platformSize, platformSize);
32
+
33
+ // Load and draw piclet
34
+ const picletImg = await loadImage(piclet.imageData || piclet.imageUrl);
35
+ const picletSize = 300;
36
+ const picletX = (canvasSize - picletSize) / 2;
37
+ const picletY = platformY - picletSize + 100;
38
+ ctx.drawImage(picletImg, picletX, picletY, picletSize, picletSize);
39
+
40
+ // Add piclet info text
41
+ ctx.fillStyle = 'white';
42
+ ctx.strokeStyle = 'black';
43
+ ctx.lineWidth = 4;
44
+ ctx.font = 'bold 48px Arial';
45
+ ctx.textAlign = 'center';
46
+
47
+ const nameText = piclet.nickname || piclet.typeId;
48
+ const levelText = `Lv.${piclet.level}`;
49
+
50
+ // Draw name with outline
51
+ ctx.strokeText(nameText, canvasSize / 2, 100);
52
+ ctx.fillText(nameText, canvasSize / 2, 100);
53
+
54
+ // Draw level with outline
55
+ ctx.font = 'bold 36px Arial';
56
+ ctx.strokeText(levelText, canvasSize / 2, 150);
57
+ ctx.fillText(levelText, canvasSize / 2, 150);
58
+
59
+ // Load and draw watermark
60
+ const logoImg = await loadImage('/assets/snap_logo.png');
61
+ const logoSize = 120;
62
+ ctx.globalAlpha = 0.7; // Semi-transparent
63
+ ctx.drawImage(logoImg, canvasSize - logoSize - 20, canvasSize - logoSize - 20, logoSize, logoSize);
64
+ ctx.globalAlpha = 1.0;
65
+
66
+ // Get the image as blob
67
+ const blob = await canvasToBlob(canvas);
68
+
69
+ // Embed metadata in the blob
70
+ return embedPicletMetadata(blob, piclet);
71
+ }
72
+
73
+ /**
74
+ * Downloads a piclet card image
75
+ */
76
+ export async function downloadPicletCard(piclet: PicletInstance, filename?: string): Promise<void> {
77
+ const blob = await generateShareableImage(piclet);
78
+ const url = URL.createObjectURL(blob);
79
+
80
+ const a = document.createElement('a');
81
+ a.href = url;
82
+ a.download = filename || `Piclet_${piclet.nickname || piclet.typeId}_Lv${piclet.level}.png`;
83
+ document.body.appendChild(a);
84
+ a.click();
85
+ document.body.removeChild(a);
86
+
87
+ URL.revokeObjectURL(url);
88
+ }
89
+
90
+ /**
91
+ * Helper to load an image
92
+ */
93
+ function loadImage(src: string): Promise<HTMLImageElement> {
94
+ return new Promise((resolve, reject) => {
95
+ const img = new Image();
96
+ img.crossOrigin = 'anonymous';
97
+ img.onload = () => resolve(img);
98
+ img.onerror = reject;
99
+ img.src = src;
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Convert canvas to blob
105
+ */
106
+ function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
107
+ return new Promise((resolve, reject) => {
108
+ canvas.toBlob((blob) => {
109
+ if (blob) resolve(blob);
110
+ else reject(new Error('Failed to create blob'));
111
+ }, 'image/png');
112
+ });
113
+ }
114
+
src/lib/services/picletMetadata.ts ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { PicletInstance } from '$lib/db/schema';
2
+
3
+ const METADATA_KEY = 'snaplings-piclet-v1';
4
+
5
+ interface PicletMetadata {
6
+ version: 1;
7
+ data: Omit<PicletInstance, 'id' | 'rosterPosition' | 'isInRoster' | 'caughtAt'>;
8
+ checksum?: string;
9
+ }
10
+
11
+ /**
12
+ * Extract piclet metadata from a PNG image
13
+ */
14
+ export async function extractPicletMetadata(file: File): Promise<PicletInstance | null> {
15
+ try {
16
+ const arrayBuffer = await file.arrayBuffer();
17
+ const bytes = new Uint8Array(arrayBuffer);
18
+
19
+ // Check PNG signature
20
+ if (!isPNG(bytes)) {
21
+ return null;
22
+ }
23
+
24
+ // Find tEXt chunks
25
+ const chunks = parsePNGChunks(bytes);
26
+ const textChunk = chunks.find(chunk =>
27
+ chunk.type === 'tEXt' &&
28
+ chunk.keyword === METADATA_KEY
29
+ );
30
+
31
+ if (!textChunk || !textChunk.text) {
32
+ return null;
33
+ }
34
+
35
+ // Parse metadata
36
+ const metadata: PicletMetadata = JSON.parse(textChunk.text);
37
+
38
+ // Validate version
39
+ if (metadata.version !== 1) {
40
+ console.warn('Unsupported piclet metadata version:', metadata.version);
41
+ return null;
42
+ }
43
+
44
+ // Create PicletInstance from metadata
45
+ const piclet: PicletInstance = {
46
+ ...metadata.data,
47
+ caughtAt: new Date(), // Use current date for import
48
+ isInRoster: false,
49
+ rosterPosition: undefined
50
+ };
51
+
52
+ return piclet;
53
+ } catch (error) {
54
+ console.error('Failed to extract piclet metadata:', error);
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Embed piclet metadata into a PNG image
61
+ */
62
+ export async function embedPicletMetadata(imageBlob: Blob, piclet: PicletInstance): Promise<Blob> {
63
+ const arrayBuffer = await imageBlob.arrayBuffer();
64
+ const bytes = new Uint8Array(arrayBuffer);
65
+
66
+ // Prepare metadata
67
+ const metadata: PicletMetadata = {
68
+ version: 1,
69
+ data: {
70
+ typeId: piclet.typeId,
71
+ nickname: piclet.nickname,
72
+ primaryTypeString: piclet.primaryTypeString,
73
+ secondaryTypeString: piclet.secondaryTypeString,
74
+ currentHp: piclet.maxHp, // Reset to full HP for sharing
75
+ maxHp: piclet.maxHp,
76
+ level: piclet.level,
77
+ xp: piclet.xp,
78
+ attack: piclet.attack,
79
+ defense: piclet.defense,
80
+ fieldAttack: piclet.fieldAttack,
81
+ fieldDefense: piclet.fieldDefense,
82
+ speed: piclet.speed,
83
+ baseHp: piclet.baseHp,
84
+ baseAttack: piclet.baseAttack,
85
+ baseDefense: piclet.baseDefense,
86
+ baseFieldAttack: piclet.baseFieldAttack,
87
+ baseFieldDefense: piclet.baseFieldDefense,
88
+ baseSpeed: piclet.baseSpeed,
89
+ moves: piclet.moves,
90
+ nature: piclet.nature,
91
+ bst: piclet.bst,
92
+ tier: piclet.tier,
93
+ role: piclet.role,
94
+ variance: piclet.variance,
95
+ imageUrl: piclet.imageUrl,
96
+ imageData: piclet.imageData,
97
+ imageCaption: piclet.imageCaption,
98
+ concept: piclet.concept,
99
+ imagePrompt: piclet.imagePrompt
100
+ }
101
+ };
102
+
103
+ // Create tEXt chunk
104
+ const textChunk = createTextChunk(METADATA_KEY, JSON.stringify(metadata));
105
+
106
+ // Insert chunk after IHDR
107
+ const newBytes = insertChunkAfterIHDR(bytes, textChunk);
108
+
109
+ return new Blob([newBytes], { type: 'image/png' });
110
+ }
111
+
112
+ /**
113
+ * Check if bytes represent a PNG file
114
+ */
115
+ function isPNG(bytes: Uint8Array): boolean {
116
+ const pngSignature = [137, 80, 78, 71, 13, 10, 26, 10];
117
+ if (bytes.length < 8) return false;
118
+
119
+ for (let i = 0; i < 8; i++) {
120
+ if (bytes[i] !== pngSignature[i]) return false;
121
+ }
122
+
123
+ return true;
124
+ }
125
+
126
+ /**
127
+ * Parse PNG chunks
128
+ */
129
+ function parsePNGChunks(bytes: Uint8Array): any[] {
130
+ const chunks = [];
131
+ let pos = 8; // Skip PNG signature
132
+
133
+ while (pos < bytes.length) {
134
+ // Read chunk length
135
+ const length = readUInt32BE(bytes, pos);
136
+ pos += 4;
137
+
138
+ // Read chunk type
139
+ const type = String.fromCharCode(...bytes.slice(pos, pos + 4));
140
+ pos += 4;
141
+
142
+ // Read chunk data
143
+ const data = bytes.slice(pos, pos + length);
144
+ pos += length;
145
+
146
+ // Skip CRC
147
+ pos += 4;
148
+
149
+ // Parse tEXt chunks
150
+ if (type === 'tEXt') {
151
+ const nullIndex = data.indexOf(0);
152
+ if (nullIndex !== -1) {
153
+ const keyword = String.fromCharCode(...data.slice(0, nullIndex));
154
+ const text = String.fromCharCode(...data.slice(nullIndex + 1));
155
+ chunks.push({ type, keyword, text });
156
+ }
157
+ } else {
158
+ chunks.push({ type, data });
159
+ }
160
+
161
+ if (type === 'IEND') break;
162
+ }
163
+
164
+ return chunks;
165
+ }
166
+
167
+ /**
168
+ * Create a tEXt chunk
169
+ */
170
+ function createTextChunk(keyword: string, text: string): Uint8Array {
171
+ const keywordBytes = new TextEncoder().encode(keyword);
172
+ const textBytes = new TextEncoder().encode(text);
173
+
174
+ // Create chunk data: keyword + null + text
175
+ const data = new Uint8Array(keywordBytes.length + 1 + textBytes.length);
176
+ data.set(keywordBytes);
177
+ data[keywordBytes.length] = 0; // null separator
178
+ data.set(textBytes, keywordBytes.length + 1);
179
+
180
+ // Create full chunk: length + type + data + crc
181
+ const chunk = new Uint8Array(4 + 4 + data.length + 4);
182
+
183
+ // Length
184
+ writeUInt32BE(chunk, 0, data.length);
185
+
186
+ // Type: 'tEXt'
187
+ chunk[4] = 116; // t
188
+ chunk[5] = 69; // E
189
+ chunk[6] = 88; // X
190
+ chunk[7] = 116; // t
191
+
192
+ // Data
193
+ chunk.set(data, 8);
194
+
195
+ // CRC
196
+ const crc = calculateCRC(chunk.slice(4, 8 + data.length));
197
+ writeUInt32BE(chunk, 8 + data.length, crc);
198
+
199
+ return chunk;
200
+ }
201
+
202
+ /**
203
+ * Insert chunk after IHDR
204
+ */
205
+ function insertChunkAfterIHDR(bytes: Uint8Array, newChunk: Uint8Array): Uint8Array {
206
+ // Find IHDR chunk end
207
+ let ihdrEnd = 8; // PNG signature
208
+ ihdrEnd += 4; // IHDR length
209
+ ihdrEnd += 4; // IHDR type
210
+ const ihdrLength = readUInt32BE(bytes, 8);
211
+ ihdrEnd += ihdrLength; // IHDR data
212
+ ihdrEnd += 4; // IHDR CRC
213
+
214
+ // Create new array
215
+ const result = new Uint8Array(bytes.length + newChunk.length);
216
+
217
+ // Copy up to IHDR end
218
+ result.set(bytes.slice(0, ihdrEnd));
219
+
220
+ // Insert new chunk
221
+ result.set(newChunk, ihdrEnd);
222
+
223
+ // Copy rest
224
+ result.set(bytes.slice(ihdrEnd), ihdrEnd + newChunk.length);
225
+
226
+ return result;
227
+ }
228
+
229
+ /**
230
+ * Read 32-bit unsigned integer (big endian)
231
+ */
232
+ function readUInt32BE(bytes: Uint8Array, offset: number): number {
233
+ return (bytes[offset] << 24) |
234
+ (bytes[offset + 1] << 16) |
235
+ (bytes[offset + 2] << 8) |
236
+ bytes[offset + 3];
237
+ }
238
+
239
+ /**
240
+ * Write 32-bit unsigned integer (big endian)
241
+ */
242
+ function writeUInt32BE(bytes: Uint8Array, offset: number, value: number): void {
243
+ bytes[offset] = (value >>> 24) & 0xff;
244
+ bytes[offset + 1] = (value >>> 16) & 0xff;
245
+ bytes[offset + 2] = (value >>> 8) & 0xff;
246
+ bytes[offset + 3] = value & 0xff;
247
+ }
248
+
249
+ /**
250
+ * Calculate CRC32 for PNG chunk
251
+ */
252
+ function calculateCRC(bytes: Uint8Array): number {
253
+ const crcTable = getCRCTable();
254
+ let crc = 0xffffffff;
255
+
256
+ for (let i = 0; i < bytes.length; i++) {
257
+ crc = crcTable[(crc ^ bytes[i]) & 0xff] ^ (crc >>> 8);
258
+ }
259
+
260
+ return crc ^ 0xffffffff;
261
+ }
262
+
263
+ /**
264
+ * Get CRC table (cached)
265
+ */
266
+ let crcTable: Uint32Array | null = null;
267
+ function getCRCTable(): Uint32Array {
268
+ if (crcTable) return crcTable;
269
+
270
+ crcTable = new Uint32Array(256);
271
+ for (let i = 0; i < 256; i++) {
272
+ let c = i;
273
+ for (let j = 0; j < 8; j++) {
274
+ c = (c & 1) ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
275
+ }
276
+ crcTable[i] = c;
277
+ }
278
+
279
+ return crcTable;
280
+ }