<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: hua sun</title>
    <description>The latest articles on DEV Community by hua sun (@hua_sun_6c12aacb6561fc6c5).</description>
    <link>https://dev.to/hua_sun_6c12aacb6561fc6c5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4013339%2Fdaaff91e-bde0-4fda-8f2d-def8482662c7.png</url>
      <title>DEV Community: hua sun</title>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hua_sun_6c12aacb6561fc6c5"/>
    <language>en</language>
    <item>
      <title>Put yourself in a viral clip with one photo — no prompt</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Sun, 15 Nov 2026 14:33:36 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/put-yourself-in-a-viral-clip-with-one-photo-no-prompt-1760</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/put-yourself-in-a-viral-clip-with-one-photo-no-prompt-1760</guid>
      <description>&lt;p&gt;I shipped &lt;strong&gt;&lt;a href="https://ai-genjutsu.com/" rel="noopener noreferrer"&gt;Genjutsu AI&lt;/a&gt;&lt;/strong&gt; — a small web tool for a narrow job: take a video people already share, swap the people for photos you pick, and keep the original take.&lt;/p&gt;

&lt;p&gt;No prompt. No storyboard. You upload the clip, the app splits it into scenes and lists who’s in frame. You add &lt;strong&gt;one still photo per person&lt;/strong&gt; you want to change. Everyone else, the background, and the props stay as filmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays the same
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shot order, timing, and camera&lt;/li&gt;
&lt;li&gt;Cuts and pacing&lt;/li&gt;
&lt;li&gt;Original audio&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changes
&lt;/h2&gt;

&lt;p&gt;Only the people you assign a photo to. One photo = one identity across every shot they appear in. That can be you, a friend, a pet, a product, or a character.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Upload &lt;strong&gt;mp4&lt;/strong&gt;, &lt;strong&gt;mov&lt;/strong&gt;, or &lt;strong&gt;webm&lt;/strong&gt; (up to 80MB), or pick a template (Hotel Lobby, Tiny Desk AI, Night Dance, Bulin 47, and others).&lt;/li&gt;
&lt;li&gt;Scenes and faces are detected automatically.&lt;/li&gt;
&lt;li&gt;Attach a photo for each face you want to replace.&lt;/li&gt;
&lt;li&gt;Generate. Download is &lt;strong&gt;720p&lt;/strong&gt; with the source audio.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Under the hood it’s a &lt;strong&gt;Next.js&lt;/strong&gt; app with a Python cast pipeline for scene/face work, &lt;strong&gt;Prisma&lt;/strong&gt; + Postgres for accounts and credits, object storage for uploads, and a video model API for the swap. Happy to talk stack details in the comments if anyone’s curious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits worth knowing
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scene length&lt;/td&gt;
&lt;td&gt;A single scene &lt;strong&gt;cannot exceed 15 seconds&lt;/strong&gt; (shorter clips work best).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Resolution&lt;/td&gt;
&lt;td&gt;Output is &lt;strong&gt;720p&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credits&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;10 credits per 15 seconds&lt;/strong&gt;; failed runs refund credits.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sign-in&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Google&lt;/strong&gt; — generations stay on your account.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Credits don’t expire. First &lt;strong&gt;15-second&lt;/strong&gt; video on Starter is &lt;strong&gt;$6.90&lt;/strong&gt;, then &lt;strong&gt;$9.99&lt;/strong&gt; (10 credits). Plus and Creator packs are on the &lt;a href="https://ai-genjutsu.com/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not the same as “Higgsfield Genjutsu”
&lt;/h2&gt;

&lt;p&gt;Genjutsu AI is a separate product: &lt;strong&gt;you bring the video&lt;/strong&gt;, and you attach &lt;strong&gt;one photo per person&lt;/strong&gt; you want in the frame. Same filmed motion, different faces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;👉 &lt;strong&gt;ai-genjutsu.com&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A clip under 15 seconds is enough for a first run. I’d love feedback on scene splits, face consistency, and whether the credit pricing feels fair — especially from folks who ship short-form or meme clips.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: I built this. It’s a paid credit product, not open source.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>sideprojects</category>
      <category>webdev</category>
    </item>
    <item>
      <title>ClipRedo lets you remake a trending clip with your own photos</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Wed, 04 Nov 2026 13:48:40 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/clipredo-lets-you-remake-a-trending-clip-with-your-own-photos-756</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/clipredo-lets-you-remake-a-trending-clip-with-your-own-photos-756</guid>
      <description>&lt;p&gt;I shipped &lt;a href="https://clipredo.com" rel="noopener noreferrer"&gt;ClipRedo&lt;/a&gt; for people who want their own version of a short clip that is already going around.&lt;/p&gt;

