dodey917 commited on
Commit
6159e33
·
verified ·
1 Parent(s): 6e998f5

good use the OpenAI’s DALL·E / gpt-image-1

Browse files

Generate a high-quality, realistic image of a plain unisex T-shirt on a clean, neutral background or worn by a gender-neutral model; the T-shirt must have soft lighting, natural folds, and visible fabric texture; apply the uploaded image as the shirt design and make it look naturally printed on the fabric by following the shirt’s wrinkles, lighting, and shadows; blend the design seamlessly so it appears part of the fabric, not pasted; preserve realistic shadows, highlights, and depth; the shirt color should be editable through the API to any selected color for user customization.


here is the API key: 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

Files changed (3) hide show
  1. .env.example +14 -0
  2. api/mockup-generator.js +152 -0
  3. meme.html +137 -43
.env.example ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```
2
+ # OpenAI API Configuration
3
+ OPENAI_API_KEY=your_openai_api_key_here
4
+
5
+ # Server Configuration
6
+ PORT=3000
7
+ NODE_ENV=production
8
+
9
+ # File Upload Configuration
10
+ MAX_FILE_SIZE=10485760
11
+ UPLOAD_DIR=uploads
12
+ ```
13
+
14
+ Finally, let me update the meme.html to better handle the new functionality:
api/mockup-generator.js ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // This is a Node.js backend file for secure API handling
2
+ // Place this in your backend server, not in the frontend
3
+
4
+ const express = require('express');
5
+ const { Configuration, OpenAIApi } = require('openai');
6
+ const multer = require('multer');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+
10
+ const router = express.Router();
11
+
12
+ // Configure OpenAI - IMPORTANT: Store this in environment variables
13
+ const configuration = new Configuration({
14
+ apiKey: process.env.OPENAI_API_KEY, // Never expose API keys in frontend
15
+ });
16
+
17
+ const openai = new OpenAIApi(configuration);
18
+
19
+ // Configure multer for file uploads
20
+ const upload = multer({
21
+ dest: 'uploads/',
22
+ limits: {
23
+ fileSize: 10 * 1024 * 1024, // 10MB limit
24
+ },
25
+ fileFilter: (req, file, cb) => {
26
+ if (file.mimetype.startsWith('image/')) {
27
+ cb(null, true);
28
+ } else {
29
+ cb(new Error('Only image files are allowed'));
30
+ }
31
+ }
32
+ });
33
+
34
+ // Generate realistic T-shirt mockup endpoint
35
+ router.post('/generate-mockup', upload.single('image'), async (req, res) => {
36
+ try {
37
+ const { caption, shirtColor = 'white' } = req.body;
38
+ const imagePath = req.file ? req.file.path : null;
39
+
40
+ if (!imagePath && !req.body.image) {
41
+ return res.status(400).json({
42
+ success: false,
43
+ error: 'No image provided'
44
+ });
45
+ }
46
+
47
+ let base64Image = req.body.image;
48
+
49
+ // If file was uploaded, convert to base64
50
+ if (imagePath) {
51
+ const imageBuffer = fs.readFileSync(imagePath);
52
+ base64Image = imageBuffer.toString('base64');
53
+ // Clean up uploaded file
54
+ fs.unlinkSync(imagePath);
55
+ }
56
+
57
+ // Construct the detailed prompt for DALL-E
58
+ const prompt = `
59
+ Generate a high-quality, realistic photograph of a plain unisex T-shirt on a clean, neutral background.
60
+ The T-shirt must:
61
+ - Be ${shirtColor} colored
62
+ - Have soft, natural lighting with visible fabric texture
63
+ - Show natural folds and wrinkles in the fabric
64
+ - Have the following design seamlessly printed on it: "${caption}"
65
+ - The printed design should follow the shirt's wrinkles, lighting, and shadows naturally
66
+ - Blend the design so it appears part of the fabric, not pasted on
67
+ - Preserve realistic shadows, highlights, and depth
68
+ - Style: photorealistic, commercial product photography, high detail
69
+ `;
70
+
71
+ // Call DALL-E API
72
+ const response = await openai.createImage({
73
+ model: "dall-e-3",
74
+ prompt: prompt,
75
+ n: 1,
76
+ size: "1024x1024",
77
+ quality: "hd",
78
+ response_format: "url",
79
+ });
80
+
81
+ const imageUrl = response.data.data[0].url;
82
+
83
+ res.json({
84
+ success: true,
85
+ imageUrl: imageUrl,
86
+ message: 'Mockup generated successfully'
87
+ });
88
+
89
+ } catch (error) {
90
+ console.error('Mockup generation error:', error);
91
+ res.status(500).json({
92
+ success: false,
93
+ error: 'Failed to generate mockup: ' + error.message
94
+ });
95
+ }
96
+ });
97
+
98
+ // Alternative endpoint for when image is sent as base64
99
+ router.post('/generate-mockup-base64', async (req, res) => {
100
+ try {
101
+ const { image, caption, shirtColor = 'white' } = req.body;
102
+
103
+ if (!image) {
104
+ return res.status(400).json({
105
+ success: false,
106
+ error: 'No image provided'
107
+ });
108
+ }
109
+
110
+ // Same DALL-E prompt as above
111
+ const prompt = `
112
+ Generate a high-quality, realistic photograph of a plain unisex T-shirt on a clean, neutral background.
113
+ The T-shirt must:
114
+ - Be ${shirtColor} colored
115
+ - Have soft, natural lighting with visible fabric texture
116
+ - Show natural folds and wrinkles in the fabric
117
+ - Have the user's uploaded image seamlessly printed on the chest area
118
+ - The printed design should follow the shirt's wrinkles, lighting, and shadows naturally
119
+ - Blend the design so it appears part of the fabric, not pasted on
120
+ - Preserve realistic shadows, highlights, and depth
121
+ - Include the text "${caption}" as part of the design if appropriate
122
+ - Style: photorealistic, commercial product photography, high detail
123
+ `;
124
+
125
+ // Call DALL-E API with image editing capability
126
+ const response = await openai.createImageEdit(
127
+ // Convert base64 to file buffer for API
128
+ Buffer.from(image.split(',')[1], 'base64'),
129
+ prompt,
130
+ null, // No mask needed for full image
131
+ 1,
132
+ "1024x1024"
133
+ );
134
+
135
+ const imageUrl = response.data.data[0].url;
136
+
137
+ res.json({
138
+ success: true,
139
+ imageUrl: imageUrl,
140
+ message: 'Mockup generated successfully'
141
+ });
142
+
143
+ } catch (error) {
144
+ console.error('Mockup generation error:', error);
145
+ res.status(500).json({
146
+ success: false,
147
+ error: 'Failed to generate mockup: ' + error.message
148
+ });
149
+ }
150
+ });
151
+
152
+ module.exports = router;
meme.html CHANGED
@@ -256,60 +256,154 @@ function generateMockCaptions() {
256
  });
