jbilcke-hf HF staff commited on
Commit
42642e4
β€’
1 Parent(s): 4c069c7

add the beta mode

Browse files
src/app/engine/render.ts CHANGED
@@ -84,6 +84,8 @@ export async function newRender({
84
 
85
  const placeholder = "<USE YOUR OWN TOKEN>"
86
 
 
 
87
  // console.log("settings:", JSON.stringify(settings, null, 2))
88
 
89
  if (
@@ -315,6 +317,7 @@ export async function newRender({
315
  inputs: Buffer.from(blob).toString('base64'),
316
  parameters: {
317
  prompt: positivePrompt,
 
318
  num_inference_steps: nbInferenceSteps,
319
  guidance_scale: guidanceScale,
320
  width,
@@ -369,7 +372,7 @@ export async function newRender({
369
  },
370
  body: JSON.stringify({
371
  prompt,
372
- negativePrompt: "speech, bubble, speech bubble, caption",
373
 
374
  // for a future version of the comic factory
375
  identityImage: "",
 
84
 
85
  const placeholder = "<USE YOUR OWN TOKEN>"
86
 
87
+ const negativePrompt = "speech bubble, caption, subtitle"
88
+
89
  // console.log("settings:", JSON.stringify(settings, null, 2))
90
 
91
  if (
 
317
  inputs: Buffer.from(blob).toString('base64'),
318
  parameters: {
319
  prompt: positivePrompt,
320
+ negative_prompt: negativePrompt,
321
  num_inference_steps: nbInferenceSteps,
322
  guidance_scale: guidanceScale,
323
  width,
 
372
  },
373
  body: JSON.stringify({
374
  prompt,
375
+ negativePrompt,
376
 
377
  // for a future version of the comic factory
378
  identityImage: "",
src/app/interface/panel/index.tsx CHANGED
@@ -60,6 +60,8 @@ export function Panel({
60
 
61
  const setPanelPrompt = useStore(s => s.setPanelPrompt)
62
 
 
 
63
  const speeches = useStore(s => s.speeches)
64
  const speech = speeches[panelIndex] || ""
65
  const setPanelSpeech = useStore(s => s.setPanelSpeech)
@@ -96,17 +98,16 @@ export function Panel({
96
 
97
  let delay = enableRateLimiter ? (1000 + (500 * panelIndex)) : 1000
98
 
99
- const isBeta = false
100
-
101
  const addSpeechBubble = async () => {
102
  if (!renderedRef.current) { return }
103
 
104
  // story generation failed
105
  if (speech.trim() === "...") { return }
106
 
107
- if (!isBeta) { return }
108
 
109
- console.log('Generating speech bubble...')
110
  try {
111
  const result = await injectSpeechBubbleInTheBackground({
112
  inputImageInBase64: renderedRef.current.assetUrl,
 
60
 
61
  const setPanelPrompt = useStore(s => s.setPanelPrompt)
62
 
63
+ const showSpeeches = useStore(s => s.showSpeeches)
64
+
65
  const speeches = useStore(s => s.speeches)
66
  const speech = speeches[panelIndex] || ""
67
  const setPanelSpeech = useStore(s => s.setPanelSpeech)
 
98
 
99
  let delay = enableRateLimiter ? (1000 + (500 * panelIndex)) : 1000
100
 
101
+
 
102
  const addSpeechBubble = async () => {
103
  if (!renderedRef.current) { return }
104
 
105
  // story generation failed
106
  if (speech.trim() === "...") { return }
107
 
108
+ if (!showSpeeches) { return }
109
 
110
+ console.log('Generating speech bubbles (this is experimental!)')
111
  try {
112
  const result = await injectSpeechBubbleInTheBackground({
113
  inputImageInBase64: renderedRef.current.assetUrl,
src/app/interface/top-menu/index.tsx CHANGED
@@ -179,8 +179,8 @@ export function TopMenu() {
179
  onCheckedChange={setShowSpeeches}
180
  />
181
  <Label className="text-gray-200 dark:text-gray-200">
182
- <span className="hidden md:inline">Bubbles</span>
183
- <span className="inline md:hidden">Bub.</span>
184
  </Label>
185
  </div>
186
  {/*
 
179
  onCheckedChange={setShowSpeeches}
180
  />
181
  <Label className="text-gray-200 dark:text-gray-200">
182
+ <span className="hidden md:inline">Beta</span>
183
+ <span className="inline md:hidden">Beta</span>
184
  </Label>
185
  </div>
186
  {/*
src/lib/bubble/injectSpeechBubbleInTheBackground.ts CHANGED
@@ -1,4 +1,5 @@
1
  import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision"
 
2
 
3
  interface BoundingBox {
4
  top: number;
@@ -25,7 +26,7 @@ export async function injectSpeechBubbleInTheBackground(params: {
25
  text,
26
  shape = "oval",
27
  line = "handdrawn",
28
- font = "Arial",
29
  debug = false,
30
  } = params;
31
 
@@ -116,8 +117,11 @@ function analyzeSegmentationMask(mask: Uint8Array, width: number, height: number
116
  }
117
 
118
  function splitTextIntoBubbles(text: string): string[] {
119
- const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
120
- return sentences.map(sentence => sentence.trim());
 
 
 
121
  }
122
 
123
  function calculateBubbleLocations(
@@ -215,7 +219,7 @@ function drawSpeechBubble(
215
  ) {
216
  const bubbleWidth = Math.min(300, imageWidth * 0.4);
217
  const bubbleHeight = Math.min(150, imageHeight * 0.3);
218
- const padding = 20;
219
 
220
  const fontSize = 20;
221
  ctx.font = `${fontSize}px ${font}`;
@@ -232,7 +236,9 @@ function drawSpeechBubble(
232
  };
233
 
234
  ctx.fillStyle = 'white';
235
- ctx.strokeStyle = 'black';
 
 
236
  ctx.lineWidth = 2;
237
 
238
  let tailTarget = null;
@@ -353,34 +359,60 @@ function drawTail(
353
  tailTarget: { x: number, y: number },
354
  shape: string
355
  ) {
356
- const tailWidth = 20;
 
 
 
 
 
357
  const tailHeight = 30;
358
-
 
 
 
 
 
 
 
 
 
 
359
  ctx.beginPath();
360
- ctx.moveTo(bubbleLocation.x, bubbleLocation.y + bubbleHeight / 2);
361
-
362
  const controlPoint1 = {
363
- x: bubbleLocation.x + (tailTarget.x - bubbleLocation.x) / 3,
364
- y: bubbleLocation.y + bubbleHeight / 2
365
  };
366
-
367
  const controlPoint2 = {
368
- x: bubbleLocation.x + (tailTarget.x - bubbleLocation.x) * 2 / 3,
369
- y: tailTarget.y
370
  };
371
-
372
  ctx.bezierCurveTo(
373
  controlPoint1.x, controlPoint1.y,
374
  controlPoint2.x, controlPoint2.y,
375
- tailTarget.x, tailTarget.y
376
  );
 
 
 
 
 
 
377
 
 
 
 
 
 
378
  ctx.bezierCurveTo(
379
- controlPoint2.x + tailWidth, controlPoint2.y,
380
- controlPoint1.x + tailWidth, controlPoint1.y,
381
- bubbleLocation.x + tailWidth, bubbleLocation.y + bubbleHeight / 2
382
  );
383
-
384
  ctx.closePath();
385
  ctx.fill();
386
  ctx.stroke();
 
1
  import { ImageSegmenter, FilesetResolver } from "@mediapipe/tasks-vision"
2
+ import { actionman } from "../fonts";
3
 
4
  interface BoundingBox {
5
  top: number;
 
26
  text,
27
  shape = "oval",
28
  line = "handdrawn",
29
+ font = actionman.style.fontFamily,
30
  debug = false,
31
  } = params;
32
 
 
117
  }
118
 
119
  function splitTextIntoBubbles(text: string): string[] {
120
+ // Define a regular expression pattern
121
+ const pattern = /(?:[A-Z][a-z]*\.\s*)*(?:[^.!?\s]+[^.!?]*[.!?]+)|\S+/g;
122
+
123
+ const matches = text.match(pattern) || [text];
124
+ return matches.map(sentence => sentence.trim());
125
  }
126
 
127
  function calculateBubbleLocations(
 
219
  ) {
220
  const bubbleWidth = Math.min(300, imageWidth * 0.4);
221
  const bubbleHeight = Math.min(150, imageHeight * 0.3);
222
+ const padding = 24;
223
 
224
  const fontSize = 20;
225
  ctx.font = `${fontSize}px ${font}`;
 
236
  };
237
 
238
  ctx.fillStyle = 'white';
239
+
240
+ // let's disable the border for now
241
+ ctx.strokeStyle = 'white'; // 'black';
242
  ctx.lineWidth = 2;
243
 
244
  let tailTarget = null;
 
359
  tailTarget: { x: number, y: number },
360
  shape: string
361
  ) {
362
+ // Calculate new maximum length for tail
363
+ const bubbleCenterX = bubbleLocation.x;
364
+ const bubbleCenterY = bubbleLocation.y;
365
+ const maxTailLength = Math.max(bubbleHeight, bubbleWidth) * 0.6;
366
+
367
+ const tailBaseWidth = 30; // 50% larger than before
368
  const tailHeight = 30;
369
+
370
+ // Calculate the length from bubble center to tail target
371
+ const deltaX = tailTarget.x - bubbleCenterX;
372
+ const deltaY = tailTarget.y - bubbleCenterY;
373
+ const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
374
+
375
+ // Normalize the length if it exceeds the max tail length
376
+ const limitedDistance = Math.min(distance, maxTailLength);
377
+ const tailEndX = bubbleCenterX + (deltaX / distance) * limitedDistance;
378
+ const tailEndY = bubbleCenterY + (deltaY / distance) * limitedDistance;
379
+
380
  ctx.beginPath();
381
+ ctx.moveTo(bubbleCenterX, bubbleCenterY);
382
+
383
  const controlPoint1 = {
384
+ x: bubbleCenterX + deltaX / 3,
385
+ y: bubbleCenterY + deltaY / 3
386
  };
387
+
388
  const controlPoint2 = {
389
+ x: bubbleCenterX + (deltaX * 2) / 3,
390
+ y: bubbleCenterY + (deltaY * 2) / 3
391
  };
392
+
393
  ctx.bezierCurveTo(
394
  controlPoint1.x, controlPoint1.y,
395
  controlPoint2.x, controlPoint2.y,
396
+ tailEndX, tailEndY
397
  );
398
+
399
+ // Mirror to create the width at base
400
+ const mirroredControlPoint1 = {
401
+ x: controlPoint1.x + tailBaseWidth / 3,
402
+ y: controlPoint1.y
403
+ };
404
 
405
+ const mirroredControlPoint2 = {
406
+ x: controlPoint2.x + (tailBaseWidth * 2) / 3,
407
+ y: controlPoint2.y
408
+ };
409
+
410
  ctx.bezierCurveTo(
411
+ mirroredControlPoint2.x, mirroredControlPoint2.y,
412
+ mirroredControlPoint1.x, mirroredControlPoint1.y,
413
+ bubbleCenterX + tailBaseWidth, bubbleCenterY
414
  );
415
+
416
  ctx.closePath();
417
  ctx.fill();
418
  ctx.stroke();