雑記

縁取り文字生成アプリを作ったよ!(自分用)【Processing: Text Outline App】

文字縁取りアプリ

イラストレーターで作成したような縁取りした文字を生成するアプリ(PC用)を自作してみました。
アウトラインされたテキストがサクッとできるので、よかったら使ってみてくださいね〜〜^-^

縁取り文字生成アプリでは『段付き斜めグラデーション縁取り文字』を作ることができます。
(勝手に命名しました😁)

縁取り文字生成アプリの情報
  • アプリはPC用、MacでもWindowsでもOK!(たぶん..)
  • フォントはヒラギノ角ゴシックorヒラギノ丸ゴシック
  • グラデーションの方向は斜めのみ、指定は3色まで
  • フォントの大きさ、段付きありなし、縁取りの太さが調節可能
  • 色は、カラーパネルから選択
  • 1800×300の画像を作成します、Macだと画面からはみ出ます。。汗
  • Processing 3(Java系)で作成(Processing 4でも動きます)

※自分用に開発したので、使いやすいわけではありません。
・画像生成まで数分かかる場合があります
・もちろん改変OK、再配布の際は出典明記でお願いします。

縁取り文字生成アプリのダウンロード

Processingのデフォルトで、Mac版(.app)とWindows版(.exe)のアプリとして書き出せたので、
載せておきます〜
動作検証などは行っていないので、動かなくても許してね^-^ (つくもりは動いているのでOK)
一応Processingの3と4の両方を載せておきます

縁取り文字生成アプリのサンプルプログラム

Processing 3で作成していますが、Processing4でも動きました。
詳細な説明は省いています。ご了承ください😌

import javax.swing.*;
import java.awt.*;
import controlP5.*;

ControlP5 cp5;
JLayeredPane pane;
JTextField jt1;

//png用
PGraphics gr;

//フォント
PFont font;
PFont font_w6;

boolean color_r[][];
boolean color_save[][];

String input_text = "";
String input_pretext = "";

color mid_color1;
color mid_color2;
color mid_color3;
color front_color;
color back_color;
boolean color3_flag = true;

int front_value;
int mid_value;
int side_value;
int back_value;