&lt;p&gt;You do not write a prompt, and the site does not post the file for you. You pick a template, upload the photos it asks for, and download a new video. The room, the camera, and the order of the moves stay with the template. The people come from the photos.&lt;/p&gt;

&lt;p&gt;The site is in English. The line on the homepage is "Redo the trend. Make it yours."&lt;/p&gt;

&lt;h2&gt;
  
  
  What is live
&lt;/h2&gt;

&lt;p&gt;Four templates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hotel Lobby.&lt;/strong&gt; Two photos. 15 seconds, 4:3. Two people, an orange wall, and one hanging microphone. 10 credits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulin 47.&lt;/strong&gt; One photo. 15 seconds, vertical. One person in the middle of a crowd. 10 credits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bulin 47 Trio Dance.&lt;/strong&gt; Three photos. 15 seconds, vertical. Three people dancing together. 10 credits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fancy Cafe.&lt;/strong&gt; Two photos. 21 seconds, vertical. One person sits on a small round table. Another walks in and sits on the bench. 480p is 10 credits. 720p is 20 credits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Photos are JPG, PNG, or WEBP, up to 10MB each. Sign-in is Google.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the file does not include
&lt;/h2&gt;

&lt;p&gt;The download has no soundtrack. If you post on TikTok, add the trend's sound inside TikTok. On Reels, add it inside Instagram. ClipRedo does not attach a song, and it does not publish the post.&lt;/p&gt;

&lt;p&gt;Faces, clothes, and timing can still differ from the preview. The template aims to keep the place and which person does which move.&lt;/p&gt;

&lt;p&gt;A run that fails before the video is ready returns the credits. A finished video keeps the charge.&lt;/p&gt;

&lt;p&gt;There is no subscription. Credits are a one-time purchase, and they do not expire.&lt;/p&gt;

&lt;h2&gt;
  
  
  Price
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Starter: $9.99 for 10 credits&lt;/li&gt;
&lt;li&gt;Plus: $24.99 for 30 credits&lt;/li&gt;
&lt;li&gt;Creator: $69.99 for 100 credits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most templates cost 10 credits. Fancy Cafe at 720p costs 20, so the Starter pack is not enough for that option on its own. The 1 / 3 / 10 video counts on the pricing page assume 10 credits per video.&lt;/p&gt;

&lt;p&gt;If you open &lt;a href="https://clipredo.com" rel="noopener noreferrer"&gt;clipredo.com&lt;/a&gt;, the useful thing to look at first is the preview on each template, before you spend anything.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>When “No Speech Found” Actually Means “Wrong Language”</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Sat, 12 Sep 2026 07:41:08 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/when-no-speech-found-actually-means-wrong-language-1el0</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/when-no-speech-found-actually-means-wrong-language-1el0</guid>
      <description>&lt;p&gt;A user uploads a video with clearly audible speech.&lt;br&gt;
The transcription API returns nothing.&lt;br&gt;
The obvious error message is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;No speech found.&lt;br&gt;
But that message may be wrong. The audio is not silent—the transcription model may simply be listening for the wrong language.&lt;br&gt;
We ran into this while building &lt;a href="https://www.subtitlegenerator.net" rel="noopener noreferrer"&gt;SubtitleGenerator&lt;/a&gt;, and it led to an interesting product decision: should we keep adding automatic detection, or just ask the user what language they are speaking?&lt;/p&gt;
&lt;h2&gt;
  
  
  How the problem happens
&lt;/h2&gt;

&lt;p&gt;A speech-to-text request usually contains three important pieces of information:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The audio&lt;/li&gt;
&lt;li&gt;The transcription model&lt;/li&gt;
&lt;li&gt;The expected language
A simplified request might look like this:&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
ts
const formData = new FormData();
formData.append("audio", audioBlob);
formData.append("language", "en");
await fetch("/api/transcribe", {
  method: "POST",
  body: formData,
});
This works when the speaker is using English.

But if the video contains Chinese, Spanish or Arabic speech, forcing en can produce:

An empty transcript
Very low-confidence words
English-looking nonsense
Text written in an unexpected alphabet
A misleading “no speech found” error
The audio exists. The model just cannot map it confidently to the expected language.

Why not always use automatic detection?
The obvious solution is automatic language detection.