257
  captionsContainer.classList.remove('hidden');
258
  }
259
- function showMockup(caption) {
260
  if (!uploadedImage) return;
261
 
262
- const tshirtImage = document.getElementById('tshirtImage');
263
- const tshirtCaption = document.getElementById('tshirtCaption');
264
-
265
- // Set the image on the t-shirt
266
- tshirtImage.setAttributeNS('http://www.w3.org/1999/xlink', 'href', uploadedImage);
267
 
268
- // Set the caption with word wrap
269
- const words = caption.split(' ');
270
- const maxCharsPerLine = 15;
271
- let lines = [];
272
- let currentLine = '';
 
 
 
273
 
274
- words.forEach(word => {
275
- if ((currentLine + word).length <= maxCharsPerLine) {
276
- currentLine += (currentLine ? ' ' : '') + word;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
277
  } else {
278
- if (currentLine) lines.push(currentLine);
279
- currentLine = word;
280
  }
281
- });
282
- if (currentLine) lines.push(currentLine);
283
-
284
- // Clear existing text elements
285
- const mockupPreview = document.getElementById('mockupPreview');
286
- const existingTexts = mockupPreview.querySelectorAll('text:not(#tshirtCaption)');
287
- existingTexts.forEach(text => text.remove());
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
 
289
- // Add new text elements for each line
290
- const svg = mockupPreview.querySelector('svg');
291
- const startY = lines.length === 1 ? 240 : (lines.length === 2 ? 235 : 230);
292
 
293
- lines.forEach((line, index) => {
294
- const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
295
- text.setAttribute('x', '150');
296
- text.setAttribute('y', startY + (index * 15));
297
- text.setAttribute('text-anchor', 'middle');
298
- text.setAttribute('fill', 'white');
299
- text.setAttribute('font-size', '12');
300
- text.setAttribute('font-weight', 'bold');
301
- text.setAttribute('font-family', 'Arial, sans-serif');
302
- text.textContent = line;
303
- svg.appendChild(text);
304
- });
305
 
306
- // Hide the original text element
307
- tshirtCaption.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
 
309
- mockupPlaceholder.classList.add('hidden');
310
- mockupPreview.classList.remove('hidden');
 
 
 
311
  }