float min_x = 0;
float min_y = 0;
float max_x = 0;
float max_y = 0; 
float width_x = 0;
float height_y = 0;
float wh = 0;

  void settings() {
    size(1800, 800);
    color_r = new boolean [600][100];
    color_save = new boolean [1800][300];
  }

  void setup() {
    background(150);
    font = createFont("arial", 20, true);
    font_w6 = createFont("HiraginoSans-W6", 30, true);
    //font_w6 = createFont("HiraMaruProN-W4", 30, true);
    
    java.awt.Canvas canvas =
                      (java.awt.Canvas) surface.getNative();
    pane = (JLayeredPane) canvas.getParent().getParent();
  
    textFont(font);
    textSize(20);
    textAlign(LEFT, TOP);
    text("【 縁取り文字生成アプリ 】", 10,  10);
    
    //テキストボックス
    jt1 = new JTextField();
    jt1.setBounds(10, 40, 220, 40);
    pane.add(jt1);
    text("← ここにテキストを入力", 250,  50);
    
    cp5 = new ControlP5(this);
      
    cp5.addColorWheel("c" , 800 , 20 , 400 ).setRGB(color(200,200,255));
    
    cp5.addButton("COLOR_F")
     .setPosition(10, 100)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
   cp5.addButton("COLOR_M1")
     .setPosition(160, 100)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
   cp5.addButton("COLOR_M2")
     .setPosition(310, 100)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
    cp5.addButton("COLOR_M3")
     .setPosition(460, 100)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
   cp5.addButton("COLOR_B")
     .setPosition(610, 100)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
     cp5.addSlider("front_value") //valueという名前のスライダーを作成する
      .setRange(100, 300) //変化させる値の範囲 
      .setValue(200)//初期値
      .setPosition(10, 250)//スライダーを表示する位置
      .setSize(300, 30)//.setSize(スライダーの横幅, 縦幅)
      .setNumberOfTickMarks(21)//値を何段階で変化できるようにするか
      .setFont(font);

     cp5.addSlider("mid_value") //valueという名前のスライダーを作成する
      .setRange(0, 100) //変化させる値の範囲 
      .setValue(30)//初期値
      .setPosition(10, 300)//スライダーを表示する位置
      .setSize(300, 30)//.setSize(スライダーの横幅, 縦幅)
      .setNumberOfTickMarks(101)//値を何段階で変化できるようにするか
      .setFont(font);
      
     cp5.addSlider("side_value") //valueという名前のスライダーを作成する
      .setRange(0, 100) //変化させる値の範囲 
      .setValue(10)//初期値
      .setPosition(10, 350)//スライダーを表示する位置
      .setSize(300, 30)//.setSize(スライダーの横幅, 縦幅)
      .setNumberOfTickMarks(101)//値を何段階で変化できるようにするか
      .setFont(font);
      
     cp5.addSlider("back_value") //valueという名前のスライダーを作成する
      .setRange(0, 100) //変化させる値の範囲 
      .setValue(20)//初期値
      .setPosition(10, 400)//スライダーを表示する位置
      .setSize(300, 30)//.setSize(スライダーの横幅, 縦幅)
      .setNumberOfTickMarks(101)//値を何段階で変化できるようにするか
      .setFont(font);
        
      cp5.addToggle("toggle_3")
       .setPosition(470,350)
       .setValue(true)
       .setSize(80,20)
       ;
      
     cp5.addButton("Preview")
     .setPosition(200, 450)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
    cp5.addButton("Save")
     .setPosition(400, 450)
     .setSize(100, 30)//.setSize(スライダーの横幅, 縦幅)
     .updateSize()
     .setFont(font);
     
      mid_color1 = color(255,255, 0);
      mid_color2 = color(0,255, 255);
      mid_color3 = color(255,0, 255);
      front_color = color(255);
      back_color = color(255);
      
      stroke(255);
      strokeWeight(1);
      fill(255);
      rect(10, 130, 100, 100);
      rect(610, 130, 100, 100);
      fill(mid_color1);
      rect(160, 130, 100, 100);
      fill(mid_color2);
      rect(310, 130, 100, 100);
      fill(mid_color3);
      rect(460, 130, 100, 100);
  }

  void draw() { 
    
    
    if(jt1.getText().length() < 1){
        input_text = "てすとテスト";
    }else{
      input_text = jt1.getText();
    }
    
    if(input_pretext.equals(input_text) == false){
      fill(150);
      noStroke();
      rect(0, 500, 1800, 300);
      input_pretext = input_text;
    }
    
      textFont(font_w6);
      textSize(front_value);
      textAlign(CENTER, CENTER);
      fill(front_color);
      text(input_text, 900, 650);
   
  }
  
  public void COLOR_F() {
      fill(cp5.get(ColorWheel.class,"c").getRGB());
      rect(10, 130, 100, 100);
      front_color = cp5.get(ColorWheel.class,"c").getRGB();
  }
  
  public void COLOR_M1() {
      fill(cp5.get(ColorWheel.class,"c").getRGB());
      rect(160, 130, 100, 100);
      mid_color1 = cp5.get(ColorWheel.class,"c").getRGB();
      println(red(mid_color1), green(mid_color1), blue(mid_color1));
  }
  
  public void COLOR_M2() {
      fill(cp5.get(ColorWheel.class,"c").getRGB());
      rect(310, 130, 100, 100);
      mid_color2 = cp5.get(ColorWheel.class,"c").getRGB();
      println(red(mid_color2), green(mid_color2), blue(mid_color2));
  }
  
  public void COLOR_M3() {
      fill(cp5.get(ColorWheel.class,"c").getRGB());
      rect(460, 130, 100, 100);
      mid_color3 = cp5.get(ColorWheel.class,"c").getRGB();
      println(red(mid_color3), green(mid_color3), blue(mid_color3));
  }
  
  public void COLOR_B() {
      fill(cp5.get(ColorWheel.class,"c").getRGB());
      rect(610, 130, 100, 100);
      back_color = cp5.get(ColorWheel.class,"c").getRGB();
  }
  
  public void toggle_3(boolean theFlag) {
    if(theFlag==true) {
      color3_flag = true;
      stroke(255);
      strokeWeight(1);
      fill(mid_color3);
      rect(460, 130, 100, 100);
    } else {
      color3_flag = false;
      stroke(255);
      strokeWeight(1);
      fill(150);
      rect(460, 130, 100, 100);
    }
  }
  
  void reset(){
      fill(150);
      noStroke();
      rect(0, 500, 1800, 300);
      
      textFont(font_w6);
      textSize(front_value);
      textAlign(CENTER, CENTER);
      fill(255);
      text(input_text, 900, 650);
  }
  
  void controlEvent(ControlEvent theEvent) {
   if(theEvent.isController()) { 
     if(theEvent.getController().getName()=="front_value") reset();
     if(theEvent.getController().getName()=="mid_value") reset();
     if(theEvent.getController().getName()=="side_value") reset();
     if(theEvent.getController().getName()=="back_value") reset();
   }
  }

  public void Preview() {
  
    reset();
    
    min_x = 900;
    min_y = 150;
    max_x = 900;
    max_y = 150; 
      
    noStroke();
    for (int k = 0; k < 1800; k ++) {
            for (int j = 0; j < 300; j ++) {
                  color c = get(k, j+500); //ピクセルを取得する
                  if(red(c) >= 200){
                          color_save[k][j] = true;
                          color_r[int(k/3)][int(j/3)] = true;
                          if(k < min_x) min_x = k;
                          if(j < min_y) min_y = j;
                          if(k > max_x) max_x = k;
                          if(j > max_y) max_y = j;
                    }else{
                      color_save[k][j] = false;
                      color_r[int(k/3)][int(j/3)] = false;
                    }
                    
            }
    }
    width_x = max_x - min_x;
    height_y = max_y - min_y;
    wh = width_x/height_y;
    
    println(min_x, min_y, width_x, height_y);
      
      //back
      for (int k = 0; k < 600; k ++) {
              for (int j = 0; j < 100; j ++) {
                    if(color_r[k][j]){
                          fill(back_color);
                          ellipse(k*3, j*3+500, mid_value + back_value, mid_value + back_value);
                          ellipse(k*3+side_value, j*3+500+side_value, mid_value + back_value, mid_value + back_value);
                    }
              }
      }
      
      //mid
      for (float k = 0; k < 600; k ++) {
              for (float j = 0; j < 100; j ++) {
                    if(color_r[int(k)][int(j)]){
                        if(color3_flag){
                            if(((k*3-min_x)+((j*3-min_y)*width_x)/height_y) / width_x <= 1){
                              color c = lerpColor(mid_color1, mid_color2, ((k*3-min_x)+((j*3-min_y)*width_x)/height_y) / width_x);
                              fill(c);
                            }else{
                              color c = lerpColor(mid_color2, mid_color3, ((k*3-min_x)+((j*3-min_y)*(width_x/height_y))-(width_x)) / (width_x));
                              fill(c);
                            }
                        }else{
                          color c = lerpColor(mid_color1, mid_color2, ((k*3-min_x) + (j*3-min_y)) / (width_x + height_y));
                          fill(c);
                        }
                          ellipse(k*3, j*3+500, mid_value, mid_value);
                          ellipse(k*3+side_value, j*3+500+side_value, mid_value, mid_value);
                    }
              }
      }
      ////front
      //    textFont(font_w6);
      //    textSize(front_value);
      //    textAlign(CENTER, CENTER);
      //    fill(front_color);
      //    text(input_text, 900, 650);
  }
  
  public void Save() {
    
      gr=createGraphics(1800,300);
      gr.beginDraw();
      gr.background(0,0,0,0); //不透明度(4パラメータ目)が0なら色はなんでもいい
        
      gr.noStroke();
      //back
      for (int k = 0; k < 1800; k ++) {
              for (int j = 0; j < 300; j ++) {
                    if(color_save[k][j]){
                          gr.fill(back_color);
                          gr.ellipse(k, j, mid_value + back_value, mid_value + back_value);
                          for(int l=0; l<=side_value; l++){
                              gr.ellipse(k+l, j+l, mid_value + back_value, mid_value + back_value);
                          }
                    }
              }
      }
      
      //mid
      for (float k = 0; k < 1800; k ++) {
              for (float j = 0; j < 300; j ++) {
                    if(color_save[int(k)][int(j)]){
                          if(color3_flag){
                              if(((k-min_x)+((j-min_y)*width_x)/height_y) / width_x <= 1){
                                color c = lerpColor(mid_color1, mid_color2, ((k-min_x)+((j-min_y)*width_x)/height_y) / width_x);
                                gr.fill(c);
                              }else{
                                color c = lerpColor(mid_color2, mid_color3, ((k-min_x)+((j-min_y)*(width_x/height_y))-(width_x)) / (width_x));
                                gr.fill(c);
                              }
                          }else{
                            color c = lerpColor(mid_color1, mid_color2, ((k-min_x) + (j-min_y)) / (width_x + height_y));
                            gr.fill(c);
                          }
                          gr.ellipse(k, j, mid_value, mid_value);
                          for(int l=0; l<=side_value; l++){
                                if(color3_flag){
                                  if((((k+l)-min_x)+(((j+l)-min_y)*width_x)/height_y) / width_x <= 1){
                                    color cc = lerpColor(mid_color1, mid_color2, (((k+l)-min_x)+(((j+l)-min_y)*width_x)/height_y) / width_x);
                                    gr.fill(cc);
                                  }else{
                                    color cc = lerpColor(mid_color2, mid_color3, (((k+l)-min_x)+(((j+l)-min_y)*(width_x/height_y))-(width_x)) / (width_x));
                                    gr.fill(cc);
                                  }
                              }else{
                                color cc = lerpColor(mid_color1, mid_color2, (((k+l)-min_x) + ((j+l)-min_y)) / (width_x + height_y));
                                gr.fill(cc);
                              }
                              gr.ellipse(k+l, j+l, mid_value, mid_value);
                          }
                    }
              }
      }
      //front
          gr.textFont(font_w6);
          gr.textSize(front_value);
          gr.textAlign(CENTER, CENTER);
          gr.fill(front_color);
          gr.text(input_text, 900, 150);
        
        
        gr.endDraw();  
        gr.save(str(year()) + nf(month(), 2) + nf(day(), 2) + nf(hour(), 2) + nf(minute(), 2) + nf(second(), 2) + ".png");
  }

