
自由研究でSNSをつくろう! 呪いのアイコン編
今回の自由研究SNS(仮)作成は、アイコン関連の設定です。前回作成したプロフィール画面を拡張していきます!
初期アイコンを作り直す
最初にやったのは初期アイコンの作り直しです。うっかり準備を忘れていたのでおじいちゃんを使いまわしていましたが、ようやくお役御免となりました。おじいちゃん、ありがとう! おつかれさま!

初期アイコンは、ご覧のとおりマネキン的な顔にしておきました。先日、顔アイコンを描いたときは悪い癖で左向きに描いてしまっていましたが、右向きの顔にしました。SNSでは、左にはアイコン、右には発言を配置したいですからね。

ところで、これまではまず必要な要素を揃えるのと、ソースコードの可読性をマシにするため表面的なデザインを無視してシンプルに作ってきました。でも今回のアイコン周りは多用することになる要素なので、先に仕様を固めておきたい都合もあり、"スタイル"の設定を行なうようにしました。
せっかく自分で作るSNSですから、仕様についてはワガママを通します。複数サイズのドット絵を活かせる96ドット(16、24、32、48の公倍数)をアイコンサイズとし、この周辺はHTMLタグを手打ちせず関数を使って出力するようにしました。また、拡大しても画像がボケないように処理しています。
このあたりのことは、先日のアイコン拡大テストツール(下記参照)でもあらかじめ念頭に置いて作ったので、コードをかなり流用できました。これだけでも夏休みの自由研究としては充分だったかもしれませんね。
編集画面の仕様
さて、では実際に作ったアイコン編集画面を見ながら、その内容を紹介していきます。一番上には現在のアイコンが表示されており、その下に変更可能な項目が並んでいます。

現時点での設定要素は、「独自画像を利用」「プリセット画像を使用する」「枠」の3項目です。
「独自画像を利用」は、自分で用意した画像をアップロードしてアイコンとして設定するための機能です。画像はPNG形式しか選べませんが、さすがにJPEGも対応しましょうか。あとでこっそり変えておくかもしれません。
「プリセット画像を利用する」は、こちらで用意した画像から好きなものをアイコンとして設定する機能です。いわゆるラジオボタンで選択する形式ですが、"◎"の部分だけでなく画像をクリックしても選択できるようにしてあります。いままでやったことなかったので「自由研究らしくていいぞ!」と自画自賛しています。
「枠」は、アイコン全体の形状を決める設定要素です。枠線をこの太さで付けるかどうかはまだわかりませんが、形状としては「ましかく(正方形)」「カドまる」「まんまる(正円形)」から選べます。ミニマムSNSだと言いつつ、ちょっと凝ってみました。
ところでこの3種の枠、ちょうどTwitterにおけるアイコン形状変化の歴史となっています。最初は正方形だったのに、ある日急にカドが丸くなり、やがては丸そのものに……Instagramに形だけ似せたと言われていましたね。
でも、こういうのって画像を用意する側からするとすごく困るんです。
真四角であることを前提に独自の額縁を描いておいたのにカドが丸くなって台無しにされたり、丸にされたときにはキャラの髪先が欠けてしまったり……と、翻弄されるアイコン(と作者)をよく見かけたものです。そう、これが円形脱毛症の真実なのです。いや……ウソかも(かもじゃない)。
とにかく! キャンバスの中身のバランスは絵の重要な要素ですから、こういうのは無許可の改竄、著作権侵害と言えることをもっと世間に認識してもらいところです。形状変更の経緯を知らない人に「このアイコン描いたやつ、バランス感覚悪いなw」とか思われたら名誉棄損じゃないですか。もうプンプンですよ……絶対に許せない!
……というわけで、アイコン形状をユーザー自身が決められることはミニマムSNSといえど必須機能だったわけです。
というわけでこの仕組みを自作して積年の恨みを晴らしつつ、アイコン形状変更犯が円形脱毛症になる呪いをかけて次回へ続きます。