312
- function resetCaptions() {
313
  captionsContainer.classList.add('hidden');
314
  noCaptions.classList.remove('hidden');
315
  mockupPlaceholder.classList.remove('hidden');
 
256
  });
257
  captionsContainer.classList.remove('hidden');
258
  }
259
+ async function showMockup(caption) {
260
  if (!uploadedImage) return;
261
 
262
+ mockupPlaceholder.classList.add('hidden');
263
+ mockupPreview.classList.remove('hidden');
 
 
 
264
 
265
+ // Show loading state
266
+ mockupPreview.innerHTML = `
267
+ <div class="text-center py-8">
268
+ <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-red-600 mx-auto mb-4"></div>
269
+ <p class="text-gray-400 mb-2">Generating realistic T-shirt mockup...</p>
270
+ <p class="text-gray-500 text-sm">Using AI to create a photorealistic design</p>
271
+ </div>
272
+ `;
273
 
274
+ try {
275
+ const shirtColor = window.selectedShirtColor || 'white';
276
+
277
+ // Call backend API for DALL-E generation
278
+ const response = await fetch('/api/generate-mockup', {
279
+ method: 'POST',
280
+ headers: {
281
+ 'Content-Type': 'application/json',
282
+ },
283
+ body: JSON.stringify({
284
+ image: uploadedImage,
285
+ caption: caption,
286
+ shirtColor: shirtColor
287
+ })
288
+ });
289
+
290
+ if (!response.ok) {
291
+ throw new Error('Failed to generate mockup');
292
+ }
293
+
294
+ const data = await response.json();
295
+
296
+ if (data.success && data.imageUrl) {
297
+ // Display the generated realistic mockup
298
+ mockupPreview.innerHTML = `
299
+ <div class="space-y-4">
300
+ <div class="relative rounded-lg overflow-hidden shadow-2xl">
301
+ <img src="${data.imageUrl}" alt="T-shirt Mockup" class="w-full h-auto">
302
+ <div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-4">
303
+ <p class="text-white text-sm font-medium">${caption}</p>
304
+ </div>
305
+ </div>
306
+ <div class="flex gap-2 justify-center">
307
+ <button onclick="regenerateMockup('${caption}')" class="bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-lg text-sm transition">
308
+ <i data-feather="refresh-cw" class="w-4 h-4 inline mr-1"></i>
309
+ Regenerate
310
+ </button>
311
+ <button onclick="downloadMockup('${data.imageUrl}')" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg text-sm transition">
312
+ <i data-feather="download" class="w-4 h-4 inline mr-1"></i>
313
+ Download
314
+ </button>
315
+ </div>
316
+ </div>
317
+ `;
318
+ feather.replace();
319
  } else {
320
+ throw new Error(data.error || 'Generation failed');
 
321
  }
322
+ } catch (error) {
323
+ console.error('Mockup generation error:', error);
324
+ showNotification('Failed to generate realistic mockup. Using fallback preview.', 'warning');
325
+ // Fallback to SVG mockup if API fails
326
+ showFallbackMockup(caption);
327
+ }
328
+ }
329
+
330
+ function regenerateMockup(caption) {
331
+ showMockup(caption);
332
+ }
333
+
334
+ function downloadMockup(imageUrl) {
335
+ const link = document.createElement('a');
336
+ link.href = imageUrl;
337
+ link.download = 'tshirt-mockup.png';
338
+ link.target = '_blank';
339
+ document.body.appendChild(link);
340
+ link.click();
341
+ document.body.removeChild(link);
342
+ }
343
+
344
+ function showNotification(message, type = 'info') {
345
+ const notification = document.createElement('div');
346
+ const bgColor = type === 'warning' ? 'bg-yellow-600' : type === 'error' ? 'bg-red-600' : 'bg-green-600';
347
+ notification.className = `fixed top-20 right-4 ${bgColor} text-white px-6 py-3 rounded-lg shadow-lg z-50 transform translate-x-full transition-transform duration-300`;
348
+ notification.innerHTML = `
349
+ <div class="flex items-center">
350
+ <i data-feather="${type === 'warning' ? 'alert-triangle' : type === 'error' ? 'x-circle' : 'check-circle'}" class="w-5 h-5 mr-2"></i>
351
+ <span>${message}</span>
352
+ </div>
353
+ `;
354
+ document.body.appendChild(notification);
355
+ feather.replace();
356
 
357
+ setTimeout(() => {
358
+ notification.style.transform = 'translateX(0)';
359
+ }, 100);
360
 
361
+ setTimeout(() => {
362
+ notification.style.transform = 'translateX(100%)';
363
+ setTimeout(() => {
364
+ document.body.removeChild(notification);
365
+ }, 300);
366
+ }, 5000);
367
+ }
368
+ function showFallbackMockup(caption) {
369
+ const tshirtImage = document.getElementById('tshirtImage');
370
+ const tshirtCaption = document.getElementById('tshirtCaption');
 
 
371
 
372
+ // Create the SVG fallback structure
373
+ mockupPreview.innerHTML = `
374
+ <svg width="300" height="350" viewBox="0 0 300 350" class="relative">
375
+ <!-- T-Shirt Base -->
376
+ <path d="M75 80 L75 40 L100 20 L120 30 L150 25 L180 30 L200 20 L225 40 L225 80 L200 100 L200 320 L100 320 L100 100 Z"
377
+ fill="#1a1a1a" stroke="#333" stroke-width="2"/>
378
+ <!-- Neckline -->
379
+ <ellipse cx="150" cy="40" rx="25" ry="20" fill="none" stroke="#333" stroke-width="2"/>
380
+ <!-- Sleeves -->
381
+ <path d="M75 80 L50 120 L40 180 L70 180 L100 100" fill="#1a1a1a" stroke="#333" stroke-width="2"/>
382
+ <path d="M225 80 L250 120 L260 180 L230 180 L200 100" fill="#1a1a1a" stroke="#333" stroke-width="2"/>
383
+
384
+ <!-- Design Area -->
385
+ <defs>
386
+ <clipPath id="designArea">
387
+ <rect x="90" y="100" width="120" height="120" rx="5"/>
388
+ </clipPath>
389
+ </defs>
390
+ <rect x="90" y="100" width="120" height="120" rx="5" fill="white" opacity="0.1"/>
391
+ <g clip-path="url(#designArea)">
392
+ <image id="tshirtImage" x="90" y="100" width="120" height="120" preserveAspectRatio="xMidYMid meet"/>
393
+ </g>
394
+ </svg>
395
+ <div class="mt-4 p-4 bg-gray-800 rounded-lg">
396
+ <p class="text-white text-center font-medium">${caption}</p>
397
+ </div>
398
+ `;
399
 
400
+ // Set the image on the t-shirt
401
+ const tshirtImg = document.getElementById('tshirtImage');
402
+ if (tshirtImg) {
403
+ tshirtImg.setAttributeNS('http://www.w3.org/1999/xlink', 'href', uploadedImage);
404
+ }
405
  }
406
+ function resetCaptions() {
407
  captionsContainer.classList.add('hidden');
408
  noCaptions.classList.remove('hidden');
409
  mockupPlaceholder.classList.remove('hidden');