・ライブラリのcontrolP5を使用しています
・日本語入力に対応するためにjavax.swing、java.awtをインポートしています

縁取り文字生成アプリの基本的な使い方

アプリを起動すると下記のような画面が出ます。
テキストを何も入力していないと「てすとテスト」と表示されます。
なんでも良いのでテキストを赤枠内に入れましょう!

文字縁取りアプリ

・FRONT VALUE:フォントの大きさ
・MID VALUE:1番目の縁取りの太さ
・SIDE VALUE:1番目の縁取りの段の大きさ
・BACK VALUE:2番目の縁取りの太さ

・PREVIEW:縁取り文字の確認(荒めの表示です)
・SAVE:画像として保存(1分ほどかかります)

画像の保存場所は、アプリと同じです。
保存が成功するとこんな感じの画像が作成できます^-^
背景が透明なので、そのままでも十分使えると思います〜〜

文字縁取りアプリ

#3色グラデーションの場合、少し荒さが出て、段々に見えますね。。汗

2色グラデーションにする

TOGGLE_3をクリックすると3色目がなくなり、2色グラデーションになります。
実はこっちの方が綺麗なのです汗
3色の場合は、少し段々になっていますね。。

文字縁取りアプリ
文字縁取りアプリ

段をなくす、縁を1つにする、色の暗さを変える

