<?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: rogin yang</title>
    <description>The latest articles on DEV Community by rogin yang (@rogin_yang_045a6718690f48).</description>
    <link>https://dev.to/rogin_yang_045a6718690f48</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%2F4164001%2F2af8e19a-4eaa-44d8-9775-ec722fb92ffd.png</url>
      <title>DEV Community: rogin yang</title>
      <link>https://dev.to/rogin_yang_045a6718690f48</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rogin_yang_045a6718690f48"/>
    <language>en</language>
    <item>
      <title>Building a passport/ID photo maker that never uploads the photo (MediaPipe in the browser)</title>
      <dc:creator>rogin yang</dc:creator>
      <pubDate>Mon, 05 Oct 2026 16:49:15 +0000</pubDate>
      <link>https://dev.to/rogin_yang_045a6718690f48/building-a-passportid-photo-maker-that-never-uploads-the-photo-mediapipe-in-the-browser-1fk0</link>
      <guid>https://dev.to/rogin_yang_045a6718690f48/building-a-passportid-photo-maker-that-never-uploads-the-photo-mediapipe-in-the-browser-1fk0</guid>
      <description>&lt;p&gt;Most "passport photo online" sites ask you to upload a selfie and then charge to remove the watermark. I wanted to see how far a &lt;strong&gt;browser-only&lt;/strong&gt; version could go: crop to the official spec, replace the background and export print-ready files without the image ever leaving the phone. Here is what turned out to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Specs are about head height, not zoom
&lt;/h2&gt;

