ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

仿水印相机样式:Canvas 图片水印合成与定位时间字段设计

仿水印相机样式:Canvas 图片水印合成与定位时间字段设计 我最早接触“图片添加水印”这件事是给一个工程巡检记录工具做收尾。现场同事拍完照片要发到群里做留档但普通照片没有地点、时间、备注后期靠文件名和人脑记忆特别容易乱。后来我们做了个仿水印相机样式的小工具拍照后自动把定位、时间、备注、项目名叠在图片左下角生成一张一眼能看明白的记录图。这篇文章就把这套方案拆开讲清楚包括水印字段设计、定位权限申请、时间格式化、Canvas 绘制、移动端落地、常见坑和排查方法。适合前端、移动端开发者也适合想给自己的记录流程加一层结构化水印的产品、运营和现场管理人员。需要先把边界说清楚定位只做用户授权后的当前位置展示不涉及任何伪造、篡改、越权查询也不建议把水印图当成唯一凭证。1. 需求拆解与整体设计仿水印相机样式到底仿什么1.1 水印相机的常见信息结构很多人第一次做图片水印会把需求想成“在图片上写一行字”。真按这个思路做交付后大概率会被打回来因为水印相机样式的核心不是文字而是信息层级和可读性。用户要的是一张图在聊天窗口缩略图状态下也能快速看到时间、地点、备注。通常字段分成三组第一组是时间包括年月日、时分秒、星期第二组是位置包括省市区、街道、POI 名称有条件时再加经纬度第三组是业务备注包括项目名、工序、负责人、自定义文本。部分场景还会加天气、海拔、速度、设备型号和 logo。字段越多排版越不能随便堆否则图片边缘会变成一锅粥。我在实际项目里一般把水印分为“强字段”和“弱字段”。强字段是时间、地点、备注字号最大放在左下角或左上角带半透明底板弱字段是经纬度、天气、海拔、设备信息字号小一号放在强字段下方或右侧。这样做的理由是缩略图里人眼最先扫到的是大字号区域点开大图后才需要看细节。如果把经纬度放在第一行用户反而找不到重点。下面这张表是我们内部常用的字段配置你可以直接照着改。字段示例数据来源是否必填字号建议时间2026-05-12 14:32:08设备系统时间是图片短边的 4.5%地点杭州市余杭区文一西路定位 逆地理编码是图片短边的 4.5%备注3 号楼外墙巡检用户输入或业务系统是图片短边的 4.5%经纬度30.123456, 120.123456定位结果否图片短边的 2.8%天气多云 24 摄氏度天气服务否图片短边的 2.8%项目名某某改造项目配置否图片短边的 3.2%注意如果水印要用于正式留档地点字段最好保留“用户可编辑”入口。定位会有漂移室内尤其明显直接写死反而容易引发争议。1.2 先拍照后合成还是预览实时叠加实现水印相机样式有两条路线。第一条是相机预览时就把水印画在预览层上拍照时把预览层和相机帧一起合成第二条是先正常拍一张原始照片再在后期把水印渲染上去。两条路线没有绝对好坏但适用场景差别很大。实时叠加的优点是“所见即所得”用户按下快门之前就能确认水印位置缺点是相机预览帧和最终照片分辨率不一致容易导致水印位置偏移、文字发虚而且不同厂商相机实现的色彩处理不同最后合成出来可能偏色。后期合成的优点是稳定、可控、容易适配多端。原始照片先保存然后用同一套绘制逻辑在服务端、前端或客户端合成水印输出尺寸、字号、边距都能按原图短边计算不会因为预览分辨率变化而乱掉。缺点是用户拍完后才看到水印效果可能需要返工。我的建议是如果只是做记录、巡检、活动打卡类工具优先选后期合成如果产品强依赖“按快门即出图”的体验可以用实时叠加做预览但最终输出仍走后期合成。这样既保留体验又保证输出质量。还有一个容易被忽略的问题原图要不要保留。我的做法永远是保留原图水印图另存一份。原因很简单原图是资产水印图是展示和留档版本。用户可能后面要换模板、改备注、重新生成没有原图就只能重新拍。文件命名可以用“原图_时间戳.jpg”和“水印_时间戳.jpg”区分避免覆盖。1.3 技术路线Web Canvas、Flutter、原生 Android、小程序端对比技术选型要看你的交付形态。如果只是内部工具Web Canvas 最快如果要上应用商店Flutter 或原生更稳如果要在小程序端跑包体和 API 限制要提前评估。下面这张表是我在几个项目里踩过坑之后整理的对比重点看定位能力、图片处理能力和上线成本。路线定位能力图片合成优点缺点适用场景HTML5 Canvas浏览器定位需授权Canvas 绘制导出 Blob开发快跨平台大图性能一般定位精度依赖浏览器内部工具、网页端记录Flutter插件获取定位Canvas 或 image 库一套代码多端UI 一致字体、内存要调优移动端记录工具原生 Android系统定位 APICanvas/Bitmap控制力强性能好开发成本高相机类、工程类应用小程序端平台定位 APICanvas 2D分发方便包体、域名、权限限制轻量记录、活动留档选型时不要只看“能不能画字”要看“定位失败时怎么办”“用户拒绝授权时怎么办”“图片旋转 90 度怎么办”“中文字体在不同设备上是否一致”。这些细节才是决定项目能不能上线的关键。我的经验是先用 Web Canvas 做一版原型把字段、排版、导出流程跑通再迁移到 Flutter 或原生能省很多返工时间。2. 核心原理与参数定位、时间、坐标系与排版2.1 图片坐标系与设备像素比图片水印最容易翻车的地方是坐标系。原图有原始像素尺寸比如 4000×3000屏幕上显示时可能被缩放到 800×600Canvas 又有自己的逻辑尺寸和物理像素比。你如果在显示坐标系里画水印导出原图时水印会小得看不见如果直接按原图尺寸画又没有考虑用户预览时的位置。正确做法是所有排版参数都相对于“输出图片的短边”计算而不是相对于屏幕。比如字号设置为短边的 4.5%边距设置为短边的 3%行高设置为字号的 1.35 倍。这样无论原图是横屏还是竖屏水印比例都一致。设备像素比也要注意。Web 端如果 Canvas 的 width/height 属性设成 CSS 尺寸再叠加 devicePixelRatio可能导致绘制模糊。我的做法是离屏 Canvas 直接使用原图像素尺寸不做 DPR 缩放屏幕预览用另一个 Canvas按容器尺寸绘制。导出时只导出离屏 Canvas。移动端也是同理Bitmap 用原图尺寸创建绘制完成后再压缩输出。这样做的好处是水印位置和字号只算一次预览和导出不会互相干扰。还有一个细节是图片方向。手机拍的照片通常带有 EXIF Orientation 标记实际像素可能是横着的但查看器会根据 EXIF 旋转。如果你直接读像素画水印水印方向会错。必须在绘制前读取 EXIF 方向或者用支持自动方向处理的图片加载库。Web 端可以用 createImageBitmap 的 imageOrientation: from-image移动端可以用 ExifInterface 读取方向并旋转 Bitmap。这个坑不处理用户会看到水印倒着或侧着。2.2 合法定位权限与逆地理编码定位权限是水印相机样式的关键也是合规重点。正确流程是先向用户说明为什么需要定位再触发系统授权弹窗用户拒绝后不要反复弹窗而是提供“手动填写地点”的降级入口。Web 端用 navigator.geolocation.getCurrentPosition移动端用系统定位 API。拿到经纬度后再调用地图服务的逆地理编码接口换成人话地址。这里要注意逆地理编码通常需要地图服务商的 key并且有调用频率限制不要在前端硬编码长期有效的密钥最好由后端代理转发并做频率控制。定位精度方面室内、地下车库、高楼密集区容易出现漂移。我的经验是设置一个超时时间比如 8 到 10 秒超时后先展示经纬度或“定位中”不要卡住整个拍照流程。如果业务允许可以记录定位来源和精度半径比如“精度 35 米”让用户知道这个位置不是绝对精确。对于正式留档场景地点字段应该允许用户手动修正并在水印里区分“自动定位”和“手动填写”但不要伪造定位结果。注意定位信息属于个人敏感信息能不存就不存能本地处理就本地处理。如果必须上传要明确告知用户用途并避免在日志里明文打印完整经纬度和地址。2.3 时间与备注字段可信度与格式时间字段看起来简单其实也有讲究。设备时间可能被用户手动修改所以水印时间只能表示“设备当时显示的时间”不能宣称是权威时间。如果业务需要更强可信度可以在后端生成一个服务端时间戳但水印上仍然展示用户本地时间二者分开存储。格式上我建议用“YYYY-MM-DD HH:mm:ss”加星期例如“2026-05-12 14:32:08 星期二”。这样在缩略图和放大图里都容易识别。不要用“05/12/2026”这种容易产生地域歧义的格式。备注字段是业务价值的核心。它可以是用户手动输入也可以从业务系统带入比如项目名、工序、设备编号、负责人。我的做法是给备注设置最大长度比如 30 个汉字超过后自动换行或省略避免水印区域无限增高。换行时按字符宽度计算不要按字符数硬切因为中英文混排宽度不同。如果需要多行备注行高统一左对齐底板高度根据实际行数动态计算。时间、地点、备注的显示顺序也有讲究。我测试下来最容易被接受的顺序是第一行时间第二行地点第三行备注。因为时间和地点是“何时何地”备注是“做了什么”符合叙事逻辑。如果项目名很长可以放在地点上方用小字号做标题。不要把所有字段都加粗否则等于没有重点。2.4 水印排版的数学边距、字号、行高、阴影排版参数不要拍脑袋。我的常用公式是以输出图片短边 S 为基准主字号 F1 S × 0.045副字号 F2 S × 0.028边距 P S × 0.03行高 L F1 × 1.35底板内边距 IP F1 × 0.45底板圆角 R F1 × 0.25。比如一张 4000×3000 的图短边是 3000主字号约 135 像素边距约 90 像素。这个比例在手机屏幕上缩略看也清楚放大看也不突兀。水印底板建议用半透明黑色透明度 0.35 到 0.5文字用白色。如果照片背景本身很亮白色文字会看不清所以文字最好加一层轻微阴影偏移 2 到 4 像素模糊 4 到 8 像素颜色黑色透明度 0.5。不要用纯黑底板加纯白文字那样太像广告贴片观感很硬。圆角底板加一点内边距视觉上更像原生水印相机。如果水印要放在左下角底板坐标可以这样算x Py 图片高度 - P - 底板高度。底板宽度取最长一行文字的宽度加两倍内边距但不要超过图片宽度的 80%。如果超过就缩小字号或换行。右侧可以留出安全区避免被某些平台的裁剪规则切掉。横屏和竖屏分别处理竖屏可以适当加大边距横屏可以适当缩小。3. 用 HTML5 Canvas 实现可复现的水印合成3.1 准备页面与加载图片先给一个最小可运行的 Web 方案。页面只需要一个文件选择框、一个预览 Canvas、一个导出按钮。核心思路是用户选择图片后用 createImageBitmap 加载读取原图宽高创建同尺寸离屏 Canvas先画底图再画水印最后导出 Blob。这样不依赖任何后端适合快速验证排版。实际项目里可以把这段逻辑封装成 addWatermark(file, options) 函数方便复用。input typefile idfileInput acceptimage/* / canvas idpreview/canvas button idexportBtn导出水印图/buttonasync function loadImage(file) { const bitmap await createImageBitmap(file, { imageOrientation: from-image }); return bitmap; }这段代码的关键是 imageOrientation: from-image它会自动处理 EXIF 方向避免水印画歪。如果浏览器不支持这个参数就要自己读取 EXIF 并旋转。加载完成后离屏 Canvas 的宽高直接设为 bitmap.width 和 bitmap.height确保输出原图尺寸。3.2 EXIF 方向矫正与绘制底图拿到 bitmap 后创建离屏 Canvasfunction createCanvas(width, height) { const canvas document.createElement(canvas); canvas.width width; canvas.height height; return canvas; } function drawBaseImage(bitmap) { const canvas createCanvas(bitmap.width, bitmap.height); const ctx canvas.getContext(2d); ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height); return canvas; }如果不用 createImageBitmap而是用 Image 对象加载就要注意图片的 naturalWidth 和 naturalHeight以及 EXIF 方向。很多老项目在这里踩坑用户拍的是竖图读出来却是横的水印位置全错。解决方法是引入 exif-js 读取 Orientation然后根据 1 到 8 的方向值做旋转和镜像。虽然多几行代码但比上线后被用户投诉强。绘制底图时不要做任何缩放除非业务明确要求压缩。因为缩放会损失画质还会让后续水印字号计算变得复杂。如果原图太大比如超过 8000 像素可以在导出阶段再按最大边限制压缩但水印绘制阶段仍按原图尺寸处理。3.3 绘制水印卡片时间、地点、备注下面是核心绘制函数。参数都按短边比例计算字体用系统无衬线字体中文环境建议写 PingFang SC, Microsoft YaHei, sans-serif。function drawWatermark(canvas, info) { const ctx canvas.getContext(2d); const S Math.min(canvas.width, canvas.height); const F1 Math.round(S * 0.045); const F2 Math.round(S * 0.028); const P Math.round(S * 0.03); const lineHeight Math.round(F1 * 1.35); const innerPad Math.round(F1 * 0.45); const radius Math.round(F1 * 0.25); const lines [ { text: info.time, font: 600 ${F1}px PingFang SC, Microsoft YaHei, sans-serif }, { text: info.address, font: 500 ${F1}px PingFang SC, Microsoft YaHei, sans-serif }, { text: info.remark, font: 500 ${F1}px PingFang SC, Microsoft YaHei, sans-serif }, { text: info.latLng, font: 400 ${F2}px PingFang SC, Microsoft YaHei, sans-serif } ]; let maxWidth 0; lines.forEach(line { ctx.font line.font; maxWidth Math.max(maxWidth, ctx.measureText(line.text).width); }); const boxWidth Math.min(maxWidth innerPad * 2, canvas.width * 0.8); const boxHeight lines.length * lineHeight innerPad * 2; const x P; const y canvas.height - P - boxHeight; ctx.save(); ctx.fillStyle rgba(0, 0, 0, 0.42); roundRect(ctx, x, y, boxWidth, boxHeight, radius); ctx.fill(); ctx.textBaseline top; ctx.shadowColor rgba(0, 0, 0, 0.55); ctx.shadowBlur Math.max(2, Math.round(F1 * 0.08)); ctx.shadowOffsetX 0; ctx.shadowOffsetY Math.max(1, Math.round(F1 * 0.03)); lines.forEach((line, index) { ctx.font line.font; ctx.fillStyle #ffffff; ctx.fillText( line.text, x innerPad, y innerPad index * lineHeight ); }); ctx.restore(); } function roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x r, y); ctx.arcTo(x w, y, x w, y h, r); ctx.arcTo(x w, y h, x, y h, r); ctx.arcTo(x, y h, x, y, r); ctx.arcTo(x, y, x w, y, r); ctx.closePath(); }这段代码里maxWidth 通过 measureText 动态测量底板宽度不会拍脑袋。boxWidth 最大限制为图片宽度的 80%防止长备注撑爆画面。如果备注太长应该在传入之前做截断或换行不要在绘制时硬压。实际使用时可以把 lines 改成动态数组根据配置决定显示哪些字段。3.4 定位获取与地址回填Web 端定位可以这样写function getCurrentPosition() { return new Promise((resolve, reject) { if (!navigator.geolocation) { reject(new Error(当前环境不支持定位)); return; } navigator.geolocation.getCurrentPosition( position { resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude, accuracy: position.coords.accuracy }); }, error reject(error), { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 } ); }); }拿到经纬度后再调用逆地理编码。这里不要把地图服务密钥写死在前端最好由后端提供接口前端只传经纬度。后端返回地址后回填到地点字段。如果用户拒绝定位就显示“未获取定位”并允许手动输入。这个降级流程非常重要很多项目只做成功路径用户一拒绝就白屏体验很差。注意定位权限弹窗不要一进页面就弹。先让用户看到拍照或选图界面点击“添加水印”时再申请权限用户更容易理解为什么需要定位。3.5 导出高清图、批量与水印配置绘制完成后导出可以用 canvas.toBlobcanvas.toBlob(blob { const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download watermark_${Date.now()}.jpg; a.click(); URL.revokeObjectURL(url); }, image/jpeg, 0.92);JPEG 质量建议 0.9 到 0.95太低会出现文字边缘模糊太高文件又太大。如果图片里有大面积纯色PNG 可能更清晰但体积会大很多。批量处理时不要一次创建太多 Canvas容易内存暴涨。我的做法是串行处理每张图处理完释放 bitmap 和 Canvas 引用。配置方面可以把字段、字号比例、边距、底板透明度写成 JSON前端根据配置渲染。这样换模板不用改代码只改配置即可。4. Flutter/Android 端落地要点4.1 Flutter 依赖与定位权限Flutter 方案适合要上架移动端的项目。常用组合是 image 库做图片解码和绘制geolocator 做定位path_provider 做文件存储。Android 和 iOS 都要在配置文件中声明定位权限说明并且要在运行时请求权限。定位失败时同样要提供手动输入入口。Flutter 的 Canvas 绘制逻辑和 Web 类似也是按图片短边计算字号和边距。dependencies: image: ^4.1.0 geolocator: ^11.0.0 path_provider: ^2.1.0final position await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, timeLimit: const Duration(seconds: 10), );这段代码的关键是 timeLimit避免定位一直不返回导致界面卡死。拿到经纬度后可以调用后端接口换地址。注意Flutter 里中文字体需要确认是否被打包否则在某些设备上可能显示方框。最稳妥的方式是随应用打包一份中文字体并在绘制时指定字体文件。4.2 图片绘制与中文水印Flutter 用 image 库绘制文字的流程是解码原图创建画布画底图再画水印底板和文字。image 库的 drawString 支持指定字体但复杂排版不如 Canvas 灵活。如果需要精确控制多行、圆角、阴影我更推荐用 Flutter 的 Canvas 绘制 UI再用 RepaintBoundary 转成图片。这样字体、行高、阴影都能用 Flutter 自己的能力控制跨端一致性更好。实际落地时我会把水印渲染封装成一个 Widget传入图片和字段预览时直接显示导出时用系统截图或 RepaintBoundary.toImage。注意导出分辨率要设置为原图尺寸不要用屏幕尺寸。否则用户拿到的图片只有手机屏幕那么大放大就糊。4.3 Android 原生CameraX 与 MediaStore如果做原生 AndroidCameraX 负责拍照ExifInterface 读取方向Bitmap 负责合成MediaStore 负责保存。权限方面定位权限和存储权限分开申请Android 新版本对存储访问有限制建议用 MediaStore 写入相册不要直接写外部存储路径。合成时用原图 Bitmap按短边计算水印尺寸绘制完成后压缩成 JPEG。原生方案的优势是性能好大图处理不容易 OOM。但要注意 Bitmap 内存4000×3000 的 ARGB_8888 图片大约占 48MB如果同时开好几张很容易崩溃。我的做法是采样读取原图计算合适采样率再创建输出 Bitmap。水印绘制完成后及时 recycle 中间 Bitmap。拍照后立刻保存原图再异步合成水印图不要阻塞拍照流程。4.4 模板配置化与多端一致多端项目最怕水印样式各写一套。我的做法是定义一份模板 JSON包含字段顺序、字号比例、颜色、边距、圆角、是否显示经纬度等。Web、Flutter、Android 都读同一份配置渲染逻辑按各自平台实现。哪怕实现不同最终输出的比例和位置也一致。测试时用同一张样图跑三端导出后叠在一起对比肉眼看不出来才算过关。模板配置还要考虑业务变化。比如今天显示“项目名”明天要改成“工单号”如果写死在代码里就要发版。配置化之后后台改字段即可。字段为空时自动隐藏不要显示“null”或“undefined”。这是很多工具的小毛病用户看到会觉得不专业。5. 常见问题与排查表定位失败、文字模糊、导出异常5.1 定位权限与定位失败定位问题占比最高。常见表现是用户拒绝授权、系统定位开关关闭、室内信号弱、超时、逆地理编码失败。排查时先看权限状态再看系统定位是否开启然后看是否设置了超时和降级。下面这张表可以直接当排查清单用。现象可能原因排查方法解决方式点添加水印无反应未申请定位权限查看权限状态运行时申请拒绝后手动输入一直显示定位中超时未处理加日志看回调设置 8 到 10 秒超时地址为空逆地理编码失败看接口返回展示经纬度允许手动填写地点明显偏移室内或信号弱看精度半径提示精度允许修正安卓正常 iOS 失败权限描述缺失检查配置文件补充权限说明文案定位权限检测不要做成“反复弹窗”。用户拒绝一次后应该展示一个设置入口让用户主动去系统设置里开。强行弹窗会让用户反感甚至导致应用被差评。5.2 文字模糊、错位、拉伸文字模糊通常是因为 Canvas 逻辑尺寸和实际输出尺寸不一致或者导出时被压缩得太狠。错位通常是因为预览和导出用了两套坐标。拉伸通常是因为绘制时没有保持宽高比或者图片方向没矫正。我的排查顺序是先看原图宽高再看 Canvas 宽高再看水印计算用的短边最后看导出质量。只要这四步一致基本不会出问题。另外一个隐藏问题是字体。不同设备默认字体不同同样的字号可能宽度不一样。解决办法是打包固定字体或者用 measureText 动态计算底板宽度不要写死。对于中文尽量用系统中文字体不要用只支持英文的字体否则会显示方框。5.3 中文乱码与跨端字体中文乱码在服务端合成时更常见因为服务器可能没有安装中文字体。如果水印在服务端生成要确认服务器字体目录里有可用的中文字体并在代码里指定字体路径。客户端合成相对好一些但也要注意 Flutter 打包字体、Android 字体族、iOS 字体名的差异。测试时至少覆盖一台 Android 和一台 iOS不要只看模拟器。如果水印包含特殊符号比如摄氏度、括号、冒号也要确认字体支持。最稳妥的测试方法是生成一张包含全部字段的样图放大到 200% 检查边缘再缩小到缩略图检查可读性。5.4 导出体积、偏色与隐私导出体积过大通常是 JPEG 质量太高或原图太大。可以在导出时限制最大边比如 4096 像素质量 0.92。偏色可能是颜色空间问题Canvas 默认 sRGB一般不需要特殊处理。如果发现偏色检查是否在绘制过程中用了滤镜或混合模式。隐私方面水印里的定位、时间、备注可能包含敏感信息分享前要给用户确认机会不要自动上传到公开空间。注意不要在水印里默认显示完整经纬度。如果业务不需要精确到城市或街道即可减少隐私暴露。6. 实操心得与避坑清单6.1 绝不伪造时间地点这一点必须放在最前面。水印相机的价值是记录真实信息任何伪造时间、地点、备注的行为都会破坏记录的可信度也可能违反平台规则和职业道德。定位只使用用户授权后的当前位置时间只使用设备或服务端真实时间备注由用户或业务系统填写。不要做“手动改定位”“模拟位置”“修改时间”的功能。哪怕技术上能做也不要放进产品里。合规和诚信是这类工具能长期使用的前提。6.2 保留原图与可追溯我吃过一次亏用户生成水印图后想改备注但原图被覆盖了只能重新拍。从那以后我的方案永远保留原图水印图另存。文件名里带时间戳和随机串避免重复。如果业务需要追溯可以把原图、水印图、字段 JSON 一起存JSON 里记录生成时间、模板版本、定位精度。这样后续换模板可以重新生成不会丢数据。6.3 性能与体验细节大图合成很吃内存尤其是移动端。我的经验是不要在 UI 线程做完整尺寸合成放到后台线程或 isolate批量处理串行执行导出后及时释放资源预览用缩略图不要直接用原图。体验上定位过程要有加载状态水印生成要有进度提示失败要有明确原因和下一步操作。不要只显示“失败”用户不知道该怎么办。6.4 上线前测试清单上线前至少跑一遍这些用例横图、竖图、带 EXIF 方向、无定位权限、定位超时、地址为空、备注超长、中文英文混排、批量 10 张、低内存设备、深色背景、浅色背景、导出后查看缩略图和放大图。每一条都过一遍能避免大部分线上问题。模板配置变更后也要回归测试确保字段为空时不会出现空白底板或错位。最后分享一个小技巧如果你不确定水印字号是否合适就把导出图缩到手机聊天窗口里那个大小看一眼。时间、地点、备注如果还能一眼看清说明比例没问题如果糊成一团就把主字号提高 0.5 个百分点。这个办法比在电脑上放大看有效得多因为用户大多数时候就是在小窗口里看图。
返回列表