SIDE VALUEを0にすると段のない文字になります。
BACK VALUEを0にすると2番目の縁がなくなります。

カラーマップの左側の三角を上下に移動させると色の明るさ/暗さを調節できます。

文字縁取りアプリ
文字縁取りアプリ

色の配置を変える

つくもりは文字と2番目の縁は白色が好きなので、デフォルトが白色になっていますが、
変更できますので、好みに合わせて変更してくださいな

文字縁取りアプリ

文字色を赤、1つ目の縁を白、2つ目の縁を赤にするとこんな感じ。

文字縁取りアプリ

文字数が多い場合、文字の大きさを変える

文字数が大きい場合は、FRONT VALUEを変更して、すべて枠内に入るように設定してください。

文字縁取りアプリ
文字縁取りアプリ

つくもりの住んでいる守谷市のふるさと納税返礼品はビールなのです!!(突然の宣伝!笑)
よかったらぜひ〜〜😊

余談:アプリを作った背景

前回のブログでも言いましたが、「イラレが高い!」が根本にありました汗
イラレを使わないで、縁取り文字ができないかな〜と悩んだ次第です。

古いPCに古いイラレが入っていますが、毎回文字を作るのに手間ですし、
文字を少し修正したいだけなのに、背面ペーストを何回も行わないといけないのは大変でした。。

今回作成した縁取り文字生成アプリを使用した縁取り文字の作成は、多少時間がかかりますが、
「同じパターンでの文字生成を量産しやすい!」「修正しやすい!」ことがポイントかなと思ってます。


誰か縁取り文字生成のwebサービス始めないかな〜
フォントを変えたり、縁の付け方をいろいろ変更できたり、、

webサービスがあったら、つくもりも使いたいな〜〜^^;

コメント