&lt;p&gt;Official rules describe the &lt;strong&gt;head height&lt;/strong&gt; (chin to top of head) and the margin above it:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Photo&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Head&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Brazil 3x4&lt;/td&gt;
&lt;td&gt;30 x 40 mm&lt;/td&gt;
&lt;td&gt;≈ 30 mm (70–80%)&lt;/td&gt;
&lt;td&gt;ICAO guidance published by ANAC (gov.br)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;US visa&lt;/td&gt;
&lt;td&gt;2 x 2 in; DS-160 file 600x600 px, ≤ 240 KB&lt;/td&gt;
&lt;td&gt;25–35 mm (50–69%)&lt;/td&gt;
&lt;td&gt;travel.state.gov&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canada visa&lt;/td&gt;
&lt;td&gt;35 x 45 mm&lt;/td&gt;
&lt;td&gt;31–36 mm&lt;/td&gt;
&lt;td&gt;canada.ca (IRCC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Schengen&lt;/td&gt;
&lt;td&gt;35 x 45 mm&lt;/td&gt;
&lt;td&gt;32–36 mm&lt;/td&gt;
&lt;td&gt;EU Visa Code / ICAO 9303&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you know the chin and the crown in pixels, the crop is just arithmetic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeCrop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;chinY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;crownY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;centerX&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pxPerMm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chinY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;crownY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headTargetMm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;wMm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;pxPerMm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hMm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;pxPerMm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;centerX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;crownY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;topMm&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;pxPerMm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. The chin is easy, the crown is not
&lt;/h2&gt;

&lt;p&gt;MediaPipe's FaceLandmarker gives 478 points. Point 152 is the chin, but the highest point (10) sits mid-forehead. What worked for me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;De-rotate the image using the eye line first.&lt;/li&gt;
&lt;li&gt;Run a person segmenter on a window around the head and take the first row, from the top, where the mask covers a central band of the face. That row is the top of the hair.&lt;/li&gt;
&lt;li&gt;For specs that measure to the &lt;strong&gt;skull&lt;/strong&gt; (Canada, ICAO), clamp that value. Otherwise big curly hair shrinks the face below the allowed range.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Segmentation: download size vs. quality
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;selfie_segmenter&lt;/code&gt; is 250 KB and good enough for a flat white background. &lt;code&gt;selfie_multiclass_256x256&lt;/code&gt; is 16 MB and handles curly hair much better. I load the small model first, show a result, and then fetch the big one in the background unless &lt;code&gt;navigator.connection&lt;/code&gt; reports 2g/3g or &lt;code&gt;saveData&lt;/code&gt;. Running the segmenter &lt;strong&gt;on the crop region&lt;/strong&gt; instead of the full photo makes the 256-px mask far sharper.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Getting the print size right: write DPI into the JPEG
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;canvas.toBlob('image/jpeg')&lt;/code&gt; doesn't set a useful density, so print shops print the file at the wrong size. Patch the JFIF header bytes directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dpi&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dpi&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dpi&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dpi&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. A4 PDF without a library
&lt;/h2&gt;

&lt;p&gt;A one-page PDF with the JPEG embedded as &lt;code&gt;/DCTDecode&lt;/code&gt; and each copy placed with a &lt;code&gt;cm&lt;/code&gt; operator (mm × 72 / 25.4) takes about 40 lines of JS. An A4 sheet with 12 mm margins and 3 mm gaps fits 30 copies of a 3x4 photo.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Feedback before printing
&lt;/h2&gt;

&lt;p&gt;MediaPipe blendshapes (&lt;code&gt;eyeBlink*&lt;/code&gt;, &lt;code&gt;mouthSmile*&lt;/code&gt;, &lt;code&gt;jawOpen&lt;/code&gt;), plus two cheap measurements (nose offset from the eye midpoint for a turned head, cheek luminance difference for side lighting), are enough to warn people about closed eyes, smiling, a turned head or one-sided light before they print.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code and data
&lt;/h2&gt;

&lt;p&gt;The specs (each with its official link) and the engine are open: &lt;strong&gt;&lt;a href="https://github.com/MMMystery/foto-documento-specs" rel="noopener noreferrer"&gt;https://github.com/MMMystery/foto-documento-specs&lt;/a&gt;&lt;/strong&gt; (MIT code, CC BY 4.0 data). The live tool built on it, in Portuguese for Brazil, is free and has no watermark: &lt;a href="https://fotoapta.com/" rel="noopener noreferrer"&gt;https://fotoapta.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built FotoApta. This post was drafted with AI assistance and reviewed; all numbers come from the official sources linked above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Fiz uma API grátis (sem chave) para calcular salário líquido, INSS e IRRF de 2026</title>
      <dc:creator>rogin yang</dc:creator>
      <pubDate>Mon, 05 Oct 2026 13:21:49 +0000</pubDate>
      <link>https://dev.to/rogin_yang_045a6718690f48/fiz-uma-api-gratis-sem-chave-para-calcular-salario-liquido-inss-e-irrf-de-2026-3i6m</link>
      <guid>https://dev.to/rogin_yang_045a6718690f48/fiz-uma-api-gratis-sem-chave-para-calcular-salario-liquido-inss-e-irrf-de-2026-3i6m</guid>
      <description>&lt;p&gt;Todo mês de janeiro a mesma coisa: sai a portaria nova do INSS, o salário mínimo muda, e quem mantém sistema de folha, planilha de RH ou simulador precisa caçar os números em PDF do governo. Em 2026 ainda veio a Lei 15.270/2025, que zera o IRRF até R$ 5.000 e cria uma faixa de transição até R$ 7.350, com uma fórmula própria (R$ 978,62 − 0,133145 × rendimento).&lt;/p&gt;

&lt;p&gt;Juntei tudo num registro de regras com fonte oficial e data de conferência em cada valor, e coloquei um motor de cálculo na frente. O resultado é uma API pública:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://contaclt.com/api/v1/irrf?salario=6000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Responde em JSON, sem cadastro, sem chave e com CORS liberado (dá para chamar direto do front-end). Para R$ 6.000 sem dependentes: INSS de R$ 641,51, imposto pela tabela de R$ 564,85, redução de R$ 179,75 e IRRF de R$ 385,10.&lt;/p&gt;

&lt;h3&gt;
  
  
  Endpoints
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;Exemplo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;salario-liquido&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/salario-liquido?salario=3500&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;inss&lt;/code&gt; (empregado, autônomo, plano simplificado, baixa renda)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/inss?salario=3500&amp;amp;categoria=individual&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;irrf&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/irrf?salario=6000&amp;amp;dependentes=1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;seguro-desemprego&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/seguro-desemprego?salario1=3000&amp;amp;salario2=3000&amp;amp;salario3=3000&amp;amp;solicitacao=1&amp;amp;meses=18&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;saque-aniversario&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/saque-aniversario?saldo=5000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;das-mei&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/das-mei?atividade=servicos&amp;amp;faturamento=52000&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;empregada-domestica&lt;/code&gt; (folha + guia DAE)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/empregada-domestica?salario=1621&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tabelas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api/v1/tabelas&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Três detalhes que pegam muita gente
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;INSS é por faixa, não alíquota única.&lt;/strong&gt; Para R$ 3.500 são 7,5% sobre R$ 1.621,00 + 9% sobre R$ 1.281,84 + 12% sobre R$ 597,16 = R$ 308,60.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;O eSocial trunca, não arredonda.&lt;/strong&gt; Na folha da doméstica com salário mínimo, o INSS sai R$ 121,57 (não 121,58) e a DAE fecha em R$ 445,76. Se o seu sistema arredonda, vai dar um centavo de diferença.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Desconto simplificado x deduções legais.&lt;/strong&gt; O IRRF usa o que for mais vantajoso: R$ 607,20 fixo ou INSS + R$ 189,59 por dependente + pensão. A redução da Lei 15.270 entra depois disso.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Dados abertos
&lt;/h3&gt;

&lt;p&gt;As tabelas (com fontes, vigência e data de conferência) estão em JSON no GitHub, licença CC BY 4.0: &lt;a href="https://github.com/MMMystery/tabelas-clt-2026" rel="noopener noreferrer"&gt;https://github.com/MMMystery/tabelas-clt-2026&lt;/a&gt; . Documentação completa da API: &lt;a href="https://contaclt.com/dados/api/" rel="noopener noreferrer"&gt;https://contaclt.com/dados/api/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Encontrou divergência com a fonte oficial? Abre uma issue com o link do ato que eu corrijo. Sugestões de endpoint também são bem-vindas.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transparência: eu mantenho o ContaCLT, que usa esses mesmos dados nas calculadoras.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>braziliandevs</category>
      <category>api</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