(つづく)
参考ソースコード(profile.php) ※サブルーチンやスタイル設定は別ファイルを参照しています。
<?php
//変数初期設定
$form_command='';
//表示のみなら未指定
//change=対象user変更(フォーム指定)
//edit=編集(編集項目は別途指定する)
//update=更新、全体プロフィール表示
$form_subcommand='';
//name
//icon
//about
$safe=true; //チェック初期化(有効判定)
$short_error=''; //エラーの理由
//サブタイトル
$sub_title='プロフィール';
//ログ(表示用)
$edit_log='';
//共通設定読み込み
include './common.php';
?>
<?php
//引数を取得(追加)
$target_new='';
if($form_command=='change') { //対象user変更時
if(isset($_POST['target_new'])) {
$target_new = $_POST['target_new'];
}
if($target_new<>''){
$target_new=$url_profile.'?'.$target_new;
header('location: '.$target_new); //リダイレクト指定
}
}
if($form_command=='edit' or $form_command=='update'){
//update実行時に受け取る
if(isset($_POST['form_subcommand'])) {
$form_subcommand=$_POST['form_subcommand'];
}
//edit開始時に受け取る
if(isset($_POST['change_name'])) {
$form_subcommand='name';
}
if(isset($_POST['change_about'])) {
$form_subcommand='about';
$form_command='update'; //updateへ移行
}
if(isset($_POST['change_icon'])) {
$form_subcommand='icon';
}
//edit(icon)用
$new_icon_frame=-1; //-1=未指定
$new_icon_number=0; //0=変更なし
if(isset($_POST['new_icon_frame'])) { //新しいicon枠の値
$new_icon_frame=$_POST['new_icon_frame'];
}
if(isset($_POST['new_icon_number'])) { //新しいプリセットicon番号
$new_icon_number=$_POST['new_icon_number'];
}
//edit,update共通
if(isset($_POST['new_value'])) { //更新後の値
$new_value=$_POST['new_value'];
}
if(isset($_POST['close'])) { //閉じる実行時
$form_command=''; //updateへ進まない/editを閉じる
}
}
//変数追加設定
//自身の保存情報を取得
utUserProfileLoad($account_id);
utUserAboutLoad($account_id);
$me_name=$loaded_name;
$me_icon_number=$loaded_icon_number;
$me_icon_frame=$loaded_icon_frame;
$me_icon_path=$loaded_icon_path;
$me_about=$loaded_about;
//表示対象user
$target_user=''; //指定なし
$target_me=0; //1=自分自身を対象とする場合
$target_found=false; //未発見
$target_error='';
if($url_ex[1]<>''){
//(URL引数による指定あり)
$target_user=$url_ex[1]; //URLで指定されたuser
}else{
if($target_new==''){
if($login==1){
//自身を指定して再表示
$target_new=$url_profile.'?'.$account_id;
header('location: '.$target_new); //リダイレクト指定
}else{
$target_error='プロフィールを表示する対象を指定してください。';
}
}
}
//自分自身が対象?
if($login==1 and $target_user==$account_id){
//(YES)
$target_me=1;
}
//対象user指定ありの場合
if($target_user<>''){
//user存在確認
$target_found=utUserExists($target_user);
if($target_found==true){
if($target_me==1){
$target_name=$me_name;
$target_icon_number=$me_icon_number;
$target_icon_frame=$me_icon_frame;
$target_icon_path=$me_icon_path;
$target_about=$me_about;
}else{
utUserProfileLoad($target_user);
utUserAboutLoad($target_user);
$target_name=$loaded_name;
$target_icon_number=$loaded_icon_number;
$target_icon_frame=$loaded_icon_frame;
$target_icon_path=$loaded_icon_path;
$target_about=$loaded_about;
}
}else{
$target_error='[ '.$target_user.' ]は、存在しません。';
}
}
?>
<?php //--------反映処理(update/edit)--------
if($target_me==1 and $form_command=='edit'){ //edit
if($form_subcommand=='icon'){
$go_save=0; //セーブ不要
//アイコン変更(プリセット)
if($new_icon_number==0){
//(0)
//変更なし
}else{
//(1以上)
$target_icon_number=$new_icon_number; //icon番号更新
$target_icon_path=utIconPath($target_icon_number); //path更新
$go_save=1; //要セーブ
//独自画像があれば削除
if(file_exists($user_upicon_path)){
unlink($user_upicon_path);
}
//ログ
$edit_log=$edit_log.'icon画像を変更!<br>';
}
//枠
if($new_icon_frame<>-1 and $new_icon_frame<>$target_icon_frame){
//変数更新
$target_icon_frame=$new_icon_frame;
$go_save=1; //要セーブ
$edit_log=$edit_log.'icon枠を変更!<br>';
}
//独自画像
$file_up=0;
if(is_uploaded_file($_FILES['file']['tmp_name'])){
move_uploaded_file($_FILES['file']['tmp_name'], $user_tempfile_path);
$file_up=1;
}
if ($file_up==1){
//拡大描画キャンバス
$image_canvas=imagecreatetruecolor(96,96);//キャンバスを作成
imagecolortransparent($image_canvas,imagecolorallocate($image_canvas,0,0,0));
//元画像
$image_icon=imagecreatefrompng($user_tempfile_path); //ロード
$sx=imagesx($image_icon); //サイズ取得
$sy=imagesy($image_icon);
//拡大描画
imageLayerEffect($image_canvas, IMG_EFFECT_ALPHABLEND);//透過設定
imagecopyresized($image_canvas, $image_icon,0,0,0,0,96,96,$sx,$sy); //実行
//ファイルに保存
imagepng($image_canvas,$user_upicon_path);
//事後処理
imagedestroy($image_icon); //破棄
imagedestroy($image_canvas); //破棄
unlink($user_tempfile_path); //一時画像を削除
//変数更新
$target_icon_number=0;
$target_icon_path=$user_upicon_path.'?'.date("ymdHis",filemtime($user_upicon_path));
$go_save=1; //要セーブ
$edit_log=$edit_log.'iconを独自画像に変更!<br>';
}
//プロフィール保存
if($go_save==1){
utUserProfileSave($target_user,$target_name,$target_icon_number,$target_icon_frame); //プロフィール保存
}
}
}
if($target_me==1 and $form_command=="update"){ //update
if($form_subcommand=='about'){
//ログ
$edit_log='about更新!';
//更新
$target_about=$new_value;
//セーブ
utUserAboutSave($target_user,$new_value);
}
if($form_subcommand=='name'){
$edit_log='name更新!';
//更新
$target_name=$new_value;
//セーブ
utUserProfileSave($target_user,$target_name,$target_icon_number,$target_icon_frame);
}
}
?>
<?php //--------共通処理:ページ開始--------
//style読み込み
include './style.php';
//head
echo "<head>";
$pagetitle=$main_title.'/'.$sub_title;
$pageurl=$url_profile;
//基礎設定
utHeadBasic($pagetitle,$pageicon,480);
//OGP設定
utHeadOgp($main_title,$main_title,$pagetitle,$pageurl,$ogppicture);
echo "</head>";
//body冒頭
echo '<body>';
echo '<b><a href="'.$url_index.'">'.$main_title.'</a></b><br>';
echo '<br>';
echo '<b>'.$sub_title.'</b><br>';
echo '<br>';
?>
<?php //--------共通処理:準備表示--------
//対象に関する警告
if($target_error<>''){
echo '<p>';
echo '・'.$target_error.'<br>';
echo '</p>';
}
//エラー
if($short_error<>''){
echo '<p>';
echo '・エラー: '.$short_error.'<br>';
echo '</p>';
}
//対象の表示名を作成
$target_fullname=$target_name;
if($target_name<>$target_user){
$target_fullname=$target_fullname.'('.$target_user.')';
}
?>
<?php //--------編集UI表示(edit)--------
if($target_me==1 and $form_command=='edit'){
//フォーム冒頭(編集時)
echo '<p>';
echo '<b>[ '.$target_fullname.' ]のプロフィール編集</b>';
echo '</p>';
echo '<form action="'.$url_profile.'?'.$target_user.'" method="post" enctype="multipart/form-data">';
if($form_subcommand=="name"){
echo '<input type="hidden" name="form_subcommand" value="name">';
echo '<input type="hidden" name="form_command" value="update">'; //閉じてupdate
echo '現在のニックネーム[ '.$target_name.' ]<br>';
echo '新しいニックネーム<br>';
echo '<input type="text" name="new_value" value="'.$target_name.'" size="30" required placeholder=""></p>';
echo '<input type="submit" name="close" value="取消"> ';
echo '<input type="submit" name="exec" value="決定">';
}
if($form_subcommand=="icon"){
echo '<input type="hidden" name="form_subcommand" value="icon">';
echo '<input type="hidden" name="form_command" value="edit">'; //editのまま更新
echo '<p>現在の設定<br></p>';
echo '<p>'.utPrintIcon($target_icon_path,$target_icon_frame).'</p>'; //<img>タグ出力
echo '<p>新しいアイコン<br></p>';
echo '<p>・独自画像を利用…ファイルを選択してから[アップロード]してください。<br></p>';
echo '<p> <input type="file" accept=".png" id="fileopen" name="file"></p>';
echo '<p> <input type="submit" value="アップロード"></p>';
echo '<p>・プリセット画像を使用する</p>';
echo '<p>';
$i_count=0;
echo '<div id="icon_select">';
while($i_count<=$last_preset_icon){
$checked="";
$skip=0;
if($i_count==0){
$radio_title='変更なし';
$radio_image=$target_icon_path;
$checked="checked";
}else{
$radio_title='No.'.$i_count;
$radio_image=utIconPath($i_count);
if($target_icon_number==$i_count){$skip=1;}
}
if($skip==0){
echo '<div id="icon_box">';
echo '<input class="radio_icon96" type="radio" name="new_icon_number" value="'.$i_count.'" '.$checked.' id="radio_icon_number'.$i_count.'">';
echo '<label for="radio_icon_number'.$i_count.'">';
echo $radio_title;
echo utPrintIcon($radio_image,$target_icon_frame);
echo '</label><br>';
echo '</div>';
}
$i_count=$i_count+1; //次へ
}
echo '</div>';
echo '</p>';
echo '<p>・枠</p>';
echo '<p>';
$i_count=0;
$radio_image=utIconPath(0);
echo '<div id="icon_select">';
while($i_count<=$last_icon_frame){
if($i_count==0){$radio_title='ましかく';}
if($i_count==1){$radio_title='カドまる';}
if($i_count==2){$radio_title='まんまる';}
if($target_icon_frame==$i_count){$checked="checked";}else{$checked="";};
echo '<div id="icon_box">';
echo '<input class="radio_icon96" type="radio" name="new_icon_frame" value="'.$i_count.'" '.$checked.' id="radio_frame'.$i_count.'">';
echo '<label for="radio_frame'.$i_count.'">';
echo $radio_title;
echo utPrintIcon($radio_image,$i_count);
echo '</label><br>';
echo '</div>';
$i_count=$i_count+1; //次へ
}
echo '</div>';
echo '</p>';
echo '<p> <input type="submit" name="exec" value="更新"></p>';
echo '<p> <input type="submit" name="close" value="閉じる"></p>';
}
}
?>
<style>
#icon_select{
width:100%;
height:128px;
}
#icon_box{
float:left;
width:112px;
height:112px;
margin-right:10px;
}
</style>
<?php //--------対象あり(プロフィール表示)--------
if($form_command<>'edit' and $target_found==true){
//フォーム表示
echo '<p>';
echo '<b>[ '.$target_fullname.' ]のプロフィール</b>';
echo '</p>';
echo '<form action="'.$url_profile.'?'.$target_user.'" method="post">';
echo '<input type="hidden" name="form_command" value="edit">';
echo '<p>・ID: '.$target_user.'<br></p>'; //ID
echo '<p>・Name: '.$target_name.' '; //ニックネーム(name)
if($target_me==1){
echo '<input type="submit" name="change_name" value="編集">';
}
echo '<br></p>';
$icon_title='プリセット No.'.$target_icon_number;
if($target_icon_number==0){
$icon_title='初期設定';
if(strpos($target_icon_path,$account_id)){
$icon_title='独自画像';
}
}
echo '<p>・Icon: '.$icon_title.' '; //icon名称
if($target_me==1){ //zzz
echo '<input type="submit" name="change_icon" value="編集">';
}
echo '<br>';
echo ' '.utPrintIcon($target_icon_path,$target_icon_frame); //<img>タグ出力
echo '<br></p>';
if($target_me==1){
//(自身のabout)
echo '<p>・About<br>';
echo ' <textarea name="new_value" cols="40" rows="10">'.$target_about.'</textarea><br>';
echo ' <input type="submit" name="change_about" value="更新">';
}else{
//(他者のabout)
if(trim($target_about)<>''){
echo '<div>・About:<br>'.str_replace("\n",'<br>',$target_about).'</div>';
}
}
echo '</p>';
}
?>
<?php //--------対象なし(対象入力)--------
if($form_command<>'edit' and $target_found==false){
//フォーム表示
echo '<p>';
echo '<b>プロフィールを表示する対象を指定して[実行]してください。</b><br>';
echo '</p>';
echo '<form action="'.$url_profile.'" method="post">';
echo '<input type="hidden" name="form_command" value="change">';
echo '対象ユーザーのID(半角英数字)<br>';
echo '<input type="text" name="target_new" size="30" required placeholder="半角英数字でIDを記入"></p>';
echo '<input type="submit" name="send" value="実行">';
}
?>
<?php //共通処理:フォーム締め
if($target_found==true and $login==1 and $target_me==0){
//他者のプロフィール
echo '<br>';
echo '<br>';
echo '<input type="submit" name="follow" disabled value="フォロー(未対応)">';
}
if($login==1){
echo '<input type="hidden" name="account_id" value="'.$account_id.'">';
echo '<input type="hidden" name="token" value="'.$token.'">';
}
echo '</form>';
?>
<?php //共通処理:締め
//実行結果
if($edit_log<>''){
echo '<p><b>実行結果</b><br>';
echo ''.$edit_log;
echo '</p>';
}
echo '<p>';
echo '<b>デバッグ情報</b><br>';
if($login==1){
echo '*ログイン済み('.$account_id.')<br>';
}else{
echo '*未ログイン<br>';
}
echo '*target_user: '.$target_user.'<br>';
echo '*form_command: '.$form_command.'<br>';
echo '*target_icon_number: '.$target_icon_number.'<br>';
echo '*target_icon_frame: '.$target_icon_frame.'<br>';
echo '*target_icon_path: '.$target_icon_path.'<br>';
if($common_error<>''){
echo '*common_error: '.$common_error.'<br>';
}
echo '</p>';
echo '</body>';
?>