From ea281d79741337ba6d35622adce20c1522bf082e Mon Sep 17 00:00:00 2001 From: 3dtours Date: Tue, 8 Sep 2026 11:30:47 +0700 Subject: [PATCH] feat: monochrome Noto Emoji subset icons (pin, camera) before watermark lines --- assets/NotoEmoji-GPS.ttf | Bin 0 -> 2564 bytes src/components/Viewfinder.tsx | 18 +++++++++++++++--- src/utils/exportEngine.ts | 24 ++++++++++++++---------- 3 files changed, 29 insertions(+), 13 deletions(-) create mode 100644 assets/NotoEmoji-GPS.ttf diff --git a/assets/NotoEmoji-GPS.ttf b/assets/NotoEmoji-GPS.ttf new file mode 100644 index 0000000000000000000000000000000000000000..207709b676934b741808ab961994da4cc5312883 GIT binary patch literal 2564 zcmZuzZERat8Gg?__rv$vwd48PZqqEWoseW`O)4k$B-E~}x6x@TsINgcp@PMy!4xp?N) z?-Qvm9?hMdgx(ALEJn}h*f&l++jju*q&&W(qp+X2*Yz9NA1C7H^CxpBc76QYe+@V)FFaDY-y1sAspB7YT#cc6^RV{|j)(dBpcV$ziM8z#bC4<1}-6?s$DT zuxFLL9w>-z|2Z`(#!r5??>ltec~`%z{vOanlF)+Sr;Ry2l-Nzz>AHT|jntTHaA_m< z_uwT+WO|fjqmx}B1-14mZ^59oG4c=Tl=PS{ph%1}Nsw^cOf2LBhYlT}XX>Cv4cZtJ zyS0%)8-pApoqtMsqnqofPml;8<1?{d!yQpQsN3aYo~``B{X`_TvgUr9iNzksTzB5p zV(3JGcG7No9)^j|&QyOo-It6+!ltTn9_$N-`aFSlG`T3>4@6Gmm`m=kT6Xs5?HivYt#~X|WTiZ0=@%pUZ?yZBbhM2{ zb;XdIS|iO|4R``Owgn>Cq3iV~>~S6|Yo|VXmPY8S^csB^dz0u)1^d%|@nj?%G}ZRc zBzmM&|I<=mvQ-M3eyJ;&7AwR8RW)ozB2WEKN0fLnol3=1@u2wnJKN(4H5`nGfx~e) zgi`&89S)a}{-fL97Ky((yEu1g zadvj`(%jBg)%smOxAG<{lKn_Vx1N%%}==|Dqh`$J4cM+u8AJt?lB23 z1WCVw?3yv|${UEp8~G>+tB3Q8D1wB(N!)Qax5v$kEL+hr!C)gYGrSZ+&)~b2nYr&r zi(3^@OoXIs?8nd0`*cA<#;XYMCe#=9Dt^;611eST7mLOF)i%>C7HwJZk2F>(udi3j z<+9^2%^Sp%5%!f=mmjRGz;>lkG%(sJ1*ZKh`zL(}k(;)l)(=8i(f`zlCwm0Tc+b=B5Oj%FD5 zONJpiU;*MBZ!ib1t*@=GmQbN;7|i0enj`HhmARq8ux>CYje4n6v8&Z`rB&C={c}Fa8)bMg=CDOL0pUU~5em z&>joDvRc%Zcnj5*KPeOnAK#%kl?o*lT_qbGx^F4wvRQ_yTHIO0-UtB~P?nh`*|u$Q zh_a>DA=^0bjgTob9uKKYg~CUlu96yRDd_R|TESE*4~oTmW%3t`WFi=uCSd@B;=d6Q zomsMoShpkqx}a1`psJ-P21v1z_S%EHckez}tC6H_3kHLJougc+mTi)KQ21o3mElts z7Vy!ywao_Bv{H$42)5NEf-Nr_MhTl|D9~9t1gcd)HA|6HkYp*2z|JO~m4xqRA^Q+h z)L|1h{74({DI{uh3#hsURYgIi(t3@!2jM+3Ib}B?iM$cPsIfe1lu{~@Vptruh9x~x k$!29-RK_g{ct+eW*0l9M2F|Fgci@gaO3dh%j$$tVAGeln1ONa4 literal 0 HcmV?d00001 diff --git a/src/components/Viewfinder.tsx b/src/components/Viewfinder.tsx index 08e8e7e..3820d78 100644 --- a/src/components/Viewfinder.tsx +++ b/src/components/Viewfinder.tsx @@ -93,6 +93,12 @@ export default function Viewfinder({ require('../../assets/CourierPrime-Regular.ttf'), Math.round(vw * 0.032) ); + // Monochrome Noto Emoji subset (📍 U+1F4CD, 📷 U+1F4F7) for the watermark + // icons — glyphs painted with the same color as the text next to them. + const iconFont = useFont( + require('../../assets/NotoEmoji-GPS.ttf'), + Math.round(vw * 0.032) + ); const adjustments = recipe.adjustments; // Camera captures apply exposure compensation as a hardware AE bias (see the @@ -478,7 +484,7 @@ export default function Viewfinder({ }; const renderGPSWatermark = () => { - if (!useGeotag || !gpsInfo || !customFont) return null; + if (!useGeotag || !gpsInfo || !customFont || !iconFont) return null; const locationName = gpsInfo.locality || 'STREET VIEW'; const d = new Date(gpsInfo.timestamp); @@ -489,11 +495,17 @@ export default function Viewfinder({ // readable; polaroid frames push it further up onto the white area edge. const yOffset = vh - (selectedFrame === 'polaroid' ? vh * 0.18 + 24 : 150); const xOffset = selectedFrame === 'polaroid' ? vw * 0.08 : 16; + const gap = (rect: { width?: number } | null) => + Math.round((rect?.width ?? Math.round(vw * 0.032)) + vw * 0.012); + const pinW = iconFont.measureText('📍'); + const camW = iconFont.measureText('📷'); return ( - - + + + + ); }; diff --git a/src/utils/exportEngine.ts b/src/utils/exportEngine.ts index 4211be0..6e9edba 100644 --- a/src/utils/exportEngine.ts +++ b/src/utils/exportEngine.ts @@ -236,18 +236,19 @@ export async function processAndExportPhoto( // 8. Render GPS Geotag Watermark if (useGeotag && gpsInfo) { const fontAsset = Asset.fromModule(require('../../assets/CourierPrime-Regular.ttf')); - if (!fontAsset.localUri) { - await fontAsset.downloadAsync(); - } - - const fontData = await FileSystem.readAsStringAsync(fontAsset.localUri!, { - encoding: FileSystem.EncodingType.Base64, - }); + const iconFontAsset = Asset.fromModule(require('../../assets/NotoEmoji-GPS.ttf')); + await Promise.all([fontAsset.downloadAsync(), iconFontAsset.downloadAsync()]); + const [fontData, iconFontData] = await Promise.all([ + FileSystem.readAsStringAsync(fontAsset.localUri!, { encoding: FileSystem.EncodingType.Base64 }), + FileSystem.readAsStringAsync(iconFontAsset.localUri!, { encoding: FileSystem.EncodingType.Base64 }), + ]); const typeface = Skia.Typeface.MakeFreeTypeFaceFromData(Skia.Data.fromBase64(fontData)); + const iconTypeface = Skia.Typeface.MakeFreeTypeFaceFromData(Skia.Data.fromBase64(iconFontData)); - if (typeface) { + if (typeface && iconTypeface) { const fontSize = Math.round(width * 0.032); // 3.2% of image width const font = Skia.Font(typeface, fontSize); + const iconFont = Skia.Font(iconTypeface, fontSize); const textPaint = Skia.Paint(); textPaint.setColor(Skia.Color('#f59e0b')); // Amber 500 @@ -258,9 +259,12 @@ export async function processAndExportPhoto( const yOffset = height - (frameId === 'polaroid' ? height * 0.15 : fontSize * 1.5); const xOffset = frameId === 'polaroid' ? width * 0.08 : fontSize * 1.2; + const gap = Math.round(fontSize * 1.2); // icon box (~1em) + spacing - canvas.drawText(`📍 ${locationName}`, xOffset, yOffset - fontSize * 1.4, textPaint, font); - canvas.drawText(timestampStr, xOffset, yOffset, textPaint, font); + canvas.drawText('📍', xOffset, yOffset - fontSize * 1.4, textPaint, iconFont); + canvas.drawText(locationName, xOffset + gap, yOffset - fontSize * 1.4, textPaint, font); + canvas.drawText('📷', xOffset, yOffset, textPaint, iconFont); + canvas.drawText(timestampStr, xOffset + gap, yOffset, textPaint, font); } }