壁紙アプリを長く運営していると、ユーザーが作った画像が SNS で広がる瞬間がいちばん嬉しい一方で、出どころのわからないまま拡散していくのがもったいなくも感じます。私自身、個人開発で壁紙・癒し系のアプリを App Store と Google Play で運営してきましたが、シェアされた1枚にアプリ名が小さく入っているだけで、そこから検索して入ってくる人が確実にいます。
ただし、透かしの入れ方を間違えると逆効果です。ユーザーが自分の端末に保存する壁紙にまで透かしを焼き込むと、「ロック画面にロゴが乗る」と嫌われて低評価につながります。狙いは一つで、保存用は無加工のまま、SNSシェア用に書き出すときにだけ透かしを合成する二段構えを作ることです。Rork が出力する Expo(React Native)のコードを土台に、この作り分けを実装します。
保存とシェアで画像を分ける設計
最初に方針を決めます。アプリ内で扱う画像は「原本」一つですが、出口を二つに分けます。
端末への保存・壁紙設定に渡すのは、原本そのまま。透かしは入れません。
SNS や他アプリへシェアするときだけ、原本の上に透かしを合成した新しい画像を生成し、それを共有シートに渡します。
この分岐をシェア処理の手前に一段かませるだけで、ユーザー体験を損なわずに導線だけを残せます。私はこの判断を最優先で推奨します。透かしは「ユーザーのため」ではなく「拡散先の人のため」に入れる、と割り切ると設計がぶれません。
手早く作るならview-shotで重ねて撮る
いちばん実装が軽いのは、画面に画像と透かしを重ねて表示し、その領域をキャプチャする方法です。react-native-view-shot を使います。
import { useRef } from "react" ;
import { View, Image, Text, StyleSheet } from "react-native" ;
import ViewShot, { captureRef } from "react-native-view-shot" ;
import * as Sharing from "expo-sharing" ;
export function WatermarkedShare ({ uri } : { uri : string }) {
const shotRef = useRef < ViewShot >( null );
async function shareWithWatermark () {
// オフスクリーン相当のViewを原本解像度でキャプチャする
const out = await captureRef (shotRef, {
format: "jpg" ,
quality: 0.95 ,
result: "tmpfile" ,
});
await Sharing. shareAsync (out);
}
return (
< ViewShot ref = { shotRef } style = { styles.canvas } >
< Image source = { { uri } } style = { styles.base } resizeMode = "cover" />
< Text style = { styles.mark } >made with Aurora Wallpapers</ Text >
</ ViewShot >
);
}
const styles = StyleSheet. create ({
canvas: { width: 1080 , height: 1920 },
base: { width: 1080 , height: 1920 },
mark: {
position: "absolute" , right: 24 , bottom: 28 ,
color: "rgba(255,255,255,0.85)" , fontSize: 28 , fontWeight: "600" ,
},
});
この方法の利点は実装が速いことですが、落とし穴もあります。captureRef は基本的に画面のピクセル密度で撮るため、width: 1080 と書いても Retina 端末では実寸が変わり、書き出し画像がぼやけたり、逆に巨大になったりします。原本が高解像度の壁紙だと、この「画面依存」がそのまま画質劣化として出ます。試作や低めの解像度なら view-shot で十分ですが、解像度を厳密に保ちたい壁紙アプリでは次の方法を推奨します。
解像度を保つならSkiaでオフスクリーン合成する
@shopify/react-native-skia を使うと、画面に出さずに指定したピクセル数で合成できます。原本の解像度を一切落とさず、透かしだけを焼き込めるのが強みです。
import {
Skia, Canvas, Image as SkImage, Text as SkText,
useImage, useFont,
} from "@shopify/react-native-skia" ;
export async function renderWatermarked (
srcUri : string , width : number , height : number
) : Promise < string > {
const image = await Skia.Data. fromURI (srcUri)
. then (( d ) => Skia.Image. MakeImageFromEncoded (d));
if ( ! image) throw new Error ( "decode failed" );
// 原本と同じ解像度のオフスクリーンSurfaceを用意する
const surface = Skia.Surface. MakeOffscreen (width, height) ! ;
const canvas = surface. getCanvas ();
canvas. drawImageRect (
image,
Skia. XYWHRect ( 0 , 0 , image. width (), image. height ()),
Skia. XYWHRect ( 0 , 0 , width, height),
Skia. Paint ()
);
const font = Skia. Font ( /* typeface */ undefined , Math. round (width * 0.026 ));
const paint = Skia. Paint ();
paint. setColor (Skia. Color ( "rgba(255,255,255,0.85)" ));
canvas. drawText ( "made with Aurora Wallpapers" , width * 0.04 , height - height * 0.03 , paint, font);
surface. flush ();
const snapshot = surface. makeImageSnapshot ();
const base64 = snapshot. encodeToBase64 (); // 後段でファイル化して共有
return base64;
}
ポイントは、透かしの文字サイズや余白を固定ピクセルではなく width * 0.026 のように比率で決めることです。縦長の壁紙も正方形のアイコン素材も同じ関数で扱えますし、端で見切れる事故も避けられます。私の場合、フォントサイズは横幅の2.6%前後、右下の余白は高さの3%前後に落ち着きました。
どちらを選ぶかの基準
二つの方法は、速さと品質のトレードオフです。私の使い分けはこうしています。
view-shot は、まず動くものを出したいプロトタイプ段階や、SNS のタイムライン表示が主目的でそこまで高解像度を要求しない場合に向きます。実装が数十行で済むので、最初の検証には十分です。
Skia は、本番の壁紙アプリのように原本の解像度が命で、拡散先で等倍表示されても粗が出てはいけない場合に向きます。導入のコストは上がりますが、画質の主導権をこちらが握れます。長く運用するアプリには Skia を推奨します。
リリース前に潰しておきたい劣化
透かし機能は、出してから「なんとなく画質が悪い」と気づかれるのが厄介です。私が本番前に必ず見ているのは次の3点です。
書き出し後の画像の実ピクセル数が、原本と一致しているか。view-shot 経由だと知らぬ間に2倍や0.5倍になりがちです。
JPEG の品質を落としすぎていないか。quality を 0.8 未満にすると、空のグラデーションにバンディングが出ます。私は 0.92〜0.95 を基準にしています。
透かしの不透明度。85%前後だと視認できて邪魔になりませんが、100%だと主張が強すぎてシェアされなくなります。
拡散から課金導線へつなぐ
透かしはブランド露出の入口ですが、そこで終わらせないほうがもったいないです。シェア用画像の透かしに「アプリ名」だけでなく、控えめに製品の世界観を込めておくと、流入したユーザーがそのまま離脱せずに残ります。
私の運営では、無料ユーザーのシェア画像には透かしを入れ、有料メンバーには透かしなしの書き出しを開放する設計にしています。透かしを消せること自体が小さな課金理由になり、AdMob の広告収益とは別の収益線として効いてきます。透かしを「制限」ではなく「無料で使ってもらうかわりの、ささやかなお返し」として位置づけると、ユーザーも納得しやすいと感じています。
まずは自分のアプリのシェア処理に一段、書き出しの分岐を足すところから始めてみてください。原本を汚さずに出口だけを変える。その一手だけで、拡散と流入の質がはっきり変わります。