feat(gps): a Settings GPS switch that reads the location; the watermark chip only prints it

The watermark chip used to be the only GPS control, so turning the watermark
off also stopped the photo from carrying coordinates. Split the two: a GPS
master switch in Settings decides whether the location is read at all, and the
chip decides whether it is drawn on the photo.

0x889d is also written as UTF-8 now — a place name with accents ("TAM KỲ, ĐÀ
NẴNG") was going out latin-1 and reading back as mojibake.
This commit is contained in:
2026-09-11 20:08:59 +07:00
parent ea9ff01a22
commit 40c91d2a75
4 changed files with 71 additions and 13 deletions
+14 -3
View File
@@ -84,10 +84,21 @@ function u32w(b: Uint8Array, p: number, v: number): void {
// --- entry builders --------------------------------------------------------
// ASCII-typed byte string, NUL-terminated as EXIF requires. The bytes are UTF-8:
// a place name ("TAM KỲ, ĐÀ NẴNG") or a market name can carry accents, and the
// charCodeAt & 0xff shortcut would mangle them into latin-1. Pure-ASCII text
// encodes byte-for-byte the same as before.
function asciiEntry(tag: number, text: string): Entry {
const data = new Uint8Array(text.length + 1); // NUL-terminated, as EXIF requires
for (let i = 0; i < text.length; i++) data[i] = text.charCodeAt(i) & 0xff;
return { tag, type: ASCII, count: data.length, data };
const bytes: number[] = [];
for (const ch of text) {
const cp = ch.codePointAt(0) as number;
if (cp < 0x80) bytes.push(cp);
else if (cp < 0x800) bytes.push(0xc0 | (cp >> 6), 0x80 | (cp & 0x3f));
else if (cp < 0x10000) bytes.push(0xe0 | (cp >> 12), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
else bytes.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f), 0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
}
bytes.push(0);
return { tag, type: ASCII, count: bytes.length, data: new Uint8Array(bytes) };
}
function shortEntry(tag: number, v: number): Entry {
+2 -2
View File
@@ -717,11 +717,11 @@ export async function processAndExportPhoto(
bytes,
{
dateTime: options?.capturedAt ? new Date(options.capturedAt) : null,
gps: useGeotag && gpsInfo ? { latitude: gpsInfo.latitude, longitude: gpsInfo.longitude } : null,
gps: gpsInfo ? { latitude: gpsInfo.latitude, longitude: gpsInfo.longitude } : null,
width: resultImage.width(),
height: resultImage.height(),
dpi: options?.dpi ?? null,
locationName: useGeotag && gpsInfo ? gpsInfo.locality : null,
locationName: gpsInfo ? gpsInfo.locality : null,
device: getDeviceIdentity(),
},
await readPhotoBytes(sourceUri)