In ideal conditions, that works well. The problem appears with real creator content:

Five-second clips
Music before the first spoken sentence
Strong accents
Noisy phone recordings
Multiple speakers
Mixed-language speech
Hindi and English code-switching
One or two words surrounded by silence
Language detection needs evidence. A short clip may not provide enough of it.

This creates a dangerous failure mode: the application looks fully automatic, but when detection fails, the user has no idea why.

The fallback we considered
We initially considered a pipeline like this:

Run normal transcription
        ↓
Result is empty or uncertain
        ↓
Send audio to another model for language detection
        ↓
Run transcription again with detected language
It sounds smart, but it has hidden costs.

If a ten-minute video is processed twice, the application may pay for twenty minutes of model usage. The user also waits for two requests while believing they submitted only one job.

Worse, the second model can still guess incorrectly.

Adding another AI call does not automatically make a workflow more reliable. Sometimes it just makes the failure slower and more expensive.

The decision: ask before processing
We chose a simpler workflow:

Show a Spoken language selector before upload
Default to English for our current audience
Send the selected language explicitly
Remember the choice locally
Ask the user to check it when the result looks suspicious
The client sends the language with the audio:

const formData = new FormData();
formData.append("audio", audioBlob);
formData.append("language", spokenLanguage);
const response = await fetch("/api/transcribe", {
  method: "POST",
  body: formData,
});
The server validates the value and applies a predictable fallback:

const supportedLanguages = new Set([
  "en",
  "es",
  "fr",
  "de",
  "hi",
  "ar",
  "zh",
]);
const requestedLanguage = formData.get("language");
const language =
  typeof requestedLanguage === "string" &amp;amp;&amp;amp;
  supportedLanguages.has(requestedLanguage)
    ? requestedLanguage
    : "en";
The selected value is then passed to the transcription provider.

This approach is not as magical as “we detect everything automatically,” but its behavior is easier to explain and debug.

Detecting a suspicious result
Users can still choose the wrong language, so we need a recovery path.

After transcription, we inspect the returned words:

type TranscriptWord = {
  text: string;
  confidence?: number;
};
function needsLanguageReview(words: TranscriptWord[]): boolean {
  if (words.length === 0) {
    return true;
  }
  const wordsWithConfidence = words.filter(
    (word) =&amp;gt; typeof word.confidence === "number",
  );
  if (wordsWithConfidence.length === 0) {
    return false;
  }
  return wordsWithConfidence.every(
    (word) =&amp;gt; (word.confidence ?? 1) &amp;lt; 0.75,
  );
}
An empty result or consistently low confidence does not prove that the language is wrong. The recording might simply be poor.

So we do not display:

Wrong language detected.

Instead, we say something closer to:

The result looks uncertain. Check the spoken language and try again.

That distinction matters. Confidence is a signal, not proof.

Spoken language is not translation language
This also forced us to make the interface clearer.

Consider a Spanish video that needs English subtitles:

Spoken language: Spanish
Translation target: English
The correct pipeline is:

Spanish audio
    ↓ Transcription
Spanish transcript
    ↓ Translation
English subtitles
Trying to transcribe Spanish audio as English is not translation. It is a transcription request with incorrect context.

Keeping these two settings separate prevents a surprising number of errors.

Make the retry cost visible
Changing the language and running transcription again means making another API request.

That may consume additional processing time, so the interface should say so before the retry.

The alternative—silently retrying until something works—creates unpredictable costs for both the user and the application.

Our rule became:

Never hide a second paid model call behind an innocent-looking loading state.

If the retry costs time or credits, tell the user.

What about mixed-language videos?
A single language selector does not solve everything.

A speaker may switch between Hindi and English, Spanish and English, or Arabic and French. Some transcription providers support multilingual modes, but support and accuracy vary by model.

A practical language configuration might therefore look like this:

type LanguageMode =
  | { type: "single"; language: string }
  | { type: "multilingual" };
The important part is not promising multilingual accuracy unless the selected provider and model genuinely support it.

For unsupported combinations, a clear limitation is better than a confident-looking bad transcript.

What we learned
1. Ask users for information they already know
The speaker usually knows which language is in the video. Asking takes seconds and can prevent an expensive failed request.

2. Automatic does not always mean simpler
Automatic detection removes one control from the interface, but it can add retries, cost and confusing failure states behind the scenes.

3. Error messages should describe evidence
“No words were returned” is evidence.

“The video contains no speech” is an interpretation—and it may be wrong.

4. Confidence is not correctness
A low-confidence word needs review. It should not automatically be presented as an error.

5. AI costs are part of UX design
Fallback calls, retries and detection requests affect both the bill and the waiting experience. They are product decisions, not merely backend details.

Final thought
Developers often try to make AI products feel magical by removing every question.

But when the user already knows something the model must guess, one small question can make the product faster, cheaper and more trustworthy.

Sometimes the best AI fallback is not another model.

It is a dropdown.

I'm building [SubtitleGenerator](https://www.subtitlegenerator.net/), an AI subtitle editor that highlights uncertain words so creators can review what matters instead of checking every line.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>I Built an AI Subtitle Editor Where Fixing Mistakes Matters More Than Transcription</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Tue, 08 Sep 2026 11:31:33 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/i-built-an-ai-subtitle-editor-where-fixing-mistakes-matters-more-than-transcription-4nfj</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/i-built-an-ai-subtitle-editor-where-fixing-mistakes-matters-more-than-transcription-4nfj</guid>
      <description>&lt;p&gt;I thought transcription was the hard part.&lt;/p&gt;

&lt;p&gt;It turns out that generating 95% correct subtitles is relatively easy. Finding and fixing the remaining 5% is where people lose their time.&lt;/p&gt;

&lt;p&gt;A person’s name is wrong. A brand name becomes a common word. One sentence has strange timing. The user then has to replay the entire video just to find a handful of mistakes.&lt;/p&gt;

&lt;p&gt;That is the problem I wanted to solve with &lt;a href="https://www.subtitlegenerator.net/" rel="noopener noreferrer"&gt;SubtitleGenerator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Instead of treating transcription as the finished product, I built the editor around a correction workflow I call the &lt;strong&gt;Fix Loop&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate subtitles&lt;/li&gt;
&lt;li&gt;Highlight words that may be wrong&lt;/li&gt;
&lt;li&gt;Jump directly to the relevant audio&lt;/li&gt;
&lt;li&gt;Fix or approve the word&lt;/li&gt;
&lt;li&gt;Repeat until the track is clear&lt;/li&gt;
&lt;li&gt;Style and export&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh7hi1kd7ydun81pzizrm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh7hi1kd7ydun81pzizrm.png" alt=" " width="800" height="509"&gt;&lt;/a&gt;&lt;br&gt;
This post covers some of the technical decisions behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;The application currently uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 16&lt;/li&gt;
&lt;li&gt;React 19&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;PostgreSQL with Drizzle ORM&lt;/li&gt;
&lt;li&gt;Auth.js&lt;/li&gt;
&lt;li&gt;Browser media APIs&lt;/li&gt;
&lt;li&gt;ffmpeg.wasm as a fallback&lt;/li&gt;
&lt;li&gt;IndexedDB for local project persistence&lt;/li&gt;
&lt;li&gt;External speech-to-text and translation APIs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part was not choosing the stack. It was deciding which work belonged in the browser and which work belonged on the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the original video in the browser
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxvwjcu1zzpgluauiole.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkxvwjcu1zzpgluauiole.png" alt=" " width="800" height="610"&gt;&lt;/a&gt;&lt;br&gt;
Uploading a large video to a server before transcription creates several problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Slow uploads&lt;/li&gt;
&lt;li&gt;More storage&lt;/li&gt;
&lt;li&gt;More bandwidth&lt;/li&gt;
&lt;li&gt;More privacy concerns&lt;/li&gt;
&lt;li&gt;More cleanup jobs&lt;/li&gt;
&lt;li&gt;A worse experience on unreliable connections&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Subtitle generation only needs the audio track, so the original video stays in the browser.&lt;/p&gt;

&lt;p&gt;The client extracts a mono 16 kHz WAV file and sends that smaller audio representation to the transcription endpoint.&lt;/p&gt;

&lt;p&gt;The simplified flow looks like this:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
ts
const audio = await extractAudioFromMedia(videoFile)

const formData = new FormData()
formData.append("audio", audio, "audio.wav")
formData.append("duration", String(duration))
formData.append("language", language)

const response = await fetch("/api/transcribe", {
  method: "POST",
  body: formData,
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>nextjs</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>How We Map Photos to Printable Bead Palettes (Without 80 Near-Identical Colors)</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Wed, 26 Aug 2026 09:05:05 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/how-we-map-photos-to-printable-bead-palettes-without-80-near-identical-colors-3m6e</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/how-we-map-photos-to-printable-bead-palettes-without-80-near-identical-colors-3m6e</guid>
      <description>&lt;h1&gt;
  
  
  How We Map Photos to Printable Bead Palettes
&lt;/h1&gt;

&lt;p&gt;Turning a photo into a craftable perler-bead chart is mostly an &lt;strong&gt;image-processing&lt;/strong&gt; problem: downsample to a grid, then map every cell to a &lt;strong&gt;finite palette&lt;/strong&gt; that people can actually buy.&lt;/p&gt;

&lt;p&gt;If you only “pixelate” an image, you still get hundreds of RGB values. Makers need something closer to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a grid sized for a real pegboard&lt;/li&gt;
&lt;li&gt;colors from a brand palette (Perler / Hama / etc.)&lt;/li&gt;
&lt;li&gt;a count list so they know what to buy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That workflow is what we ship in the browser at &lt;a href="https://www.pixelbeads.io" rel="noopener noreferrer"&gt;PixelBeads&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline (high level)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load + optional crop&lt;/strong&gt; — focus on the subject; less background = fewer junk colors
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resize to target grid&lt;/strong&gt; — e.g. 29×29 / 50×50 style boards, not arbitrary megapixel pixels
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quantize to a palette&lt;/strong&gt; — nearest color in Lab (or similar) to brand swatches
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional color reduction&lt;/strong&gt; — merge near-duplicates so the project stays buildable
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export&lt;/strong&gt; — printable chart + per-color counts
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The hard part is step 3–4: &lt;em&gt;looks like the photo&lt;/em&gt; vs &lt;em&gt;you can finish it this weekend&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why naive nearest-RGB fails
&lt;/h2&gt;

&lt;p&gt;RGB Euclidean distance is a bad perceptual metric. Two shades can be “close” in RGB but look very different (or vice versa). For craft palettes we care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;perceptual distance (Lab / ΔE-style thinking)&lt;/li&gt;
&lt;li&gt;inventory constraints (user only owns 40 codes)&lt;/li&gt;
&lt;li&gt;count explosion (80 colors ≈ abandoned project)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we expose &lt;strong&gt;custom palettes&lt;/strong&gt;: users pick only the codes they own, then quantization is limited to that subset. Write-up on the product side: &lt;a href="https://www.pixelbeads.io/blog/how-to-use-custom-bead-palettes" rel="noopener noreferrer"&gt;custom bead palettes&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A tiny mental model (pseudo)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
text
for each cell in grid:
  sample average color of the source region
  find nearest swatch in allowed_palette  # Lab distance
  write swatch_id to cell

then optionally:
  merge swatches below a ΔE threshold
  recompute counts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
    </item>
    <item>
      <title>How to Create Pixel Bead Patterns from Any Image</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Tue, 25 Aug 2026 12:27:06 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/how-to-create-pixel-bead-patterns-from-any-image-571d</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/how-to-create-pixel-bead-patterns-from-any-image-571d</guid>
      <description>&lt;p&gt;I recently tried pixelbeads.io to convert a photo into a printable bead pattern. It was super easy and free, no sign-up needed. The color counts were accurate and helped me plan my project. Check it out: &lt;a href="https://www.pixelbeads.io/zh-cn" rel="noopener noreferrer"&gt;pixelbeads.io&lt;/a&gt;&lt;/p&gt;

</description>
      <category>pixelbeads</category>
      <category>pixelart</category>
      <category>diy</category>
      <category>crafts</category>
    </item>
    <item>
      <title>How to Turn Any Photo into a Perler Bead Pattern (Free Online)</title>
      <dc:creator>hua sun</dc:creator>
      <pubDate>Fri, 03 Jul 2026 09:31:12 +0000</pubDate>
      <link>https://dev.to/hua_sun_6c12aacb6561fc6c5/how-to-turn-any-photo-into-a-perler-bead-pattern-free-online-p4k</link>
      <guid>https://dev.to/hua_sun_6c12aacb6561fc6c5/how-to-turn-any-photo-into-a-perler-bead-pattern-free-online-p4k</guid>
      <description>&lt;p&gt;Creating a Perler bead pattern used to require hours of manual work. You had to resize your image, reduce colors, count beads, and redraw everything on graph paper.&lt;/p&gt;

&lt;p&gt;Today, AI can do all of that automatically.&lt;/p&gt;

&lt;p&gt;If you've ever wanted to turn a pet photo, anime character, game sprite, or family picture into a printable Perler bead pattern, here's the easiest way.&lt;/p&gt;

&lt;p&gt;Step 1 — Upload Your Image&lt;/p&gt;

&lt;p&gt;Start by choosing any photo.&lt;/p&gt;

&lt;p&gt;Good examples include:&lt;/p&gt;

&lt;p&gt;Pet portraits&lt;br&gt;
Family photos&lt;br&gt;
Pixel art&lt;br&gt;
Game characters&lt;br&gt;
Anime characters&lt;br&gt;
Logos&lt;br&gt;
Simple illustrations&lt;/p&gt;

&lt;p&gt;The clearer the image, the better the final bead pattern will look.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzqgj3w2bzke4c15fbt1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzqgj3w2bzke4c15fbt1n.png" alt=" " width="800" height="763"&gt;&lt;/a&gt;&lt;br&gt;
Step 2 — Generate the Pattern&lt;/p&gt;

&lt;p&gt;Upload the image to PixelBeads.io.&lt;/p&gt;

&lt;p&gt;The generator automatically:&lt;/p&gt;

&lt;p&gt;Detects image colors&lt;br&gt;
Converts the picture into bead-friendly pixels&lt;br&gt;
Optimizes the color palette&lt;br&gt;
Matches colors to common Perler bead colors&lt;br&gt;
Generates a printable bead grid&lt;br&gt;
Counts the exact number of beads required&lt;/p&gt;

&lt;p&gt;The entire process usually takes only a few seconds.&lt;/p&gt;

&lt;p&gt;Step 3 — Print the Grid&lt;/p&gt;

&lt;p&gt;Once the conversion is complete, you can download:&lt;/p&gt;

&lt;p&gt;Printable bead grid&lt;br&gt;
Color legend&lt;br&gt;
Bead count&lt;br&gt;
Pattern preview&lt;/p&gt;

&lt;p&gt;Simply print the pattern or display it on a tablet while building your project.&lt;/p&gt;

&lt;p&gt;Tips for Better Results&lt;/p&gt;

&lt;p&gt;For the best-looking bead art:&lt;/p&gt;

&lt;p&gt;Use high-resolution images.&lt;br&gt;
Avoid blurry photos.&lt;br&gt;
Choose subjects with strong contrast.&lt;br&gt;
Crop unnecessary backgrounds before uploading.&lt;br&gt;
Keep the image focused on one main object.&lt;/p&gt;

&lt;p&gt;These small adjustments can dramatically improve the final pattern.&lt;/p&gt;

&lt;p&gt;Popular Projects&lt;/p&gt;

&lt;p&gt;Many makers use photo-to-pattern conversion for:&lt;/p&gt;

&lt;p&gt;Pet memorials&lt;br&gt;
Family portraits&lt;br&gt;
Pokémon&lt;br&gt;
Mario characters&lt;br&gt;
Minecraft art&lt;br&gt;
Retro game sprites&lt;br&gt;
Anime characters&lt;br&gt;
Custom gifts&lt;/p&gt;

&lt;p&gt;Because every uploaded image is unique, every pattern is unique as well.&lt;/p&gt;

&lt;p&gt;Why Use an Online Perler Bead Generator?&lt;/p&gt;

&lt;p&gt;Compared with drawing patterns manually, an online generator saves time and reduces mistakes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffmtb5j8np8ubdheizpb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fffmtb5j8np8ubdheizpb.png" alt=" " width="800" height="766"&gt;&lt;/a&gt;&lt;br&gt;
Benefits include:&lt;/p&gt;

&lt;p&gt;Fast conversion&lt;br&gt;
Accurate color matching&lt;br&gt;
Printable layouts&lt;br&gt;
Automatic bead counting&lt;br&gt;
No software installation&lt;br&gt;
Works directly in your browser&lt;/p&gt;

&lt;p&gt;Whether you're a beginner or an experienced crafter, generating patterns online is much easier than creating them by hand.&lt;/p&gt;

&lt;p&gt;Start Creating Today&lt;/p&gt;

&lt;p&gt;Ready to make your own custom Perler bead pattern?&lt;/p&gt;

&lt;p&gt;Upload your favorite image and let &lt;a href="https://www.pixelbeads.io/" rel="noopener noreferrer"&gt;PixelBeads.io&lt;/a&gt; transform it into a printable bead design in just seconds.&lt;/p&gt;

&lt;p&gt;Happy crafting!&lt;/p&gt;

</description>
      <category>pixelbeads</category>
      <category>perler</category>
      <category>bead</category>
      <category>pattern</category>
    </item>
  </channel>
</rss>
